Translation update done using Pootle.
[phpmyadmin.git] / libraries / sqlparser.lib.php
blobca30471c7b50bde98687ca2691576d2cbe126c08
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 * Minimum inclusion? (i.e. for the stylesheet builder)
36 if (! defined('PMA_MINIMUM_COMMON')) {
37 /**
38 * Include the string library as we use it heavily
40 include_once './libraries/string.lib.php';
42 /**
43 * Include data for the SQL Parser
45 include_once './libraries/sqlparser.data.php';
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 if (!defined('DEBUG_TIMING')) {
55 /**
56 * currently we don't need the $pos (token position in query)
57 * for other purposes than LIMIT clause verification,
58 * so many calls to this function do not include the 4th parameter
60 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
62 $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos);
63 $arrsize++;
64 } // end of the "PMA_SQP_arrayAdd()" function
65 } else {
66 /**
67 * This is debug variant of above.
68 * @ignore
70 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
72 global $timer;
74 $t = $timer;
75 $arr[] = array(
76 'type' => $type,
77 'data' => $data,
78 'pos' => $pos,
79 'time' => $t);
80 $timer = microtime();
81 $arrsize++;
82 } // end of the "PMA_SQP_arrayAdd()" function
83 } // end if... else...
86 /**
87 * Reset the error variable for the SQL parser
89 * @access public
91 function PMA_SQP_resetError()
93 global $SQP_errorString;
94 $SQP_errorString = '';
95 unset($SQP_errorString);
98 /**
99 * Get the contents of the error variable for the SQL parser
101 * @return string Error string from SQL parser
103 * @access public
105 function PMA_SQP_getErrorString()
107 global $SQP_errorString;
108 return isset($SQP_errorString) ? $SQP_errorString : '';
112 * Check if the SQL parser hit an error
114 * @return boolean error state
116 * @access public
118 function PMA_SQP_isError()
120 global $SQP_errorString;
121 return isset($SQP_errorString) && !empty($SQP_errorString);
125 * Set an error message for the system
127 * @param string The error message
128 * @param string The failing SQL query
130 * @access private
131 * @scope SQL Parser internal
133 function PMA_SQP_throwError($message, $sql)
135 global $SQP_errorString;
136 $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"
137 . '<pre>' . "\n"
138 . 'ERROR: ' . $message . "\n"
139 . 'SQL: ' . htmlspecialchars($sql) . "\n"
140 . '</pre>' . "\n";
142 } // end of the "PMA_SQP_throwError()" function
146 * Do display the bug report
148 * @param string The error message
149 * @param string The failing SQL query
151 * @access public
153 function PMA_SQP_bug($message, $sql)
155 global $SQP_errorString;
156 $debugstr = 'ERROR: ' . $message . "\n";
157 $debugstr .= 'MySQL: '.PMA_MYSQL_STR_VERSION . "\n";
158 $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS . ' ';
159 $debugstr .= PMA_USR_BROWSER_AGENT . ' ' . PMA_USR_BROWSER_VER . "\n";
160 $debugstr .= 'PMA: ' . PMA_VERSION . "\n";
161 $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION . ' ' . PHP_OS . "\n";
162 $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
163 $debugstr .= 'SQL: ' . htmlspecialchars($sql);
165 $encodedstr = $debugstr;
166 if (@function_exists('gzcompress')) {
167 $encodedstr = gzcompress($debugstr, 9);
169 $encodedstr = preg_replace(
170 "/(\015\012)|(\015)|(\012)/",
171 '<br />' . "\n",
172 chunk_split(base64_encode($encodedstr)));
175 $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:')
176 . '<br />' . "\n"
177 . '----' . __('BEGIN CUT') . '----' . '<br />' . "\n"
178 . $encodedstr . "\n"
179 . '----' . __('END CUT') . '----' . '<br />' . "\n";
181 $SQP_errorString .= '----' . __('BEGIN RAW') . '----<br />' . "\n"
182 . '<pre>' . "\n"
183 . $debugstr
184 . '</pre>' . "\n"
185 . '----' . __('END RAW') . '----<br />' . "\n";
187 } // end of the "PMA_SQP_bug()" function
191 * Parses the SQL queries
193 * @param string The SQL query list
195 * @return mixed Most of times, nothing...
197 * @global array The current PMA configuration
198 * @global array MySQL column attributes
199 * @global array MySQL reserved words
200 * @global array MySQL column types
201 * @global array MySQL function names
202 * @global array List of available character sets
203 * @global array List of available collations
205 * @access public
207 function PMA_SQP_parse($sql)
209 static $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word;
210 static $PMA_SQPdata_column_type;
211 static $PMA_SQPdata_function_name, $PMA_SQPdata_forbidden_word;
212 global $mysql_charsets, $mysql_collations_flat;
214 // Convert all line feeds to Unix style
215 $sql = str_replace("\r\n", "\n", $sql);
216 $sql = str_replace("\r", "\n", $sql);
218 $len = PMA_strlen($sql);
219 if ($len == 0) {
220 return array();
223 // Create local hashtables
224 if (!isset($PMA_SQPdata_column_attrib)) {
225 $PMA_SQPdata_column_attrib = array_flip(
226 $GLOBALS['PMA_SQPdata_column_attrib']
228 $PMA_SQPdata_function_name = array_flip(
229 $GLOBALS['PMA_SQPdata_function_name']
231 $PMA_SQPdata_reserved_word = array_flip(
232 $GLOBALS['PMA_SQPdata_reserved_word']
234 $PMA_SQPdata_forbidden_word = array_flip(
235 $GLOBALS['PMA_SQPdata_forbidden_word']
237 $PMA_SQPdata_column_type = array_flip(
238 $GLOBALS['PMA_SQPdata_column_type']
242 $sql_array = array();
243 $sql_array['raw'] = $sql;
244 $count1 = 0;
245 $count2 = 0;
246 $punct_queryend = ';';
247 $punct_qualifier = '.';
248 $punct_listsep = ',';
249 $punct_level_plus = '(';
250 $punct_level_minus = ')';
251 $punct_user = '@';
252 $digit_floatdecimal = '.';
253 $digit_hexset = 'x';
254 $bracket_list = '()[]{}';
255 $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
256 $allpunct_list_pair = array(
257 '!=' => 1,
258 '&&' => 1,
259 ':=' => 1,
260 '<<' => 1,
261 '<=' => 1,
262 '<=>' => 1,
263 '<>' => 1,
264 '>=' => 1,
265 '>>' => 1,
266 '||' => 1,
267 '==' => 1
269 $quote_list = '\'"`';
270 $arraysize = 0;
272 $previous_was_space = false;
273 $this_was_space = false;
274 $previous_was_bracket = false;
275 $this_was_bracket = false;
276 $previous_was_punct = false;
277 $this_was_punct = false;
278 $previous_was_listsep = false;
279 $this_was_listsep = false;
280 $previous_was_quote = false;
281 $this_was_quote = false;
283 while ($count2 < $len) {
284 $c = PMA_substr($sql, $count2, 1);
285 $count1 = $count2;
287 $previous_was_space = $this_was_space;
288 $this_was_space = false;
289 $previous_was_bracket = $this_was_bracket;
290 $this_was_bracket = false;
291 $previous_was_punct = $this_was_punct;
292 $this_was_punct = false;
293 $previous_was_listsep = $this_was_listsep;
294 $this_was_listsep = false;
295 $previous_was_quote = $this_was_quote;
296 $this_was_quote = false;
298 if (($c == "\n")) {
299 $this_was_space = true;
300 $count2++;
301 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
302 continue;
305 // Checks for white space
306 if (PMA_STR_isSpace($c)) {
307 $this_was_space = true;
308 $count2++;
309 continue;
312 // Checks for comment lines.
313 // MySQL style #
314 // C style /* */
315 // ANSI style --
316 $next_c = PMA_substr($sql, $count2 + 1, 1);
317 if (($c == '#')
318 || (($count2 + 1 < $len) && ($c == '/') && ($next_c == '*'))
319 || (($count2 + 2 == $len) && ($c == '-') && ($next_c == '-'))
320 || (($count2 + 2 < $len) && ($c == '-') && ($next_c == '-') && ((PMA_substr($sql, $count2 + 2, 1) <= ' ')))) {
321 $count2++;
322 $pos = 0;
323 $type = 'bad';
324 switch ($c) {
325 case '#':
326 $type = 'mysql';
327 case '-':
328 $type = 'ansi';
329 $pos = PMA_strpos($sql, "\n", $count2);
330 break;
331 case '/':
332 $type = 'c';
333 $pos = PMA_strpos($sql, '*/', $count2);
334 $pos += 2;
335 break;
336 default:
337 break;
338 } // end switch
339 $count2 = ($pos < $count2) ? $len : $pos;
340 $str = PMA_substr($sql, $count1, $count2 - $count1);
341 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
342 continue;
343 } // end if
345 // Checks for something inside quotation marks
346 if (PMA_strpos($quote_list, $c) !== false) {
347 $startquotepos = $count2;
348 $quotetype = $c;
349 $count2++;
350 $escaped = false;
351 $pos = $count2;
352 $oldpos = 0;
353 do {
354 $oldpos = $pos;
355 $pos = PMA_strpos(' ' . $sql, $quotetype, $oldpos + 1) - 1;
356 // ($pos === false)
357 if ($pos < 0) {
358 if ($c == '`') {
360 * Behave same as MySQL and accept end of query as end of backtick.
361 * I know this is sick, but MySQL behaves like this:
363 * SELECT * FROM `table
365 * is treated like
367 * SELECT * FROM `table`
369 $pos_quote_separator = PMA_strpos(' ' . $sql, $GLOBALS['sql_delimiter'], $oldpos + 1) - 1;
370 if ($pos_quote_separator < 0) {
371 $len += 1;
372 $sql .= '`';
373 $sql_array['raw'] .= '`';
374 $pos = $len;
375 } else {
376 $len += 1;
377 $sql = PMA_substr($sql, 0, $pos_quote_separator) . '`' . PMA_substr($sql, $pos_quote_separator);
378 $sql_array['raw'] = $sql;
379 $pos = $pos_quote_separator;
381 if (class_exists('PMA_Message') && $GLOBALS['is_ajax_request'] != true) {
382 PMA_Message::notice(__('Automatically appended backtick to the end of query!'))->display();
384 } else {
385 $debugstr = __('Unclosed quote') . ' @ ' . $startquotepos. "\n"
386 . 'STR: ' . htmlspecialchars($quotetype);
387 PMA_SQP_throwError($debugstr, $sql);
388 return $sql_array;
392 // If the quote is the first character, it can't be
393 // escaped, so don't do the rest of the code
394 if ($pos == 0) {
395 break;
398 // Checks for MySQL escaping using a \
399 // And checks for ANSI escaping using the $quotetype character
400 if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos) && $c != '`') {
401 $pos ++;
402 continue;
403 } elseif (($pos + 1 < $len) && (PMA_substr($sql, $pos, 1) == $quotetype) && (PMA_substr($sql, $pos + 1, 1) == $quotetype)) {
404 $pos = $pos + 2;
405 continue;
406 } else {
407 break;
409 } while ($len > $pos); // end do
411 $count2 = $pos;
412 $count2++;
413 $type = 'quote_';
414 switch ($quotetype) {
415 case '\'':
416 $type .= 'single';
417 $this_was_quote = true;
418 break;
419 case '"':
420 $type .= 'double';
421 $this_was_quote = true;
422 break;
423 case '`':
424 $type .= 'backtick';
425 $this_was_quote = true;
426 break;
427 default:
428 break;
429 } // end switch
430 $data = PMA_substr($sql, $count1, $count2 - $count1);
431 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
432 continue;
435 // Checks for brackets
436 if (PMA_strpos($bracket_list, $c) !== false) {
437 // All bracket tokens are only one item long
438 $this_was_bracket = true;
439 $count2++;
440 $type_type = '';
441 if (PMA_strpos('([{', $c) !== false) {
442 $type_type = 'open';
443 } else {
444 $type_type = 'close';
447 $type_style = '';
448 if (PMA_strpos('()', $c) !== false) {
449 $type_style = 'round';
450 } elseif (PMA_strpos('[]', $c) !== false) {
451 $type_style = 'square';
452 } else {
453 $type_style = 'curly';
456 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
457 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
458 continue;
461 /* DEBUG
462 echo '<pre>1';
463 var_dump(PMA_STR_isSqlIdentifier($c, false));
464 var_dump($c == '@');
465 var_dump($c == '.');
466 var_dump(PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1)));
467 var_dump($previous_was_space);
468 var_dump($previous_was_bracket);
469 var_dump($previous_was_listsep);
470 echo '</pre>';
473 // Checks for identifier (alpha or numeric)
474 if (PMA_STR_isSqlIdentifier($c, false)
475 || $c == '@'
476 || ($c == '.'
477 && PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1))
478 && ($previous_was_space || $previous_was_bracket || $previous_was_listsep))) {
480 /* DEBUG
481 echo PMA_substr($sql, $count2);
482 echo '<hr />';
485 $count2++;
488 * @todo a @ can also be present in expressions like
489 * FROM 'user'@'%' or TO 'user'@'%'
490 * in this case, the @ is wrongly marked as alpha_variable
492 $is_identifier = $previous_was_punct;
493 $is_sql_variable = $c == '@' && ! $previous_was_quote;
494 $is_user = $c == '@' && $previous_was_quote;
495 $is_digit = !$is_identifier && !$is_sql_variable && PMA_STR_isDigit($c);
496 $is_hex_digit = $is_digit && $c == '0' && $count2 < $len && PMA_substr($sql, $count2, 1) == 'x';
497 $is_float_digit = $c == '.';
498 $is_float_digit_exponent = false;
500 /* DEBUG
501 echo '<pre>2';
502 var_dump($is_identifier);
503 var_dump($is_sql_variable);
504 var_dump($is_digit);
505 var_dump($is_float_digit);
506 echo '</pre>';
509 // Fast skip is especially needed for huge BLOB data
510 if ($is_hex_digit) {
511 $count2++;
512 $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
513 if ($pos > $count2) {
514 $count2 = $pos;
516 unset($pos);
517 } elseif ($is_digit) {
518 $pos = strspn($sql, '0123456789', $count2);
519 if ($pos > $count2) {
520 $count2 = $pos;
522 unset($pos);
525 while (($count2 < $len) && PMA_STR_isSqlIdentifier(PMA_substr($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
526 $c2 = PMA_substr($sql, $count2, 1);
527 if ($is_sql_variable && ($c2 == '.')) {
528 $count2++;
529 continue;
531 if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
532 $count2++;
533 if (!$is_float_digit) {
534 $is_float_digit = true;
535 continue;
536 } else {
537 $debugstr = __('Invalid Identifer') . ' @ ' . ($count1+1) . "\n"
538 . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
539 PMA_SQP_throwError($debugstr, $sql);
540 return $sql_array;
543 if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
544 if (!$is_float_digit_exponent) {
545 $is_float_digit_exponent = true;
546 $is_float_digit = true;
547 $count2++;
548 continue;
549 } else {
550 $is_digit = false;
551 $is_float_digit = false;
554 if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && PMA_STR_isDigit($c2))) {
555 $count2++;
556 continue;
557 } else {
558 $is_digit = false;
559 $is_hex_digit = false;
562 $count2++;
563 } // end while
565 $l = $count2 - $count1;
566 $str = PMA_substr($sql, $count1, $l);
568 $type = '';
569 if ($is_digit || $is_float_digit || $is_hex_digit) {
570 $type = 'digit';
571 if ($is_float_digit) {
572 $type .= '_float';
573 } elseif ($is_hex_digit) {
574 $type .= '_hex';
575 } else {
576 $type .= '_integer';
578 } elseif ($is_user) {
579 $type = 'punct_user';
580 } elseif ($is_sql_variable != false) {
581 $type = 'alpha_variable';
582 } else {
583 $type = 'alpha';
584 } // end if... else....
585 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize, $count2);
587 continue;
590 // Checks for punct
591 if (PMA_strpos($allpunct_list, $c) !== false) {
592 while (($count2 < $len) && PMA_strpos($allpunct_list, PMA_substr($sql, $count2, 1)) !== false) {
593 $count2++;
595 $l = $count2 - $count1;
596 if ($l == 1) {
597 $punct_data = $c;
598 } else {
599 $punct_data = PMA_substr($sql, $count1, $l);
602 // Special case, sometimes, althought two characters are
603 // adjectent directly, they ACTUALLY need to be seperate
604 /* DEBUG
605 echo '<pre>';
606 var_dump($l);
607 var_dump($punct_data);
608 echo '</pre>';
611 if ($l == 1) {
612 $t_suffix = '';
613 switch ($punct_data) {
614 case $punct_queryend:
615 $t_suffix = '_queryend';
616 break;
617 case $punct_qualifier:
618 $t_suffix = '_qualifier';
619 $this_was_punct = true;
620 break;
621 case $punct_listsep:
622 $this_was_listsep = true;
623 $t_suffix = '_listsep';
624 break;
625 default:
626 break;
628 PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
629 } elseif ($punct_data == $GLOBALS['sql_delimiter'] || isset($allpunct_list_pair[$punct_data])) {
630 // Ok, we have one of the valid combined punct expressions
631 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
632 } else {
633 // Bad luck, lets split it up more
634 $first = $punct_data[0];
635 $first2 = $punct_data[0] . $punct_data[1];
636 $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
637 $last = $punct_data[$l - 1];
638 if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
639 $count2 = $count1 + 1;
640 $punct_data = $first;
641 } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || PMA_substr($sql, $count2, 1) <= ' '))) {
642 $count2 -= 2;
643 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
644 } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
645 $count2--;
646 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
647 } elseif ($last != '~') {
649 * @todo for negation operator, split in 2 tokens ?
650 * "select x&~1 from t"
651 * becomes "select x & ~ 1 from t" ?
653 $debugstr = __('Unknown Punctuation String') . ' @ ' . ($count1+1) . "\n"
654 . 'STR: ' . htmlspecialchars($punct_data);
655 PMA_SQP_throwError($debugstr, $sql);
656 return $sql_array;
658 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
659 continue;
660 } // end if... elseif... else
661 continue;
664 // DEBUG
665 $count2++;
667 $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
668 . 'STR: ' . PMA_substr($sql, $count1, $count2 - $count1) . "\n";
669 PMA_SQP_bug($debugstr, $sql);
670 return $sql_array;
672 } // end while ($count2 < $len)
675 echo '<pre>';
676 print_r($sql_array);
677 echo '</pre>';
680 if ($arraysize > 0) {
681 $t_next = $sql_array[0]['type'];
682 $t_prev = '';
683 $t_bef_prev = '';
684 $t_cur = '';
685 $d_next = $sql_array[0]['data'];
686 $d_prev = '';
687 $d_bef_prev = '';
688 $d_cur = '';
689 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
690 $d_prev_upper = '';
691 $d_bef_prev_upper = '';
692 $d_cur_upper = '';
695 for ($i = 0; $i < $arraysize; $i++) {
696 $t_bef_prev = $t_prev;
697 $t_prev = $t_cur;
698 $t_cur = $t_next;
699 $d_bef_prev = $d_prev;
700 $d_prev = $d_cur;
701 $d_cur = $d_next;
702 $d_bef_prev_upper = $d_prev_upper;
703 $d_prev_upper = $d_cur_upper;
704 $d_cur_upper = $d_next_upper;
705 if (($i + 1) < $arraysize) {
706 $t_next = $sql_array[$i + 1]['type'];
707 $d_next = $sql_array[$i + 1]['data'];
708 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
709 } else {
710 $t_next = '';
711 $d_next = '';
712 $d_next_upper = '';
715 //DEBUG echo "[prev: <strong>".$d_prev."</strong> ".$t_prev."][cur: <strong>".$d_cur."</strong> ".$t_cur."][next: <strong>".$d_next."</strong> ".$t_next."]<br />";
717 if ($t_cur == 'alpha') {
718 $t_suffix = '_identifier';
719 // for example: `thebit` bit(8) NOT NULL DEFAULT b'0'
720 if ($t_prev == 'alpha' && $d_prev == 'DEFAULT' && $d_cur == 'b' && $t_next == 'quote_single') {
721 $t_suffix = '_bitfield_constant_introducer';
722 } elseif (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
723 $t_suffix = '_identifier';
724 } elseif (($t_next == 'punct_bracket_open_round')
725 && isset($PMA_SQPdata_function_name[$d_cur_upper])) {
727 * @todo 2005-10-16: in the case of a CREATE TABLE containing
728 * a TIMESTAMP, since TIMESTAMP() is also a function, it's
729 * found here and the token is wrongly marked as alpha_functionName.
730 * But we compensate for this when analysing for timestamp_not_null
731 * later in this script.
733 * Same applies to CHAR vs. CHAR() function.
735 $t_suffix = '_functionName';
736 /* There are functions which might be as well column types */
737 } elseif (isset($PMA_SQPdata_column_type[$d_cur_upper])) {
738 $t_suffix = '_columnType';
741 * Temporary fix for BUG #621357 and #2027720
743 * @todo FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
745 if (($d_cur_upper == 'SET' || $d_cur_upper == 'BINARY') && $t_next != 'punct_bracket_open_round') {
746 $t_suffix = '_reservedWord';
748 //END OF TEMPORARY FIX
750 // CHARACTER is a synonym for CHAR, but can also be meant as
751 // CHARACTER SET. In this case, we have a reserved word.
752 if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
753 $t_suffix = '_reservedWord';
756 // experimental
757 // current is a column type, so previous must not be
758 // a reserved word but an identifier
759 // CREATE TABLE SG_Persons (first varchar(64))
761 //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
762 // $sql_array[$i-1]['type'] = 'alpha_identifier';
765 } elseif (isset($PMA_SQPdata_reserved_word[$d_cur_upper])) {
766 $t_suffix = '_reservedWord';
767 } elseif (isset($PMA_SQPdata_column_attrib[$d_cur_upper])) {
768 $t_suffix = '_columnAttrib';
769 // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
770 // it should be regarded as a reserved word.
771 if ($d_cur_upper == 'INNODB' && $d_prev_upper == 'SHOW' && $d_next_upper == 'STATUS') {
772 $t_suffix = '_reservedWord';
775 if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
776 $t_suffix = '_reservedWord';
778 // Binary as character set
779 if ($d_cur_upper == 'BINARY' && (
780 ($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
781 || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
782 || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
783 || $d_prev_upper == 'CHARSET'
784 ) && in_array($d_cur, $mysql_charsets)) {
785 $t_suffix = '_charset';
787 } elseif (in_array($d_cur, $mysql_charsets)
788 || in_array($d_cur, $mysql_collations_flat)
789 || ($d_cur{0} == '_' && in_array(substr($d_cur, 1), $mysql_charsets))) {
790 $t_suffix = '_charset';
791 } else {
792 // Do nothing
794 // check if present in the list of forbidden words
795 if ($t_suffix == '_reservedWord' && isset($PMA_SQPdata_forbidden_word[$d_cur_upper])) {
796 $sql_array[$i]['forbidden'] = true;
797 } else {
798 $sql_array[$i]['forbidden'] = false;
800 $sql_array[$i]['type'] .= $t_suffix;
802 } // end for
804 // Stores the size of the array inside the array, as count() is a slow
805 // operation.
806 $sql_array['len'] = $arraysize;
808 // DEBUG echo 'After parsing<pre>'; print_r($sql_array); echo '</pre>';
809 // Sends the data back
810 return $sql_array;
811 } // end of the "PMA_SQP_parse()" function
814 * Checks for token types being what we want...
816 * @param string String of type that we have
817 * @param string String of type that we want
819 * @return boolean result of check
821 * @access private
823 function PMA_SQP_typeCheck($toCheck, $whatWeWant)
825 $typeSeperator = '_';
826 if (strcmp($whatWeWant, $toCheck) == 0) {
827 return true;
828 } else {
829 if (strpos($whatWeWant, $typeSeperator) === false) {
830 return strncmp($whatWeWant, $toCheck, strpos($toCheck, $typeSeperator)) == 0;
831 } else {
832 return false;
839 * Analyzes SQL queries
841 * @param array The SQL queries
843 * @return array The analyzed SQL queries
845 * @access public
847 function PMA_SQP_analyze($arr)
849 if ($arr == array() || ! isset($arr['len'])) {
850 return array();
852 $result = array();
853 $size = $arr['len'];
854 $subresult = array(
855 'querytype' => '',
856 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
857 'position_of_first_select' => '', // the array index
858 'from_clause'=> '',
859 'group_by_clause'=> '',
860 'order_by_clause'=> '',
861 'having_clause' => '',
862 'limit_clause' => '',
863 'where_clause' => '',
864 'where_clause_identifiers' => array(),
865 'unsorted_query' => '',
866 'queryflags' => array(),
867 'select_expr' => array(),
868 'table_ref' => array(),
869 'foreign_keys' => array(),
870 'create_table_fields' => array()
872 $subresult_empty = $subresult;
873 $seek_queryend = false;
874 $seen_end_of_table_ref = false;
875 $number_of_brackets_in_extract = 0;
876 $number_of_brackets_in_group_concat = 0;
878 $number_of_brackets = 0;
879 $in_subquery = false;
880 $seen_subquery = false;
881 $seen_from = false;
883 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
884 // we must not use CURDATE as a table_ref
885 // so we track whether we are in the EXTRACT()
886 $in_extract = false;
888 // for GROUP_CONCAT(...)
889 $in_group_concat = false;
891 /* Description of analyzer results
893 * db, table, column, alias
894 * ------------------------
896 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
898 * The SELECT syntax (simplified) is
900 * SELECT
901 * select_expression,...
902 * [FROM [table_references]
905 * ['select_expr'] is filled with each expression, the key represents the
906 * expression position in the list (0-based) (so we don't lose track of
907 * multiple occurences of the same column).
909 * ['table_ref'] is filled with each table ref, same thing for the key.
911 * I create all sub-values empty, even if they are
912 * not present (for example no select_expression alias).
914 * There is a debug section at the end of loop #1, if you want to
915 * see the exact contents of select_expr and table_ref
917 * queryflags
918 * ----------
920 * In $subresult, array 'queryflags' is filled, according to what we
921 * find in the query.
923 * Currently, those are generated:
925 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
926 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
927 * ['queryflags']['distinct'] = 1; for a DISTINCT
928 * ['queryflags']['union'] = 1; for a UNION
929 * ['queryflags']['join'] = 1; for a JOIN
930 * ['queryflags']['offset'] = 1; for the presence of OFFSET
931 * ['queryflags']['procedure'] = 1; for the presence of PROCEDURE
933 * query clauses
934 * -------------
936 * The select is splitted in those clauses:
937 * ['select_expr_clause']
938 * ['from_clause']
939 * ['group_by_clause']
940 * ['order_by_clause']
941 * ['having_clause']
942 * ['limit_clause']
943 * ['where_clause']
945 * The identifiers of the WHERE clause are put into the array
946 * ['where_clause_identifier']
948 * For a SELECT, the whole query without the ORDER BY clause is put into
949 * ['unsorted_query']
951 * foreign keys
952 * ------------
953 * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
954 * analyzed and ['foreign_keys'] is an array filled with
955 * the constraint name, the index list,
956 * the REFERENCES table name and REFERENCES index list,
957 * and ON UPDATE | ON DELETE clauses
959 * position_of_first_select
960 * ------------------------
962 * The array index of the first SELECT we find. Will be used to
963 * insert a SQL_CALC_FOUND_ROWS.
965 * create_table_fields
966 * -------------------
968 * Used to detect the DEFAULT CURRENT_TIMESTAMP and
969 * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
970 * Also used to store the default value of the field.
971 * An array, each element is the identifier name.
972 * Note that for now, the timestamp_not_null element is created
973 * even for non-TIMESTAMP fields.
975 * Sub-elements: ['type'] which contains the column type
976 * optional (currently they are never false but can be absent):
977 * ['default_current_timestamp'] boolean
978 * ['on_update_current_timestamp'] boolean
979 * ['timestamp_not_null'] boolean
981 * section_before_limit, section_after_limit
982 * -----------------------------------------
984 * Marks the point of the query where we can insert a LIMIT clause;
985 * so the section_before_limit will contain the left part before
986 * a possible LIMIT clause
989 * End of description of analyzer results
992 // must be sorted
993 // TODO: current logic checks for only one word, so I put only the
994 // first word of the reserved expressions that end a table ref;
995 // maybe this is not ok (the first word might mean something else)
996 // $words_ending_table_ref = array(
997 // 'FOR UPDATE',
998 // 'GROUP BY',
999 // 'HAVING',
1000 // 'LIMIT',
1001 // 'LOCK IN SHARE MODE',
1002 // 'ORDER BY',
1003 // 'PROCEDURE',
1004 // 'UNION',
1005 // 'WHERE'
1006 // );
1007 $words_ending_table_ref = array(
1008 'FOR' => 1,
1009 'GROUP' => 1,
1010 'HAVING' => 1,
1011 'LIMIT' => 1,
1012 'LOCK' => 1,
1013 'ORDER' => 1,
1014 'PROCEDURE' => 1,
1015 'UNION' => 1,
1016 'WHERE' => 1
1019 $words_ending_clauses = array(
1020 'FOR' => 1,
1021 'LIMIT' => 1,
1022 'LOCK' => 1,
1023 'PROCEDURE' => 1,
1024 'UNION' => 1
1027 $supported_query_types = array(
1028 'SELECT' => 1,
1030 // Support for these additional query types will come later on.
1031 'DELETE' => 1,
1032 'INSERT' => 1,
1033 'REPLACE' => 1,
1034 'TRUNCATE' => 1,
1035 'UPDATE' => 1,
1036 'EXPLAIN' => 1,
1037 'DESCRIBE' => 1,
1038 'SHOW' => 1,
1039 'CREATE' => 1,
1040 'SET' => 1,
1041 'ALTER' => 1
1045 // loop #1 for each token: select_expr, table_ref for SELECT
1047 for ($i = 0; $i < $size; $i++) {
1048 //DEBUG echo "Loop1 <strong>" . $arr[$i]['data'] . "</strong> (" . $arr[$i]['type'] . ")<br />";
1050 // High speed seek for locating the end of the current query
1051 if ($seek_queryend == true) {
1052 if ($arr[$i]['type'] == 'punct_queryend') {
1053 $seek_queryend = false;
1054 } else {
1055 continue;
1056 } // end if (type == punct_queryend)
1057 } // end if ($seek_queryend)
1060 * Note: do not split if this is a punct_queryend for the first and only query
1061 * @todo when we find a UNION, should we split in another subresult?
1063 if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
1064 $result[] = $subresult;
1065 $subresult = $subresult_empty;
1066 continue;
1067 } // end if (type == punct_queryend)
1069 // ==============================================================
1070 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1071 $number_of_brackets++;
1072 if ($in_extract) {
1073 $number_of_brackets_in_extract++;
1075 if ($in_group_concat) {
1076 $number_of_brackets_in_group_concat++;
1079 // ==============================================================
1080 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1081 $number_of_brackets--;
1082 if ($number_of_brackets == 0) {
1083 $in_subquery = false;
1085 if ($in_extract) {
1086 $number_of_brackets_in_extract--;
1087 if ($number_of_brackets_in_extract == 0) {
1088 $in_extract = false;
1091 if ($in_group_concat) {
1092 $number_of_brackets_in_group_concat--;
1093 if ($number_of_brackets_in_group_concat == 0) {
1094 $in_group_concat = false;
1099 if ($in_subquery) {
1101 * skip the subquery to avoid setting
1102 * select_expr or table_ref with the contents
1103 * of this subquery; this is to avoid a bug when
1104 * trying to edit the results of
1105 * select * from child where not exists (select id from
1106 * parent where child.parent_id = parent.id);
1108 continue;
1110 // ==============================================================
1111 if ($arr[$i]['type'] == 'alpha_functionName') {
1112 $upper_data = strtoupper($arr[$i]['data']);
1113 if ($upper_data =='EXTRACT') {
1114 $in_extract = true;
1115 $number_of_brackets_in_extract = 0;
1117 if ($upper_data =='GROUP_CONCAT') {
1118 $in_group_concat = true;
1119 $number_of_brackets_in_group_concat = 0;
1123 // ==============================================================
1124 if ($arr[$i]['type'] == 'alpha_reservedWord'
1125 //&& $arr[$i]['forbidden'] == false) {
1127 // We don't know what type of query yet, so run this
1128 if ($subresult['querytype'] == '') {
1129 $subresult['querytype'] = strtoupper($arr[$i]['data']);
1130 } // end if (querytype was empty)
1132 // Check if we support this type of query
1133 if (!isset($supported_query_types[$subresult['querytype']])) {
1134 // Skip ahead to the next one if we don't
1135 $seek_queryend = true;
1136 continue;
1137 } // end if (query not supported)
1139 // upper once
1140 $upper_data = strtoupper($arr[$i]['data']);
1142 * @todo reset for each query?
1145 if ($upper_data == 'SELECT') {
1146 if ($number_of_brackets > 0) {
1147 $in_subquery = true;
1148 $seen_subquery = true;
1149 // this is a subquery so do not analyze inside it
1150 continue;
1152 $seen_from = false;
1153 $previous_was_identifier = false;
1154 $current_select_expr = -1;
1155 $seen_end_of_table_ref = false;
1156 } // end if (data == SELECT)
1158 if ($upper_data =='FROM' && !$in_extract) {
1159 $current_table_ref = -1;
1160 $seen_from = true;
1161 $previous_was_identifier = false;
1162 $save_table_ref = true;
1163 } // end if (data == FROM)
1165 // here, do not 'continue' the loop, as we have more work for
1166 // reserved words below
1167 } // end if (type == alpha_reservedWord)
1169 // ==============================
1170 if ($arr[$i]['type'] == 'quote_backtick'
1171 || $arr[$i]['type'] == 'quote_double'
1172 || $arr[$i]['type'] == 'quote_single'
1173 || $arr[$i]['type'] == 'alpha_identifier'
1174 || ($arr[$i]['type'] == 'alpha_reservedWord'
1175 && $arr[$i]['forbidden'] == false)) {
1177 switch ($arr[$i]['type']) {
1178 case 'alpha_identifier':
1179 case 'alpha_reservedWord':
1181 * this is not a real reservedWord, because it's not
1182 * present in the list of forbidden words, for example
1183 * "storage" which can be used as an identifier
1185 * @todo avoid the pretty printing in color in this case
1187 $identifier = $arr[$i]['data'];
1188 break;
1190 case 'quote_backtick':
1191 case 'quote_double':
1192 case 'quote_single':
1193 $identifier = PMA_unQuote($arr[$i]['data']);
1194 break;
1195 } // end switch
1197 if ($subresult['querytype'] == 'SELECT'
1198 && ! $in_group_concat
1199 && ! ($seen_subquery && $arr[$i - 1]['type'] == 'punct_bracket_close_round')) {
1200 if (!$seen_from) {
1201 if ($previous_was_identifier && isset($chain)) {
1202 // found alias for this select_expr, save it
1203 // but only if we got something in $chain
1204 // (for example, SELECT COUNT(*) AS cnt
1205 // puts nothing in $chain, so we avoid
1206 // setting the alias)
1207 $alias_for_select_expr = $identifier;
1208 } else {
1209 $chain[] = $identifier;
1210 $previous_was_identifier = true;
1212 } // end if !$previous_was_identifier
1213 } else {
1214 // ($seen_from)
1215 if ($save_table_ref && !$seen_end_of_table_ref) {
1216 if ($previous_was_identifier) {
1217 // found alias for table ref
1218 // save it for later
1219 $alias_for_table_ref = $identifier;
1220 } else {
1221 $chain[] = $identifier;
1222 $previous_was_identifier = true;
1224 } // end if ($previous_was_identifier)
1225 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
1226 } // end if (!$seen_from)
1227 } // end if (querytype SELECT)
1228 } // end if (quote_backtick or double quote or alpha_identifier)
1230 // ===================================
1231 if ($arr[$i]['type'] == 'punct_qualifier') {
1232 // to be able to detect an identifier following another
1233 $previous_was_identifier = false;
1234 continue;
1235 } // end if (punct_qualifier)
1238 * @todo check if 3 identifiers following one another -> error
1241 // s a v e a s e l e c t e x p r
1242 // finding a list separator or FROM
1243 // means that we must save the current chain of identifiers
1244 // into a select expression
1246 // for now, we only save a select expression if it contains
1247 // at least one identifier, as we are interested in checking
1248 // the columns and table names, so in "select * from persons",
1249 // the "*" is not saved
1251 if (isset($chain) && !$seen_end_of_table_ref
1252 && ((!$seen_from && $arr[$i]['type'] == 'punct_listsep')
1253 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM'))) {
1254 $size_chain = count($chain);
1255 $current_select_expr++;
1256 $subresult['select_expr'][$current_select_expr] = array(
1257 'expr' => '',
1258 'alias' => '',
1259 'db' => '',
1260 'table_name' => '',
1261 'table_true_name' => '',
1262 'column' => ''
1265 if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
1266 // we had found an alias for this select expression
1267 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
1268 unset($alias_for_select_expr);
1270 // there is at least a column
1271 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
1272 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
1274 // maybe a table
1275 if ($size_chain > 1) {
1276 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
1277 // we assume for now that this is also the true name
1278 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
1279 $subresult['select_expr'][$current_select_expr]['expr']
1280 = $subresult['select_expr'][$current_select_expr]['table_name']
1281 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1282 } // end if ($size_chain > 1)
1284 // maybe a db
1285 if ($size_chain > 2) {
1286 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
1287 $subresult['select_expr'][$current_select_expr]['expr']
1288 = $subresult['select_expr'][$current_select_expr]['db']
1289 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1290 } // end if ($size_chain > 2)
1291 unset($chain);
1294 * @todo explain this:
1296 if (($arr[$i]['type'] == 'alpha_reservedWord')
1297 && ($upper_data != 'FROM')) {
1298 $previous_was_identifier = true;
1301 } // end if (save a select expr)
1304 //======================================
1305 // s a v e a t a b l e r e f
1306 //======================================
1308 // maybe we just saw the end of table refs
1309 // but the last table ref has to be saved
1310 // or we are at the last token
1311 // or we just got a reserved word
1313 * @todo there could be another query after this one
1316 if (isset($chain) && $seen_from && $save_table_ref
1317 && ($arr[$i]['type'] == 'punct_listsep'
1318 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
1319 || $seen_end_of_table_ref
1320 || $i==$size-1)) {
1322 $size_chain = count($chain);
1323 $current_table_ref++;
1324 $subresult['table_ref'][$current_table_ref] = array(
1325 'expr' => '',
1326 'db' => '',
1327 'table_name' => '',
1328 'table_alias' => '',
1329 'table_true_name' => ''
1331 if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
1332 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1333 unset($alias_for_table_ref);
1335 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1336 // we assume for now that this is also the true name
1337 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1338 $subresult['table_ref'][$current_table_ref]['expr']
1339 = $subresult['table_ref'][$current_table_ref]['table_name'];
1340 // maybe a db
1341 if ($size_chain > 1) {
1342 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1343 $subresult['table_ref'][$current_table_ref]['expr']
1344 = $subresult['table_ref'][$current_table_ref]['db']
1345 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1346 } // end if ($size_chain > 1)
1348 // add the table alias into the whole expression
1349 $subresult['table_ref'][$current_table_ref]['expr']
1350 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1352 unset($chain);
1353 $previous_was_identifier = true;
1354 //continue;
1356 } // end if (save a table ref)
1359 // when we have found all table refs,
1360 // for each table_ref alias, put the true name of the table
1361 // in the corresponding select expressions
1363 if (isset($current_table_ref) && ($seen_end_of_table_ref || $i == $size-1) && $subresult != $subresult_empty) {
1364 for ($tr=0; $tr <= $current_table_ref; $tr++) {
1365 $alias = $subresult['table_ref'][$tr]['table_alias'];
1366 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1367 for ($se=0; $se <= $current_select_expr; $se++) {
1368 if (isset($alias)
1369 && strlen($alias)
1370 && $subresult['select_expr'][$se]['table_true_name'] == $alias
1372 $subresult['select_expr'][$se]['table_true_name'] = $truename;
1373 } // end if (found the alias)
1374 } // end for (select expressions)
1376 } // end for (table refs)
1377 } // end if (set the true names)
1380 // e n d i n g l o o p #1
1381 // set the $previous_was_identifier to false if the current
1382 // token is not an identifier
1383 if (($arr[$i]['type'] != 'alpha_identifier')
1384 && ($arr[$i]['type'] != 'quote_double')
1385 && ($arr[$i]['type'] != 'quote_single')
1386 && ($arr[$i]['type'] != 'quote_backtick')) {
1387 $previous_was_identifier = false;
1388 } // end if
1390 // however, if we are on AS, we must keep the $previous_was_identifier
1391 if (($arr[$i]['type'] == 'alpha_reservedWord')
1392 && ($upper_data == 'AS')) {
1393 $previous_was_identifier = true;
1396 if (($arr[$i]['type'] == 'alpha_reservedWord')
1397 && ($upper_data =='ON' || $upper_data =='USING')) {
1398 $save_table_ref = false;
1399 } // end if (data == ON)
1401 if (($arr[$i]['type'] == 'alpha_reservedWord')
1402 && ($upper_data =='JOIN' || $upper_data =='FROM')) {
1403 $save_table_ref = true;
1404 } // end if (data == JOIN)
1407 * no need to check the end of table ref if we already did
1409 * @todo maybe add "&& $seen_from"
1411 if (!$seen_end_of_table_ref) {
1412 // if this is the last token, it implies that we have
1413 // seen the end of table references
1414 // Check for the end of table references
1416 // Note: if we are analyzing a GROUP_CONCAT clause,
1417 // we might find a word that seems to indicate that
1418 // we have found the end of table refs (like ORDER)
1419 // but it's a modifier of the GROUP_CONCAT so
1420 // it's not the real end of table refs
1421 if (($i == $size-1)
1422 || ($arr[$i]['type'] == 'alpha_reservedWord'
1423 && !$in_group_concat
1424 && isset($words_ending_table_ref[$upper_data]))) {
1425 $seen_end_of_table_ref = true;
1426 // to be able to save the last table ref, but do not
1427 // set it true if we found a word like "ON" that has
1428 // already set it to false
1429 if (isset($save_table_ref) && $save_table_ref != false) {
1430 $save_table_ref = true;
1431 } //end if
1433 } // end if (check for end of table ref)
1434 } //end if (!$seen_end_of_table_ref)
1436 if ($seen_end_of_table_ref) {
1437 $save_table_ref = false;
1438 } // end if
1440 } // end for $i (loop #1)
1442 //DEBUG
1444 if (isset($current_select_expr)) {
1445 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1446 echo "<br />";
1447 reset ($subresult['select_expr'][$trace]);
1448 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1449 echo "sel expr $trace $key => $val<br />\n";
1453 if (isset($current_table_ref)) {
1454 echo "current_table_ref = " . $current_table_ref . "<br>";
1455 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1457 echo "<br />";
1458 reset ($subresult['table_ref'][$trace]);
1459 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1460 echo "table ref $trace $key => $val<br />\n";
1464 // -------------------------------------------------------
1467 // loop #2: - queryflags
1468 // - querytype (for queries != 'SELECT')
1469 // - section_before_limit, section_after_limit
1471 // we will also need this queryflag in loop 2
1472 // so set it here
1473 if (isset($current_table_ref) && $current_table_ref > -1) {
1474 $subresult['queryflags']['select_from'] = 1;
1477 $section_before_limit = '';
1478 $section_after_limit = ''; // truly the section after the limit clause
1479 $seen_reserved_word = false;
1480 $seen_group = false;
1481 $seen_order = false;
1482 $seen_order_by = false;
1483 $in_group_by = false; // true when we are inside the GROUP BY clause
1484 $in_order_by = false; // true when we are inside the ORDER BY clause
1485 $in_having = false; // true when we are inside the HAVING clause
1486 $in_select_expr = false; // true when we are inside the select expr clause
1487 $in_where = false; // true when we are inside the WHERE clause
1488 $seen_limit = false; // true if we have seen a LIMIT clause
1489 $in_limit = false; // true when we are inside the LIMIT clause
1490 $after_limit = false; // true when we are after the LIMIT clause
1491 $in_from = false; // true when we are in the FROM clause
1492 $in_group_concat = false;
1493 $first_reserved_word = '';
1494 $current_identifier = '';
1495 $unsorted_query = $arr['raw']; // in case there is no ORDER BY
1496 $number_of_brackets = 0;
1497 $in_subquery = false;
1499 for ($i = 0; $i < $size; $i++) {
1500 //DEBUG echo "Loop2 <strong>" . $arr[$i]['data'] . "</strong> (" . $arr[$i]['type'] . ")<br />";
1502 // need_confirm
1504 // check for reserved words that will have to generate
1505 // a confirmation request later in sql.php
1506 // the cases are:
1507 // DROP TABLE
1508 // DROP DATABASE
1509 // ALTER TABLE... DROP
1510 // DELETE FROM...
1512 // this code is not used for confirmations coming from functions.js
1514 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1515 $number_of_brackets++;
1518 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1519 $number_of_brackets--;
1520 if ($number_of_brackets == 0) {
1521 $in_subquery = false;
1525 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1526 $upper_data = strtoupper($arr[$i]['data']);
1528 if ($upper_data == 'SELECT' && $number_of_brackets > 0) {
1529 $in_subquery = true;
1532 if (!$seen_reserved_word) {
1533 $first_reserved_word = $upper_data;
1534 $subresult['querytype'] = $upper_data;
1535 $seen_reserved_word = true;
1537 // if the first reserved word is DROP or DELETE,
1538 // we know this is a query that needs to be confirmed
1539 if ($first_reserved_word=='DROP'
1540 || $first_reserved_word == 'DELETE'
1541 || $first_reserved_word == 'TRUNCATE') {
1542 $subresult['queryflags']['need_confirm'] = 1;
1545 if ($first_reserved_word=='SELECT') {
1546 $position_of_first_select = $i;
1549 } else {
1550 if ($upper_data == 'DROP' && $first_reserved_word == 'ALTER') {
1551 $subresult['queryflags']['need_confirm'] = 1;
1555 if ($upper_data == 'LIMIT' && ! $in_subquery) {
1556 $section_before_limit = substr($arr['raw'], 0, $arr[$i]['pos'] - 5);
1557 $in_limit = true;
1558 $seen_limit = true;
1559 $limit_clause = '';
1560 $in_order_by = false; // @todo maybe others to set false
1563 if ($upper_data == 'PROCEDURE') {
1564 $subresult['queryflags']['procedure'] = 1;
1565 $in_limit = false;
1566 $after_limit = true;
1569 * @todo set also to false if we find FOR UPDATE or LOCK IN SHARE MODE
1571 if ($upper_data == 'SELECT') {
1572 $in_select_expr = true;
1573 $select_expr_clause = '';
1575 if ($upper_data == 'DISTINCT' && !$in_group_concat) {
1576 $subresult['queryflags']['distinct'] = 1;
1579 if ($upper_data == 'UNION') {
1580 $subresult['queryflags']['union'] = 1;
1583 if ($upper_data == 'JOIN') {
1584 $subresult['queryflags']['join'] = 1;
1587 if ($upper_data == 'OFFSET') {
1588 $subresult['queryflags']['offset'] = 1;
1591 // if this is a real SELECT...FROM
1592 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1593 $in_from = true;
1594 $from_clause = '';
1595 $in_select_expr = false;
1599 // (we could have less resetting of variables to false
1600 // if we trust that the query respects the standard
1601 // MySQL order for clauses)
1603 // we use $seen_group and $seen_order because we are looking
1604 // for the BY
1605 if ($upper_data == 'GROUP') {
1606 $seen_group = true;
1607 $seen_order = false;
1608 $in_having = false;
1609 $in_order_by = false;
1610 $in_where = false;
1611 $in_select_expr = false;
1612 $in_from = false;
1614 if ($upper_data == 'ORDER' && !$in_group_concat) {
1615 $seen_order = true;
1616 $seen_group = false;
1617 $in_having = false;
1618 $in_group_by = false;
1619 $in_where = false;
1620 $in_select_expr = false;
1621 $in_from = false;
1623 if ($upper_data == 'HAVING') {
1624 $in_having = true;
1625 $having_clause = '';
1626 $seen_group = false;
1627 $seen_order = false;
1628 $in_group_by = false;
1629 $in_order_by = false;
1630 $in_where = false;
1631 $in_select_expr = false;
1632 $in_from = false;
1635 if ($upper_data == 'WHERE') {
1636 $in_where = true;
1637 $where_clause = '';
1638 $where_clause_identifiers = array();
1639 $seen_group = false;
1640 $seen_order = false;
1641 $in_group_by = false;
1642 $in_order_by = false;
1643 $in_having = false;
1644 $in_select_expr = false;
1645 $in_from = false;
1648 if ($upper_data == 'BY') {
1649 if ($seen_group) {
1650 $in_group_by = true;
1651 $group_by_clause = '';
1653 if ($seen_order) {
1654 $seen_order_by = true;
1655 // Here we assume that the ORDER BY keywords took
1656 // exactly 8 characters.
1657 // We use PMA_substr() to be charset-safe; otherwise
1658 // if the table name contains accents, the unsorted
1659 // query would be missing some characters.
1660 $unsorted_query = PMA_substr($arr['raw'], 0, $arr[$i]['pos'] - 8);
1661 $in_order_by = true;
1662 $order_by_clause = '';
1666 // if we find one of the words that could end the clause
1667 if (isset($words_ending_clauses[$upper_data])) {
1669 $in_group_by = false;
1670 $in_order_by = false;
1671 $in_having = false;
1672 $in_where = false;
1673 $in_select_expr = false;
1674 $in_from = false;
1677 } // endif (reservedWord)
1680 // do not add a space after a function name
1682 * @todo can we combine loop 2 and loop 1? some code is repeated here...
1685 $sep = ' ';
1686 if ($arr[$i]['type'] == 'alpha_functionName') {
1687 $sep='';
1688 $upper_data = strtoupper($arr[$i]['data']);
1689 if ($upper_data =='GROUP_CONCAT') {
1690 $in_group_concat = true;
1691 $number_of_brackets_in_group_concat = 0;
1695 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1696 if ($in_group_concat) {
1697 $number_of_brackets_in_group_concat++;
1700 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1701 if ($in_group_concat) {
1702 $number_of_brackets_in_group_concat--;
1703 if ($number_of_brackets_in_group_concat == 0) {
1704 $in_group_concat = false;
1709 // do not add a space after an identifier if followed by a dot
1710 if ($arr[$i]['type'] == 'alpha_identifier' && $i < $size - 1 && $arr[$i + 1]['data'] == '.') {
1711 $sep = '';
1714 // do not add a space after a dot if followed by an identifier
1715 if ($arr[$i]['data'] == '.' && $i < $size - 1 && $arr[$i + 1]['type'] == 'alpha_identifier') {
1716 $sep = '';
1719 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1720 $select_expr_clause .= $arr[$i]['data'] . $sep;
1722 if ($in_from && $upper_data != 'FROM') {
1723 $from_clause .= $arr[$i]['data'] . $sep;
1725 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1726 $group_by_clause .= $arr[$i]['data'] . $sep;
1728 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1729 // add a space only before ASC or DESC
1730 // not around the dot between dbname and tablename
1731 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1732 $order_by_clause .= $sep;
1734 $order_by_clause .= $arr[$i]['data'];
1736 if ($in_having && $upper_data != 'HAVING') {
1737 $having_clause .= $arr[$i]['data'] . $sep;
1739 if ($in_where && $upper_data != 'WHERE') {
1740 $where_clause .= $arr[$i]['data'] . $sep;
1742 if (($arr[$i]['type'] == 'quote_backtick')
1743 || ($arr[$i]['type'] == 'alpha_identifier')) {
1744 $where_clause_identifiers[] = $arr[$i]['data'];
1748 // to grab the rest of the query after the ORDER BY clause
1749 if (isset($subresult['queryflags']['select_from'])
1750 && $subresult['queryflags']['select_from'] == 1
1751 && ! $in_order_by
1752 && $seen_order_by
1753 && $upper_data != 'BY') {
1754 $unsorted_query .= $arr[$i]['data'];
1755 if ($arr[$i]['type'] != 'punct_bracket_open_round'
1756 && $arr[$i]['type'] != 'punct_bracket_close_round'
1757 && $arr[$i]['type'] != 'punct') {
1758 $unsorted_query .= $sep;
1762 if ($in_limit) {
1763 if ($upper_data == 'OFFSET') {
1764 $limit_clause .= $sep;
1766 $limit_clause .= $arr[$i]['data'];
1767 if ($upper_data == 'LIMIT' || $upper_data == 'OFFSET') {
1768 $limit_clause .= $sep;
1771 if ($after_limit && $seen_limit) {
1772 $section_after_limit .= $arr[$i]['data'] . $sep;
1775 // clear $upper_data for next iteration
1776 $upper_data='';
1777 } // end for $i (loop #2)
1778 if (empty($section_before_limit)) {
1779 $section_before_limit = $arr['raw'];
1782 // -----------------------------------------------------
1783 // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
1784 // (for now, check only the first query)
1785 // (for now, identifiers are assumed to be backquoted)
1787 // If we find that we are dealing with a CREATE TABLE query,
1788 // we look for the next punct_bracket_open_round, which
1789 // introduces the fields list. Then, when we find a
1790 // quote_backtick, it must be a field, so we put it into
1791 // the create_table_fields array. Even if this field is
1792 // not a timestamp, it will be useful when logic has been
1793 // added for complete field attributes analysis.
1795 $seen_foreign = false;
1796 $seen_references = false;
1797 $seen_constraint = false;
1798 $foreign_key_number = -1;
1799 $seen_create_table = false;
1800 $seen_create = false;
1801 $seen_alter = false;
1802 $in_create_table_fields = false;
1803 $brackets_level = 0;
1804 $in_timestamp_options = false;
1805 $seen_default = false;
1807 for ($i = 0; $i < $size; $i++) {
1808 // DEBUG echo "Loop 3 <strong>" . $arr[$i]['data'] . "</strong> " . $arr[$i]['type'] . "<br />";
1810 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1811 $upper_data = strtoupper($arr[$i]['data']);
1813 if ($upper_data == 'NOT' && $in_timestamp_options) {
1814 $create_table_fields[$current_identifier]['timestamp_not_null'] = true;
1818 if ($upper_data == 'CREATE') {
1819 $seen_create = true;
1822 if ($upper_data == 'ALTER') {
1823 $seen_alter = true;
1826 if ($upper_data == 'TABLE' && $seen_create) {
1827 $seen_create_table = true;
1828 $create_table_fields = array();
1831 if ($upper_data == 'CURRENT_TIMESTAMP') {
1832 if ($in_timestamp_options) {
1833 if ($seen_default) {
1834 $create_table_fields[$current_identifier]['default_current_timestamp'] = true;
1839 if ($upper_data == 'CONSTRAINT') {
1840 $foreign_key_number++;
1841 $seen_foreign = false;
1842 $seen_references = false;
1843 $seen_constraint = true;
1845 if ($upper_data == 'FOREIGN') {
1846 $seen_foreign = true;
1847 $seen_references = false;
1848 $seen_constraint = false;
1850 if ($upper_data == 'REFERENCES') {
1851 $seen_foreign = false;
1852 $seen_references = true;
1853 $seen_constraint = false;
1857 // Cases covered:
1859 // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1860 // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1862 // but we set ['on_delete'] or ['on_cascade'] to
1863 // CASCADE | SET_NULL | NO_ACTION | RESTRICT
1865 // ON UPDATE CURRENT_TIMESTAMP
1867 if ($upper_data == 'ON') {
1868 if (isset($arr[$i+1]) && $arr[$i+1]['type'] == 'alpha_reservedWord') {
1869 $second_upper_data = strtoupper($arr[$i+1]['data']);
1870 if ($second_upper_data == 'DELETE') {
1871 $clause = 'on_delete';
1873 if ($second_upper_data == 'UPDATE') {
1874 $clause = 'on_update';
1876 if (isset($clause)
1877 && ($arr[$i+2]['type'] == 'alpha_reservedWord'
1879 // ugly workaround because currently, NO is not
1880 // in the list of reserved words in sqlparser.data
1881 // (we got a bug report about not being able to use
1882 // 'no' as an identifier)
1883 || ($arr[$i+2]['type'] == 'alpha_identifier'
1884 && strtoupper($arr[$i+2]['data'])=='NO'))
1886 $third_upper_data = strtoupper($arr[$i+2]['data']);
1887 if ($third_upper_data == 'CASCADE'
1888 || $third_upper_data == 'RESTRICT') {
1889 $value = $third_upper_data;
1890 } elseif ($third_upper_data == 'SET'
1891 || $third_upper_data == 'NO') {
1892 if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
1893 $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
1895 } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
1896 if ($clause == 'on_update'
1897 && $in_timestamp_options) {
1898 $create_table_fields[$current_identifier]['on_update_current_timestamp'] = true;
1899 $seen_default = false;
1902 } else {
1903 $value = '';
1905 if (!empty($value)) {
1906 $foreign[$foreign_key_number][$clause] = $value;
1908 unset($clause);
1909 } // endif (isset($clause))
1913 } // end of reserved words analysis
1916 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1917 $brackets_level++;
1918 if ($seen_create_table && $brackets_level == 1) {
1919 $in_create_table_fields = true;
1924 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1925 $brackets_level--;
1926 if ($seen_references) {
1927 $seen_references = false;
1929 if ($seen_create_table && $brackets_level == 0) {
1930 $in_create_table_fields = false;
1934 if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
1935 $upper_data = strtoupper($arr[$i]['data']);
1936 if ($seen_create_table && $in_create_table_fields) {
1937 if ($upper_data == 'DEFAULT') {
1938 $seen_default = true;
1939 $create_table_fields[$current_identifier]['default_value'] = $arr[$i + 1]['data'];
1945 * @see @todo 2005-10-16 note: the "or" part here is a workaround for a bug
1947 if (($arr[$i]['type'] == 'alpha_columnType') || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)) {
1948 $upper_data = strtoupper($arr[$i]['data']);
1949 if ($seen_create_table && $in_create_table_fields && isset($current_identifier)) {
1950 $create_table_fields[$current_identifier]['type'] = $upper_data;
1951 if ($upper_data == 'TIMESTAMP') {
1952 $arr[$i]['type'] = 'alpha_columnType';
1953 $in_timestamp_options = true;
1954 } else {
1955 $in_timestamp_options = false;
1956 if ($upper_data == 'CHAR') {
1957 $arr[$i]['type'] = 'alpha_columnType';
1964 if ($arr[$i]['type'] == 'quote_backtick' || $arr[$i]['type'] == 'alpha_identifier') {
1966 if ($arr[$i]['type'] == 'quote_backtick') {
1967 // remove backquotes
1968 $identifier = PMA_unQuote($arr[$i]['data']);
1969 } else {
1970 $identifier = $arr[$i]['data'];
1973 if ($seen_create_table && $in_create_table_fields) {
1974 $current_identifier = $identifier;
1975 // we set this one even for non TIMESTAMP type
1976 $create_table_fields[$current_identifier]['timestamp_not_null'] = false;
1979 if ($seen_constraint) {
1980 $foreign[$foreign_key_number]['constraint'] = $identifier;
1983 if ($seen_foreign && $brackets_level > 0) {
1984 $foreign[$foreign_key_number]['index_list'][] = $identifier;
1987 if ($seen_references) {
1988 if ($seen_alter && $brackets_level > 0) {
1989 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1990 // here, the first bracket level corresponds to the
1991 // bracket of CREATE TABLE
1992 // so if we are on level 2, it must be the index list
1993 // of the foreign key REFERENCES
1994 } elseif ($brackets_level > 1) {
1995 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1996 } elseif ($arr[$i+1]['type'] == 'punct_qualifier') {
1997 // identifier is `db`.`table`
1998 // the first pass will pick the db name
1999 // the next pass will pick the table name
2000 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
2001 } else {
2002 // identifier is `table`
2003 $foreign[$foreign_key_number]['ref_table_name'] = $identifier;
2007 } // end for $i (loop #3)
2010 // Fill the $subresult array
2012 if (isset($create_table_fields)) {
2013 $subresult['create_table_fields'] = $create_table_fields;
2016 if (isset($foreign)) {
2017 $subresult['foreign_keys'] = $foreign;
2020 if (isset($select_expr_clause)) {
2021 $subresult['select_expr_clause'] = $select_expr_clause;
2023 if (isset($from_clause)) {
2024 $subresult['from_clause'] = $from_clause;
2026 if (isset($group_by_clause)) {
2027 $subresult['group_by_clause'] = $group_by_clause;
2029 if (isset($order_by_clause)) {
2030 $subresult['order_by_clause'] = $order_by_clause;
2032 if (isset($having_clause)) {
2033 $subresult['having_clause'] = $having_clause;
2035 if (isset($limit_clause)) {
2036 $subresult['limit_clause'] = $limit_clause;
2038 if (isset($where_clause)) {
2039 $subresult['where_clause'] = $where_clause;
2041 if (isset($unsorted_query) && !empty($unsorted_query)) {
2042 $subresult['unsorted_query'] = $unsorted_query;
2044 if (isset($where_clause_identifiers)) {
2045 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
2048 if (isset($position_of_first_select)) {
2049 $subresult['position_of_first_select'] = $position_of_first_select;
2050 $subresult['section_before_limit'] = $section_before_limit;
2051 $subresult['section_after_limit'] = $section_after_limit;
2054 // They are naughty and didn't have a trailing semi-colon,
2055 // then still handle it properly
2056 if ($subresult['querytype'] != '') {
2057 $result[] = $subresult;
2059 return $result;
2060 } // end of the "PMA_SQP_analyze()" function
2064 * Colorizes SQL queries html formatted
2066 * @todo check why adding a "\n" after the </span> would cause extra blanks
2067 * to be displayed: SELECT p . person_name
2068 * @param array The SQL queries html formatted
2070 * @return array The colorized SQL queries
2072 * @access public
2074 function PMA_SQP_formatHtml_colorize($arr)
2076 $i = PMA_strpos($arr['type'], '_');
2077 $class = '';
2078 if ($i > 0) {
2079 $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
2082 $class .= 'syntax_' . $arr['type'];
2084 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
2085 } // end of the "PMA_SQP_formatHtml_colorize()" function
2089 * Formats SQL queries to html
2091 * @param array The SQL queries
2092 * @param string mode
2093 * @param integer starting token
2094 * @param integer number of tokens to format, -1 = all
2096 * @return string The formatted SQL queries
2098 * @access public
2100 function PMA_SQP_formatHtml($arr, $mode='color', $start_token=0,
2101 $number_of_tokens=-1)
2103 global $PMA_SQPdata_operators_docs, $PMA_SQPdata_functions_docs;
2104 //DEBUG echo 'in Format<pre>'; print_r($arr); echo '</pre>';
2105 // then check for an array
2106 if (! is_array($arr)) {
2107 return htmlspecialchars($arr);
2109 // first check for the SQL parser having hit an error
2110 if (PMA_SQP_isError()) {
2111 return htmlspecialchars($arr['raw']);
2113 // else do it properly
2114 switch ($mode) {
2115 case 'color':
2116 $str = '<span class="syntax">';
2117 $html_line_break = '<br />';
2118 $docu = true;
2119 break;
2120 case 'query_only':
2121 $str = '';
2122 $html_line_break = "\n";
2123 $docu = false;
2124 break;
2125 case 'text':
2126 $str = '';
2127 $html_line_break = '<br />';
2128 $docu = true;
2129 break;
2130 } // end switch
2131 // inner_sql is a span that exists for all cases, except query_only
2132 // of $cfg['SQP']['fmtType'] to make possible a replacement
2133 // for inline editing
2134 if ($mode!='query_only') {
2135 $str .= '<span class="inner_sql">';
2137 $close_docu_link = false;
2138 $indent = 0;
2139 $bracketlevel = 0;
2140 $functionlevel = 0;
2141 $infunction = false;
2142 $space_punct_listsep = ' ';
2143 $space_punct_listsep_function_name = ' ';
2144 // $space_alpha_reserved_word = '<br />'."\n";
2145 $space_alpha_reserved_word = ' ';
2147 $keywords_with_brackets_1before = array(
2148 'INDEX' => 1,
2149 'KEY' => 1,
2150 'ON' => 1,
2151 'USING' => 1
2154 $keywords_with_brackets_2before = array(
2155 'IGNORE' => 1,
2156 'INDEX' => 1,
2157 'INTO' => 1,
2158 'KEY' => 1,
2159 'PRIMARY' => 1,
2160 'PROCEDURE' => 1,
2161 'REFERENCES' => 1,
2162 'UNIQUE' => 1,
2163 'USE' => 1
2166 // These reserved words do NOT get a newline placed near them.
2167 $keywords_no_newline = array(
2168 'AS' => 1,
2169 'ASC' => 1,
2170 'DESC' => 1,
2171 'DISTINCT' => 1,
2172 'DUPLICATE' => 1,
2173 'HOUR' => 1,
2174 'INTERVAL' => 1,
2175 'IS' => 1,
2176 'LIKE' => 1,
2177 'NOT' => 1,
2178 'NULL' => 1,
2179 'ON' => 1,
2180 'REGEXP' => 1
2183 // These reserved words introduce a privilege list
2184 $keywords_priv_list = array(
2185 'GRANT' => 1,
2186 'REVOKE' => 1
2189 if ($number_of_tokens == -1) {
2190 $number_of_tokens = $arr['len'];
2192 $typearr = array();
2193 if ($number_of_tokens >= 0) {
2194 $typearr[0] = '';
2195 $typearr[1] = '';
2196 $typearr[2] = '';
2197 $typearr[3] = $arr[$start_token]['type'];
2200 $in_priv_list = false;
2201 for ($i = $start_token; $i < $number_of_tokens; $i++) {
2202 // DEBUG echo "Loop format <strong>" . $arr[$i]['data'] . "</strong> " . $arr[$i]['type'] . "<br />";
2203 $before = '';
2204 $after = '';
2205 // array_shift($typearr);
2207 0 prev2
2208 1 prev
2209 2 current
2210 3 next
2212 if (($i + 1) < $number_of_tokens) {
2213 $typearr[4] = $arr[$i + 1]['type'];
2214 } else {
2215 $typearr[4] = '';
2218 for ($j=0; $j<4; $j++) {
2219 $typearr[$j] = $typearr[$j + 1];
2222 switch ($typearr[2]) {
2223 case 'alpha_bitfield_constant_introducer':
2224 $before = ' ';
2225 $after = '';
2226 break;
2227 case 'white_newline':
2228 $before = '';
2229 break;
2230 case 'punct_bracket_open_round':
2231 $bracketlevel++;
2232 $infunction = false;
2233 // Make sure this array is sorted!
2234 if (($typearr[1] == 'alpha_functionName') || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
2235 || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex') || ($typearr[3] == 'digit_float')
2236 || (($typearr[0] == 'alpha_reservedWord')
2237 && isset($keywords_with_brackets_2before[strtoupper($arr[$i - 2]['data'])]))
2238 || (($typearr[1] == 'alpha_reservedWord')
2239 && isset($keywords_with_brackets_1before[strtoupper($arr[$i - 1]['data'])]))
2241 $functionlevel++;
2242 $infunction = true;
2243 $after .= ' ';
2244 } else {
2245 $indent++;
2246 $after .= ($mode != 'query_only' ? '<div class="syntax_indent' . $indent . '">' : ' ');
2248 break;
2249 case 'alpha_identifier':
2250 if (($typearr[1] == 'punct_qualifier') || ($typearr[3] == 'punct_qualifier')) {
2251 $after = '';
2252 $before = '';
2254 // for example SELECT 1 somealias
2255 if ($typearr[1] == 'digit_integer') {
2256 $before = ' ';
2258 if (($typearr[3] == 'alpha_columnType') || ($typearr[3] == 'alpha_identifier')) {
2259 $after .= ' ';
2261 break;
2262 case 'punct_user':
2263 case 'punct_qualifier':
2264 $before = '';
2265 $after = '';
2266 break;
2267 case 'punct_listsep':
2268 if ($infunction == true) {
2269 $after .= $space_punct_listsep_function_name;
2270 } else {
2271 $after .= $space_punct_listsep;
2273 break;
2274 case 'punct_queryend':
2275 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi') && $typearr[3] != 'comment_c') {
2276 $after .= $html_line_break;
2277 $after .= $html_line_break;
2279 $space_punct_listsep = ' ';
2280 $space_punct_listsep_function_name = ' ';
2281 $space_alpha_reserved_word = ' ';
2282 $in_priv_list = false;
2283 break;
2284 case 'comment_mysql':
2285 case 'comment_ansi':
2286 $after .= $html_line_break;
2287 break;
2288 case 'punct':
2289 $before .= ' ';
2290 if ($docu && isset($PMA_SQPdata_operators_docs[$arr[$i]['data']]) &&
2291 ($arr[$i]['data'] != '*' || in_array($arr[$i]['type'], array('digit_integer','digit_float','digit_hex')))) {
2292 $before .= PMA_showMySQLDocu(
2293 'functions',
2294 $PMA_SQPdata_operators_docs[$arr[$i]['data']]['link'],
2295 false,
2296 $PMA_SQPdata_operators_docs[$arr[$i]['data']]['anchor'],
2297 true);
2298 $after .= '</a>';
2301 // workaround for
2302 // select * from mytable limit 0,-1
2303 // (a side effect of this workaround is that
2304 // select 20 - 9
2305 // becomes
2306 // select 20 -9
2307 // )
2308 if ($typearr[3] != 'digit_integer') {
2309 $after .= ' ';
2311 break;
2312 case 'punct_bracket_close_round':
2313 // only close bracket level when it was opened before
2314 if ($bracketlevel > 0) {
2315 $bracketlevel--;
2316 if ($infunction == true) {
2317 $functionlevel--;
2318 $after .= ' ';
2319 $before .= ' ';
2320 } else {
2321 $indent--;
2322 $before .= ($mode != 'query_only' ? '</div>' : ' ');
2324 $infunction = ($functionlevel > 0) ? true : false;
2326 break;
2327 case 'alpha_columnType':
2328 if ($docu) {
2329 switch ($arr[$i]['data']) {
2330 case 'tinyint':
2331 case 'smallint':
2332 case 'mediumint':
2333 case 'int':
2334 case 'bigint':
2335 case 'decimal':
2336 case 'float':
2337 case 'double':
2338 case 'real':
2339 case 'bit':
2340 case 'boolean':
2341 case 'serial':
2342 $before .= PMA_showMySQLDocu('data-types', 'numeric-types', false, '', true);
2343 $after = '</a>' . $after;
2344 break;
2345 case 'date':
2346 case 'datetime':
2347 case 'timestamp':
2348 case 'time':
2349 case 'year':
2350 $before .= PMA_showMySQLDocu('data-types', 'date-and-time-types', false, '', true);
2351 $after = '</a>' . $after;
2352 break;
2353 case 'char':
2354 case 'varchar':
2355 case 'tinytext':
2356 case 'text':
2357 case 'mediumtext':
2358 case 'longtext':
2359 case 'binary':
2360 case 'varbinary':
2361 case 'tinyblob':
2362 case 'mediumblob':
2363 case 'blob':
2364 case 'longblob':
2365 case 'enum':
2366 case 'set':
2367 $before .= PMA_showMySQLDocu('data-types', 'string-types', false, '', true);
2368 $after = '</a>' . $after;
2369 break;
2372 if ($typearr[3] == 'alpha_columnAttrib') {
2373 $after .= ' ';
2375 if ($typearr[1] == 'alpha_columnType') {
2376 $before .= ' ';
2378 break;
2379 case 'alpha_columnAttrib':
2381 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
2382 // COLLATE LATIN1_GENERAL_CI DEFAULT
2383 if ($typearr[1] == 'alpha_identifier' || $typearr[1] == 'alpha_charset') {
2384 $before .= ' ';
2386 if (($typearr[3] == 'alpha_columnAttrib') || ($typearr[3] == 'quote_single') || ($typearr[3] == 'digit_integer')) {
2387 $after .= ' ';
2389 // workaround for
2390 // AUTO_INCREMENT = 31DEFAULT_CHARSET = utf-8
2392 if ($typearr[2] == 'alpha_columnAttrib' && $typearr[3] == 'alpha_reservedWord') {
2393 $before .= ' ';
2395 // workaround for
2396 // select * from mysql.user where binary user="root"
2397 // binary is marked as alpha_columnAttrib
2398 // but should be marked as a reserved word
2399 if (strtoupper($arr[$i]['data']) == 'BINARY'
2400 && $typearr[3] == 'alpha_identifier') {
2401 $after .= ' ';
2403 break;
2404 case 'alpha_functionName':
2405 $funcname = strtoupper($arr[$i]['data']);
2406 if ($docu && isset($PMA_SQPdata_functions_docs[$funcname])) {
2407 $before .= PMA_showMySQLDocu(
2408 'functions',
2409 $PMA_SQPdata_functions_docs[$funcname]['link'],
2410 false,
2411 $PMA_SQPdata_functions_docs[$funcname]['anchor'],
2412 true);
2413 $after .= '</a>';
2415 break;
2416 case 'alpha_reservedWord':
2417 // do not uppercase the reserved word if we are calling
2418 // this function in query_only mode, because we need
2419 // the original query (otherwise we get problems with
2420 // semi-reserved words like "storage" which is legal
2421 // as an identifier name)
2423 if ($mode != 'query_only') {
2424 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
2427 if ((($typearr[1] != 'alpha_reservedWord')
2428 || (($typearr[1] == 'alpha_reservedWord')
2429 && isset($keywords_no_newline[strtoupper($arr[$i - 1]['data'])])))
2430 && ($typearr[1] != 'punct_level_plus')
2431 && (!isset($keywords_no_newline[$arr[$i]['data']]))) {
2432 // do not put a space before the first token, because
2433 // we use a lot of pattern matching checking for the
2434 // first reserved word at beginning of query
2435 // so do not put a newline before
2437 // also we must not be inside a privilege list
2438 if ($i > 0) {
2439 // the alpha_identifier exception is there to
2440 // catch cases like
2441 // GRANT SELECT ON mydb.mytable TO myuser@localhost
2442 // (else, we get mydb.mytableTO)
2444 // the quote_single exception is there to
2445 // catch cases like
2446 // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
2448 * @todo fix all cases and find why this happens
2451 if (!$in_priv_list || $typearr[1] == 'alpha_identifier' || $typearr[1] == 'quote_single' || $typearr[1] == 'white_newline') {
2452 $before .= $space_alpha_reserved_word;
2454 } else {
2455 // on first keyword, check if it introduces a
2456 // privilege list
2457 if (isset($keywords_priv_list[$arr[$i]['data']])) {
2458 $in_priv_list = true;
2461 } else {
2462 $before .= ' ';
2465 switch ($arr[$i]['data']) {
2466 case 'CREATE':
2467 case 'ALTER':
2468 case 'DROP':
2469 case 'RENAME';
2470 case 'TRUNCATE':
2471 case 'ANALYZE':
2472 case 'ANALYSE':
2473 case 'OPTIMIZE':
2474 if ($docu) {
2475 switch ($arr[$i + 1]['data']) {
2476 case 'EVENT':
2477 case 'TABLE':
2478 case 'TABLESPACE':
2479 case 'FUNCTION':
2480 case 'INDEX':
2481 case 'PROCEDURE':
2482 case 'TRIGGER':
2483 case 'SERVER':
2484 case 'DATABASE':
2485 case 'VIEW':
2486 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'] . '_' . $arr[$i + 1]['data'], false, '', true);
2487 $close_docu_link = true;
2488 break;
2490 if ($arr[$i + 1]['data'] == 'LOGFILE' && $arr[$i + 2]['data'] == 'GROUP') {
2491 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'] . '_LOGFILE_GROUP', false, '', true);
2492 $close_docu_link = true;
2495 if (!$in_priv_list) {
2496 $space_punct_listsep = $html_line_break;
2497 $space_alpha_reserved_word = ' ';
2499 break;
2500 case 'EVENT':
2501 case 'TABLESPACE':
2502 case 'TABLE':
2503 case 'FUNCTION':
2504 case 'INDEX':
2505 case 'PROCEDURE':
2506 case 'SERVER':
2507 case 'TRIGGER':
2508 case 'DATABASE':
2509 case 'VIEW':
2510 case 'GROUP':
2511 if ($close_docu_link) {
2512 $after = '</a>' . $after;
2513 $close_docu_link = false;
2515 break;
2516 case 'SET':
2517 if ($docu && ($i == 0 || $arr[$i - 1]['data'] != 'CHARACTER')) {
2518 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2519 $after = '</a>' . $after;
2521 if (!$in_priv_list) {
2522 $space_punct_listsep = $html_line_break;
2523 $space_alpha_reserved_word = ' ';
2525 break;
2526 case 'EXPLAIN':
2527 case 'DESCRIBE':
2528 case 'DELETE':
2529 case 'SHOW':
2530 case 'UPDATE':
2531 if ($docu) {
2532 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2533 $after = '</a>' . $after;
2535 if (!$in_priv_list) {
2536 $space_punct_listsep = $html_line_break;
2537 $space_alpha_reserved_word = ' ';
2539 break;
2540 case 'INSERT':
2541 case 'REPLACE':
2542 if ($docu) {
2543 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2544 $after = '</a>' . $after;
2546 if (!$in_priv_list) {
2547 $space_punct_listsep = $html_line_break;
2548 $space_alpha_reserved_word = $html_line_break;
2550 break;
2551 case 'VALUES':
2552 $space_punct_listsep = ' ';
2553 $space_alpha_reserved_word = $html_line_break;
2554 break;
2555 case 'SELECT':
2556 if ($docu) {
2557 $before .= PMA_showMySQLDocu('SQL-Syntax', 'SELECT', false, '', true);
2558 $after = '</a>' . $after;
2560 $space_punct_listsep = ' ';
2561 $space_alpha_reserved_word = $html_line_break;
2562 break;
2563 case 'CALL':
2564 case 'DO':
2565 case 'HANDLER':
2566 if ($docu) {
2567 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2568 $after = '</a>' . $after;
2570 break;
2571 default:
2572 if ($close_docu_link && in_array($arr[$i]['data'], array('LIKE', 'NOT', 'IN', 'REGEXP', 'NULL'))) {
2573 $after .= '</a>';
2574 $close_docu_link = false;
2575 } else if ($docu && isset($PMA_SQPdata_functions_docs[$arr[$i]['data']])) {
2576 /* Handle multi word statements first */
2577 if (isset($typearr[4]) && $typearr[4] == 'alpha_reservedWord' && $typearr[3] == 'alpha_reservedWord' && isset($PMA_SQPdata_functions_docs[strtoupper($arr[$i]['data'] . '_' . $arr[$i + 1]['data'] . '_' . $arr[$i + 2]['data'])])) {
2578 $tempname = strtoupper($arr[$i]['data'] . '_' . $arr[$i + 1]['data'] . '_' . $arr[$i + 2]['data']);
2579 $before .= PMA_showMySQLDocu('functions', $PMA_SQPdata_functions_docs[$tempname]['link'], false, $PMA_SQPdata_functions_docs[$tempname]['anchor'], true);
2580 $close_docu_link = true;
2581 } else if (isset($typearr[3]) && $typearr[3] == 'alpha_reservedWord' && isset($PMA_SQPdata_functions_docs[strtoupper($arr[$i]['data'] . '_' . $arr[$i + 1]['data'])])) {
2582 $tempname = strtoupper($arr[$i]['data'] . '_' . $arr[$i + 1]['data']);
2583 $before .= PMA_showMySQLDocu('functions', $PMA_SQPdata_functions_docs[$tempname]['link'], false, $PMA_SQPdata_functions_docs[$tempname]['anchor'], true);
2584 $close_docu_link = true;
2585 } else {
2586 $before .= PMA_showMySQLDocu('functions', $PMA_SQPdata_functions_docs[$arr[$i]['data']]['link'], false, $PMA_SQPdata_functions_docs[$arr[$i]['data']]['anchor'], true);
2587 $after .= '</a>';
2590 break;
2591 } // end switch ($arr[$i]['data'])
2593 $after .= ' ';
2594 break;
2595 case 'digit_integer':
2596 case 'digit_float':
2597 case 'digit_hex':
2599 * @todo could there be other types preceding a digit?
2601 if ($typearr[1] == 'alpha_reservedWord') {
2602 $after .= ' ';
2604 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2605 $after .= ' ';
2607 if ($typearr[1] == 'alpha_columnAttrib') {
2608 $before .= ' ';
2610 break;
2611 case 'alpha_variable':
2612 $after = ' ';
2613 break;
2614 case 'quote_double':
2615 case 'quote_single':
2616 // workaround: for the query
2617 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
2618 // the @ is incorrectly marked as alpha_variable
2619 // in the parser, and here, the '%' gets a blank before,
2620 // which is a syntax error
2621 if ($typearr[1] != 'punct_user' && $typearr[1] != 'alpha_bitfield_constant_introducer') {
2622 $before .= ' ';
2624 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2625 $after .= ' ';
2627 break;
2628 case 'quote_backtick':
2629 // here we check for punct_user to handle correctly
2630 // DEFINER = `username`@`%`
2631 // where @ is the punct_user and `%` is the quote_backtick
2632 if ($typearr[3] != 'punct_qualifier' && $typearr[3] != 'alpha_variable' && $typearr[3] != 'punct_user') {
2633 $after .= ' ';
2635 if ($typearr[1] != 'punct_qualifier' && $typearr[1] != 'alpha_variable' && $typearr[1] != 'punct_user') {
2636 $before .= ' ';
2638 break;
2639 default:
2640 break;
2641 } // end switch ($typearr[2])
2644 if ($typearr[3] != 'punct_qualifier') {
2645 $after .= ' ';
2647 $after .= "\n";
2649 $str .= $before;
2650 if ($mode=='color') {
2651 $str .= PMA_SQP_formatHTML_colorize($arr[$i]);
2652 } elseif ($mode == 'text') {
2653 $str .= htmlspecialchars($arr[$i]['data']);
2654 } else {
2655 $str .= $arr[$i]['data'];
2657 $str .= $after;
2658 } // end for
2659 // close unclosed indent levels
2660 while ($indent > 0) {
2661 $indent--;
2662 $str .= ($mode != 'query_only' ? '</div>' : ' ');
2664 /* End possibly unclosed documentation link */
2665 if ($close_docu_link) {
2666 $str .= '</a>';
2667 $close_docu_link = false;
2669 if ($mode!='query_only') {
2670 // close inner_sql span
2671 $str .= '</span>';
2673 if ($mode=='color') {
2674 // close syntax span
2675 $str .= '</span>';
2678 return $str;
2679 } // end of the "PMA_SQP_formatHtml()" function
2683 * Builds a CSS rule used for html formatted SQL queries
2685 * @param string The class name
2686 * @param string The property name
2687 * @param string The property value
2689 * @return string The CSS rule
2691 * @access public
2693 * @see PMA_SQP_buildCssData()
2695 function PMA_SQP_buildCssRule($classname, $property, $value)
2697 $str = '.' . $classname . ' {';
2698 if ($value != '') {
2699 $str .= $property . ': ' . $value . ';';
2701 $str .= '}' . "\n";
2703 return $str;
2704 } // end of the "PMA_SQP_buildCssRule()" function
2708 * Builds CSS rules used for html formatted SQL queries
2710 * @return string The CSS rules set
2712 * @access public
2714 * @global array The current PMA configuration
2716 * @see PMA_SQP_buildCssRule()
2718 function PMA_SQP_buildCssData()
2720 global $cfg;
2722 $css_string = '';
2723 foreach ($cfg['SQP']['fmtColor'] AS $key => $col) {
2724 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
2727 for ($i = 0; $i < 8; $i++) {
2728 $css_string .= PMA_SQP_buildCssRule(
2729 'syntax_indent' . $i, 'margin-left',
2730 ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
2733 return $css_string;
2734 } // end of the "PMA_SQP_buildCssData()" function
2736 if (! defined('PMA_MINIMUM_COMMON')) {
2738 * Gets SQL queries with no format
2740 * @param array The SQL queries list
2742 * @return string The SQL queries with no format
2744 * @access public
2746 function PMA_SQP_formatNone($arr)
2748 $formatted_sql = htmlspecialchars($arr['raw']);
2749 $formatted_sql = preg_replace(
2750 "@((\015\012)|(\015)|(\012)){3,}@",
2751 "\n\n",
2752 $formatted_sql);
2754 return $formatted_sql;
2755 } // end of the "PMA_SQP_formatNone()" function
2757 } // end if: minimal common.lib needed?