Upgraded phpmyadmin to 4.0.4 (All Languages) - No modifications yet
[openemr.git] / phpmyadmin / libraries / sqlparser.lib.php
blob0f1caa042086ffa07088f04df5efee13e57e1106
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /** SQL Parser Functions for phpMyAdmin
5 * These functions define an SQL parser system, capable of understanding and
6 * extracting data from a MySQL type SQL query.
8 * The basic procedure for using the new SQL parser:
9 * On any page that needs to extract data from a query or to pretty-print a
10 * query, you need code like this up at the top:
12 * ($sql contains the query)
13 * $parsed_sql = PMA_SQP_parse($sql);
15 * If you want to extract data from it then, you just need to run
16 * $sql_info = PMA_SQP_analyze($parsed_sql);
18 * See comments in PMA_SQP_analyze for the returned info
19 * from the analyzer.
21 * If you want a pretty-printed version of the query, do:
22 * $string = PMA_SQP_formatHtml($parsed_sql);
23 * (note that that you need to have syntax.css.php included somehow in your
24 * page for it to work, I recommend '<link rel="stylesheet" type="text/css"
25 * href="syntax.css.php" />' at the moment.)
27 * @package PhpMyAdmin
29 if (! defined('PHPMYADMIN')) {
30 exit;
33 /**
34 * Include the string library as we use it heavily
36 require_once './libraries/string.lib.php';
38 /**
39 * Include data for the SQL Parser
41 require_once './libraries/sqlparser.data.php';
43 /**
44 * Charset information
46 if (!defined('TESTSUITE')) {
47 include_once './libraries/mysql_charsets.lib.php';
49 if (! isset($mysql_charsets)) {
50 $mysql_charsets = array();
51 $mysql_collations_flat = array();
54 /**
55 * Stores parsed elemented of query to array.
57 * Currently we don't need the $pos (token position in query)
58 * for other purposes than LIMIT clause verification,
59 * so many calls to this function do not include the 4th parameter
61 * @param array &$arr Array to store element
62 * @param string $type Type of element
63 * @param string $data Data (text) of element
64 * @param int &$arrsize Size of array
65 * @param int $pos Position of an element
67 * @return nothing
69 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
71 $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos);
72 $arrsize++;
73 } // end of the "PMA_SQP_arrayAdd()" function
75 /**
76 * Reset the error variable for the SQL parser
78 * @access public
80 * @return nothing
82 function PMA_SQP_resetError()
84 global $SQP_errorString;
85 $SQP_errorString = '';
86 unset($SQP_errorString);
89 /**
90 * Get the contents of the error variable for the SQL parser
92 * @return string Error string from SQL parser
94 * @access public
96 function PMA_SQP_getErrorString()
98 global $SQP_errorString;
99 return isset($SQP_errorString) ? $SQP_errorString : '';
103 * Check if the SQL parser hit an error
105 * @return boolean error state
107 * @access public
109 function PMA_SQP_isError()
111 global $SQP_errorString;
112 return isset($SQP_errorString) && !empty($SQP_errorString);
116 * Set an error message for the system
118 * @param string $message The error message
119 * @param string $sql The failing SQL query
121 * @return nothing
123 * @access private
124 * @scope SQL Parser internal
126 function PMA_SQP_throwError($message, $sql)
128 global $SQP_errorString;
129 $SQP_errorString = '<p>'.__('There seems to be an error in your SQL query. The MySQL server error output below, if there is any, may also help you in diagnosing the problem') . '</p>' . "\n"
130 . '<pre>' . "\n"
131 . 'ERROR: ' . $message . "\n"
132 . 'SQL: ' . htmlspecialchars($sql) . "\n"
133 . '</pre>' . "\n";
135 } // end of the "PMA_SQP_throwError()" function
139 * Do display the bug report
141 * @param string $message The error message
142 * @param string $sql The failing SQL query
144 * @return nothing
146 * @access public
148 function PMA_SQP_bug($message, $sql)
150 global $SQP_errorString;
151 $debugstr = 'ERROR: ' . $message . "\n";
152 $debugstr .= 'MySQL: '.PMA_MYSQL_STR_VERSION . "\n";
153 $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS . ' ';
154 $debugstr .= PMA_USR_BROWSER_AGENT . ' ' . PMA_USR_BROWSER_VER . "\n";
155 $debugstr .= 'PMA: ' . PMA_VERSION . "\n";
156 $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION . ' ' . PHP_OS . "\n";
157 $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
158 $debugstr .= 'SQL: ' . htmlspecialchars($sql);
160 $encodedstr = $debugstr;
161 if (@function_exists('gzcompress')) {
162 $encodedstr = gzcompress($debugstr, 9);
164 $encodedstr = preg_replace(
165 "/(\015\012)|(\015)|(\012)/",
166 '<br />' . "\n",
167 chunk_split(base64_encode($encodedstr))
171 $SQP_errorString .= __('There is a chance that you may have found a bug in the SQL parser. Please examine your query closely, and check that the quotes are correct and not mis-matched. Other possible failure causes may be that you are uploading a file with binary outside of a quoted text area. You can also try your query on the MySQL command line interface. The MySQL server error output below, if there is any, may also help you in diagnosing the problem. If you still have problems or if the parser fails where the command line interface succeeds, please reduce your SQL query input to the single query that causes problems, and submit a bug report with the data chunk in the CUT section below:')
172 . '<br />' . "\n"
173 . '----' . __('BEGIN CUT') . '----' . '<br />' . "\n"
174 . $encodedstr . "\n"
175 . '----' . __('END CUT') . '----' . '<br />' . "\n";
177 $SQP_errorString .= '----' . __('BEGIN RAW') . '----<br />' . "\n"
178 . '<pre>' . "\n"
179 . $debugstr
180 . '</pre>' . "\n"
181 . '----' . __('END RAW') . '----<br />' . "\n";
183 } // end of the "PMA_SQP_bug()" function
187 * Parses the SQL queries
189 * @param string $sql The SQL query list
191 * @return mixed Most of times, nothing...
193 * @global array The current PMA configuration
194 * @global array MySQL column attributes
195 * @global array MySQL reserved words
196 * @global array MySQL column types
197 * @global array MySQL function names
198 * @global array List of available character sets
199 * @global array List of available collations
201 * @access public
203 function PMA_SQP_parse($sql)
205 static $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word;
206 static $PMA_SQPdata_column_type;
207 static $PMA_SQPdata_function_name, $PMA_SQPdata_forbidden_word;
208 global $mysql_charsets, $mysql_collations_flat;
210 // Convert all line feeds to Unix style
211 $sql = str_replace("\r\n", "\n", $sql);
212 $sql = str_replace("\r", "\n", $sql);
214 $len = PMA_strlen($sql);
215 if ($len == 0) {
216 return array();
219 // Create local hashtables
220 if (!isset($PMA_SQPdata_column_attrib)) {
221 $PMA_SQPdata_column_attrib = array_flip(
222 $GLOBALS['PMA_SQPdata_column_attrib']
224 $PMA_SQPdata_function_name = array_flip(
225 $GLOBALS['PMA_SQPdata_function_name']
227 $PMA_SQPdata_reserved_word = array_flip(
228 $GLOBALS['PMA_SQPdata_reserved_word']
230 $PMA_SQPdata_forbidden_word = array_flip(
231 $GLOBALS['PMA_SQPdata_forbidden_word']
233 $PMA_SQPdata_column_type = array_flip(
234 $GLOBALS['PMA_SQPdata_column_type']
238 $sql_array = array();
239 $sql_array['raw'] = $sql;
240 $count1 = 0;
241 $count2 = 0;
242 $punct_queryend = ';';
243 $punct_qualifier = '.';
244 $punct_listsep = ',';
245 $punct_level_plus = '(';
246 $punct_level_minus = ')';
247 $punct_user = '@';
248 $digit_floatdecimal = '.';
249 $digit_hexset = 'x';
250 $bracket_list = '()[]{}';
251 $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
252 $allpunct_list_pair = array(
253 '!=' => 1,
254 '&&' => 1,
255 ':=' => 1,
256 '<<' => 1,
257 '<=' => 1,
258 '<=>' => 1,
259 '<>' => 1,
260 '>=' => 1,
261 '>>' => 1,
262 '||' => 1,
263 '==' => 1
265 $quote_list = '\'"`';
266 $arraysize = 0;
268 $previous_was_space = false;
269 $this_was_space = false;
270 $previous_was_bracket = false;
271 $this_was_bracket = false;
272 $previous_was_punct = false;
273 $this_was_punct = false;
274 $previous_was_listsep = false;
275 $this_was_listsep = false;
276 $previous_was_quote = false;
277 $this_was_quote = false;
279 while ($count2 < $len) {
280 $c = PMA_substr($sql, $count2, 1);
281 $count1 = $count2;
283 $previous_was_space = $this_was_space;
284 $this_was_space = false;
285 $previous_was_bracket = $this_was_bracket;
286 $this_was_bracket = false;
287 $previous_was_punct = $this_was_punct;
288 $this_was_punct = false;
289 $previous_was_listsep = $this_was_listsep;
290 $this_was_listsep = false;
291 $previous_was_quote = $this_was_quote;
292 $this_was_quote = false;
294 if (($c == "\n")) {
295 $this_was_space = true;
296 $count2++;
297 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
298 continue;
301 // Checks for white space
302 if (PMA_STR_isSpace($c)) {
303 $this_was_space = true;
304 $count2++;
305 continue;
308 // Checks for comment lines.
309 // MySQL style #
310 // C style /* */
311 // ANSI style --
312 $next_c = PMA_substr($sql, $count2 + 1, 1);
313 if (($c == '#')
314 || (($count2 + 1 < $len) && ($c == '/') && ($next_c == '*'))
315 || (($count2 + 2 == $len) && ($c == '-') && ($next_c == '-'))
316 || (($count2 + 2 < $len) && ($c == '-') && ($next_c == '-') && ((PMA_substr($sql, $count2 + 2, 1) <= ' ')))
318 $count2++;
319 $pos = 0;
320 $type = 'bad';
321 switch ($c) {
322 case '#':
323 $type = 'mysql';
324 case '-':
325 $type = 'ansi';
326 $pos = PMA_strpos($sql, "\n", $count2);
327 break;
328 case '/':
329 $type = 'c';
330 $pos = PMA_strpos($sql, '*/', $count2);
331 $pos += 2;
332 break;
333 default:
334 break;
335 } // end switch
336 $count2 = ($pos < $count2) ? $len : $pos;
337 $str = PMA_substr($sql, $count1, $count2 - $count1);
338 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
339 continue;
340 } // end if
342 // Checks for something inside quotation marks
343 if (PMA_strpos($quote_list, $c) !== false) {
344 $startquotepos = $count2;
345 $quotetype = $c;
346 $count2++;
347 $pos = $count2;
348 $oldpos = 0;
349 do {
350 $oldpos = $pos;
351 $pos = PMA_strpos(' ' . $sql, $quotetype, $oldpos + 1) - 1;
352 // ($pos === false)
353 if ($pos < 0) {
354 if ($c == '`') {
356 * Behave same as MySQL and accept end of query as end of backtick.
357 * I know this is sick, but MySQL behaves like this:
359 * SELECT * FROM `table
361 * is treated like
363 * SELECT * FROM `table`
365 $pos_quote_separator = PMA_strpos(' ' . $sql, $GLOBALS['sql_delimiter'], $oldpos + 1) - 1;
366 if ($pos_quote_separator < 0) {
367 $len += 1;
368 $sql .= '`';
369 $sql_array['raw'] .= '`';
370 $pos = $len;
371 } else {
372 $len += 1;
373 $sql = PMA_substr($sql, 0, $pos_quote_separator) . '`' . PMA_substr($sql, $pos_quote_separator);
374 $sql_array['raw'] = $sql;
375 $pos = $pos_quote_separator;
377 if (class_exists('PMA_Message') && $GLOBALS['is_ajax_request'] != true) {
378 PMA_Message::notice(__('Automatically appended backtick to the end of query!'))->display();
380 } else {
381 $debugstr = __('Unclosed quote') . ' @ ' . $startquotepos. "\n"
382 . 'STR: ' . htmlspecialchars($quotetype);
383 PMA_SQP_throwError($debugstr, $sql);
384 return $sql_array;
388 // If the quote is the first character, it can't be
389 // escaped, so don't do the rest of the code
390 if ($pos == 0) {
391 break;
394 // Checks for MySQL escaping using a \
395 // And checks for ANSI escaping using the $quotetype character
396 if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos) && $c != '`') {
397 $pos ++;
398 continue;
399 } elseif (($pos + 1 < $len) && (PMA_substr($sql, $pos, 1) == $quotetype) && (PMA_substr($sql, $pos + 1, 1) == $quotetype)) {
400 $pos = $pos + 2;
401 continue;
402 } else {
403 break;
405 } while ($len > $pos); // end do
407 $count2 = $pos;
408 $count2++;
409 $type = 'quote_';
410 switch ($quotetype) {
411 case '\'':
412 $type .= 'single';
413 $this_was_quote = true;
414 break;
415 case '"':
416 $type .= 'double';
417 $this_was_quote = true;
418 break;
419 case '`':
420 $type .= 'backtick';
421 $this_was_quote = true;
422 break;
423 default:
424 break;
425 } // end switch
426 $data = PMA_substr($sql, $count1, $count2 - $count1);
427 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
428 continue;
431 // Checks for brackets
432 if (PMA_strpos($bracket_list, $c) !== false) {
433 // All bracket tokens are only one item long
434 $this_was_bracket = true;
435 $count2++;
436 $type_type = '';
437 if (PMA_strpos('([{', $c) !== false) {
438 $type_type = 'open';
439 } else {
440 $type_type = 'close';
443 $type_style = '';
444 if (PMA_strpos('()', $c) !== false) {
445 $type_style = 'round';
446 } elseif (PMA_strpos('[]', $c) !== false) {
447 $type_style = 'square';
448 } else {
449 $type_style = 'curly';
452 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
453 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
454 continue;
457 /* DEBUG
458 echo '<pre>1';
459 var_dump(PMA_STR_isSqlIdentifier($c, false));
460 var_dump($c == '@');
461 var_dump($c == '.');
462 var_dump(PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1)));
463 var_dump($previous_was_space);
464 var_dump($previous_was_bracket);
465 var_dump($previous_was_listsep);
466 echo '</pre>';
469 // Checks for identifier (alpha or numeric)
470 if (PMA_STR_isSqlIdentifier($c, false)
471 || $c == '@'
472 || ($c == '.'
473 && PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1))
474 && ($previous_was_space || $previous_was_bracket || $previous_was_listsep))
476 /* DEBUG
477 echo PMA_substr($sql, $count2);
478 echo '<hr />';
481 $count2++;
484 * @todo a @ can also be present in expressions like
485 * FROM 'user'@'%' or TO 'user'@'%'
486 * in this case, the @ is wrongly marked as alpha_variable
488 $is_identifier = $previous_was_punct;
489 $is_sql_variable = $c == '@' && ! $previous_was_quote;
490 $is_user = $c == '@' && $previous_was_quote;
491 $is_digit = !$is_identifier && !$is_sql_variable && PMA_STR_isDigit($c);
492 $is_hex_digit = $is_digit && $c == '0' && $count2 < $len && PMA_substr($sql, $count2, 1) == 'x';
493 $is_float_digit = $c == '.';
494 $is_float_digit_exponent = false;
496 /* DEBUG
497 echo '<pre>2';
498 var_dump($is_identifier);
499 var_dump($is_sql_variable);
500 var_dump($is_digit);
501 var_dump($is_float_digit);
502 echo '</pre>';
505 // Fast skip is especially needed for huge BLOB data
506 if ($is_hex_digit) {
507 $count2++;
508 $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
509 if ($pos > $count2) {
510 $count2 = $pos;
512 unset($pos);
513 } elseif ($is_digit) {
514 $pos = strspn($sql, '0123456789', $count2);
515 if ($pos > $count2) {
516 $count2 = $pos;
518 unset($pos);
521 while (($count2 < $len) && PMA_STR_isSqlIdentifier(PMA_substr($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
522 $c2 = PMA_substr($sql, $count2, 1);
523 if ($is_sql_variable && ($c2 == '.')) {
524 $count2++;
525 continue;
527 if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
528 $count2++;
529 if (!$is_float_digit) {
530 $is_float_digit = true;
531 continue;
532 } else {
533 $debugstr = __('Invalid Identifer') . ' @ ' . ($count1+1) . "\n"
534 . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
535 PMA_SQP_throwError($debugstr, $sql);
536 return $sql_array;
539 if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
540 if (!$is_float_digit_exponent) {
541 $is_float_digit_exponent = true;
542 $is_float_digit = true;
543 $count2++;
544 continue;
545 } else {
546 $is_digit = false;
547 $is_float_digit = false;
550 if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && PMA_STR_isDigit($c2))) {
551 $count2++;
552 continue;
553 } else {
554 $is_digit = false;
555 $is_hex_digit = false;
558 $count2++;
559 } // end while
561 $l = $count2 - $count1;
562 $str = PMA_substr($sql, $count1, $l);
564 $type = '';
565 if ($is_digit || $is_float_digit || $is_hex_digit) {
566 $type = 'digit';
567 if ($is_float_digit) {
568 $type .= '_float';
569 } elseif ($is_hex_digit) {
570 $type .= '_hex';
571 } else {
572 $type .= '_integer';
574 } elseif ($is_user) {
575 $type = 'punct_user';
576 } elseif ($is_sql_variable != false) {
577 $type = 'alpha_variable';
578 } else {
579 $type = 'alpha';
580 } // end if... else....
581 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize, $count2);
583 continue;
586 // Checks for punct
587 if (PMA_strpos($allpunct_list, $c) !== false) {
588 while (($count2 < $len) && PMA_strpos($allpunct_list, PMA_substr($sql, $count2, 1)) !== false) {
589 $count2++;
591 $l = $count2 - $count1;
592 if ($l == 1) {
593 $punct_data = $c;
594 } else {
595 $punct_data = PMA_substr($sql, $count1, $l);
598 // Special case, sometimes, althought two characters are
599 // adjectent directly, they ACTUALLY need to be seperate
600 /* DEBUG
601 echo '<pre>';
602 var_dump($l);
603 var_dump($punct_data);
604 echo '</pre>';
607 if ($l == 1) {
608 $t_suffix = '';
609 switch ($punct_data) {
610 case $punct_queryend:
611 $t_suffix = '_queryend';
612 break;
613 case $punct_qualifier:
614 $t_suffix = '_qualifier';
615 $this_was_punct = true;
616 break;
617 case $punct_listsep:
618 $this_was_listsep = true;
619 $t_suffix = '_listsep';
620 break;
621 default:
622 break;
624 PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
625 } elseif ($punct_data == $GLOBALS['sql_delimiter'] || isset($allpunct_list_pair[$punct_data])) {
626 // Ok, we have one of the valid combined punct expressions
627 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
628 } else {
629 // Bad luck, lets split it up more
630 $first = $punct_data[0];
631 $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
632 $last = $punct_data[$l - 1];
633 if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
634 $count2 = $count1 + 1;
635 $punct_data = $first;
636 } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || PMA_substr($sql, $count2, 1) <= ' '))) {
637 $count2 -= 2;
638 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
639 } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
640 $count2--;
641 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
642 } elseif ($last != '~') {
644 * @todo for negation operator, split in 2 tokens ?
645 * "select x&~1 from t"
646 * becomes "select x & ~ 1 from t" ?
648 $debugstr = __('Unknown Punctuation String') . ' @ ' . ($count1+1) . "\n"
649 . 'STR: ' . htmlspecialchars($punct_data);
650 PMA_SQP_throwError($debugstr, $sql);
651 return $sql_array;
653 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
654 continue;
655 } // end if... elseif... else
656 continue;
659 // DEBUG
660 $count2++;
662 $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
663 . 'STR: ' . PMA_substr($sql, $count1, $count2 - $count1) . "\n";
664 PMA_SQP_bug($debugstr, $sql);
665 return $sql_array;
667 } // end while ($count2 < $len)
670 echo '<pre>';
671 print_r($sql_array);
672 echo '</pre>';
675 if ($arraysize > 0) {
676 $t_next = $sql_array[0]['type'];
677 $t_prev = '';
678 $t_bef_prev = '';
679 $t_cur = '';
680 $d_next = $sql_array[0]['data'];
681 $d_prev = '';
682 $d_bef_prev = '';
683 $d_cur = '';
684 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
685 $d_prev_upper = '';
686 $d_bef_prev_upper = '';
687 $d_cur_upper = '';
690 for ($i = 0; $i < $arraysize; $i++) {
691 $t_bef_prev = $t_prev;
692 $t_prev = $t_cur;
693 $t_cur = $t_next;
694 $d_bef_prev = $d_prev;
695 $d_prev = $d_cur;
696 $d_cur = $d_next;
697 $d_bef_prev_upper = $d_prev_upper;
698 $d_prev_upper = $d_cur_upper;
699 $d_cur_upper = $d_next_upper;
700 if (($i + 1) < $arraysize) {
701 $t_next = $sql_array[$i + 1]['type'];
702 $d_next = $sql_array[$i + 1]['data'];
703 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
704 } else {
705 $t_next = '';
706 $d_next = '';
707 $d_next_upper = '';
710 //DEBUG echo "[prev: <strong>".$d_prev."</strong> ".$t_prev."][cur: <strong>".$d_cur."</strong> ".$t_cur."][next: <strong>".$d_next."</strong> ".$t_next."]<br />";
712 if ($t_cur == 'alpha') {
713 $t_suffix = '_identifier';
714 // for example: `thebit` bit(8) NOT NULL DEFAULT b'0'
715 if ($t_prev == 'alpha' && $d_prev == 'DEFAULT' && $d_cur == 'b' && $t_next == 'quote_single') {
716 $t_suffix = '_bitfield_constant_introducer';
717 } elseif (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
718 $t_suffix = '_identifier';
719 } elseif (($t_next == 'punct_bracket_open_round')
720 && isset($PMA_SQPdata_function_name[$d_cur_upper])) {
722 * @todo 2005-10-16: in the case of a CREATE TABLE containing
723 * a TIMESTAMP, since TIMESTAMP() is also a function, it's
724 * found here and the token is wrongly marked as alpha_functionName.
725 * But we compensate for this when analysing for timestamp_not_null
726 * later in this script.
728 * Same applies to CHAR vs. CHAR() function.
730 $t_suffix = '_functionName';
731 /* There are functions which might be as well column types */
732 } elseif (isset($PMA_SQPdata_column_type[$d_cur_upper])) {
733 $t_suffix = '_columnType';
736 * Temporary fix for bugs #621357 and #2027720
738 * @todo FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
740 if (($d_cur_upper == 'SET' || $d_cur_upper == 'BINARY') && $t_next != 'punct_bracket_open_round') {
741 $t_suffix = '_reservedWord';
743 //END OF TEMPORARY FIX
745 // CHARACTER is a synonym for CHAR, but can also be meant as
746 // CHARACTER SET. In this case, we have a reserved word.
747 if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
748 $t_suffix = '_reservedWord';
751 // experimental
752 // current is a column type, so previous must not be
753 // a reserved word but an identifier
754 // CREATE TABLE SG_Persons (first varchar(64))
756 //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
757 // $sql_array[$i-1]['type'] = 'alpha_identifier';
760 } elseif (isset($PMA_SQPdata_reserved_word[$d_cur_upper])) {
761 $t_suffix = '_reservedWord';
762 } elseif (isset($PMA_SQPdata_column_attrib[$d_cur_upper])) {
763 $t_suffix = '_columnAttrib';
764 // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
765 // it should be regarded as a reserved word.
766 if ($d_cur_upper == 'INNODB'
767 && $d_prev_upper == 'SHOW'
768 && $d_next_upper == 'STATUS'
770 $t_suffix = '_reservedWord';
773 if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
774 $t_suffix = '_reservedWord';
776 // Binary as character set
777 if ($d_cur_upper == 'BINARY'
778 && (($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
779 || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
780 || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
781 || $d_prev_upper == 'CHARSET')
782 && in_array($d_cur, $mysql_charsets)
784 $t_suffix = '_charset';
786 } elseif (in_array($d_cur, $mysql_charsets)
787 || in_array($d_cur, $mysql_collations_flat)
788 || ($d_cur{0} == '_' && in_array(substr($d_cur, 1), $mysql_charsets))) {
789 $t_suffix = '_charset';
790 } else {
791 // Do nothing
793 // check if present in the list of forbidden words
794 if ($t_suffix == '_reservedWord'
795 && isset($PMA_SQPdata_forbidden_word[$d_cur_upper])
797 $sql_array[$i]['forbidden'] = true;
798 } else {
799 $sql_array[$i]['forbidden'] = false;
801 $sql_array[$i]['type'] .= $t_suffix;
803 } // end for
805 // Stores the size of the array inside the array, as count() is a slow
806 // operation.
807 $sql_array['len'] = $arraysize;
809 // DEBUG echo 'After parsing<pre>'; print_r($sql_array); echo '</pre>';
810 // Sends the data back
811 return $sql_array;
812 } // end of the "PMA_SQP_parse()" function
815 * Checks for token types being what we want...
817 * @param string $toCheck String of type that we have
818 * @param string $whatWeWant String of type that we want
820 * @return boolean result of check
822 * @access private
824 function PMA_SQP_typeCheck($toCheck, $whatWeWant)
826 $typeSeparator = '_';
827 if (strcmp($whatWeWant, $toCheck) == 0) {
828 return true;
829 } else {
830 if (strpos($whatWeWant, $typeSeparator) === false) {
831 return strncmp(
832 $whatWeWant, $toCheck,
833 strpos($toCheck, $typeSeparator)
834 ) == 0;
835 } else {
836 return false;
843 * Analyzes SQL queries
845 * @param array $arr The SQL queries
847 * @return array The analyzed SQL queries
849 * @access public
851 function PMA_SQP_analyze($arr)
853 if ($arr == array() || ! isset($arr['len'])) {
854 return array();
856 $result = array();
857 $size = $arr['len'];
858 $subresult = array(
859 'querytype' => '',
860 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
861 'position_of_first_select' => '', // the array index
862 'from_clause'=> '',
863 'group_by_clause'=> '',
864 'order_by_clause'=> '',
865 'having_clause' => '',
866 'limit_clause' => '',
867 'where_clause' => '',
868 'where_clause_identifiers' => array(),
869 'unsorted_query' => '',
870 'queryflags' => array(),
871 'select_expr' => array(),
872 'table_ref' => array(),
873 'foreign_keys' => array(),
874 'create_table_fields' => array()
876 $subresult_empty = $subresult;
877 $seek_queryend = false;
878 $seen_end_of_table_ref = false;
879 $number_of_brackets_in_extract = 0;
880 $number_of_brackets_in_group_concat = 0;
882 $number_of_brackets = 0;
883 $in_subquery = false;
884 $seen_subquery = false;
885 $seen_from = false;
887 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
888 // we must not use CURDATE as a table_ref
889 // so we track whether we are in the EXTRACT()
890 $in_extract = false;
892 // for GROUP_CONCAT(...)
893 $in_group_concat = false;
895 /* Description of analyzer results
897 * db, table, column, alias
898 * ------------------------
900 * Inside the $subresult array, we create ['select_expr'] and ['table_ref']
901 * arrays.
903 * The SELECT syntax (simplified) is
905 * SELECT
906 * select_expression,...
907 * [FROM [table_references]
910 * ['select_expr'] is filled with each expression, the key represents the
911 * expression position in the list (0-based) (so we don't lose track of
912 * multiple occurences of the same column).
914 * ['table_ref'] is filled with each table ref, same thing for the key.
916 * I create all sub-values empty, even if they are
917 * not present (for example no select_expression alias).
919 * There is a debug section at the end of loop #1, if you want to
920 * see the exact contents of select_expr and table_ref
922 * queryflags
923 * ----------
925 * In $subresult, array 'queryflags' is filled, according to what we
926 * find in the query.
928 * Currently, those are generated:
930 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
931 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
932 * ['queryflags']['distinct'] = 1; for a DISTINCT
933 * ['queryflags']['union'] = 1; for a UNION
934 * ['queryflags']['join'] = 1; for a JOIN
935 * ['queryflags']['offset'] = 1; for the presence of OFFSET
936 * ['queryflags']['procedure'] = 1; for the presence of PROCEDURE
938 * query clauses
939 * -------------
941 * The select is splitted in those clauses:
942 * ['select_expr_clause']
943 * ['from_clause']
944 * ['group_by_clause']
945 * ['order_by_clause']
946 * ['having_clause']
947 * ['limit_clause']
948 * ['where_clause']
950 * The identifiers of the WHERE clause are put into the array
951 * ['where_clause_identifier']
953 * For a SELECT, the whole query without the ORDER BY clause is put into
954 * ['unsorted_query']
956 * foreign keys
957 * ------------
958 * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
959 * analyzed and ['foreign_keys'] is an array filled with
960 * the constraint name, the index list,
961 * the REFERENCES table name and REFERENCES index list,
962 * and ON UPDATE | ON DELETE clauses
964 * position_of_first_select
965 * ------------------------
967 * The array index of the first SELECT we find. Will be used to
968 * insert a SQL_CALC_FOUND_ROWS.
970 * create_table_fields
971 * -------------------
973 * Used to detect the DEFAULT CURRENT_TIMESTAMP and
974 * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
975 * Also used to store the default value of the field.
976 * An array, each element is the identifier name.
977 * Note that for now, the timestamp_not_null element is created
978 * even for non-TIMESTAMP fields.
980 * Sub-elements: ['type'] which contains the column type
981 * optional (currently they are never false but can be absent):
982 * ['default_current_timestamp'] boolean
983 * ['on_update_current_timestamp'] boolean
984 * ['timestamp_not_null'] boolean
986 * section_before_limit, section_after_limit
987 * -----------------------------------------
989 * Marks the point of the query where we can insert a LIMIT clause;
990 * so the section_before_limit will contain the left part before
991 * a possible LIMIT clause
994 * End of description of analyzer results
997 // must be sorted
998 // TODO: current logic checks for only one word, so I put only the
999 // first word of the reserved expressions that end a table ref;
1000 // maybe this is not ok (the first word might mean something else)
1001 // $words_ending_table_ref = array(
1002 // 'FOR UPDATE',
1003 // 'GROUP BY',
1004 // 'HAVING',
1005 // 'LIMIT',
1006 // 'LOCK IN SHARE MODE',
1007 // 'ORDER BY',
1008 // 'PROCEDURE',
1009 // 'UNION',
1010 // 'WHERE'
1011 // );
1012 $words_ending_table_ref = array(
1013 'FOR' => 1,
1014 'GROUP' => 1,
1015 'HAVING' => 1,
1016 'LIMIT' => 1,
1017 'LOCK' => 1,
1018 'ORDER' => 1,
1019 'PROCEDURE' => 1,
1020 'UNION' => 1,
1021 'WHERE' => 1
1024 $words_ending_clauses = array(
1025 'FOR' => 1,
1026 'LIMIT' => 1,
1027 'LOCK' => 1,
1028 'PROCEDURE' => 1,
1029 'UNION' => 1
1032 $supported_query_types = array(
1033 'SELECT' => 1,
1035 // Support for these additional query types will come later on.
1036 'DELETE' => 1,
1037 'INSERT' => 1,
1038 'REPLACE' => 1,
1039 'TRUNCATE' => 1,
1040 'UPDATE' => 1,
1041 'EXPLAIN' => 1,
1042 'DESCRIBE' => 1,
1043 'SHOW' => 1,
1044 'CREATE' => 1,
1045 'SET' => 1,
1046 'ALTER' => 1
1050 // loop #1 for each token: select_expr, table_ref for SELECT
1052 for ($i = 0; $i < $size; $i++) {
1053 //DEBUG echo "Loop1 <strong>" . $arr[$i]['data']
1054 //. "</strong> (" . $arr[$i]['type'] . ")<br />";
1056 // High speed seek for locating the end of the current query
1057 if ($seek_queryend == true) {
1058 if ($arr[$i]['type'] == 'punct_queryend') {
1059 $seek_queryend = false;
1060 } else {
1061 continue;
1062 } // end if (type == punct_queryend)
1063 } // end if ($seek_queryend)
1066 * Note: do not split if this is a punct_queryend for the first and only
1067 * query
1068 * @todo when we find a UNION, should we split in another subresult?
1070 if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
1071 $result[] = $subresult;
1072 $subresult = $subresult_empty;
1073 continue;
1074 } // end if (type == punct_queryend)
1076 // ==============================================================
1077 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1078 $number_of_brackets++;
1079 if ($in_extract) {
1080 $number_of_brackets_in_extract++;
1082 if ($in_group_concat) {
1083 $number_of_brackets_in_group_concat++;
1086 // ==============================================================
1087 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1088 $number_of_brackets--;
1089 if ($number_of_brackets == 0) {
1090 $in_subquery = false;
1092 if ($in_extract) {
1093 $number_of_brackets_in_extract--;
1094 if ($number_of_brackets_in_extract == 0) {
1095 $in_extract = false;
1098 if ($in_group_concat) {
1099 $number_of_brackets_in_group_concat--;
1100 if ($number_of_brackets_in_group_concat == 0) {
1101 $in_group_concat = false;
1106 if ($in_subquery) {
1108 * skip the subquery to avoid setting
1109 * select_expr or table_ref with the contents
1110 * of this subquery; this is to avoid a bug when
1111 * trying to edit the results of
1112 * select * from child where not exists (select id from
1113 * parent where child.parent_id = parent.id);
1115 continue;
1117 // ==============================================================
1118 if ($arr[$i]['type'] == 'alpha_functionName') {
1119 $upper_data = strtoupper($arr[$i]['data']);
1120 if ($upper_data =='EXTRACT') {
1121 $in_extract = true;
1122 $number_of_brackets_in_extract = 0;
1124 if ($upper_data =='GROUP_CONCAT') {
1125 $in_group_concat = true;
1126 $number_of_brackets_in_group_concat = 0;
1130 // ==============================================================
1131 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1132 // We don't know what type of query yet, so run this
1133 if ($subresult['querytype'] == '') {
1134 $subresult['querytype'] = strtoupper($arr[$i]['data']);
1135 } // end if (querytype was empty)
1137 // Check if we support this type of query
1138 if (!isset($supported_query_types[$subresult['querytype']])) {
1139 // Skip ahead to the next one if we don't
1140 $seek_queryend = true;
1141 continue;
1142 } // end if (query not supported)
1144 // upper once
1145 $upper_data = strtoupper($arr[$i]['data']);
1147 * @todo reset for each query?
1150 if ($upper_data == 'SELECT') {
1151 if ($number_of_brackets > 0) {
1152 $in_subquery = true;
1153 $seen_subquery = true;
1154 // this is a subquery so do not analyze inside it
1155 continue;
1157 $seen_from = false;
1158 $previous_was_identifier = false;
1159 $current_select_expr = -1;
1160 $seen_end_of_table_ref = false;
1161 } // end if (data == SELECT)
1163 if ($upper_data =='FROM' && !$in_extract) {
1164 $current_table_ref = -1;
1165 $seen_from = true;
1166 $previous_was_identifier = false;
1167 $save_table_ref = true;
1168 } // end if (data == FROM)
1170 // here, do not 'continue' the loop, as we have more work for
1171 // reserved words below
1172 } // end if (type == alpha_reservedWord)
1174 // ==============================
1175 if ($arr[$i]['type'] == 'quote_backtick'
1176 || $arr[$i]['type'] == 'quote_double'
1177 || $arr[$i]['type'] == 'quote_single'
1178 || $arr[$i]['type'] == 'alpha_identifier'
1179 || ($arr[$i]['type'] == 'alpha_reservedWord'
1180 && $arr[$i]['forbidden'] == false)
1182 switch ($arr[$i]['type']) {
1183 case 'alpha_identifier':
1184 case 'alpha_reservedWord':
1186 * this is not a real reservedWord, because it's not
1187 * present in the list of forbidden words, for example
1188 * "storage" which can be used as an identifier
1190 * @todo avoid the pretty printing in color in this case
1192 $identifier = $arr[$i]['data'];
1193 break;
1195 case 'quote_backtick':
1196 case 'quote_double':
1197 case 'quote_single':
1198 $identifier = PMA_Util::unQuote($arr[$i]['data']);
1199 break;
1200 } // end switch
1202 if ($subresult['querytype'] == 'SELECT'
1203 && ! $in_group_concat
1204 && ! ($seen_subquery && $arr[$i - 1]['type'] == 'punct_bracket_close_round')
1206 if (!$seen_from) {
1207 if ($previous_was_identifier && isset($chain)) {
1208 // found alias for this select_expr, save it
1209 // but only if we got something in $chain
1210 // (for example, SELECT COUNT(*) AS cnt
1211 // puts nothing in $chain, so we avoid
1212 // setting the alias)
1213 $alias_for_select_expr = $identifier;
1214 } else {
1215 $chain[] = $identifier;
1216 $previous_was_identifier = true;
1218 } // end if !$previous_was_identifier
1219 } else {
1220 // ($seen_from)
1221 if ($save_table_ref && !$seen_end_of_table_ref) {
1222 if ($previous_was_identifier) {
1223 // found alias for table ref
1224 // save it for later
1225 $alias_for_table_ref = $identifier;
1226 } else {
1227 $chain[] = $identifier;
1228 $previous_was_identifier = true;
1230 } // end if ($previous_was_identifier)
1231 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
1232 } // end if (!$seen_from)
1233 } // end if (querytype SELECT)
1234 } // end if (quote_backtick or double quote or alpha_identifier)
1236 // ===================================
1237 if ($arr[$i]['type'] == 'punct_qualifier') {
1238 // to be able to detect an identifier following another
1239 $previous_was_identifier = false;
1240 continue;
1241 } // end if (punct_qualifier)
1244 * @todo check if 3 identifiers following one another -> error
1247 // s a v e a s e l e c t e x p r
1248 // finding a list separator or FROM
1249 // means that we must save the current chain of identifiers
1250 // into a select expression
1252 // for now, we only save a select expression if it contains
1253 // at least one identifier, as we are interested in checking
1254 // the columns and table names, so in "select * from persons",
1255 // the "*" is not saved
1257 if (isset($chain) && !$seen_end_of_table_ref
1258 && ((!$seen_from && $arr[$i]['type'] == 'punct_listsep')
1259 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM'))
1261 $size_chain = count($chain);
1262 $current_select_expr++;
1263 $subresult['select_expr'][$current_select_expr] = array(
1264 'expr' => '',
1265 'alias' => '',
1266 'db' => '',
1267 'table_name' => '',
1268 'table_true_name' => '',
1269 'column' => ''
1272 if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
1273 // we had found an alias for this select expression
1274 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
1275 unset($alias_for_select_expr);
1277 // there is at least a column
1278 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
1279 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
1281 // maybe a table
1282 if ($size_chain > 1) {
1283 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
1284 // we assume for now that this is also the true name
1285 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
1286 $subresult['select_expr'][$current_select_expr]['expr']
1287 = $subresult['select_expr'][$current_select_expr]['table_name']
1288 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1289 } // end if ($size_chain > 1)
1291 // maybe a db
1292 if ($size_chain > 2) {
1293 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
1294 $subresult['select_expr'][$current_select_expr]['expr']
1295 = $subresult['select_expr'][$current_select_expr]['db']
1296 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1297 } // end if ($size_chain > 2)
1298 unset($chain);
1301 * @todo explain this:
1303 if (($arr[$i]['type'] == 'alpha_reservedWord')
1304 && ($upper_data != 'FROM')
1306 $previous_was_identifier = true;
1309 } // end if (save a select expr)
1312 //======================================
1313 // s a v e a t a b l e r e f
1314 //======================================
1316 // maybe we just saw the end of table refs
1317 // but the last table ref has to be saved
1318 // or we are at the last token
1319 // or we just got a reserved word
1321 * @todo there could be another query after this one
1324 if (isset($chain) && $seen_from && $save_table_ref
1325 && ($arr[$i]['type'] == 'punct_listsep'
1326 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data != "AS")
1327 || $seen_end_of_table_ref
1328 || $i == $size - 1)
1331 $size_chain = count($chain);
1332 $current_table_ref++;
1333 $subresult['table_ref'][$current_table_ref] = array(
1334 'expr' => '',
1335 'db' => '',
1336 'table_name' => '',
1337 'table_alias' => '',
1338 'table_true_name' => ''
1340 if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
1341 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1342 unset($alias_for_table_ref);
1344 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1345 // we assume for now that this is also the true name
1346 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1347 $subresult['table_ref'][$current_table_ref]['expr']
1348 = $subresult['table_ref'][$current_table_ref]['table_name'];
1349 // maybe a db
1350 if ($size_chain > 1) {
1351 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1352 $subresult['table_ref'][$current_table_ref]['expr']
1353 = $subresult['table_ref'][$current_table_ref]['db']
1354 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1355 } // end if ($size_chain > 1)
1357 // add the table alias into the whole expression
1358 $subresult['table_ref'][$current_table_ref]['expr']
1359 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1361 unset($chain);
1362 $previous_was_identifier = true;
1363 //continue;
1365 } // end if (save a table ref)
1368 // when we have found all table refs,
1369 // for each table_ref alias, put the true name of the table
1370 // in the corresponding select expressions
1372 if (isset($current_table_ref)
1373 && ($seen_end_of_table_ref || $i == $size-1)
1374 && $subresult != $subresult_empty
1376 for ($tr=0; $tr <= $current_table_ref; $tr++) {
1377 $alias = $subresult['table_ref'][$tr]['table_alias'];
1378 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1379 for ($se=0; $se <= $current_select_expr; $se++) {
1380 if (isset($alias)
1381 && strlen($alias)
1382 && $subresult['select_expr'][$se]['table_true_name'] == $alias
1384 $subresult['select_expr'][$se]['table_true_name'] = $truename;
1385 } // end if (found the alias)
1386 } // end for (select expressions)
1388 } // end for (table refs)
1389 } // end if (set the true names)
1392 // e n d i n g l o o p #1
1393 // set the $previous_was_identifier to false if the current
1394 // token is not an identifier
1395 if (($arr[$i]['type'] != 'alpha_identifier')
1396 && ($arr[$i]['type'] != 'quote_double')
1397 && ($arr[$i]['type'] != 'quote_single')
1398 && ($arr[$i]['type'] != 'quote_backtick')
1400 $previous_was_identifier = false;
1401 } // end if
1403 // however, if we are on AS, we must keep the $previous_was_identifier
1404 if (($arr[$i]['type'] == 'alpha_reservedWord')
1405 && ($upper_data == 'AS')
1407 $previous_was_identifier = true;
1410 if (($arr[$i]['type'] == 'alpha_reservedWord')
1411 && ($upper_data =='ON' || $upper_data =='USING')
1413 $save_table_ref = false;
1414 } // end if (data == ON)
1416 if (($arr[$i]['type'] == 'alpha_reservedWord')
1417 && ($upper_data =='JOIN' || $upper_data =='FROM')
1419 $save_table_ref = true;
1420 } // end if (data == JOIN)
1423 * no need to check the end of table ref if we already did
1425 * @todo maybe add "&& $seen_from"
1427 if (!$seen_end_of_table_ref) {
1428 // if this is the last token, it implies that we have
1429 // seen the end of table references
1430 // Check for the end of table references
1432 // Note: if we are analyzing a GROUP_CONCAT clause,
1433 // we might find a word that seems to indicate that
1434 // we have found the end of table refs (like ORDER)
1435 // but it's a modifier of the GROUP_CONCAT so
1436 // it's not the real end of table refs
1437 if (($i == $size-1)
1438 || ($arr[$i]['type'] == 'alpha_reservedWord'
1439 && !$in_group_concat
1440 && isset($words_ending_table_ref[$upper_data]))
1442 $seen_end_of_table_ref = true;
1443 // to be able to save the last table ref, but do not
1444 // set it true if we found a word like "ON" that has
1445 // already set it to false
1446 if (isset($save_table_ref) && $save_table_ref != false) {
1447 $save_table_ref = true;
1448 } //end if
1450 } // end if (check for end of table ref)
1451 } //end if (!$seen_end_of_table_ref)
1453 if ($seen_end_of_table_ref) {
1454 $save_table_ref = false;
1455 } // end if
1457 } // end for $i (loop #1)
1459 //DEBUG
1461 if (isset($current_select_expr)) {
1462 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1463 echo "<br />";
1464 reset ($subresult['select_expr'][$trace]);
1465 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1466 echo "sel expr $trace $key => $val<br />\n";
1470 if (isset($current_table_ref)) {
1471 echo "current_table_ref = " . $current_table_ref . "<br>";
1472 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1474 echo "<br />";
1475 reset ($subresult['table_ref'][$trace]);
1476 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1477 echo "table ref $trace $key => $val<br />\n";
1481 // -------------------------------------------------------
1484 // loop #2: - queryflags
1485 // - querytype (for queries != 'SELECT')
1486 // - section_before_limit, section_after_limit
1488 // we will also need this queryflag in loop 2
1489 // so set it here
1490 if (isset($current_table_ref) && $current_table_ref > -1) {
1491 $subresult['queryflags']['select_from'] = 1;
1494 $section_before_limit = '';
1495 $section_after_limit = ''; // truly the section after the limit clause
1496 $seen_reserved_word = false;
1497 $seen_group = false;
1498 $seen_order = false;
1499 $seen_order_by = false;
1500 $in_group_by = false; // true when we are inside the GROUP BY clause
1501 $in_order_by = false; // true when we are inside the ORDER BY clause
1502 $in_having = false; // true when we are inside the HAVING clause
1503 $in_select_expr = false; // true when we are inside the select expr clause
1504 $in_where = false; // true when we are inside the WHERE clause
1505 $seen_limit = false; // true if we have seen a LIMIT clause
1506 $in_limit = false; // true when we are inside the LIMIT clause
1507 $after_limit = false; // true when we are after the LIMIT clause
1508 $in_from = false; // true when we are in the FROM clause
1509 $in_group_concat = false;
1510 $first_reserved_word = '';
1511 $current_identifier = '';
1512 $unsorted_query = $arr['raw']; // in case there is no ORDER BY
1513 $number_of_brackets = 0;
1514 $in_subquery = false;
1516 for ($i = 0; $i < $size; $i++) {
1517 //DEBUG echo "Loop2 <strong>" . $arr[$i]['data']
1518 //. "</strong> (" . $arr[$i]['type'] . ")<br />";
1520 // need_confirm
1522 // check for reserved words that will have to generate
1523 // a confirmation request later in sql.php
1524 // the cases are:
1525 // DROP TABLE
1526 // DROP DATABASE
1527 // ALTER TABLE... DROP
1528 // DELETE FROM...
1530 // this code is not used for confirmations coming from functions.js
1532 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1533 $number_of_brackets++;
1536 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1537 $number_of_brackets--;
1538 if ($number_of_brackets == 0) {
1539 $in_subquery = false;
1543 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1544 $upper_data = strtoupper($arr[$i]['data']);
1546 if ($upper_data == 'SELECT' && $number_of_brackets > 0) {
1547 $in_subquery = true;
1550 if (!$seen_reserved_word) {
1551 $first_reserved_word = $upper_data;
1552 $subresult['querytype'] = $upper_data;
1553 $seen_reserved_word = true;
1555 // if the first reserved word is DROP or DELETE,
1556 // we know this is a query that needs to be confirmed
1557 if ($first_reserved_word=='DROP'
1558 || $first_reserved_word == 'DELETE'
1559 || $first_reserved_word == 'TRUNCATE'
1561 $subresult['queryflags']['need_confirm'] = 1;
1564 if ($first_reserved_word=='SELECT') {
1565 $position_of_first_select = $i;
1568 } else {
1569 if ($upper_data == 'DROP' && $first_reserved_word == 'ALTER') {
1570 $subresult['queryflags']['need_confirm'] = 1;
1574 if ($upper_data == 'LIMIT' && ! $in_subquery) {
1575 $section_before_limit = substr($arr['raw'], 0, $arr[$i]['pos'] - 5);
1576 $in_limit = true;
1577 $seen_limit = true;
1578 $limit_clause = '';
1579 $in_order_by = false; // @todo maybe others to set false
1582 if ($upper_data == 'PROCEDURE') {
1583 $subresult['queryflags']['procedure'] = 1;
1584 $in_limit = false;
1585 $after_limit = true;
1588 * @todo set also to false if we find FOR UPDATE or LOCK IN SHARE MODE
1590 if ($upper_data == 'SELECT') {
1591 $in_select_expr = true;
1592 $select_expr_clause = '';
1594 if ($upper_data == 'DISTINCT' && !$in_group_concat) {
1595 $subresult['queryflags']['distinct'] = 1;
1598 if ($upper_data == 'UNION') {
1599 $subresult['queryflags']['union'] = 1;
1602 if ($upper_data == 'JOIN') {
1603 $subresult['queryflags']['join'] = 1;
1606 if ($upper_data == 'OFFSET') {
1607 $subresult['queryflags']['offset'] = 1;
1610 // if this is a real SELECT...FROM
1611 if ($upper_data == 'FROM'
1612 && isset($subresult['queryflags']['select_from'])
1613 && $subresult['queryflags']['select_from'] == 1
1615 $in_from = true;
1616 $from_clause = '';
1617 $in_select_expr = false;
1621 // (we could have less resetting of variables to false
1622 // if we trust that the query respects the standard
1623 // MySQL order for clauses)
1625 // we use $seen_group and $seen_order because we are looking
1626 // for the BY
1627 if ($upper_data == 'GROUP') {
1628 $seen_group = true;
1629 $seen_order = false;
1630 $in_having = false;
1631 $in_order_by = false;
1632 $in_where = false;
1633 $in_select_expr = false;
1634 $in_from = false;
1636 if ($upper_data == 'ORDER' && !$in_group_concat) {
1637 $seen_order = true;
1638 $seen_group = false;
1639 $in_having = false;
1640 $in_group_by = false;
1641 $in_where = false;
1642 $in_select_expr = false;
1643 $in_from = false;
1645 if ($upper_data == 'HAVING') {
1646 $in_having = true;
1647 $having_clause = '';
1648 $seen_group = false;
1649 $seen_order = false;
1650 $in_group_by = false;
1651 $in_order_by = false;
1652 $in_where = false;
1653 $in_select_expr = false;
1654 $in_from = false;
1657 if ($upper_data == 'WHERE') {
1658 $in_where = true;
1659 $where_clause = '';
1660 $where_clause_identifiers = array();
1661 $seen_group = false;
1662 $seen_order = false;
1663 $in_group_by = false;
1664 $in_order_by = false;
1665 $in_having = false;
1666 $in_select_expr = false;
1667 $in_from = false;
1670 if ($upper_data == 'BY') {
1671 if ($seen_group) {
1672 $in_group_by = true;
1673 $group_by_clause = '';
1675 if ($seen_order) {
1676 $seen_order_by = true;
1677 // Here we assume that the ORDER BY keywords took
1678 // exactly 8 characters.
1679 // We use PMA_substr() to be charset-safe; otherwise
1680 // if the table name contains accents, the unsorted
1681 // query would be missing some characters.
1682 $unsorted_query = PMA_substr(
1683 $arr['raw'], 0, $arr[$i]['pos'] - 8
1685 $in_order_by = true;
1686 $order_by_clause = '';
1690 // if we find one of the words that could end the clause
1691 if (isset($words_ending_clauses[$upper_data])) {
1693 $in_group_by = false;
1694 $in_order_by = false;
1695 $in_having = false;
1696 $in_where = false;
1697 $in_select_expr = false;
1698 $in_from = false;
1701 } // endif (reservedWord)
1704 // do not add a space after a function name
1706 * @todo can we combine loop 2 and loop 1? some code is repeated here...
1709 $sep = ' ';
1710 if ($arr[$i]['type'] == 'alpha_functionName') {
1711 $sep='';
1712 $upper_data = strtoupper($arr[$i]['data']);
1713 if ($upper_data =='GROUP_CONCAT') {
1714 $in_group_concat = true;
1715 $number_of_brackets_in_group_concat = 0;
1719 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1720 if ($in_group_concat) {
1721 $number_of_brackets_in_group_concat++;
1724 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1725 if ($in_group_concat) {
1726 $number_of_brackets_in_group_concat--;
1727 if ($number_of_brackets_in_group_concat == 0) {
1728 $in_group_concat = false;
1733 // do not add a space after an identifier if followed by a dot
1734 if ($arr[$i]['type'] == 'alpha_identifier'
1735 && $i < $size - 1 && $arr[$i + 1]['data'] == '.'
1737 $sep = '';
1740 // do not add a space after a dot if followed by an identifier
1741 if ($arr[$i]['data'] == '.' && $i < $size - 1
1742 && $arr[$i + 1]['type'] == 'alpha_identifier'
1744 $sep = '';
1747 if ($in_select_expr && $upper_data != 'SELECT'
1748 && $upper_data != 'DISTINCT'
1750 $select_expr_clause .= $arr[$i]['data'] . $sep;
1752 if ($in_from && $upper_data != 'FROM') {
1753 $from_clause .= $arr[$i]['data'] . $sep;
1755 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1756 $group_by_clause .= $arr[$i]['data'] . $sep;
1758 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1759 // add a space only before ASC or DESC
1760 // not around the dot between dbname and tablename
1761 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1762 $order_by_clause .= $sep;
1764 $order_by_clause .= $arr[$i]['data'];
1766 if ($in_having && $upper_data != 'HAVING') {
1767 $having_clause .= $arr[$i]['data'] . $sep;
1769 if ($in_where && $upper_data != 'WHERE') {
1770 $where_clause .= $arr[$i]['data'] . $sep;
1772 if (($arr[$i]['type'] == 'quote_backtick')
1773 || ($arr[$i]['type'] == 'alpha_identifier')
1775 $where_clause_identifiers[] = $arr[$i]['data'];
1779 // to grab the rest of the query after the ORDER BY clause
1780 if (isset($subresult['queryflags']['select_from'])
1781 && $subresult['queryflags']['select_from'] == 1
1782 && ! $in_order_by
1783 && $seen_order_by
1784 && $upper_data != 'BY'
1786 $unsorted_query .= $arr[$i]['data'];
1787 if ($arr[$i]['type'] != 'punct_bracket_open_round'
1788 && $arr[$i]['type'] != 'punct_bracket_close_round'
1789 && $arr[$i]['type'] != 'punct'
1791 $unsorted_query .= $sep;
1795 if ($in_limit) {
1796 if ($upper_data == 'OFFSET') {
1797 $limit_clause .= $sep;
1799 $limit_clause .= $arr[$i]['data'];
1800 if ($upper_data == 'LIMIT' || $upper_data == 'OFFSET') {
1801 $limit_clause .= $sep;
1804 if ($after_limit && $seen_limit) {
1805 $section_after_limit .= $arr[$i]['data'] . $sep;
1808 // clear $upper_data for next iteration
1809 $upper_data='';
1810 } // end for $i (loop #2)
1811 if (empty($section_before_limit)) {
1812 $section_before_limit = $arr['raw'];
1815 // -----------------------------------------------------
1816 // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
1817 // (for now, check only the first query)
1818 // (for now, identifiers are assumed to be backquoted)
1820 // If we find that we are dealing with a CREATE TABLE query,
1821 // we look for the next punct_bracket_open_round, which
1822 // introduces the fields list. Then, when we find a
1823 // quote_backtick, it must be a field, so we put it into
1824 // the create_table_fields array. Even if this field is
1825 // not a timestamp, it will be useful when logic has been
1826 // added for complete field attributes analysis.
1828 $seen_foreign = false;
1829 $seen_references = false;
1830 $seen_constraint = false;
1831 $foreign_key_number = -1;
1832 $seen_create_table = false;
1833 $seen_create = false;
1834 $seen_alter = false;
1835 $in_create_table_fields = false;
1836 $brackets_level = 0;
1837 $in_timestamp_options = false;
1838 $seen_default = false;
1840 for ($i = 0; $i < $size; $i++) {
1841 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1842 $upper_data = strtoupper($arr[$i]['data']);
1844 if ($upper_data == 'NOT' && $in_timestamp_options) {
1845 $create_table_fields[$current_identifier]['timestamp_not_null'] = true;
1849 if ($upper_data == 'CREATE') {
1850 $seen_create = true;
1853 if ($upper_data == 'ALTER') {
1854 $seen_alter = true;
1857 if ($upper_data == 'TABLE' && $seen_create) {
1858 $seen_create_table = true;
1859 $create_table_fields = array();
1862 if ($upper_data == 'CURRENT_TIMESTAMP') {
1863 if ($in_timestamp_options) {
1864 if ($seen_default) {
1865 $create_table_fields[$current_identifier]['default_current_timestamp'] = true;
1870 if ($upper_data == 'CONSTRAINT') {
1871 $foreign_key_number++;
1872 $seen_foreign = false;
1873 $seen_references = false;
1874 $seen_constraint = true;
1876 if ($upper_data == 'FOREIGN') {
1877 $seen_foreign = true;
1878 $seen_references = false;
1879 $seen_constraint = false;
1881 if ($upper_data == 'REFERENCES') {
1882 $seen_foreign = false;
1883 $seen_references = true;
1884 $seen_constraint = false;
1888 // Cases covered:
1890 // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1891 // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1893 // but we set ['on_delete'] or ['on_cascade'] to
1894 // CASCADE | SET_NULL | NO_ACTION | RESTRICT
1896 // ON UPDATE CURRENT_TIMESTAMP
1898 if ($upper_data == 'ON') {
1899 if (isset($arr[$i+1]) && $arr[$i+1]['type'] == 'alpha_reservedWord') {
1900 $second_upper_data = strtoupper($arr[$i+1]['data']);
1901 if ($second_upper_data == 'DELETE') {
1902 $clause = 'on_delete';
1904 if ($second_upper_data == 'UPDATE') {
1905 $clause = 'on_update';
1907 if (isset($clause)
1908 && ($arr[$i+2]['type'] == 'alpha_reservedWord'
1909 // ugly workaround because currently, NO is not
1910 // in the list of reserved words in sqlparser.data
1911 // (we got a bug report about not being able to use
1912 // 'no' as an identifier)
1913 || ($arr[$i+2]['type'] == 'alpha_identifier'
1914 && strtoupper($arr[$i+2]['data'])=='NO'))
1916 $third_upper_data = strtoupper($arr[$i+2]['data']);
1917 if ($third_upper_data == 'CASCADE'
1918 || $third_upper_data == 'RESTRICT'
1920 $value = $third_upper_data;
1921 } elseif ($third_upper_data == 'SET'
1922 || $third_upper_data == 'NO'
1924 if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
1925 $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
1927 } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
1928 if ($clause == 'on_update'
1929 && $in_timestamp_options
1931 $create_table_fields[$current_identifier]['on_update_current_timestamp'] = true;
1932 $seen_default = false;
1935 } else {
1936 $value = '';
1938 if (!empty($value)) {
1939 $foreign[$foreign_key_number][$clause] = $value;
1941 unset($clause);
1942 } // endif (isset($clause))
1946 } // end of reserved words analysis
1949 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1950 $brackets_level++;
1951 if ($seen_create_table && $brackets_level == 1) {
1952 $in_create_table_fields = true;
1957 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1958 $brackets_level--;
1959 if ($seen_references) {
1960 $seen_references = false;
1962 if ($seen_create_table && $brackets_level == 0) {
1963 $in_create_table_fields = false;
1967 if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
1968 $upper_data = strtoupper($arr[$i]['data']);
1969 if ($seen_create_table && $in_create_table_fields) {
1970 if ($upper_data == 'DEFAULT') {
1971 $seen_default = true;
1972 $create_table_fields[$current_identifier]['default_value'] = $arr[$i + 1]['data'];
1978 * @see @todo 2005-10-16 note: the "or" part here is a workaround for a bug
1980 if (($arr[$i]['type'] == 'alpha_columnType')
1981 || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)
1983 $upper_data = strtoupper($arr[$i]['data']);
1984 if ($seen_create_table && $in_create_table_fields
1985 && isset($current_identifier)
1987 $create_table_fields[$current_identifier]['type'] = $upper_data;
1988 if ($upper_data == 'TIMESTAMP') {
1989 $arr[$i]['type'] = 'alpha_columnType';
1990 $in_timestamp_options = true;
1991 } else {
1992 $in_timestamp_options = false;
1993 if ($upper_data == 'CHAR') {
1994 $arr[$i]['type'] = 'alpha_columnType';
2001 if ($arr[$i]['type'] == 'quote_backtick'
2002 || $arr[$i]['type'] == 'alpha_identifier'
2005 if ($arr[$i]['type'] == 'quote_backtick') {
2006 // remove backquotes
2007 $identifier = PMA_Util::unQuote($arr[$i]['data']);
2008 } else {
2009 $identifier = $arr[$i]['data'];
2012 if ($seen_create_table && $in_create_table_fields) {
2013 $current_identifier = $identifier;
2014 // we set this one even for non TIMESTAMP type
2015 $create_table_fields[$current_identifier]['timestamp_not_null'] = false;
2018 if ($seen_constraint) {
2019 $foreign[$foreign_key_number]['constraint'] = $identifier;
2022 if ($seen_foreign && $brackets_level > 0) {
2023 $foreign[$foreign_key_number]['index_list'][] = $identifier;
2026 if ($seen_references) {
2027 if ($seen_alter && $brackets_level > 0) {
2028 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
2029 // here, the first bracket level corresponds to the
2030 // bracket of CREATE TABLE
2031 // so if we are on level 2, it must be the index list
2032 // of the foreign key REFERENCES
2033 } elseif ($brackets_level > 1) {
2034 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
2035 } elseif ($arr[$i+1]['type'] == 'punct_qualifier') {
2036 // identifier is `db`.`table`
2037 // the first pass will pick the db name
2038 // the next pass will pick the table name
2039 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
2040 } else {
2041 // identifier is `table`
2042 $foreign[$foreign_key_number]['ref_table_name'] = $identifier;
2046 } // end for $i (loop #3)
2049 // Fill the $subresult array
2051 if (isset($create_table_fields)) {
2052 $subresult['create_table_fields'] = $create_table_fields;
2055 if (isset($foreign)) {
2056 $subresult['foreign_keys'] = $foreign;
2059 if (isset($select_expr_clause)) {
2060 $subresult['select_expr_clause'] = $select_expr_clause;
2062 if (isset($from_clause)) {
2063 $subresult['from_clause'] = $from_clause;
2065 if (isset($group_by_clause)) {
2066 $subresult['group_by_clause'] = $group_by_clause;
2068 if (isset($order_by_clause)) {
2069 $subresult['order_by_clause'] = $order_by_clause;
2071 if (isset($having_clause)) {
2072 $subresult['having_clause'] = $having_clause;
2074 if (isset($limit_clause)) {
2075 $subresult['limit_clause'] = $limit_clause;
2077 if (isset($where_clause)) {
2078 $subresult['where_clause'] = $where_clause;
2080 if (isset($unsorted_query) && !empty($unsorted_query)) {
2081 $subresult['unsorted_query'] = $unsorted_query;
2083 if (isset($where_clause_identifiers)) {
2084 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
2087 if (isset($position_of_first_select)) {
2088 $subresult['position_of_first_select'] = $position_of_first_select;
2089 $subresult['section_before_limit'] = $section_before_limit;
2090 $subresult['section_after_limit'] = $section_after_limit;
2093 // They are naughty and didn't have a trailing semi-colon,
2094 // then still handle it properly
2095 if ($subresult['querytype'] != '') {
2096 $result[] = $subresult;
2098 return $result;
2099 } // end of the "PMA_SQP_analyze()" function
2103 * Colorizes SQL queries html formatted
2105 * @param array $arr The SQL queries html formatted
2107 * @return array The colorized SQL queries
2109 * @todo check why adding a "\n" after the </span> would cause extra blanks
2110 * to be displayed: SELECT p . person_name
2112 * @access public
2114 function PMA_SQP_formatHtml_colorize($arr)
2116 $i = PMA_strpos($arr['type'], '_');
2117 $class = '';
2118 if ($i > 0) {
2119 $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
2122 $class .= 'syntax_' . $arr['type'];
2124 return '<span class="' . $class . '">'
2125 . htmlspecialchars($arr['data']) . '</span>';
2126 } // end of the "PMA_SQP_formatHtml_colorize()" function
2130 * Formats SQL queries to html
2132 * @param array $arr The SQL queries
2133 * @param string $mode mode of printing
2134 * @param integer $start_token starting token
2135 * @param integer $number_of_tokens number of tokens to format, -1 = all
2137 * @return string The formatted SQL queries
2139 * @access public
2141 function PMA_SQP_formatHtml(
2142 $arr, $mode='color', $start_token=0,
2143 $number_of_tokens=-1
2145 global $PMA_SQPdata_operators_docs, $PMA_SQPdata_functions_docs;
2147 //DEBUG echo 'in Format<pre>'; print_r($arr); echo '</pre>';
2148 // then check for an array
2149 if (! is_array($arr)) {
2150 return htmlspecialchars($arr);
2152 // first check for the SQL parser having hit an error
2153 if (PMA_SQP_isError()) {
2154 return htmlspecialchars($arr['raw']);
2156 // else do it properly
2157 switch ($mode) {
2158 case 'color':
2159 $str = '<span class="syntax">';
2160 $html_line_break = '<br />';
2161 $docu = true;
2162 break;
2163 case 'query_only':
2164 $str = '';
2165 $html_line_break = "\n";
2166 $docu = false;
2167 break;
2168 case 'text':
2169 $str = '';
2170 $html_line_break = '<br />';
2171 $docu = true;
2172 break;
2173 } // end switch
2174 // inner_sql is a span that exists for all cases, except query_only
2175 // of $cfg['SQP']['fmtType'] to make possible a replacement
2176 // for inline editing
2177 if ($mode!='query_only') {
2178 $str .= '<span class="inner_sql">';
2180 $close_docu_link = false;
2181 $indent = 0;
2182 $bracketlevel = 0;
2183 $functionlevel = 0;
2184 $infunction = false;
2185 $space_punct_listsep = ' ';
2186 $space_punct_listsep_function_name = ' ';
2187 // $space_alpha_reserved_word = '<br />'."\n";
2188 $space_alpha_reserved_word = ' ';
2190 $keywords_with_brackets_1before = array(
2191 'INDEX' => 1,
2192 'KEY' => 1,
2193 'ON' => 1,
2194 'USING' => 1
2197 $keywords_with_brackets_2before = array(
2198 'IGNORE' => 1,
2199 'INDEX' => 1,
2200 'INTO' => 1,
2201 'KEY' => 1,
2202 'PRIMARY' => 1,
2203 'PROCEDURE' => 1,
2204 'REFERENCES' => 1,
2205 'UNIQUE' => 1,
2206 'USE' => 1
2209 // These reserved words do NOT get a newline placed near them.
2210 $keywords_no_newline = array(
2211 'AS' => 1,
2212 'ASC' => 1,
2213 'DESC' => 1,
2214 'DISTINCT' => 1,
2215 'DUPLICATE' => 1,
2216 'HOUR' => 1,
2217 'INTERVAL' => 1,
2218 'IS' => 1,
2219 'LIKE' => 1,
2220 'NOT' => 1,
2221 'NULL' => 1,
2222 'ON' => 1,
2223 'REGEXP' => 1
2226 // These reserved words introduce a privilege list
2227 $keywords_priv_list = array(
2228 'GRANT' => 1,
2229 'REVOKE' => 1
2232 if ($number_of_tokens == -1) {
2233 $number_of_tokens = $arr['len'];
2235 $typearr = array();
2236 if ($number_of_tokens >= 0) {
2237 $typearr[0] = '';
2238 $typearr[1] = '';
2239 $typearr[2] = '';
2240 $typearr[3] = $arr[$start_token]['type'];
2243 $in_priv_list = false;
2244 for ($i = $start_token; $i < $number_of_tokens; $i++) {
2245 // DEBUG echo "Loop format <strong>" . $arr[$i]['data']
2246 // . "</strong> " . $arr[$i]['type'] . "<br />";
2247 $before = '';
2248 $after = '';
2249 // array_shift($typearr);
2251 0 prev2
2252 1 prev
2253 2 current
2254 3 next
2256 if (($i + 1) < $number_of_tokens) {
2257 $typearr[4] = $arr[$i + 1]['type'];
2258 } else {
2259 $typearr[4] = '';
2262 for ($j=0; $j<4; $j++) {
2263 $typearr[$j] = $typearr[$j + 1];
2266 switch ($typearr[2]) {
2267 case 'alpha_bitfield_constant_introducer':
2268 $before = ' ';
2269 $after = '';
2270 break;
2271 case 'white_newline':
2272 $before = '';
2273 break;
2274 case 'punct_bracket_open_round':
2275 $bracketlevel++;
2276 $infunction = false;
2277 $keyword_brackets_2before = isset(
2278 $keywords_with_brackets_2before[strtoupper($arr[$i - 2]['data'])]
2280 $keyword_brackets_1before = isset(
2281 $keywords_with_brackets_1before[strtoupper($arr[$i - 1]['data'])]
2283 // Make sure this array is sorted!
2284 if (($typearr[1] == 'alpha_functionName')
2285 || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
2286 || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex')
2287 || ($typearr[3] == 'digit_float')
2288 || ($typearr[0] == 'alpha_reservedWord' && $keyword_brackets_2before)
2289 || ($typearr[1] == 'alpha_reservedWord' && $keyword_brackets_1before)
2291 $functionlevel++;
2292 $infunction = true;
2293 $after .= ' ';
2294 } else {
2295 $indent++;
2296 if ($mode != 'query_only') {
2297 $after .= '<div class="syntax_indent' . $indent . '">';
2298 } else {
2299 $after .= ' ';
2302 break;
2303 case 'alpha_identifier':
2304 if (($typearr[1] == 'punct_qualifier')
2305 || ($typearr[3] == 'punct_qualifier')
2307 $after = '';
2308 $before = '';
2310 // for example SELECT 1 somealias
2311 if ($typearr[1] == 'digit_integer') {
2312 $before = ' ';
2314 if (($typearr[3] == 'alpha_columnType')
2315 || ($typearr[3] == 'alpha_identifier')
2317 $after .= ' ';
2319 break;
2320 case 'punct_user':
2321 case 'punct_qualifier':
2322 $before = '';
2323 $after = '';
2324 break;
2325 case 'punct_listsep':
2326 if ($infunction == true) {
2327 $after .= $space_punct_listsep_function_name;
2328 } else {
2329 $after .= $space_punct_listsep;
2331 break;
2332 case 'punct_queryend':
2333 if (($typearr[3] != 'comment_mysql')
2334 && ($typearr[3] != 'comment_ansi')
2335 && $typearr[3] != 'comment_c'
2337 $after .= $html_line_break;
2338 $after .= $html_line_break;
2340 $space_punct_listsep = ' ';
2341 $space_punct_listsep_function_name = ' ';
2342 $space_alpha_reserved_word = ' ';
2343 $in_priv_list = false;
2344 break;
2345 case 'comment_mysql':
2346 case 'comment_ansi':
2347 $after .= $html_line_break;
2348 break;
2349 case 'punct':
2350 $before .= ' ';
2351 if ($docu && isset($PMA_SQPdata_operators_docs[$arr[$i]['data']])
2352 && ($arr[$i]['data'] != '*' || in_array($arr[$i]['type'], array('digit_integer','digit_float','digit_hex')))
2354 $before .= PMA_Util::showMySQLDocu(
2355 'functions',
2356 $PMA_SQPdata_operators_docs[$arr[$i]['data']]['link'],
2357 false,
2358 $PMA_SQPdata_operators_docs[$arr[$i]['data']]['anchor'],
2359 true
2361 $after .= '</a>';
2364 // workaround for
2365 // select * from mytable limit 0,-1
2366 // (a side effect of this workaround is that
2367 // select 20 - 9
2368 // becomes
2369 // select 20 -9
2370 // )
2371 if ($typearr[3] != 'digit_integer') {
2372 $after .= ' ';
2374 break;
2375 case 'punct_bracket_close_round':
2376 // only close bracket level when it was opened before
2377 if ($bracketlevel > 0) {
2378 $bracketlevel--;
2379 if ($infunction == true) {
2380 $functionlevel--;
2381 $after .= ' ';
2382 $before .= ' ';
2383 } else {
2384 $indent--;
2385 $before .= ($mode != 'query_only' ? '</div>' : ' ');
2387 $infunction = ($functionlevel > 0) ? true : false;
2389 break;
2390 case 'alpha_columnType':
2391 if ($docu) {
2392 switch ($arr[$i]['data']) {
2393 case 'tinyint':
2394 case 'smallint':
2395 case 'mediumint':
2396 case 'int':
2397 case 'bigint':
2398 case 'decimal':
2399 case 'float':
2400 case 'double':
2401 case 'real':
2402 case 'bit':
2403 case 'boolean':
2404 case 'serial':
2405 $before .= PMA_Util::showMySQLDocu(
2406 'data-types',
2407 'numeric-types',
2408 false,
2410 true
2412 $after = '</a>' . $after;
2413 break;
2414 case 'date':
2415 case 'datetime':
2416 case 'timestamp':
2417 case 'time':
2418 case 'year':
2419 $before .= PMA_Util::showMySQLDocu(
2420 'data-types',
2421 'date-and-time-types',
2422 false,
2424 true
2426 $after = '</a>' . $after;
2427 break;
2428 case 'char':
2429 case 'varchar':
2430 case 'tinytext':
2431 case 'text':
2432 case 'mediumtext':
2433 case 'longtext':
2434 case 'binary':
2435 case 'varbinary':
2436 case 'tinyblob':
2437 case 'mediumblob':
2438 case 'blob':
2439 case 'longblob':
2440 case 'enum':
2441 case 'set':
2442 $before .= PMA_Util::showMySQLDocu(
2443 'data-types',
2444 'string-types',
2445 false,
2447 true
2449 $after = '</a>' . $after;
2450 break;
2453 if ($typearr[3] == 'alpha_columnAttrib') {
2454 $after .= ' ';
2456 if ($typearr[1] == 'alpha_columnType') {
2457 $before .= ' ';
2459 break;
2460 case 'alpha_columnAttrib':
2462 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
2463 // COLLATE LATIN1_GENERAL_CI DEFAULT
2464 if ($typearr[1] == 'alpha_identifier'
2465 || $typearr[1] == 'alpha_charset'
2467 $before .= ' ';
2469 if (($typearr[3] == 'alpha_columnAttrib')
2470 || ($typearr[3] == 'quote_single')
2471 || ($typearr[3] == 'digit_integer')
2473 $after .= ' ';
2475 // workaround for
2476 // AUTO_INCREMENT = 31DEFAULT_CHARSET = utf-8
2478 if ($typearr[2] == 'alpha_columnAttrib'
2479 && $typearr[3] == 'alpha_reservedWord'
2481 $before .= ' ';
2483 // workaround for
2484 // select * from mysql.user where binary user="root"
2485 // binary is marked as alpha_columnAttrib
2486 // but should be marked as a reserved word
2487 if (strtoupper($arr[$i]['data']) == 'BINARY'
2488 && $typearr[3] == 'alpha_identifier'
2490 $after .= ' ';
2492 break;
2493 case 'alpha_functionName':
2494 $funcname = strtoupper($arr[$i]['data']);
2495 if ($docu && isset($PMA_SQPdata_functions_docs[$funcname])) {
2496 $before .= PMA_Util::showMySQLDocu(
2497 'functions',
2498 $PMA_SQPdata_functions_docs[$funcname]['link'],
2499 false,
2500 $PMA_SQPdata_functions_docs[$funcname]['anchor'],
2501 true
2503 $after .= '</a>';
2505 break;
2506 case 'alpha_reservedWord':
2507 // do not uppercase the reserved word if we are calling
2508 // this function in query_only mode, because we need
2509 // the original query (otherwise we get problems with
2510 // semi-reserved words like "storage" which is legal
2511 // as an identifier name)
2513 if ($mode != 'query_only') {
2514 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
2517 if ((($typearr[1] != 'alpha_reservedWord')
2518 || (($typearr[1] == 'alpha_reservedWord')
2519 && isset($keywords_no_newline[strtoupper($arr[$i - 1]['data'])])))
2520 && ($typearr[1] != 'punct_level_plus')
2521 && (!isset($keywords_no_newline[$arr[$i]['data']]))
2523 // do not put a space before the first token, because
2524 // we use a lot of pattern matching checking for the
2525 // first reserved word at beginning of query
2526 // so do not put a newline before
2528 // also we must not be inside a privilege list
2529 if ($i > 0) {
2530 // the alpha_identifier exception is there to
2531 // catch cases like
2532 // GRANT SELECT ON mydb.mytable TO myuser@localhost
2533 // (else, we get mydb.mytableTO)
2535 // the quote_single exception is there to
2536 // catch cases like
2537 // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
2539 * @todo fix all cases and find why this happens
2542 if (!$in_priv_list
2543 || $typearr[1] == 'alpha_identifier'
2544 || $typearr[1] == 'quote_single'
2545 || $typearr[1] == 'white_newline'
2547 $before .= $space_alpha_reserved_word;
2549 } else {
2550 // on first keyword, check if it introduces a
2551 // privilege list
2552 if (isset($keywords_priv_list[$arr[$i]['data']])) {
2553 $in_priv_list = true;
2556 } else {
2557 $before .= ' ';
2560 switch ($arr[$i]['data']) {
2561 case 'CREATE':
2562 case 'ALTER':
2563 case 'DROP':
2564 case 'RENAME';
2565 case 'TRUNCATE':
2566 case 'ANALYZE':
2567 case 'ANALYSE':
2568 case 'OPTIMIZE':
2569 if ($docu) {
2570 switch ($arr[$i + 1]['data']) {
2571 case 'EVENT':
2572 case 'TABLE':
2573 case 'TABLESPACE':
2574 case 'FUNCTION':
2575 case 'INDEX':
2576 case 'PROCEDURE':
2577 case 'TRIGGER':
2578 case 'SERVER':
2579 case 'DATABASE':
2580 case 'VIEW':
2581 $before .= PMA_Util::showMySQLDocu(
2582 'SQL-Syntax',
2583 $arr[$i]['data'] . '_' . $arr[$i + 1]['data'],
2584 false,
2586 true
2588 $close_docu_link = true;
2589 break;
2591 if ($arr[$i + 1]['data'] == 'LOGFILE'
2592 && $arr[$i + 2]['data'] == 'GROUP'
2594 $before .= PMA_Util::showMySQLDocu(
2595 'SQL-Syntax',
2596 $arr[$i]['data'] . '_LOGFILE_GROUP',
2597 false,
2599 true
2601 $close_docu_link = true;
2604 if (!$in_priv_list) {
2605 $space_punct_listsep = $html_line_break;
2606 $space_alpha_reserved_word = ' ';
2608 break;
2609 case 'EVENT':
2610 case 'TABLESPACE':
2611 case 'TABLE':
2612 case 'FUNCTION':
2613 case 'INDEX':
2614 case 'PROCEDURE':
2615 case 'SERVER':
2616 case 'TRIGGER':
2617 case 'DATABASE':
2618 case 'VIEW':
2619 case 'GROUP':
2620 if ($close_docu_link) {
2621 $after = '</a>' . $after;
2622 $close_docu_link = false;
2624 break;
2625 case 'SET':
2626 if ($docu && ($i == 0 || $arr[$i - 1]['data'] != 'CHARACTER')) {
2627 $before .= PMA_Util::showMySQLDocu(
2628 'SQL-Syntax',
2629 $arr[$i]['data'],
2630 false,
2632 true
2634 $after = '</a>' . $after;
2636 if (!$in_priv_list) {
2637 $space_punct_listsep = $html_line_break;
2638 $space_alpha_reserved_word = ' ';
2640 break;
2641 case 'EXPLAIN':
2642 case 'DESCRIBE':
2643 case 'DELETE':
2644 case 'SHOW':
2645 case 'UPDATE':
2646 if ($docu) {
2647 $before .= PMA_Util::showMySQLDocu(
2648 'SQL-Syntax',
2649 $arr[$i]['data'],
2650 false,
2652 true
2654 $after = '</a>' . $after;
2656 if (!$in_priv_list) {
2657 $space_punct_listsep = $html_line_break;
2658 $space_alpha_reserved_word = ' ';
2660 break;
2661 case 'INSERT':
2662 case 'REPLACE':
2663 if ($docu) {
2664 $before .= PMA_Util::showMySQLDocu(
2665 'SQL-Syntax',
2666 $arr[$i]['data'],
2667 false,
2669 true
2671 $after = '</a>' . $after;
2673 if (!$in_priv_list) {
2674 $space_punct_listsep = $html_line_break;
2675 $space_alpha_reserved_word = $html_line_break;
2677 break;
2678 case 'VALUES':
2679 $space_punct_listsep = ' ';
2680 $space_alpha_reserved_word = $html_line_break;
2681 break;
2682 case 'SELECT':
2683 if ($docu) {
2684 $before .= PMA_Util::showMySQLDocu(
2685 'SQL-Syntax',
2686 'SELECT',
2687 false,
2689 true
2691 $after = '</a>' . $after;
2693 $space_punct_listsep = ' ';
2694 $space_alpha_reserved_word = $html_line_break;
2695 break;
2696 case 'CALL':
2697 case 'DO':
2698 case 'HANDLER':
2699 if ($docu) {
2700 $before .= PMA_Util::showMySQLDocu(
2701 'SQL-Syntax',
2702 $arr[$i]['data'],
2703 false,
2705 true
2707 $after = '</a>' . $after;
2709 break;
2710 default:
2711 if ($close_docu_link
2712 && in_array(
2713 $arr[$i]['data'],
2714 array('LIKE', 'NOT', 'IN', 'REGEXP', 'NULL')
2717 $after .= '</a>';
2718 $close_docu_link = false;
2719 } else if ($docu
2720 && isset($PMA_SQPdata_functions_docs[$arr[$i]['data']])
2722 /* Handle multi word statements first */
2723 if (isset($typearr[4])
2724 && $typearr[4] == 'alpha_reservedWord'
2725 && $typearr[3] == 'alpha_reservedWord'
2726 && isset($PMA_SQPdata_functions_docs[strtoupper(
2727 $arr[$i]['data'] . '_'
2728 . $arr[$i + 1]['data'] . '_'
2729 . $arr[$i + 2]['data']
2732 $tempname = strtoupper(
2733 $arr[$i]['data'] . '_'
2734 . $arr[$i + 1]['data'] . '_'
2735 . $arr[$i + 2]['data']
2737 $before .= PMA_Util::showMySQLDocu(
2738 'functions',
2739 $PMA_SQPdata_functions_docs[$tempname]['link'],
2740 false,
2741 $PMA_SQPdata_functions_docs[$tempname]['anchor'],
2742 true
2744 $close_docu_link = true;
2745 } else if (isset($typearr[3])
2746 && $typearr[3] == 'alpha_reservedWord'
2747 && isset($PMA_SQPdata_functions_docs[strtoupper(
2748 $arr[$i]['data'] . '_' . $arr[$i + 1]['data']
2751 $tempname = strtoupper(
2752 $arr[$i]['data'] . '_' . $arr[$i + 1]['data']
2754 $before .= PMA_Util::showMySQLDocu(
2755 'functions',
2756 $PMA_SQPdata_functions_docs[$tempname]['link'],
2757 false,
2758 $PMA_SQPdata_functions_docs[$tempname]['anchor'],
2759 true
2761 $close_docu_link = true;
2762 } else {
2763 $before .= PMA_Util::showMySQLDocu(
2764 'functions',
2765 $PMA_SQPdata_functions_docs[$arr[$i]['data']]['link'],
2766 false,
2767 $PMA_SQPdata_functions_docs[$arr[$i]['data']]['anchor'],
2768 true
2770 $after .= '</a>';
2773 break;
2774 } // end switch ($arr[$i]['data'])
2776 $after .= ' ';
2777 break;
2778 case 'digit_integer':
2779 case 'digit_float':
2780 case 'digit_hex':
2782 * @todo could there be other types preceding a digit?
2784 if ($typearr[1] == 'alpha_reservedWord') {
2785 $after .= ' ';
2787 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2788 $after .= ' ';
2790 if ($typearr[1] == 'alpha_columnAttrib') {
2791 $before .= ' ';
2793 break;
2794 case 'alpha_variable':
2795 $after = ' ';
2796 break;
2797 case 'quote_double':
2798 case 'quote_single':
2799 // workaround: for the query
2800 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
2801 // the @ is incorrectly marked as alpha_variable
2802 // in the parser, and here, the '%' gets a blank before,
2803 // which is a syntax error
2804 if ($typearr[1] != 'punct_user'
2805 && $typearr[1] != 'alpha_bitfield_constant_introducer'
2807 $before .= ' ';
2809 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2810 $after .= ' ';
2812 break;
2813 case 'quote_backtick':
2814 // here we check for punct_user to handle correctly
2815 // DEFINER = `username`@`%`
2816 // where @ is the punct_user and `%` is the quote_backtick
2817 if ($typearr[3] != 'punct_qualifier'
2818 && $typearr[3] != 'alpha_variable'
2819 && $typearr[3] != 'punct_user'
2821 $after .= ' ';
2823 if ($typearr[1] != 'punct_qualifier'
2824 && $typearr[1] != 'alpha_variable'
2825 && $typearr[1] != 'punct_user'
2827 $before .= ' ';
2829 break;
2830 default:
2831 break;
2832 } // end switch ($typearr[2])
2835 if ($typearr[3] != 'punct_qualifier') {
2836 $after .= ' ';
2838 $after .= "\n";
2840 $str .= $before;
2841 if ($mode=='color') {
2842 $str .= PMA_SQP_formatHTML_colorize($arr[$i]);
2843 } elseif ($mode == 'text') {
2844 $str .= htmlspecialchars($arr[$i]['data']);
2845 } else {
2846 $str .= $arr[$i]['data'];
2848 $str .= $after;
2849 } // end for
2850 // close unclosed indent levels
2851 while ($indent > 0) {
2852 $indent--;
2853 $str .= ($mode != 'query_only' ? '</div>' : ' ');
2855 /* End possibly unclosed documentation link */
2856 if ($close_docu_link) {
2857 $str .= '</a>';
2858 $close_docu_link = false;
2860 if ($mode!='query_only') {
2861 // close inner_sql span
2862 $str .= '</span>';
2864 if ($mode=='color') {
2865 // close syntax span
2866 $str .= '</span>';
2869 return $str;
2870 } // end of the "PMA_SQP_formatHtml()" function
2873 * Gets SQL queries with no format
2875 * @param array $arr The SQL queries list
2877 * @return string The SQL queries with no format
2879 * @access public
2881 function PMA_SQP_formatNone($arr)
2883 $formatted_sql = htmlspecialchars($arr['raw']);
2884 $formatted_sql = preg_replace(
2885 "@((\015\012)|(\015)|(\012)){3,}@",
2886 "\n\n",
2887 $formatted_sql
2890 return $formatted_sql;
2891 } // end of the "PMA_SQP_formatNone()" function
2894 * Checks whether a given name is MySQL reserved word
2896 * @param string $column The word to be checked
2898 * @return boolean whether true or false
2900 function PMA_SQP_isKeyWord($column)
2902 global $PMA_SQPdata_forbidden_word;
2903 return in_array(strtoupper($column), $PMA_SQPdata_forbidden_word);