parser (analyzer) the section_before_limit should not contain the LIMIT clause itself...
[phpmyadmin/crack.git] / libraries / sqlparser.lib.php
blobde6d309cba6a76337b2a9c8dee89794485fc330f
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /** SQL Parser Functions for phpMyAdmin
5 * Copyright 2002 Robin Johnson <robbat2@users.sourceforge.net>
6 * http://www.orbis-terrarum.net/?l=people.robbat2
8 * These functions define an SQL parser system, capable of understanding and
9 * extracting data from a MySQL type SQL query.
11 * The basic procedure for using the new SQL parser:
12 * On any page that needs to extract data from a query or to pretty-print a
13 * query, you need code like this up at the top:
15 * ($sql contains the query)
16 * $parsed_sql = PMA_SQP_parse($sql);
18 * If you want to extract data from it then, you just need to run
19 * $sql_info = PMA_SQP_analyze($parsed_sql);
21 * lem9: See comments in PMA_SQP_analyze for the returned info
22 * from the analyzer.
24 * If you want a pretty-printed version of the query, do:
25 * $string = PMA_SQP_formatHtml($parsed_sql);
26 * (note that that you need to have syntax.css.php included somehow in your
27 * page for it to work, I recommend '<link rel="stylesheet" type="text/css"
28 * href="syntax.css.php" />' at the moment.)
30 * @version $Id$
34 /**
35 * Minimum inclusion? (i.e. for the stylesheet builder)
37 if (! defined('PMA_MINIMUM_COMMON')) {
38 /**
39 * Include the string library as we use it heavily
41 require_once './libraries/string.lib.php';
43 /**
44 * Include data for the SQL Parser
46 require_once './libraries/sqlparser.data.php';
47 require_once './libraries/mysql_charsets.lib.php';
48 if (!isset($mysql_charsets)) {
49 $mysql_charsets = array();
50 $mysql_charsets_count = 0;
51 $mysql_collations_flat = array();
52 $mysql_collations_count = 0;
55 if (!defined('DEBUG_TIMING')) {
56 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize)
58 $arr[] = array('type' => $type, 'data' => $data);
59 $arrsize++;
60 } // end of the "PMA_SQP_arrayAdd()" function
61 } else {
62 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize)
64 global $timer;
66 $t = $timer;
67 $arr[] = array('type' => $type, 'data' => $data, 'time' => $t);
68 $timer = microtime();
69 $arrsize++;
70 } // end of the "PMA_SQP_arrayAdd()" function
71 } // end if... else...
74 /**
75 * Reset the error variable for the SQL parser
77 * @access public
79 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
80 function PMA_SQP_resetError()
82 global $SQP_errorString;
83 $SQP_errorString = '';
84 unset($SQP_errorString);
87 /**
88 * Get the contents of the error variable for the SQL parser
90 * @return string Error string from SQL parser
92 * @access public
94 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
95 function PMA_SQP_getErrorString()
97 global $SQP_errorString;
98 return isset($SQP_errorString) ? $SQP_errorString : '';
102 * Check if the SQL parser hit an error
104 * @return boolean error state
106 * @access public
108 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
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 The error message
119 * @param string The failing SQL query
121 * @access private
122 * @scope SQL Parser internal
124 // Revised, Robbat2 - 13 Janurary 2003, 2:59PM
125 function PMA_SQP_throwError($message, $sql)
128 global $SQP_errorString;
129 $SQP_errorString = '<p>'.$GLOBALS['strSQLParserUserError'] . '</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 The error message
142 * @param string The failing SQL query
144 * @access public
146 function PMA_SQP_bug($message, $sql)
148 global $SQP_errorString;
149 $debugstr = 'ERROR: ' . $message . "\n";
150 $debugstr .= 'SVN: $Id$' . "\n";
151 $debugstr .= 'MySQL: '.PMA_MYSQL_STR_VERSION . "\n";
152 $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS . ' ' . PMA_USR_BROWSER_AGENT . ' ' . PMA_USR_BROWSER_VER . "\n";
153 $debugstr .= 'PMA: ' . PMA_VERSION . "\n";
154 $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION . ' ' . PHP_OS . "\n";
155 $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
156 $debugstr .= 'SQL: ' . htmlspecialchars($sql);
158 $encodedstr = $debugstr;
159 if (@function_exists('gzcompress')) {
160 $encodedstr = gzcompress($debugstr, 9);
162 $encodedstr = preg_replace("/(\015\012)|(\015)|(\012)/", '<br />' . "\n", chunk_split(base64_encode($encodedstr)));
164 $SQP_errorString .= $GLOBALS['strSQLParserBugMessage'] . '<br />' . "\n"
165 . '----' . $GLOBALS['strBeginCut'] . '----' . '<br />' . "\n"
166 . $encodedstr . "\n"
167 . '----' . $GLOBALS['strEndCut'] . '----' . '<br />' . "\n";
169 $SQP_errorString .= '----' . $GLOBALS['strBeginRaw'] . '----<br />' . "\n"
170 . '<pre>' . "\n"
171 . $debugstr
172 . '</pre>' . "\n"
173 . '----' . $GLOBALS['strEndRaw'] . '----<br />' . "\n";
175 } // end of the "PMA_SQP_bug()" function
179 * Parses the SQL queries
181 * @param string The SQL query list
183 * @return mixed Most of times, nothing...
185 * @global array The current PMA configuration
186 * @global array MySQL column attributes
187 * @global array MySQL reserved words
188 * @global array MySQL column types
189 * @global array MySQL function names
190 * @global integer MySQL column attributes count
191 * @global integer MySQL reserved words count
192 * @global integer MySQL column types count
193 * @global integer MySQL function names count
194 * @global array List of available character sets
195 * @global array List of available collations
196 * @global integer Character sets count
197 * @global integer Collations count
199 * @access public
201 function PMA_SQP_parse($sql)
203 global $cfg;
204 global $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word, $PMA_SQPdata_column_type, $PMA_SQPdata_function_name,
205 $PMA_SQPdata_column_attrib_cnt, $PMA_SQPdata_reserved_word_cnt, $PMA_SQPdata_column_type_cnt, $PMA_SQPdata_function_name_cnt;
206 global $mysql_charsets, $mysql_collations_flat, $mysql_charsets_count, $mysql_collations_count;
207 global $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt;
209 // rabus: Convert all line feeds to Unix style
210 $sql = str_replace("\r\n", "\n", $sql);
211 $sql = str_replace("\r", "\n", $sql);
213 $len = PMA_strlen($sql);
214 if ($len == 0) {
215 return array();
218 $sql_array = array();
219 $sql_array['raw'] = $sql;
220 $count1 = 0;
221 $count2 = 0;
222 $punct_queryend = ';';
223 $punct_qualifier = '.';
224 $punct_listsep = ',';
225 $punct_level_plus = '(';
226 $punct_level_minus = ')';
227 $punct_user = '@';
228 $digit_floatdecimal = '.';
229 $digit_hexset = 'x';
230 $bracket_list = '()[]{}';
231 $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
232 $allpunct_list_pair = array (
233 0 => '!=',
234 1 => '&&',
235 2 => ':=',
236 3 => '<<',
237 4 => '<=',
238 5 => '<=>',
239 6 => '<>',
240 7 => '>=',
241 8 => '>>',
242 9 => '||'
244 $allpunct_list_pair_size = 10; //count($allpunct_list_pair);
245 $quote_list = '\'"`';
246 $arraysize = 0;
248 $previous_was_space = false;
249 $this_was_space = false;
250 $previous_was_bracket = false;
251 $this_was_bracket = false;
252 $previous_was_punct = false;
253 $this_was_punct = false;
254 $previous_was_listsep = false;
255 $this_was_listsep = false;
256 $previous_was_quote = false;
257 $this_was_quote = false;
259 while ($count2 < $len) {
260 $c = PMA_substr($sql, $count2, 1);
261 $count1 = $count2;
263 $previous_was_space = $this_was_space;
264 $this_was_space = false;
265 $previous_was_bracket = $this_was_bracket;
266 $this_was_bracket = false;
267 $previous_was_punct = $this_was_punct;
268 $this_was_punct = false;
269 $previous_was_listsep = $this_was_listsep;
270 $this_was_listsep = false;
271 $previous_was_quote = $this_was_quote;
272 $this_was_quote = false;
274 if (($c == "\n")) {
275 $this_was_space = true;
276 $count2++;
277 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
278 continue;
281 // Checks for white space
282 if (PMA_STR_isSpace($c)) {
283 $this_was_space = true;
284 $count2++;
285 continue;
288 // Checks for comment lines.
289 // MySQL style #
290 // C style /* */
291 // ANSI style --
292 if (($c == '#')
293 || (($count2 + 1 < $len) && ($c == '/') && (PMA_substr($sql, $count2 + 1, 1) == '*'))
294 || (($count2 + 2 == $len) && ($c == '-') && (PMA_substr($sql, $count2 + 1, 1) == '-'))
295 || (($count2 + 2 < $len) && ($c == '-') && (PMA_substr($sql, $count2 + 1, 1) == '-') && ((PMA_substr($sql, $count2 + 2, 1) <= ' ')))) {
296 $count2++;
297 $pos = 0;
298 $type = 'bad';
299 switch ($c) {
300 case '#':
301 $type = 'mysql';
302 case '-':
303 $type = 'ansi';
304 $pos = $GLOBALS['PMA_strpos']($sql, "\n", $count2);
305 break;
306 case '/':
307 $type = 'c';
308 $pos = $GLOBALS['PMA_strpos']($sql, '*/', $count2);
309 $pos += 2;
310 break;
311 default:
312 break;
313 } // end switch
314 $count2 = ($pos < $count2) ? $len : $pos;
315 $str = PMA_substr($sql, $count1, $count2 - $count1);
316 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
317 continue;
318 } // end if
320 // Checks for something inside quotation marks
321 if (PMA_STR_strInStr($c, $quote_list)) {
322 $startquotepos = $count2;
323 $quotetype = $c;
324 $count2++;
325 $escaped = FALSE;
326 $escaped_escaped = FALSE;
327 $pos = $count2;
328 $oldpos = 0;
329 do {
330 $oldpos = $pos;
331 $pos = $GLOBALS['PMA_strpos'](' ' . $sql, $quotetype, $oldpos + 1) - 1;
332 // ($pos === FALSE)
333 if ($pos < 0) {
334 $debugstr = $GLOBALS['strSQPBugUnclosedQuote'] . ' @ ' . $startquotepos. "\n"
335 . 'STR: ' . htmlspecialchars($quotetype);
336 PMA_SQP_throwError($debugstr, $sql);
337 return $sql;
340 // If the quote is the first character, it can't be
341 // escaped, so don't do the rest of the code
342 if ($pos == 0) {
343 break;
346 // Checks for MySQL escaping using a \
347 // And checks for ANSI escaping using the $quotetype character
348 if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos)) {
349 $pos ++;
350 continue;
351 } elseif (($pos + 1 < $len) && (PMA_substr($sql, $pos, 1) == $quotetype) && (PMA_substr($sql, $pos + 1, 1) == $quotetype)) {
352 $pos = $pos + 2;
353 continue;
354 } else {
355 break;
357 } while ($len > $pos); // end do
359 $count2 = $pos;
360 $count2++;
361 $type = 'quote_';
362 switch ($quotetype) {
363 case '\'':
364 $type .= 'single';
365 $this_was_quote = true;
366 break;
367 case '"':
368 $type .= 'double';
369 $this_was_quote = true;
370 break;
371 case '`':
372 $type .= 'backtick';
373 $this_was_quote = true;
374 break;
375 default:
376 break;
377 } // end switch
378 $data = PMA_substr($sql, $count1, $count2 - $count1);
379 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
380 continue;
383 // Checks for brackets
384 if (PMA_STR_strInStr($c, $bracket_list)) {
385 // All bracket tokens are only one item long
386 $this_was_bracket = true;
387 $count2++;
388 $type_type = '';
389 if (PMA_STR_strInStr($c, '([{')) {
390 $type_type = 'open';
391 } else {
392 $type_type = 'close';
395 $type_style = '';
396 if (PMA_STR_strInStr($c, '()')) {
397 $type_style = 'round';
398 } elseif (PMA_STR_strInStr($c, '[]')) {
399 $type_style = 'square';
400 } else {
401 $type_style = 'curly';
404 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
405 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
406 continue;
409 /* DEBUG
410 echo '<pre>1';
411 var_dump(PMA_STR_isSqlIdentifier($c, false));
412 var_dump($c == '@');
413 var_dump($c == '.');
414 var_dump(PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1)));
415 var_dump($previous_was_space);
416 var_dump($previous_was_bracket);
417 var_dump($previous_was_listsep);
418 echo '</pre>';
421 // Checks for identifier (alpha or numeric)
422 if (PMA_STR_isSqlIdentifier($c, false)
423 || $c == '@'
424 || ($c == '.'
425 && PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1))
426 && ($previous_was_space || $previous_was_bracket || $previous_was_listsep))) {
428 /* DEBUG
429 echo PMA_substr($sql, $count2);
430 echo '<hr />';
433 $count2++;
436 * @todo a @ can also be present in expressions like
437 * FROM 'user'@'%' or TO 'user'@'%'
438 * in this case, the @ is wrongly marked as alpha_variable
440 $is_identifier = $previous_was_punct;
441 $is_sql_variable = $c == '@' && ! $previous_was_quote;
442 $is_user = $c == '@' && $previous_was_quote;
443 $is_digit = !$is_identifier && !$is_sql_variable && PMA_STR_isDigit($c);
444 $is_hex_digit = $is_digit && $c == '0' && $count2 < $len && PMA_substr($sql, $count2, 1) == 'x';
445 $is_float_digit = $c == '.';
446 $is_float_digit_exponent = FALSE;
448 /* DEBUG
449 echo '<pre>2';
450 var_dump($is_identifier);
451 var_dump($is_sql_variable);
452 var_dump($is_digit);
453 var_dump($is_float_digit);
454 echo '</pre>';
457 // Nijel: Fast skip is especially needed for huge BLOB data, requires PHP at least 4.3.0:
458 if (PMA_PHP_INT_VERSION >= 40300) {
459 if ($is_hex_digit) {
460 $count2++;
461 $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
462 if ($pos > $count2) {
463 $count2 = $pos;
465 unset($pos);
466 } elseif ($is_digit) {
467 $pos = strspn($sql, '0123456789', $count2);
468 if ($pos > $count2) {
469 $count2 = $pos;
471 unset($pos);
475 while (($count2 < $len) && PMA_STR_isSqlIdentifier(PMA_substr($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
476 $c2 = PMA_substr($sql, $count2, 1);
477 if ($is_sql_variable && ($c2 == '.')) {
478 $count2++;
479 continue;
481 if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
482 $count2++;
483 if (!$is_float_digit) {
484 $is_float_digit = TRUE;
485 continue;
486 } else {
487 $debugstr = $GLOBALS['strSQPBugInvalidIdentifer'] . ' @ ' . ($count1+1) . "\n"
488 . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
489 PMA_SQP_throwError($debugstr, $sql);
490 return $sql;
493 if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
494 if (!$is_float_digit_exponent) {
495 $is_float_digit_exponent = TRUE;
496 $is_float_digit = TRUE;
497 $count2++;
498 continue;
499 } else {
500 $is_digit = FALSE;
501 $is_float_digit = FALSE;
504 if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && PMA_STR_isDigit($c2))) {
505 $count2++;
506 continue;
507 } else {
508 $is_digit = FALSE;
509 $is_hex_digit = FALSE;
512 $count2++;
513 } // end while
515 $l = $count2 - $count1;
516 $str = PMA_substr($sql, $count1, $l);
518 $type = '';
519 if ($is_digit || $is_float_digit || $is_hex_digit) {
520 $type = 'digit';
521 if ($is_float_digit) {
522 $type .= '_float';
523 } elseif ($is_hex_digit) {
524 $type .= '_hex';
525 } else {
526 $type .= '_integer';
528 } elseif ($is_user) {
529 $type = 'punct_user';
530 } elseif ($is_sql_variable != FALSE) {
531 $type = 'alpha_variable';
532 } else {
533 $type = 'alpha';
534 } // end if... else....
535 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize);
537 continue;
540 // Checks for punct
541 if (PMA_STR_strInStr($c, $allpunct_list)) {
542 while (($count2 < $len) && PMA_STR_strInStr(PMA_substr($sql, $count2, 1), $allpunct_list)) {
543 $count2++;
545 $l = $count2 - $count1;
546 if ($l == 1) {
547 $punct_data = $c;
548 } else {
549 $punct_data = PMA_substr($sql, $count1, $l);
552 // Special case, sometimes, althought two characters are
553 // adjectent directly, they ACTUALLY need to be seperate
554 /* DEBUG
555 echo '<pre>';
556 var_dump($l);
557 var_dump($punct_data);
558 echo '</pre>';
561 if ($l == 1) {
562 $t_suffix = '';
563 switch ($punct_data) {
564 case $punct_queryend:
565 $t_suffix = '_queryend';
566 break;
567 case $punct_qualifier:
568 $t_suffix = '_qualifier';
569 $this_was_punct = true;
570 break;
571 case $punct_listsep:
572 $this_was_listsep = true;
573 $t_suffix = '_listsep';
574 break;
575 default:
576 break;
578 PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
579 } elseif (PMA_STR_binarySearchInArr($punct_data, $allpunct_list_pair, $allpunct_list_pair_size)) {
580 // Ok, we have one of the valid combined punct expressions
581 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
582 } else {
583 // Bad luck, lets split it up more
584 $first = $punct_data[0];
585 $first2 = $punct_data[0] . $punct_data[1];
586 $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
587 $last = $punct_data[$l - 1];
588 if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
589 $count2 = $count1 + 1;
590 $punct_data = $first;
591 } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || PMA_substr($sql, $count2, 1) <= ' '))) {
592 $count2 -= 2;
593 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
594 } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
595 $count2--;
596 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
598 * @todo for negation operator, split in 2 tokens ?
599 * "select x&~1 from t"
600 * becomes "select x & ~ 1 from t" ?
603 } elseif ($last != '~') {
604 $debugstr = $GLOBALS['strSQPBugUnknownPunctuation'] . ' @ ' . ($count1+1) . "\n"
605 . 'STR: ' . htmlspecialchars($punct_data);
606 PMA_SQP_throwError($debugstr, $sql);
607 return $sql;
609 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
610 continue;
611 } // end if... elseif... else
612 continue;
615 // DEBUG
616 $count2++;
618 $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
619 . 'STR: ' . PMA_substr($sql, $count1, $count2 - $count1) . "\n";
620 PMA_SQP_bug($debugstr, $sql);
621 return $sql;
623 } // end while ($count2 < $len)
626 echo '<pre>';
627 print_r($sql_array);
628 echo '</pre>';
631 if ($arraysize > 0) {
632 $t_next = $sql_array[0]['type'];
633 $t_prev = '';
634 $t_bef_prev = '';
635 $t_cur = '';
636 $d_next = $sql_array[0]['data'];
637 $d_prev = '';
638 $d_bef_prev = '';
639 $d_cur = '';
640 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
641 $d_prev_upper = '';
642 $d_bef_prev_upper = '';
643 $d_cur_upper = '';
646 for ($i = 0; $i < $arraysize; $i++) {
647 $t_bef_prev = $t_prev;
648 $t_prev = $t_cur;
649 $t_cur = $t_next;
650 $d_bef_prev = $d_prev;
651 $d_prev = $d_cur;
652 $d_cur = $d_next;
653 $d_bef_prev_upper = $d_prev_upper;
654 $d_prev_upper = $d_cur_upper;
655 $d_cur_upper = $d_next_upper;
656 if (($i + 1) < $arraysize) {
657 $t_next = $sql_array[$i + 1]['type'];
658 $d_next = $sql_array[$i + 1]['data'];
659 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
660 } else {
661 $t_next = '';
662 $d_next = '';
663 $d_next_upper = '';
666 //DEBUG echo "[prev: <b>".$d_prev."</b> ".$t_prev."][cur: <b>".$d_cur."</b> ".$t_cur."][next: <b>".$d_next."</b> ".$t_next."]<br />";
668 if ($t_cur == 'alpha') {
669 $t_suffix = '_identifier';
670 if (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
671 $t_suffix = '_identifier';
672 } elseif (($t_next == 'punct_bracket_open_round')
673 && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_function_name, $PMA_SQPdata_function_name_cnt)) {
675 * @todo 2005-10-16: in the case of a CREATE TABLE containing
676 * a TIMESTAMP, since TIMESTAMP() is also a function, it's
677 * found here and the token is wrongly marked as alpha_functionName.
678 * But we compensate for this when analysing for timestamp_not_null
679 * later in this script.
681 * Same applies to CHAR vs. CHAR() function.
683 $t_suffix = '_functionName';
684 /* There are functions which might be as well column types */
685 if (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt)) {
687 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt)) {
688 $t_suffix = '_columnType';
691 * Temporary fix for BUG #621357
693 * @todo FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
695 if ($d_cur_upper == 'SET' && $t_next != 'punct_bracket_open_round') {
696 $t_suffix = '_reservedWord';
698 //END OF TEMPORARY FIX
700 // CHARACTER is a synonym for CHAR, but can also be meant as
701 // CHARACTER SET. In this case, we have a reserved word.
702 if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
703 $t_suffix = '_reservedWord';
706 // experimental
707 // current is a column type, so previous must not be
708 // a reserved word but an identifier
709 // CREATE TABLE SG_Persons (first varchar(64))
711 //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
712 // $sql_array[$i-1]['type'] = 'alpha_identifier';
715 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_reserved_word, $PMA_SQPdata_reserved_word_cnt)) {
716 $t_suffix = '_reservedWord';
717 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_attrib, $PMA_SQPdata_column_attrib_cnt)) {
718 $t_suffix = '_columnAttrib';
719 // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
720 // it should be regarded as a reserved word.
721 if ($d_cur_upper == 'INNODB' && $d_prev_upper == 'SHOW' && $d_next_upper == 'STATUS') {
722 $t_suffix = '_reservedWord';
725 if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
726 $t_suffix = '_reservedWord';
728 // Binary as character set
729 if ($d_cur_upper == 'BINARY' && (
730 ($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
731 || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
732 || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
733 || $d_prev_upper == 'CHARSET'
734 ) && PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, count($mysql_charsets))) {
735 $t_suffix = '_charset';
737 } elseif (PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, $mysql_charsets_count)
738 || PMA_STR_binarySearchInArr($d_cur, $mysql_collations_flat, $mysql_collations_count)
739 || ($d_cur{0} == '_' && PMA_STR_binarySearchInArr(substr($d_cur, 1), $mysql_charsets, $mysql_charsets_count))) {
740 $t_suffix = '_charset';
741 } else {
742 // Do nothing
744 // check if present in the list of forbidden words
745 if ($t_suffix == '_reservedWord' && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt)) {
746 $sql_array[$i]['forbidden'] = TRUE;
747 } else {
748 $sql_array[$i]['forbidden'] = FALSE;
750 $sql_array[$i]['type'] .= $t_suffix;
752 } // end for
754 // Stores the size of the array inside the array, as count() is a slow
755 // operation.
756 $sql_array['len'] = $arraysize;
758 // DEBUG echo 'After parsing<pre>'; print_r($sql_array); echo '</pre>';
759 // Sends the data back
760 return $sql_array;
761 } // end of the "PMA_SQP_parse()" function
764 * Checks for token types being what we want...
766 * @param string String of type that we have
767 * @param string String of type that we want
769 * @return boolean result of check
771 * @access private
773 function PMA_SQP_typeCheck($toCheck, $whatWeWant)
775 $typeSeperator = '_';
776 if (strcmp($whatWeWant, $toCheck) == 0) {
777 return TRUE;
778 } else {
779 if (strpos($whatWeWant, $typeSeperator) === FALSE) {
780 return strncmp($whatWeWant, $toCheck, strpos($toCheck, $typeSeperator)) == 0;
781 } else {
782 return FALSE;
789 * Analyzes SQL queries
791 * @param array The SQL queries
793 * @return array The analyzed SQL queries
795 * @access public
797 function PMA_SQP_analyze($arr)
799 if ($arr == array()) {
800 return array();
802 $result = array();
803 $size = $arr['len'];
804 $subresult = array(
805 'querytype' => '',
806 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
807 'position_of_first_select' => '', // the array index
808 'from_clause'=> '',
809 'group_by_clause'=> '',
810 'order_by_clause'=> '',
811 'having_clause' => '',
812 'limit_clause' => '',
813 'where_clause' => '',
814 'where_clause_identifiers' => array(),
815 'unsorted_query' => '',
816 'queryflags' => array(),
817 'select_expr' => array(),
818 'table_ref' => array(),
819 'foreign_keys' => array(),
820 'create_table_fields' => array()
822 $subresult_empty = $subresult;
823 $seek_queryend = FALSE;
824 $seen_end_of_table_ref = FALSE;
825 $number_of_brackets_in_extract = 0;
826 $number_of_brackets_in_group_concat = 0;
828 $number_of_brackets = 0;
829 $in_subquery = false;
831 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
832 // we must not use CURDATE as a table_ref
833 // so we track wether we are in the EXTRACT()
834 $in_extract = FALSE;
836 // for GROUP_CONCAT(...)
837 $in_group_concat = FALSE;
839 /* Description of analyzer results by lem9
841 * db, table, column, alias
842 * ------------------------
844 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
846 * The SELECT syntax (simplified) is
848 * SELECT
849 * select_expression,...
850 * [FROM [table_references]
853 * ['select_expr'] is filled with each expression, the key represents the
854 * expression position in the list (0-based) (so we don't lose track of
855 * multiple occurences of the same column).
857 * ['table_ref'] is filled with each table ref, same thing for the key.
859 * I create all sub-values empty, even if they are
860 * not present (for example no select_expression alias).
862 * There is a debug section at the end of loop #1, if you want to
863 * see the exact contents of select_expr and table_ref
865 * queryflags
866 * ----------
868 * In $subresult, array 'queryflags' is filled, according to what we
869 * find in the query.
871 * Currently, those are generated:
873 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
874 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
875 * ['queryflags']['distinct'] = 1; for a DISTINCT
876 * ['queryflags']['union'] = 1; for a UNION
877 * ['queryflags']['join'] = 1; for a JOIN
878 * ['queryflags']['offset'] = 1; for the presence of OFFSET
880 * query clauses
881 * -------------
883 * The select is splitted in those clauses:
884 * ['select_expr_clause']
885 * ['from_clause']
886 * ['group_by_clause']
887 * ['order_by_clause']
888 * ['having_clause']
889 * ['where_clause']
890 * ['limit_clause']
892 * The identifiers of the WHERE clause are put into the array
893 * ['where_clause_identifier']
895 * For a SELECT, the whole query without the ORDER BY clause is put into
896 * ['unsorted_query']
898 * foreign keys
899 * ------------
900 * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
901 * analyzed and ['foreign_keys'] is an array filled with
902 * the constraint name, the index list,
903 * the REFERENCES table name and REFERENCES index list,
904 * and ON UPDATE | ON DELETE clauses
906 * position_of_first_select
907 * ------------------------
909 * The array index of the first SELECT we find. Will be used to
910 * insert a SQL_CALC_FOUND_ROWS.
912 * create_table_fields
913 * -------------------
915 * For now, mostly used to detect the DEFAULT CURRENT_TIMESTAMP and
916 * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
917 * An array, each element is the identifier name.
918 * Note that for now, the timestamp_not_null element is created
919 * even for non-TIMESTAMP fields.
921 * Sub-elements: ['type'] which contains the column type
922 * optional (currently they are never false but can be absent):
923 * ['default_current_timestamp'] boolean
924 * ['on_update_current_timestamp'] boolean
925 * ['timestamp_not_null'] boolean
927 * section_before_limit, section_after_limit
928 * -----------------------------------------
930 * Marks the point of the query where we can insert a LIMIT clause;
931 * so the section_before_limit will contain the left part before
932 * a possible LIMIT clause
935 * End of description of analyzer results
938 // must be sorted
939 // TODO: current logic checks for only one word, so I put only the
940 // first word of the reserved expressions that end a table ref;
941 // maybe this is not ok (the first word might mean something else)
942 // $words_ending_table_ref = array(
943 // 'FOR UPDATE',
944 // 'GROUP BY',
945 // 'HAVING',
946 // 'LIMIT',
947 // 'LOCK IN SHARE MODE',
948 // 'ORDER BY',
949 // 'PROCEDURE',
950 // 'UNION',
951 // 'WHERE'
952 // );
953 $words_ending_table_ref = array(
954 'FOR',
955 'GROUP',
956 'HAVING',
957 'LIMIT',
958 'LOCK',
959 'ORDER',
960 'PROCEDURE',
961 'UNION',
962 'WHERE'
964 $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
966 $words_ending_clauses = array(
967 'FOR',
968 'LIMIT',
969 'LOCK',
970 'PROCEDURE',
971 'UNION'
973 $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
978 // must be sorted
979 $supported_query_types = array(
980 'SELECT'
982 // Support for these additional query types will come later on.
983 'DELETE',
984 'INSERT',
985 'REPLACE',
986 'TRUNCATE',
987 'UPDATE'
988 'EXPLAIN',
989 'DESCRIBE',
990 'SHOW',
991 'CREATE',
992 'SET',
993 'ALTER'
996 $supported_query_types_cnt = count($supported_query_types);
998 // loop #1 for each token: select_expr, table_ref for SELECT
1000 for ($i = 0; $i < $size; $i++) {
1001 //DEBUG echo "Loop1 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
1003 // High speed seek for locating the end of the current query
1004 if ($seek_queryend == TRUE) {
1005 if ($arr[$i]['type'] == 'punct_queryend') {
1006 $seek_queryend = FALSE;
1007 } else {
1008 continue;
1009 } // end if (type == punct_queryend)
1010 } // end if ($seek_queryend)
1013 * Note: do not split if this is a punct_queryend for the first and only query
1014 * @todo when we find a UNION, should we split in another subresult?
1016 if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
1017 $result[] = $subresult;
1018 $subresult = $subresult_empty;
1019 continue;
1020 } // end if (type == punct_queryend)
1022 // ==============================================================
1023 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1024 $number_of_brackets++;
1025 if ($in_extract) {
1026 $number_of_brackets_in_extract++;
1028 if ($in_group_concat) {
1029 $number_of_brackets_in_group_concat++;
1032 // ==============================================================
1033 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1034 $number_of_brackets--;
1035 if ($number_of_brackets == 0) {
1036 $in_subquery = false;
1038 if ($in_extract) {
1039 $number_of_brackets_in_extract--;
1040 if ($number_of_brackets_in_extract == 0) {
1041 $in_extract = FALSE;
1044 if ($in_group_concat) {
1045 $number_of_brackets_in_group_concat--;
1046 if ($number_of_brackets_in_group_concat == 0) {
1047 $in_group_concat = FALSE;
1052 if ($in_subquery) {
1054 * skip the subquery to avoid setting
1055 * select_expr or table_ref with the contents
1056 * of this subquery; this is to avoid a bug when
1057 * trying to edit the results of
1058 * select * from child where not exists (select id from
1059 * parent where child.parent_id = parent.id);
1061 continue;
1063 // ==============================================================
1064 if ($arr[$i]['type'] == 'alpha_functionName') {
1065 $upper_data = strtoupper($arr[$i]['data']);
1066 if ($upper_data =='EXTRACT') {
1067 $in_extract = TRUE;
1068 $number_of_brackets_in_extract = 0;
1070 if ($upper_data =='GROUP_CONCAT') {
1071 $in_group_concat = TRUE;
1072 $number_of_brackets_in_group_concat = 0;
1076 // ==============================================================
1077 if ($arr[$i]['type'] == 'alpha_reservedWord'
1078 // && $arr[$i]['forbidden'] == FALSE) {
1080 // We don't know what type of query yet, so run this
1081 if ($subresult['querytype'] == '') {
1082 $subresult['querytype'] = strtoupper($arr[$i]['data']);
1083 } // end if (querytype was empty)
1085 // Check if we support this type of query
1086 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
1087 // Skip ahead to the next one if we don't
1088 $seek_queryend = TRUE;
1089 continue;
1090 } // end if (query not supported)
1092 // upper once
1093 $upper_data = strtoupper($arr[$i]['data']);
1095 * @todo reset for each query?
1098 if ($upper_data == 'SELECT') {
1099 if ($number_of_brackets > 0) {
1100 $in_subquery = true;
1101 // this is a subquery so do not analyze inside it
1102 continue;
1104 $seen_from = FALSE;
1105 $previous_was_identifier = FALSE;
1106 $current_select_expr = -1;
1107 $seen_end_of_table_ref = FALSE;
1108 } // end if (data == SELECT)
1110 if ($upper_data =='FROM' && !$in_extract) {
1111 $current_table_ref = -1;
1112 $seen_from = TRUE;
1113 $previous_was_identifier = FALSE;
1114 $save_table_ref = TRUE;
1115 } // end if (data == FROM)
1117 // here, do not 'continue' the loop, as we have more work for
1118 // reserved words below
1119 } // end if (type == alpha_reservedWord)
1121 // ==============================
1122 if ($arr[$i]['type'] == 'quote_backtick'
1123 || $arr[$i]['type'] == 'quote_double'
1124 || $arr[$i]['type'] == 'quote_single'
1125 || $arr[$i]['type'] == 'alpha_identifier'
1126 || ($arr[$i]['type'] == 'alpha_reservedWord'
1127 && $arr[$i]['forbidden'] == FALSE)) {
1129 switch ($arr[$i]['type']) {
1130 case 'alpha_identifier':
1131 case 'alpha_reservedWord':
1133 * this is not a real reservedWord, because it's not
1134 * present in the list of forbidden words, for example
1135 * "storage" which can be used as an identifier
1137 * @todo avoid the pretty printing in color in this case
1139 $identifier = $arr[$i]['data'];
1140 break;
1142 case 'quote_backtick':
1143 case 'quote_double':
1144 case 'quote_single':
1145 $identifier = PMA_unQuote($arr[$i]['data']);
1146 break;
1147 } // end switch
1149 if ($subresult['querytype'] == 'SELECT' && !$in_group_concat) {
1150 if (!$seen_from) {
1151 if ($previous_was_identifier && isset($chain)) {
1152 // found alias for this select_expr, save it
1153 // but only if we got something in $chain
1154 // (for example, SELECT COUNT(*) AS cnt
1155 // puts nothing in $chain, so we avoid
1156 // setting the alias)
1157 $alias_for_select_expr = $identifier;
1158 } else {
1159 $chain[] = $identifier;
1160 $previous_was_identifier = TRUE;
1162 } // end if !$previous_was_identifier
1163 } else {
1164 // ($seen_from)
1165 if ($save_table_ref && !$seen_end_of_table_ref) {
1166 if ($previous_was_identifier) {
1167 // found alias for table ref
1168 // save it for later
1169 $alias_for_table_ref = $identifier;
1170 } else {
1171 $chain[] = $identifier;
1172 $previous_was_identifier = TRUE;
1174 } // end if ($previous_was_identifier)
1175 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
1176 } // end if (!$seen_from)
1177 } // end if (querytype SELECT)
1178 } // end if (quote_backtick or double quote or alpha_identifier)
1180 // ===================================
1181 if ($arr[$i]['type'] == 'punct_qualifier') {
1182 // to be able to detect an identifier following another
1183 $previous_was_identifier = FALSE;
1184 continue;
1185 } // end if (punct_qualifier)
1188 * @todo check if 3 identifiers following one another -> error
1191 // s a v e a s e l e c t e x p r
1192 // finding a list separator or FROM
1193 // means that we must save the current chain of identifiers
1194 // into a select expression
1196 // for now, we only save a select expression if it contains
1197 // at least one identifier, as we are interested in checking
1198 // the columns and table names, so in "select * from persons",
1199 // the "*" is not saved
1201 if (isset($chain) && !$seen_end_of_table_ref
1202 && ((!$seen_from && $arr[$i]['type'] == 'punct_listsep')
1203 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM'))) {
1204 $size_chain = count($chain);
1205 $current_select_expr++;
1206 $subresult['select_expr'][$current_select_expr] = array(
1207 'expr' => '',
1208 'alias' => '',
1209 'db' => '',
1210 'table_name' => '',
1211 'table_true_name' => '',
1212 'column' => ''
1215 if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
1216 // we had found an alias for this select expression
1217 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
1218 unset($alias_for_select_expr);
1220 // there is at least a column
1221 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
1222 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
1224 // maybe a table
1225 if ($size_chain > 1) {
1226 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
1227 // we assume for now that this is also the true name
1228 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
1229 $subresult['select_expr'][$current_select_expr]['expr']
1230 = $subresult['select_expr'][$current_select_expr]['table_name']
1231 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1232 } // end if ($size_chain > 1)
1234 // maybe a db
1235 if ($size_chain > 2) {
1236 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
1237 $subresult['select_expr'][$current_select_expr]['expr']
1238 = $subresult['select_expr'][$current_select_expr]['db']
1239 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1240 } // end if ($size_chain > 2)
1241 unset($chain);
1244 * @todo explain this:
1246 if (($arr[$i]['type'] == 'alpha_reservedWord')
1247 && ($upper_data != 'FROM')) {
1248 $previous_was_identifier = TRUE;
1251 } // end if (save a select expr)
1254 //======================================
1255 // s a v e a t a b l e r e f
1256 //======================================
1258 // maybe we just saw the end of table refs
1259 // but the last table ref has to be saved
1260 // or we are at the last token
1261 // or we just got a reserved word
1263 * @todo there could be another query after this one
1266 if (isset($chain) && $seen_from && $save_table_ref
1267 && ($arr[$i]['type'] == 'punct_listsep'
1268 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
1269 || $seen_end_of_table_ref
1270 || $i==$size-1)) {
1272 $size_chain = count($chain);
1273 $current_table_ref++;
1274 $subresult['table_ref'][$current_table_ref] = array(
1275 'expr' => '',
1276 'db' => '',
1277 'table_name' => '',
1278 'table_alias' => '',
1279 'table_true_name' => ''
1281 if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
1282 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1283 unset($alias_for_table_ref);
1285 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1286 // we assume for now that this is also the true name
1287 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1288 $subresult['table_ref'][$current_table_ref]['expr']
1289 = $subresult['table_ref'][$current_table_ref]['table_name'];
1290 // maybe a db
1291 if ($size_chain > 1) {
1292 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1293 $subresult['table_ref'][$current_table_ref]['expr']
1294 = $subresult['table_ref'][$current_table_ref]['db']
1295 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1296 } // end if ($size_chain > 1)
1298 // add the table alias into the whole expression
1299 $subresult['table_ref'][$current_table_ref]['expr']
1300 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1302 unset($chain);
1303 $previous_was_identifier = TRUE;
1304 //continue;
1306 } // end if (save a table ref)
1309 // when we have found all table refs,
1310 // for each table_ref alias, put the true name of the table
1311 // in the corresponding select expressions
1313 if (isset($current_table_ref) && ($seen_end_of_table_ref || $i == $size-1) && $subresult != $subresult_empty) {
1314 for ($tr=0; $tr <= $current_table_ref; $tr++) {
1315 $alias = $subresult['table_ref'][$tr]['table_alias'];
1316 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1317 for ($se=0; $se <= $current_select_expr; $se++) {
1318 if (isset($alias) && strlen($alias) && $subresult['select_expr'][$se]['table_true_name']
1319 == $alias) {
1320 $subresult['select_expr'][$se]['table_true_name']
1321 = $truename;
1322 } // end if (found the alias)
1323 } // end for (select expressions)
1325 } // end for (table refs)
1326 } // end if (set the true names)
1329 // e n d i n g l o o p #1
1330 // set the $previous_was_identifier to FALSE if the current
1331 // token is not an identifier
1332 if (($arr[$i]['type'] != 'alpha_identifier')
1333 && ($arr[$i]['type'] != 'quote_double')
1334 && ($arr[$i]['type'] != 'quote_single')
1335 && ($arr[$i]['type'] != 'quote_backtick')) {
1336 $previous_was_identifier = FALSE;
1337 } // end if
1339 // however, if we are on AS, we must keep the $previous_was_identifier
1340 if (($arr[$i]['type'] == 'alpha_reservedWord')
1341 && ($upper_data == 'AS')) {
1342 $previous_was_identifier = TRUE;
1345 if (($arr[$i]['type'] == 'alpha_reservedWord')
1346 && ($upper_data =='ON' || $upper_data =='USING')) {
1347 $save_table_ref = FALSE;
1348 } // end if (data == ON)
1350 if (($arr[$i]['type'] == 'alpha_reservedWord')
1351 && ($upper_data =='JOIN' || $upper_data =='FROM')) {
1352 $save_table_ref = TRUE;
1353 } // end if (data == JOIN)
1356 * no need to check the end of table ref if we already did
1358 * @todo maybe add "&& $seen_from"
1360 if (!$seen_end_of_table_ref) {
1361 // if this is the last token, it implies that we have
1362 // seen the end of table references
1363 // Check for the end of table references
1365 // Note: if we are analyzing a GROUP_CONCAT clause,
1366 // we might find a word that seems to indicate that
1367 // we have found the end of table refs (like ORDER)
1368 // but it's a modifier of the GROUP_CONCAT so
1369 // it's not the real end of table refs
1370 if (($i == $size-1)
1371 || ($arr[$i]['type'] == 'alpha_reservedWord'
1372 && !$in_group_concat
1373 && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
1374 $seen_end_of_table_ref = TRUE;
1375 // to be able to save the last table ref, but do not
1376 // set it true if we found a word like "ON" that has
1377 // already set it to false
1378 if (isset($save_table_ref) && $save_table_ref != FALSE) {
1379 $save_table_ref = TRUE;
1380 } //end if
1382 } // end if (check for end of table ref)
1383 } //end if (!$seen_end_of_table_ref)
1385 if ($seen_end_of_table_ref) {
1386 $save_table_ref = FALSE;
1387 } // end if
1389 } // end for $i (loop #1)
1391 //DEBUG
1393 if (isset($current_select_expr)) {
1394 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1395 echo "<br />";
1396 reset ($subresult['select_expr'][$trace]);
1397 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1398 echo "sel expr $trace $key => $val<br />\n";
1402 if (isset($current_table_ref)) {
1403 echo "current_table_ref = " . $current_table_ref . "<br>";
1404 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1406 echo "<br />";
1407 reset ($subresult['table_ref'][$trace]);
1408 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1409 echo "table ref $trace $key => $val<br />\n";
1413 // -------------------------------------------------------
1416 // loop #2: - queryflags
1417 // - querytype (for queries != 'SELECT')
1418 // - section_before_limit, section_after_limit
1420 // we will also need this queryflag in loop 2
1421 // so set it here
1422 if (isset($current_table_ref) && $current_table_ref > -1) {
1423 $subresult['queryflags']['select_from'] = 1;
1426 $section_before_limit = '';
1427 $section_after_limit = '';
1428 $seen_reserved_word = FALSE;
1429 $seen_group = FALSE;
1430 $seen_order = FALSE;
1431 $in_group_by = FALSE; // true when we are inside the GROUP BY clause
1432 $in_order_by = FALSE; // true when we are inside the ORDER BY clause
1433 $in_having = FALSE; // true when we are inside the HAVING clause
1434 $in_select_expr = FALSE; // true when we are inside the select expr clause
1435 $in_where = FALSE; // true when we are inside the WHERE clause
1436 $in_limit = FALSE; // true when we are inside the LIMIT clause
1437 $before_limit = TRUE; // true when we are before the LIMIT clause
1438 $after_limit = FALSE; // true when we are after the LIMIT clause
1439 $in_from = FALSE; // true when we are in the FROM clause
1440 $in_group_concat = FALSE;
1441 $unsorted_query = '';
1442 $first_reserved_word = '';
1443 $current_identifier = '';
1445 for ($i = 0; $i < $size; $i++) {
1446 //DEBUG echo "Loop2 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
1448 // need_confirm
1450 // check for reserved words that will have to generate
1451 // a confirmation request later in sql.php
1452 // the cases are:
1453 // DROP TABLE
1454 // DROP DATABASE
1455 // ALTER TABLE... DROP
1456 // DELETE FROM...
1458 // this code is not used for confirmations coming from functions.js
1461 * @todo check for punct_queryend
1462 * @todo verify C-style comments?
1464 if ($arr[$i]['type'] == 'comment_ansi') {
1465 $before_limit = FALSE;
1466 $after_limit = FALSE;
1469 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1470 $upper_data = strtoupper($arr[$i]['data']);
1471 if (!$seen_reserved_word) {
1472 $first_reserved_word = $upper_data;
1473 $subresult['querytype'] = $upper_data;
1474 $seen_reserved_word = TRUE;
1476 // if the first reserved word is DROP or DELETE,
1477 // we know this is a query that needs to be confirmed
1478 if ($first_reserved_word=='DROP'
1479 || $first_reserved_word == 'DELETE'
1480 || $first_reserved_word == 'TRUNCATE') {
1481 $subresult['queryflags']['need_confirm'] = 1;
1484 if ($first_reserved_word=='SELECT'){
1485 $position_of_first_select = $i;
1488 } else {
1489 if ($upper_data == 'DROP' && $first_reserved_word == 'ALTER') {
1490 $subresult['queryflags']['need_confirm'] = 1;
1494 if ($upper_data == 'LIMIT') {
1495 $in_limit = TRUE;
1496 $limit_clause = '';
1497 $before_limit = FALSE;
1500 if ($upper_data == 'PROCEDURE') {
1501 $in_limit = FALSE;
1502 $after_limit = TRUE;
1505 * @todo set also to FALSE if we find FOR UPDATE or LOCK IN SHARE MODE
1507 if ($upper_data == 'SELECT') {
1508 $in_select_expr = TRUE;
1509 $select_expr_clause = '';
1511 if ($upper_data == 'DISTINCT' && !$in_group_concat) {
1512 $subresult['queryflags']['distinct'] = 1;
1515 if ($upper_data == 'UNION') {
1516 $subresult['queryflags']['union'] = 1;
1519 if ($upper_data == 'JOIN') {
1520 $subresult['queryflags']['join'] = 1;
1523 if ($upper_data == 'OFFSET') {
1524 $subresult['queryflags']['offset'] = 1;
1527 // if this is a real SELECT...FROM
1528 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1529 $in_from = TRUE;
1530 $from_clause = '';
1531 $in_select_expr = FALSE;
1535 // (we could have less resetting of variables to FALSE
1536 // if we trust that the query respects the standard
1537 // MySQL order for clauses)
1539 // we use $seen_group and $seen_order because we are looking
1540 // for the BY
1541 if ($upper_data == 'GROUP') {
1542 $seen_group = TRUE;
1543 $seen_order = FALSE;
1544 $in_having = FALSE;
1545 $in_order_by = FALSE;
1546 $in_where = FALSE;
1547 $in_select_expr = FALSE;
1548 $in_from = FALSE;
1550 if ($upper_data == 'ORDER' && !$in_group_concat) {
1551 $seen_order = TRUE;
1552 $seen_group = FALSE;
1553 $in_having = FALSE;
1554 $in_group_by = FALSE;
1555 $in_where = FALSE;
1556 $in_select_expr = FALSE;
1557 $in_from = FALSE;
1559 if ($upper_data == 'HAVING') {
1560 $in_having = TRUE;
1561 $having_clause = '';
1562 $seen_group = FALSE;
1563 $seen_order = FALSE;
1564 $in_group_by = FALSE;
1565 $in_order_by = FALSE;
1566 $in_where = FALSE;
1567 $in_select_expr = FALSE;
1568 $in_from = FALSE;
1571 if ($upper_data == 'WHERE') {
1572 $in_where = TRUE;
1573 $where_clause = '';
1574 $where_clause_identifiers = array();
1575 $seen_group = FALSE;
1576 $seen_order = FALSE;
1577 $in_group_by = FALSE;
1578 $in_order_by = FALSE;
1579 $in_having = FALSE;
1580 $in_select_expr = FALSE;
1581 $in_from = FALSE;
1584 if ($upper_data == 'BY') {
1585 if ($seen_group) {
1586 $in_group_by = TRUE;
1587 $group_by_clause = '';
1589 if ($seen_order) {
1590 $in_order_by = TRUE;
1591 $order_by_clause = '';
1595 // if we find one of the words that could end the clause
1596 if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
1598 $in_group_by = FALSE;
1599 $in_order_by = FALSE;
1600 $in_having = FALSE;
1601 $in_where = FALSE;
1602 $in_select_expr = FALSE;
1603 $in_from = FALSE;
1606 } // endif (reservedWord)
1609 // do not add a space after a function name
1611 * @todo can we combine loop 2 and loop 1? some code is repeated here...
1614 $sep = ' ';
1615 if ($arr[$i]['type'] == 'alpha_functionName') {
1616 $sep='';
1617 $upper_data = strtoupper($arr[$i]['data']);
1618 if ($upper_data =='GROUP_CONCAT') {
1619 $in_group_concat = TRUE;
1620 $number_of_brackets_in_group_concat = 0;
1624 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1625 if ($in_group_concat) {
1626 $number_of_brackets_in_group_concat++;
1629 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1630 if ($in_group_concat) {
1631 $number_of_brackets_in_group_concat--;
1632 if ($number_of_brackets_in_group_concat == 0) {
1633 $in_group_concat = FALSE;
1638 // do not add a space after an identifier if followed by a dot
1639 if ($arr[$i]['type'] == 'alpha_identifier' && $i < $size - 1 && $arr[$i + 1]['data'] == '.') {
1640 $sep = '';
1643 // do not add a space after a dot if followed by an identifier
1644 if ($arr[$i]['data'] == '.' && $i < $size - 1 && $arr[$i + 1]['type'] == 'alpha_identifier') {
1645 $sep = '';
1648 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1649 $select_expr_clause .= $arr[$i]['data'] . $sep;
1651 if ($in_from && $upper_data != 'FROM') {
1652 $from_clause .= $arr[$i]['data'] . $sep;
1654 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1655 $group_by_clause .= $arr[$i]['data'] . $sep;
1657 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1658 // add a space only before ASC or DESC
1659 // not around the dot between dbname and tablename
1660 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1661 $order_by_clause .= $sep;
1663 $order_by_clause .= $arr[$i]['data'];
1665 if ($in_having && $upper_data != 'HAVING') {
1666 $having_clause .= $arr[$i]['data'] . $sep;
1668 if ($in_where && $upper_data != 'WHERE') {
1669 $where_clause .= $arr[$i]['data'] . $sep;
1671 if (($arr[$i]['type'] == 'quote_backtick')
1672 || ($arr[$i]['type'] == 'alpha_identifier')) {
1673 $where_clause_identifiers[] = $arr[$i]['data'];
1677 if (isset($subresult['queryflags']['select_from'])
1678 && $subresult['queryflags']['select_from'] == 1
1679 && !$seen_order) {
1680 $unsorted_query .= $arr[$i]['data'];
1682 if ($arr[$i]['type'] != 'punct_bracket_open_round'
1683 && $arr[$i]['type'] != 'punct_bracket_close_round'
1684 && $arr[$i]['type'] != 'punct') {
1685 $unsorted_query .= $sep;
1689 if ($in_limit) {
1690 if ($upper_data == 'OFFSET') {
1691 $limit_clause .= $sep;
1693 $limit_clause .= $arr[$i]['data'];
1694 if ($upper_data == 'LIMIT' || $upper_data == 'OFFSET') {
1695 $limit_clause .= $sep;
1698 if (! $in_limit && $before_limit && $arr[$i]['type'] != 'punct_queryend') {
1699 $section_before_limit .= $arr[$i]['data'] . $sep;
1701 if ($after_limit) {
1702 $section_after_limit .= $arr[$i]['data'] . $sep;
1705 // clear $upper_data for next iteration
1706 $upper_data='';
1708 } // end for $i (loop #2)
1711 // -----------------------------------------------------
1712 // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
1713 // (for now, check only the first query)
1714 // (for now, identifiers are assumed to be backquoted)
1716 // If we find that we are dealing with a CREATE TABLE query,
1717 // we look for the next punct_bracket_open_round, which
1718 // introduces the fields list. Then, when we find a
1719 // quote_backtick, it must be a field, so we put it into
1720 // the create_table_fields array. Even if this field is
1721 // not a timestamp, it will be useful when logic has been
1722 // added for complete field attributes analysis.
1724 $seen_foreign = FALSE;
1725 $seen_references = FALSE;
1726 $seen_constraint = FALSE;
1727 $foreign_key_number = -1;
1728 $seen_create_table = FALSE;
1729 $seen_create = FALSE;
1730 $in_create_table_fields = FALSE;
1731 $brackets_level = 0;
1732 $in_timestamp_options = FALSE;
1733 $seen_default = FALSE;
1735 for ($i = 0; $i < $size; $i++) {
1736 // DEBUG echo "Loop 3 <b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
1738 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1739 $upper_data = strtoupper($arr[$i]['data']);
1741 if ($upper_data == 'NOT' && $in_timestamp_options) {
1742 $create_table_fields[$current_identifier]['timestamp_not_null'] = TRUE;
1746 if ($upper_data == 'CREATE') {
1747 $seen_create = TRUE;
1750 if ($upper_data == 'TABLE' && $seen_create) {
1751 $seen_create_table = TRUE;
1752 $create_table_fields = array();
1755 if ($upper_data == 'CURRENT_TIMESTAMP') {
1756 if ($in_timestamp_options) {
1757 if ($seen_default) {
1758 $create_table_fields[$current_identifier]['default_current_timestamp'] = TRUE;
1763 if ($upper_data == 'CONSTRAINT') {
1764 $foreign_key_number++;
1765 $seen_foreign = FALSE;
1766 $seen_references = FALSE;
1767 $seen_constraint = TRUE;
1769 if ($upper_data == 'FOREIGN') {
1770 $seen_foreign = TRUE;
1771 $seen_references = FALSE;
1772 $seen_constraint = FALSE;
1774 if ($upper_data == 'REFERENCES') {
1775 $seen_foreign = FALSE;
1776 $seen_references = TRUE;
1777 $seen_constraint = FALSE;
1781 // Cases covered:
1783 // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1784 // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1786 // but we set ['on_delete'] or ['on_cascade'] to
1787 // CASCADE | SET_NULL | NO_ACTION | RESTRICT
1789 // ON UPDATE CURRENT_TIMESTAMP
1791 if ($upper_data == 'ON') {
1792 if (isset($arr[$i+1]) && $arr[$i+1]['type'] == 'alpha_reservedWord') {
1793 $second_upper_data = strtoupper($arr[$i+1]['data']);
1794 if ($second_upper_data == 'DELETE') {
1795 $clause = 'on_delete';
1797 if ($second_upper_data == 'UPDATE') {
1798 $clause = 'on_update';
1800 if (isset($clause)
1801 && ($arr[$i+2]['type'] == 'alpha_reservedWord'
1803 // ugly workaround because currently, NO is not
1804 // in the list of reserved words in sqlparser.data
1805 // (we got a bug report about not being able to use
1806 // 'no' as an identifier)
1807 || ($arr[$i+2]['type'] == 'alpha_identifier'
1808 && strtoupper($arr[$i+2]['data'])=='NO'))
1810 $third_upper_data = strtoupper($arr[$i+2]['data']);
1811 if ($third_upper_data == 'CASCADE'
1812 || $third_upper_data == 'RESTRICT') {
1813 $value = $third_upper_data;
1814 } elseif ($third_upper_data == 'SET'
1815 || $third_upper_data == 'NO') {
1816 if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
1817 $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
1819 } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
1820 if ($clause == 'on_update'
1821 && $in_timestamp_options) {
1822 $create_table_fields[$current_identifier]['on_update_current_timestamp'] = TRUE;
1823 $seen_default = FALSE;
1826 } else {
1827 $value = '';
1829 if (!empty($value)) {
1830 $foreign[$foreign_key_number][$clause] = $value;
1832 unset($clause);
1833 } // endif (isset($clause))
1837 } // end of reserved words analysis
1840 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1841 $brackets_level++;
1842 if ($seen_create_table && $brackets_level == 1) {
1843 $in_create_table_fields = TRUE;
1848 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1849 $brackets_level--;
1850 if ($seen_references) {
1851 $seen_references = FALSE;
1853 if ($seen_create_table && $brackets_level == 0) {
1854 $in_create_table_fields = FALSE;
1858 if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
1859 $upper_data = strtoupper($arr[$i]['data']);
1860 if ($seen_create_table && $in_create_table_fields) {
1861 if ($upper_data == 'DEFAULT') {
1862 $seen_default = TRUE;
1868 * @see @todo 2005-10-16 note: the "or" part here is a workaround for a bug
1870 if (($arr[$i]['type'] == 'alpha_columnType') || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)) {
1871 $upper_data = strtoupper($arr[$i]['data']);
1872 if ($seen_create_table && $in_create_table_fields && isset($current_identifier)) {
1873 $create_table_fields[$current_identifier]['type'] = $upper_data;
1874 if ($upper_data == 'TIMESTAMP') {
1875 $arr[$i]['type'] = 'alpha_columnType';
1876 $in_timestamp_options = TRUE;
1877 } else {
1878 $in_timestamp_options = FALSE;
1879 if ($upper_data == 'CHAR') {
1880 $arr[$i]['type'] = 'alpha_columnType';
1887 if ($arr[$i]['type'] == 'quote_backtick' || $arr[$i]['type'] == 'alpha_identifier') {
1889 if ($arr[$i]['type'] == 'quote_backtick') {
1890 // remove backquotes
1891 $identifier = PMA_unQuote($arr[$i]['data']);
1892 } else {
1893 $identifier = $arr[$i]['data'];
1896 if ($seen_create_table && $in_create_table_fields) {
1897 $current_identifier = $identifier;
1898 // warning: we set this one even for non TIMESTAMP type
1899 $create_table_fields[$current_identifier]['timestamp_not_null'] = FALSE;
1902 if ($seen_constraint) {
1903 $foreign[$foreign_key_number]['constraint'] = $identifier;
1906 if ($seen_foreign && $brackets_level > 0) {
1907 $foreign[$foreign_key_number]['index_list'][] = $identifier;
1910 if ($seen_references) {
1911 // here, the first bracket level corresponds to the
1912 // bracket of CREATE TABLE
1913 // so if we are on level 2, it must be the index list
1914 // of the foreign key REFERENCES
1915 if ($brackets_level > 1) {
1916 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1917 } else {
1918 // for MySQL 4.0.18, identifier is
1919 // `table` or `db`.`table`
1920 // the first pass will pick the db name
1921 // the next pass will execute the else and pick the
1922 // db name in $db_table[0]
1923 if ($arr[$i+1]['type'] == 'punct_qualifier') {
1924 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
1925 } else {
1926 // for MySQL 4.0.16, identifier is
1927 // `table` or `db.table`
1928 $db_table = explode('.', $identifier);
1929 if (isset($db_table[1])) {
1930 $foreign[$foreign_key_number]['ref_db_name'] = $db_table[0];
1931 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[1];
1932 } else {
1933 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[0];
1939 } // end for $i (loop #3)
1942 // Fill the $subresult array
1944 if (isset($create_table_fields)) {
1945 $subresult['create_table_fields'] = $create_table_fields;
1948 if (isset($foreign)) {
1949 $subresult['foreign_keys'] = $foreign;
1952 if (isset($select_expr_clause)) {
1953 $subresult['select_expr_clause'] = $select_expr_clause;
1955 if (isset($from_clause)) {
1956 $subresult['from_clause'] = $from_clause;
1958 if (isset($group_by_clause)) {
1959 $subresult['group_by_clause'] = $group_by_clause;
1961 if (isset($order_by_clause)) {
1962 $subresult['order_by_clause'] = $order_by_clause;
1964 if (isset($having_clause)) {
1965 $subresult['having_clause'] = $having_clause;
1967 if (isset($limit_clause)) {
1968 $subresult['limit_clause'] = $limit_clause;
1970 if (isset($where_clause)) {
1971 $subresult['where_clause'] = $where_clause;
1973 if (isset($unsorted_query) && !empty($unsorted_query)) {
1974 $subresult['unsorted_query'] = $unsorted_query;
1976 if (isset($where_clause_identifiers)) {
1977 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
1980 if (isset($position_of_first_select)) {
1981 $subresult['position_of_first_select'] = $position_of_first_select;
1982 $subresult['section_before_limit'] = $section_before_limit;
1983 $subresult['section_after_limit'] = $section_after_limit;
1986 // They are naughty and didn't have a trailing semi-colon,
1987 // then still handle it properly
1988 if ($subresult['querytype'] != '') {
1989 $result[] = $subresult;
1991 return $result;
1992 } // end of the "PMA_SQP_analyze()" function
1996 * Colorizes SQL queries html formatted
1998 * @todo check why adding a "\n" after the </span> would cause extra blanks
1999 * to be displayed: SELECT p . person_name
2000 * @param array The SQL queries html formatted
2002 * @return array The colorized SQL queries
2004 * @access public
2006 function PMA_SQP_formatHtml_colorize($arr)
2008 $i = $GLOBALS['PMA_strpos']($arr['type'], '_');
2009 $class = '';
2010 if ($i > 0) {
2011 $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
2014 $class .= 'syntax_' . $arr['type'];
2016 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
2017 } // end of the "PMA_SQP_formatHtml_colorize()" function
2021 * Formats SQL queries to html
2023 * @param array The SQL queries
2024 * @param string mode
2025 * @param integer starting token
2026 * @param integer number of tokens to format, -1 = all
2028 * @return string The formatted SQL queries
2030 * @access public
2032 function PMA_SQP_formatHtml($arr, $mode='color', $start_token=0,
2033 $number_of_tokens=-1)
2035 //DEBUG echo 'in Format<pre>'; print_r($arr); echo '</pre>';
2036 // then check for an array
2037 if (!is_array($arr)) {
2038 return htmlspecialchars($arr);
2040 // first check for the SQL parser having hit an error
2041 if (PMA_SQP_isError()) {
2042 return htmlspecialchars($arr['raw']);
2044 // else do it properly
2045 switch ($mode) {
2046 case 'color':
2047 $str = '<span class="syntax">';
2048 $html_line_break = '<br />';
2049 break;
2050 case 'query_only':
2051 $str = '';
2052 $html_line_break = "\n";
2053 break;
2054 case 'text':
2055 $str = '';
2056 $html_line_break = '<br />';
2057 break;
2058 } // end switch
2059 $indent = 0;
2060 $bracketlevel = 0;
2061 $functionlevel = 0;
2062 $infunction = FALSE;
2063 $space_punct_listsep = ' ';
2064 $space_punct_listsep_function_name = ' ';
2065 // $space_alpha_reserved_word = '<br />'."\n";
2066 $space_alpha_reserved_word = ' ';
2068 $keywords_with_brackets_1before = array(
2069 'INDEX',
2070 'KEY',
2071 'ON',
2072 'USING'
2074 $keywords_with_brackets_1before_cnt = 4;
2076 $keywords_with_brackets_2before = array(
2077 'IGNORE',
2078 'INDEX',
2079 'INTO',
2080 'KEY',
2081 'PRIMARY',
2082 'PROCEDURE',
2083 'REFERENCES',
2084 'UNIQUE',
2085 'USE'
2087 // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
2088 $keywords_with_brackets_2before_cnt = 9;
2090 // These reserved words do NOT get a newline placed near them.
2091 $keywords_no_newline = array(
2092 'AS',
2093 'ASC',
2094 'DESC',
2095 'DISTINCT',
2096 'DUPLICATE',
2097 'HOUR',
2098 'INTERVAL',
2099 'IS',
2100 'LIKE',
2101 'NOT',
2102 'NULL',
2103 'ON',
2104 'REGEXP'
2106 $keywords_no_newline_cnt = 12;
2108 // These reserved words introduce a privilege list
2109 $keywords_priv_list = array(
2110 'GRANT',
2111 'REVOKE'
2113 $keywords_priv_list_cnt = 2;
2115 if ($number_of_tokens == -1) {
2116 $arraysize = $arr['len'];
2117 } else {
2118 $arraysize = $number_of_tokens;
2120 $typearr = array();
2121 if ($arraysize >= 0) {
2122 $typearr[0] = '';
2123 $typearr[1] = '';
2124 $typearr[2] = '';
2125 //$typearr[3] = $arr[0]['type'];
2126 $typearr[3] = $arr[$start_token]['type'];
2129 $in_priv_list = FALSE;
2130 for ($i = $start_token; $i < $arraysize; $i++) {
2131 // DEBUG echo "Loop format <b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
2132 $before = '';
2133 $after = '';
2134 $indent = 0;
2135 // array_shift($typearr);
2137 0 prev2
2138 1 prev
2139 2 current
2140 3 next
2142 if (($i + 1) < $arraysize) {
2143 // array_push($typearr, $arr[$i + 1]['type']);
2144 $typearr[4] = $arr[$i + 1]['type'];
2145 } else {
2146 //array_push($typearr, null);
2147 $typearr[4] = '';
2150 for ($j=0; $j<4; $j++) {
2151 $typearr[$j] = $typearr[$j + 1];
2154 switch ($typearr[2]) {
2155 case 'white_newline':
2156 $before = '';
2157 break;
2158 case 'punct_bracket_open_round':
2159 $bracketlevel++;
2160 $infunction = FALSE;
2161 // Make sure this array is sorted!
2162 if (($typearr[1] == 'alpha_functionName') || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
2163 || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex') || ($typearr[3] == 'digit_float')
2164 || (($typearr[0] == 'alpha_reservedWord')
2165 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
2166 || (($typearr[1] == 'alpha_reservedWord')
2167 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
2169 $functionlevel++;
2170 $infunction = TRUE;
2171 $after .= ' ';
2172 } else {
2173 $indent++;
2174 $after .= ($mode != 'query_only' ? '<div class="syntax_indent' . $indent . '">' : ' ');
2176 break;
2177 case 'alpha_identifier':
2178 if (($typearr[1] == 'punct_qualifier') || ($typearr[3] == 'punct_qualifier')) {
2179 $after = '';
2180 $before = '';
2182 if (($typearr[3] == 'alpha_columnType') || ($typearr[3] == 'alpha_identifier')) {
2183 $after .= ' ';
2185 break;
2186 case 'punct_user':
2187 case 'punct_qualifier':
2188 $before = '';
2189 $after = '';
2190 break;
2191 case 'punct_listsep':
2192 if ($infunction == TRUE) {
2193 $after .= $space_punct_listsep_function_name;
2194 } else {
2195 $after .= $space_punct_listsep;
2197 break;
2198 case 'punct_queryend':
2199 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi') && $typearr[3] != 'comment_c') {
2200 $after .= $html_line_break;
2201 $after .= $html_line_break;
2203 $space_punct_listsep = ' ';
2204 $space_punct_listsep_function_name = ' ';
2205 $space_alpha_reserved_word = ' ';
2206 $in_priv_list = FALSE;
2207 break;
2208 case 'comment_mysql':
2209 case 'comment_ansi':
2210 $after .= $html_line_break;
2211 break;
2212 case 'punct':
2213 $before .= ' ';
2214 // workaround for
2215 // select * from mytable limit 0,-1
2216 // (a side effect of this workaround is that
2217 // select 20 - 9
2218 // becomes
2219 // select 20 -9
2220 // )
2221 if ($typearr[3] != 'digit_integer') {
2222 $after .= ' ';
2224 break;
2225 case 'punct_bracket_close_round':
2226 $bracketlevel--;
2227 if ($infunction == TRUE) {
2228 $functionlevel--;
2229 $after .= ' ';
2230 $before .= ' ';
2231 } else {
2232 $indent--;
2233 $before .= ($mode != 'query_only' ? '</div>' : ' ');
2235 $infunction = ($functionlevel > 0) ? TRUE : FALSE;
2236 break;
2237 case 'alpha_columnType':
2238 if ($typearr[3] == 'alpha_columnAttrib') {
2239 $after .= ' ';
2241 if ($typearr[1] == 'alpha_columnType') {
2242 $before .= ' ';
2244 break;
2245 case 'alpha_columnAttrib':
2247 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
2248 // COLLATE LATIN1_GENERAL_CI DEFAULT
2249 if ($typearr[1] == 'alpha_identifier' || $typearr[1] == 'alpha_charset') {
2250 $before .= ' ';
2252 if (($typearr[3] == 'alpha_columnAttrib') || ($typearr[3] == 'quote_single') || ($typearr[3] == 'digit_integer')) {
2253 $after .= ' ';
2255 // workaround for
2256 // AUTO_INCREMENT = 31DEFAULT_CHARSET = utf-8
2258 if ($typearr[2] == 'alpha_columnAttrib' && $typearr[3] == 'alpha_reservedWord') {
2259 $before .= ' ';
2261 // workaround for
2262 // select * from mysql.user where binary user="root"
2263 // binary is marked as alpha_columnAttrib
2264 // but should be marked as a reserved word
2265 if (strtoupper($arr[$i]['data']) == 'BINARY'
2266 && $typearr[3] == 'alpha_identifier') {
2267 $after .= ' ';
2269 break;
2270 case 'alpha_reservedWord':
2271 // do not uppercase the reserved word if we are calling
2272 // this function in query_only mode, because we need
2273 // the original query (otherwise we get problems with
2274 // semi-reserved words like "storage" which is legal
2275 // as an identifier name)
2277 if ($mode != 'query_only') {
2278 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
2281 if ((($typearr[1] != 'alpha_reservedWord')
2282 || (($typearr[1] == 'alpha_reservedWord')
2283 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
2284 && ($typearr[1] != 'punct_level_plus')
2285 && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
2286 // do not put a space before the first token, because
2287 // we use a lot of eregi() checking for the first
2288 // reserved word at beginning of query
2289 // so do not put a newline before
2291 // also we must not be inside a privilege list
2292 if ($i > 0) {
2293 // the alpha_identifier exception is there to
2294 // catch cases like
2295 // GRANT SELECT ON mydb.mytable TO myuser@localhost
2296 // (else, we get mydb.mytableTO)
2298 // the quote_single exception is there to
2299 // catch cases like
2300 // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
2302 * @todo fix all cases and find why this happens
2305 if (!$in_priv_list || $typearr[1] == 'alpha_identifier' || $typearr[1] == 'quote_single' || $typearr[1] == 'white_newline') {
2306 $before .= $space_alpha_reserved_word;
2308 } else {
2309 // on first keyword, check if it introduces a
2310 // privilege list
2311 if (PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_priv_list, $keywords_priv_list_cnt)) {
2312 $in_priv_list = TRUE;
2315 } else {
2316 $before .= ' ';
2319 switch ($arr[$i]['data']) {
2320 case 'CREATE':
2321 if (!$in_priv_list) {
2322 $space_punct_listsep = $html_line_break;
2323 $space_alpha_reserved_word = ' ';
2325 break;
2326 case 'EXPLAIN':
2327 case 'DESCRIBE':
2328 case 'SET':
2329 case 'ALTER':
2330 case 'DELETE':
2331 case 'SHOW':
2332 case 'DROP':
2333 case 'UPDATE':
2334 case 'TRUNCATE':
2335 case 'ANALYZE':
2336 case 'ANALYSE':
2337 if (!$in_priv_list) {
2338 $space_punct_listsep = $html_line_break;
2339 $space_alpha_reserved_word = ' ';
2341 break;
2342 case 'INSERT':
2343 case 'REPLACE':
2344 if (!$in_priv_list) {
2345 $space_punct_listsep = $html_line_break;
2346 $space_alpha_reserved_word = $html_line_break;
2348 break;
2349 case 'VALUES':
2350 $space_punct_listsep = ' ';
2351 $space_alpha_reserved_word = $html_line_break;
2352 break;
2353 case 'SELECT':
2354 $space_punct_listsep = ' ';
2355 $space_alpha_reserved_word = $html_line_break;
2356 break;
2357 default:
2358 break;
2359 } // end switch ($arr[$i]['data'])
2361 $after .= ' ';
2362 break;
2363 case 'digit_integer':
2364 case 'digit_float':
2365 case 'digit_hex':
2367 * @todo could there be other types preceding a digit?
2369 if ($typearr[1] == 'alpha_reservedWord') {
2370 $after .= ' ';
2372 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2373 $after .= ' ';
2375 if ($typearr[1] == 'alpha_columnAttrib') {
2376 $before .= ' ';
2378 break;
2379 case 'alpha_variable':
2380 $after = ' ';
2381 break;
2382 case 'quote_double':
2383 case 'quote_single':
2384 // workaround: for the query
2385 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
2386 // the @ is incorrectly marked as alpha_variable
2387 // in the parser, and here, the '%' gets a blank before,
2388 // which is a syntax error
2389 if ($typearr[1] != 'punct_user') {
2390 $before .= ' ';
2392 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2393 $after .= ' ';
2395 break;
2396 case 'quote_backtick':
2397 // here we check for punct_user to handle correctly
2398 // DEFINER = `username`@`%`
2399 // where @ is the punct_user and `%` is the quote_backtick
2400 if ($typearr[3] != 'punct_qualifier' && $typearr[3] != 'alpha_variable' && $typearr[3] != 'punct_user') {
2401 $after .= ' ';
2403 if ($typearr[1] != 'punct_qualifier' && $typearr[1] != 'alpha_variable' && $typearr[1] != 'punct_user') {
2404 $before .= ' ';
2406 break;
2407 default:
2408 break;
2409 } // end switch ($typearr[2])
2412 if ($typearr[3] != 'punct_qualifier') {
2413 $after .= ' ';
2415 $after .= "\n";
2417 $str .= $before . ($mode=='color' ? PMA_SQP_formatHTML_colorize($arr[$i]) : $arr[$i]['data']). $after;
2418 } // end for
2419 if ($mode=='color') {
2420 $str .= '</span>';
2423 return $str;
2424 } // end of the "PMA_SQP_formatHtml()" function
2428 * Builds a CSS rule used for html formatted SQL queries
2430 * @param string The class name
2431 * @param string The property name
2432 * @param string The property value
2434 * @return string The CSS rule
2436 * @access public
2438 * @see PMA_SQP_buildCssData()
2440 function PMA_SQP_buildCssRule($classname, $property, $value)
2442 $str = '.' . $classname . ' {';
2443 if ($value != '') {
2444 $str .= $property . ': ' . $value . ';';
2446 $str .= '}' . "\n";
2448 return $str;
2449 } // end of the "PMA_SQP_buildCssRule()" function
2453 * Builds CSS rules used for html formatted SQL queries
2455 * @return string The CSS rules set
2457 * @access public
2459 * @global array The current PMA configuration
2461 * @see PMA_SQP_buildCssRule()
2463 function PMA_SQP_buildCssData()
2465 global $cfg;
2467 $css_string = '';
2468 foreach ($cfg['SQP']['fmtColor'] AS $key => $col) {
2469 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
2472 for ($i = 0; $i < 8; $i++) {
2473 $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
2476 return $css_string;
2477 } // end of the "PMA_SQP_buildCssData()" function
2479 if (! defined('PMA_MINIMUM_COMMON')) {
2481 * Gets SQL queries with no format
2483 * @param array The SQL queries list
2485 * @return string The SQL queries with no format
2487 * @access public
2489 function PMA_SQP_formatNone($arr)
2491 $formatted_sql = htmlspecialchars($arr['raw']);
2492 $formatted_sql = preg_replace("@((\015\012)|(\015)|(\012)){3,}@", "\n\n", $formatted_sql);
2494 return $formatted_sql;
2495 } // end of the "PMA_SQP_formatNone()" function
2499 * Gets SQL queries in text format
2501 * @todo WRITE THIS!
2502 * @param array The SQL queries list
2504 * @return string The SQL queries in text format
2506 * @access public
2508 function PMA_SQP_formatText($arr)
2510 return PMA_SQP_formatNone($arr);
2511 } // end of the "PMA_SQP_formatText()" function
2512 } // end if: minimal common.lib needed?