RFE #1435922 [gui] navigation frame shows listing of databases when none selected
[phpmyadmin/last10db.git] / libraries / sqlparser.lib.php
blob91ca7cd73482ed2be7c7b5a0594152b4730cd6c2
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 'where_clause' => '',
813 'where_clause_identifiers' => array(),
814 'unsorted_query' => '',
815 'queryflags' => array(),
816 'select_expr' => array(),
817 'table_ref' => array(),
818 'foreign_keys' => array(),
819 'create_table_fields' => array()
821 $subresult_empty = $subresult;
822 $seek_queryend = FALSE;
823 $seen_end_of_table_ref = FALSE;
824 $number_of_brackets_in_extract = 0;
825 $number_of_brackets_in_group_concat = 0;
827 $number_of_brackets = 0;
828 $in_subquery = false;
830 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
831 // we must not use CURDATE as a table_ref
832 // so we track wether we are in the EXTRACT()
833 $in_extract = FALSE;
835 // for GROUP_CONCAT(...)
836 $in_group_concat = FALSE;
838 /* Description of analyzer results by lem9
840 * db, table, column, alias
841 * ------------------------
843 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
845 * The SELECT syntax (simplified) is
847 * SELECT
848 * select_expression,...
849 * [FROM [table_references]
852 * ['select_expr'] is filled with each expression, the key represents the
853 * expression position in the list (0-based) (so we don't lose track of
854 * multiple occurences of the same column).
856 * ['table_ref'] is filled with each table ref, same thing for the key.
858 * I create all sub-values empty, even if they are
859 * not present (for example no select_expression alias).
861 * There is a debug section at the end of loop #1, if you want to
862 * see the exact contents of select_expr and table_ref
864 * queryflags
865 * ----------
867 * In $subresult, array 'queryflags' is filled, according to what we
868 * find in the query.
870 * Currently, those are generated:
872 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
873 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
874 * ['queryflags']['distinct'] = 1; for a DISTINCT
875 * ['queryflags']['union'] = 1; for a UNION
876 * ['queryflags']['join'] = 1; for a JOIN
877 * ['queryflags']['offset'] = 1; for the presence of OFFSET
879 * query clauses
880 * -------------
882 * The select is splitted in those clauses:
883 * ['select_expr_clause']
884 * ['from_clause']
885 * ['group_by_clause']
886 * ['order_by_clause']
887 * ['having_clause']
888 * ['where_clause']
890 * The identifiers of the WHERE clause are put into the array
891 * ['where_clause_identifier']
893 * For a SELECT, the whole query without the ORDER BY clause is put into
894 * ['unsorted_query']
896 * foreign keys
897 * ------------
898 * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
899 * analyzed and ['foreign_keys'] is an array filled with
900 * the constraint name, the index list,
901 * the REFERENCES table name and REFERENCES index list,
902 * and ON UPDATE | ON DELETE clauses
904 * position_of_first_select
905 * ------------------------
907 * The array index of the first SELECT we find. Will be used to
908 * insert a SQL_CALC_FOUND_ROWS.
910 * create_table_fields
911 * -------------------
913 * For now, mostly used to detect the DEFAULT CURRENT_TIMESTAMP and
914 * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
915 * An array, each element is the identifier name.
916 * Note that for now, the timestamp_not_null element is created
917 * even for non-TIMESTAMP fields.
919 * Sub-elements: ['type'] which contains the column type
920 * optional (currently they are never false but can be absent):
921 * ['default_current_timestamp'] boolean
922 * ['on_update_current_timestamp'] boolean
923 * ['timestamp_not_null'] boolean
925 * section_before_limit, section_after_limit
926 * -----------------------------------------
928 * Marks the point of the query where we can insert a LIMIT clause;
929 * so the section_before_limit will contain the left part before
930 * a possible LIMIT clause
933 * End of description of analyzer results
936 // must be sorted
937 // TODO: current logic checks for only one word, so I put only the
938 // first word of the reserved expressions that end a table ref;
939 // maybe this is not ok (the first word might mean something else)
940 // $words_ending_table_ref = array(
941 // 'FOR UPDATE',
942 // 'GROUP BY',
943 // 'HAVING',
944 // 'LIMIT',
945 // 'LOCK IN SHARE MODE',
946 // 'ORDER BY',
947 // 'PROCEDURE',
948 // 'UNION',
949 // 'WHERE'
950 // );
951 $words_ending_table_ref = array(
952 'FOR',
953 'GROUP',
954 'HAVING',
955 'LIMIT',
956 'LOCK',
957 'ORDER',
958 'PROCEDURE',
959 'UNION',
960 'WHERE'
962 $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
964 $words_ending_clauses = array(
965 'FOR',
966 'LIMIT',
967 'LOCK',
968 'PROCEDURE',
969 'UNION'
971 $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
976 // must be sorted
977 $supported_query_types = array(
978 'SELECT'
980 // Support for these additional query types will come later on.
981 'DELETE',
982 'INSERT',
983 'REPLACE',
984 'TRUNCATE',
985 'UPDATE'
986 'EXPLAIN',
987 'DESCRIBE',
988 'SHOW',
989 'CREATE',
990 'SET',
991 'ALTER'
994 $supported_query_types_cnt = count($supported_query_types);
996 // loop #1 for each token: select_expr, table_ref for SELECT
998 for ($i = 0; $i < $size; $i++) {
999 //DEBUG echo "Loop1 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
1001 // High speed seek for locating the end of the current query
1002 if ($seek_queryend == TRUE) {
1003 if ($arr[$i]['type'] == 'punct_queryend') {
1004 $seek_queryend = FALSE;
1005 } else {
1006 continue;
1007 } // end if (type == punct_queryend)
1008 } // end if ($seek_queryend)
1011 * Note: do not split if this is a punct_queryend for the first and only query
1012 * @todo when we find a UNION, should we split in another subresult?
1014 if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
1015 $result[] = $subresult;
1016 $subresult = $subresult_empty;
1017 continue;
1018 } // end if (type == punct_queryend)
1020 // ==============================================================
1021 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1022 $number_of_brackets++;
1023 if ($in_extract) {
1024 $number_of_brackets_in_extract++;
1026 if ($in_group_concat) {
1027 $number_of_brackets_in_group_concat++;
1030 // ==============================================================
1031 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1032 $number_of_brackets--;
1033 if ($number_of_brackets == 0) {
1034 $in_subquery = false;
1036 if ($in_extract) {
1037 $number_of_brackets_in_extract--;
1038 if ($number_of_brackets_in_extract == 0) {
1039 $in_extract = FALSE;
1042 if ($in_group_concat) {
1043 $number_of_brackets_in_group_concat--;
1044 if ($number_of_brackets_in_group_concat == 0) {
1045 $in_group_concat = FALSE;
1050 if ($in_subquery) {
1052 * skip the subquery to avoid setting
1053 * select_expr or table_ref with the contents
1054 * of this subquery; this is to avoid a bug when
1055 * trying to edit the results of
1056 * select * from child where not exists (select id from
1057 * parent where child.parent_id = parent.id);
1059 continue;
1061 // ==============================================================
1062 if ($arr[$i]['type'] == 'alpha_functionName') {
1063 $upper_data = strtoupper($arr[$i]['data']);
1064 if ($upper_data =='EXTRACT') {
1065 $in_extract = TRUE;
1066 $number_of_brackets_in_extract = 0;
1068 if ($upper_data =='GROUP_CONCAT') {
1069 $in_group_concat = TRUE;
1070 $number_of_brackets_in_group_concat = 0;
1074 // ==============================================================
1075 if ($arr[$i]['type'] == 'alpha_reservedWord'
1076 // && $arr[$i]['forbidden'] == FALSE) {
1078 // We don't know what type of query yet, so run this
1079 if ($subresult['querytype'] == '') {
1080 $subresult['querytype'] = strtoupper($arr[$i]['data']);
1081 } // end if (querytype was empty)
1083 // Check if we support this type of query
1084 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
1085 // Skip ahead to the next one if we don't
1086 $seek_queryend = TRUE;
1087 continue;
1088 } // end if (query not supported)
1090 // upper once
1091 $upper_data = strtoupper($arr[$i]['data']);
1093 * @todo reset for each query?
1096 if ($upper_data == 'SELECT') {
1097 if ($number_of_brackets > 0) {
1098 $in_subquery = true;
1099 // this is a subquery so do not analyze inside it
1100 continue;
1102 $seen_from = FALSE;
1103 $previous_was_identifier = FALSE;
1104 $current_select_expr = -1;
1105 $seen_end_of_table_ref = FALSE;
1106 } // end if (data == SELECT)
1108 if ($upper_data =='FROM' && !$in_extract) {
1109 $current_table_ref = -1;
1110 $seen_from = TRUE;
1111 $previous_was_identifier = FALSE;
1112 $save_table_ref = TRUE;
1113 } // end if (data == FROM)
1115 // here, do not 'continue' the loop, as we have more work for
1116 // reserved words below
1117 } // end if (type == alpha_reservedWord)
1119 // ==============================
1120 if ($arr[$i]['type'] == 'quote_backtick'
1121 || $arr[$i]['type'] == 'quote_double'
1122 || $arr[$i]['type'] == 'quote_single'
1123 || $arr[$i]['type'] == 'alpha_identifier'
1124 || ($arr[$i]['type'] == 'alpha_reservedWord'
1125 && $arr[$i]['forbidden'] == FALSE)) {
1127 switch ($arr[$i]['type']) {
1128 case 'alpha_identifier':
1129 case 'alpha_reservedWord':
1131 * this is not a real reservedWord, because it's not
1132 * present in the list of forbidden words, for example
1133 * "storage" which can be used as an identifier
1135 * @todo avoid the pretty printing in color in this case
1137 $identifier = $arr[$i]['data'];
1138 break;
1140 case 'quote_backtick':
1141 case 'quote_double':
1142 case 'quote_single':
1143 $identifier = PMA_unQuote($arr[$i]['data']);
1144 break;
1145 } // end switch
1147 if ($subresult['querytype'] == 'SELECT' && !$in_group_concat) {
1148 if (!$seen_from) {
1149 if ($previous_was_identifier && isset($chain)) {
1150 // found alias for this select_expr, save it
1151 // but only if we got something in $chain
1152 // (for example, SELECT COUNT(*) AS cnt
1153 // puts nothing in $chain, so we avoid
1154 // setting the alias)
1155 $alias_for_select_expr = $identifier;
1156 } else {
1157 $chain[] = $identifier;
1158 $previous_was_identifier = TRUE;
1160 } // end if !$previous_was_identifier
1161 } else {
1162 // ($seen_from)
1163 if ($save_table_ref && !$seen_end_of_table_ref) {
1164 if ($previous_was_identifier) {
1165 // found alias for table ref
1166 // save it for later
1167 $alias_for_table_ref = $identifier;
1168 } else {
1169 $chain[] = $identifier;
1170 $previous_was_identifier = TRUE;
1172 } // end if ($previous_was_identifier)
1173 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
1174 } // end if (!$seen_from)
1175 } // end if (querytype SELECT)
1176 } // end if (quote_backtick or double quote or alpha_identifier)
1178 // ===================================
1179 if ($arr[$i]['type'] == 'punct_qualifier') {
1180 // to be able to detect an identifier following another
1181 $previous_was_identifier = FALSE;
1182 continue;
1183 } // end if (punct_qualifier)
1186 * @todo check if 3 identifiers following one another -> error
1189 // s a v e a s e l e c t e x p r
1190 // finding a list separator or FROM
1191 // means that we must save the current chain of identifiers
1192 // into a select expression
1194 // for now, we only save a select expression if it contains
1195 // at least one identifier, as we are interested in checking
1196 // the columns and table names, so in "select * from persons",
1197 // the "*" is not saved
1199 if (isset($chain) && !$seen_end_of_table_ref
1200 && ((!$seen_from && $arr[$i]['type'] == 'punct_listsep')
1201 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM'))) {
1202 $size_chain = count($chain);
1203 $current_select_expr++;
1204 $subresult['select_expr'][$current_select_expr] = array(
1205 'expr' => '',
1206 'alias' => '',
1207 'db' => '',
1208 'table_name' => '',
1209 'table_true_name' => '',
1210 'column' => ''
1213 if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
1214 // we had found an alias for this select expression
1215 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
1216 unset($alias_for_select_expr);
1218 // there is at least a column
1219 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
1220 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
1222 // maybe a table
1223 if ($size_chain > 1) {
1224 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
1225 // we assume for now that this is also the true name
1226 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
1227 $subresult['select_expr'][$current_select_expr]['expr']
1228 = $subresult['select_expr'][$current_select_expr]['table_name']
1229 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1230 } // end if ($size_chain > 1)
1232 // maybe a db
1233 if ($size_chain > 2) {
1234 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
1235 $subresult['select_expr'][$current_select_expr]['expr']
1236 = $subresult['select_expr'][$current_select_expr]['db']
1237 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1238 } // end if ($size_chain > 2)
1239 unset($chain);
1242 * @todo explain this:
1244 if (($arr[$i]['type'] == 'alpha_reservedWord')
1245 && ($upper_data != 'FROM')) {
1246 $previous_was_identifier = TRUE;
1249 } // end if (save a select expr)
1252 //======================================
1253 // s a v e a t a b l e r e f
1254 //======================================
1256 // maybe we just saw the end of table refs
1257 // but the last table ref has to be saved
1258 // or we are at the last token
1259 // or we just got a reserved word
1261 * @todo there could be another query after this one
1264 if (isset($chain) && $seen_from && $save_table_ref
1265 && ($arr[$i]['type'] == 'punct_listsep'
1266 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
1267 || $seen_end_of_table_ref
1268 || $i==$size-1)) {
1270 $size_chain = count($chain);
1271 $current_table_ref++;
1272 $subresult['table_ref'][$current_table_ref] = array(
1273 'expr' => '',
1274 'db' => '',
1275 'table_name' => '',
1276 'table_alias' => '',
1277 'table_true_name' => ''
1279 if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
1280 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1281 unset($alias_for_table_ref);
1283 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1284 // we assume for now that this is also the true name
1285 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1286 $subresult['table_ref'][$current_table_ref]['expr']
1287 = $subresult['table_ref'][$current_table_ref]['table_name'];
1288 // maybe a db
1289 if ($size_chain > 1) {
1290 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1291 $subresult['table_ref'][$current_table_ref]['expr']
1292 = $subresult['table_ref'][$current_table_ref]['db']
1293 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1294 } // end if ($size_chain > 1)
1296 // add the table alias into the whole expression
1297 $subresult['table_ref'][$current_table_ref]['expr']
1298 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1300 unset($chain);
1301 $previous_was_identifier = TRUE;
1302 //continue;
1304 } // end if (save a table ref)
1307 // when we have found all table refs,
1308 // for each table_ref alias, put the true name of the table
1309 // in the corresponding select expressions
1311 if (isset($current_table_ref) && ($seen_end_of_table_ref || $i == $size-1) && $subresult != $subresult_empty) {
1312 for ($tr=0; $tr <= $current_table_ref; $tr++) {
1313 $alias = $subresult['table_ref'][$tr]['table_alias'];
1314 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1315 for ($se=0; $se <= $current_select_expr; $se++) {
1316 if (isset($alias) && strlen($alias) && $subresult['select_expr'][$se]['table_true_name']
1317 == $alias) {
1318 $subresult['select_expr'][$se]['table_true_name']
1319 = $truename;
1320 } // end if (found the alias)
1321 } // end for (select expressions)
1323 } // end for (table refs)
1324 } // end if (set the true names)
1327 // e n d i n g l o o p #1
1328 // set the $previous_was_identifier to FALSE if the current
1329 // token is not an identifier
1330 if (($arr[$i]['type'] != 'alpha_identifier')
1331 && ($arr[$i]['type'] != 'quote_double')
1332 && ($arr[$i]['type'] != 'quote_single')
1333 && ($arr[$i]['type'] != 'quote_backtick')) {
1334 $previous_was_identifier = FALSE;
1335 } // end if
1337 // however, if we are on AS, we must keep the $previous_was_identifier
1338 if (($arr[$i]['type'] == 'alpha_reservedWord')
1339 && ($upper_data == 'AS')) {
1340 $previous_was_identifier = TRUE;
1343 if (($arr[$i]['type'] == 'alpha_reservedWord')
1344 && ($upper_data =='ON' || $upper_data =='USING')) {
1345 $save_table_ref = FALSE;
1346 } // end if (data == ON)
1348 if (($arr[$i]['type'] == 'alpha_reservedWord')
1349 && ($upper_data =='JOIN' || $upper_data =='FROM')) {
1350 $save_table_ref = TRUE;
1351 } // end if (data == JOIN)
1354 * no need to check the end of table ref if we already did
1356 * @todo maybe add "&& $seen_from"
1358 if (!$seen_end_of_table_ref) {
1359 // if this is the last token, it implies that we have
1360 // seen the end of table references
1361 // Check for the end of table references
1363 // Note: if we are analyzing a GROUP_CONCAT clause,
1364 // we might find a word that seems to indicate that
1365 // we have found the end of table refs (like ORDER)
1366 // but it's a modifier of the GROUP_CONCAT so
1367 // it's not the real end of table refs
1368 if (($i == $size-1)
1369 || ($arr[$i]['type'] == 'alpha_reservedWord'
1370 && !$in_group_concat
1371 && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
1372 $seen_end_of_table_ref = TRUE;
1373 // to be able to save the last table ref, but do not
1374 // set it true if we found a word like "ON" that has
1375 // already set it to false
1376 if (isset($save_table_ref) && $save_table_ref != FALSE) {
1377 $save_table_ref = TRUE;
1378 } //end if
1380 } // end if (check for end of table ref)
1381 } //end if (!$seen_end_of_table_ref)
1383 if ($seen_end_of_table_ref) {
1384 $save_table_ref = FALSE;
1385 } // end if
1387 } // end for $i (loop #1)
1389 //DEBUG
1391 if (isset($current_select_expr)) {
1392 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1393 echo "<br />";
1394 reset ($subresult['select_expr'][$trace]);
1395 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1396 echo "sel expr $trace $key => $val<br />\n";
1400 if (isset($current_table_ref)) {
1401 echo "current_table_ref = " . $current_table_ref . "<br>";
1402 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1404 echo "<br />";
1405 reset ($subresult['table_ref'][$trace]);
1406 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1407 echo "table ref $trace $key => $val<br />\n";
1411 // -------------------------------------------------------
1414 // loop #2: - queryflags
1415 // - querytype (for queries != 'SELECT')
1416 // - section_before_limit, section_after_limit
1418 // we will also need this queryflag in loop 2
1419 // so set it here
1420 if (isset($current_table_ref) && $current_table_ref > -1) {
1421 $subresult['queryflags']['select_from'] = 1;
1424 $collect_section_before_limit = TRUE;
1425 $section_before_limit = '';
1426 $section_after_limit = '';
1427 $seen_reserved_word = FALSE;
1428 $seen_group = FALSE;
1429 $seen_order = FALSE;
1430 $in_group_by = FALSE; // true when we are inside the GROUP BY clause
1431 $in_order_by = FALSE; // true when we are inside the ORDER BY clause
1432 $in_having = FALSE; // true when we are inside the HAVING clause
1433 $in_select_expr = FALSE; // true when we are inside the select expr clause
1434 $in_where = FALSE; // true when we are inside the WHERE clause
1435 $in_from = FALSE;
1436 $in_group_concat = FALSE;
1437 $unsorted_query = '';
1438 $first_reserved_word = '';
1439 $current_identifier = '';
1441 for ($i = 0; $i < $size; $i++) {
1442 //DEBUG echo "Loop2 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
1444 // need_confirm
1446 // check for reserved words that will have to generate
1447 // a confirmation request later in sql.php
1448 // the cases are:
1449 // DROP TABLE
1450 // DROP DATABASE
1451 // ALTER TABLE... DROP
1452 // DELETE FROM...
1454 // this code is not used for confirmations coming from functions.js
1457 * @todo check for punct_queryend
1458 * @todo verify C-style comments?
1460 if ($arr[$i]['type'] == 'comment_ansi') {
1461 $collect_section_before_limit = FALSE;
1464 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1465 $upper_data = strtoupper($arr[$i]['data']);
1466 if (!$seen_reserved_word) {
1467 $first_reserved_word = $upper_data;
1468 $subresult['querytype'] = $upper_data;
1469 $seen_reserved_word = TRUE;
1471 // if the first reserved word is DROP or DELETE,
1472 // we know this is a query that needs to be confirmed
1473 if ($first_reserved_word=='DROP'
1474 || $first_reserved_word == 'DELETE'
1475 || $first_reserved_word == 'TRUNCATE') {
1476 $subresult['queryflags']['need_confirm'] = 1;
1479 if ($first_reserved_word=='SELECT'){
1480 $position_of_first_select = $i;
1483 } else {
1484 if ($upper_data=='DROP' && $first_reserved_word=='ALTER') {
1485 $subresult['queryflags']['need_confirm'] = 1;
1489 if ($upper_data == 'PROCEDURE') {
1490 $collect_section_before_limit = FALSE;
1493 * @todo set also to FALSE if we find FOR UPDATE or LOCK IN SHARE MODE
1495 if ($upper_data == 'SELECT') {
1496 $in_select_expr = TRUE;
1497 $select_expr_clause = '';
1499 if ($upper_data == 'DISTINCT' && !$in_group_concat) {
1500 $subresult['queryflags']['distinct'] = 1;
1503 if ($upper_data == 'UNION') {
1504 $subresult['queryflags']['union'] = 1;
1507 if ($upper_data == 'JOIN') {
1508 $subresult['queryflags']['join'] = 1;
1511 if ($upper_data == 'OFFSET') {
1512 $subresult['queryflags']['offset'] = 1;
1515 // if this is a real SELECT...FROM
1516 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1517 $in_from = TRUE;
1518 $from_clause = '';
1519 $in_select_expr = FALSE;
1523 // (we could have less resetting of variables to FALSE
1524 // if we trust that the query respects the standard
1525 // MySQL order for clauses)
1527 // we use $seen_group and $seen_order because we are looking
1528 // for the BY
1529 if ($upper_data == 'GROUP') {
1530 $seen_group = TRUE;
1531 $seen_order = FALSE;
1532 $in_having = FALSE;
1533 $in_order_by = FALSE;
1534 $in_where = FALSE;
1535 $in_select_expr = FALSE;
1536 $in_from = FALSE;
1538 if ($upper_data == 'ORDER' && !$in_group_concat) {
1539 $seen_order = TRUE;
1540 $seen_group = FALSE;
1541 $in_having = FALSE;
1542 $in_group_by = FALSE;
1543 $in_where = FALSE;
1544 $in_select_expr = FALSE;
1545 $in_from = FALSE;
1547 if ($upper_data == 'HAVING') {
1548 $in_having = TRUE;
1549 $having_clause = '';
1550 $seen_group = FALSE;
1551 $seen_order = FALSE;
1552 $in_group_by = FALSE;
1553 $in_order_by = FALSE;
1554 $in_where = FALSE;
1555 $in_select_expr = FALSE;
1556 $in_from = FALSE;
1559 if ($upper_data == 'WHERE') {
1560 $in_where = TRUE;
1561 $where_clause = '';
1562 $where_clause_identifiers = array();
1563 $seen_group = FALSE;
1564 $seen_order = FALSE;
1565 $in_group_by = FALSE;
1566 $in_order_by = FALSE;
1567 $in_having = FALSE;
1568 $in_select_expr = FALSE;
1569 $in_from = FALSE;
1572 if ($upper_data == 'BY') {
1573 if ($seen_group) {
1574 $in_group_by = TRUE;
1575 $group_by_clause = '';
1577 if ($seen_order) {
1578 $in_order_by = TRUE;
1579 $order_by_clause = '';
1583 // if we find one of the words that could end the clause
1584 if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
1586 $in_group_by = FALSE;
1587 $in_order_by = FALSE;
1588 $in_having = FALSE;
1589 $in_where = FALSE;
1590 $in_select_expr = FALSE;
1591 $in_from = FALSE;
1594 } // endif (reservedWord)
1597 // do not add a space after a function name
1599 * @todo can we combine loop 2 and loop 1? some code is repeated here...
1602 $sep = ' ';
1603 if ($arr[$i]['type'] == 'alpha_functionName') {
1604 $sep='';
1605 $upper_data = strtoupper($arr[$i]['data']);
1606 if ($upper_data =='GROUP_CONCAT') {
1607 $in_group_concat = TRUE;
1608 $number_of_brackets_in_group_concat = 0;
1612 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1613 if ($in_group_concat) {
1614 $number_of_brackets_in_group_concat++;
1617 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1618 if ($in_group_concat) {
1619 $number_of_brackets_in_group_concat--;
1620 if ($number_of_brackets_in_group_concat == 0) {
1621 $in_group_concat = FALSE;
1626 // do not add a space after an identifier if followed by a dot
1627 if ($arr[$i]['type'] == 'alpha_identifier' && $i < $size - 1 && $arr[$i + 1]['data'] == '.') {
1628 $sep = '';
1631 // do not add a space after a dot if followed by an identifier
1632 if ($arr[$i]['data'] == '.' && $i < $size - 1 && $arr[$i + 1]['type'] == 'alpha_identifier') {
1633 $sep = '';
1636 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1637 $select_expr_clause .= $arr[$i]['data'] . $sep;
1639 if ($in_from && $upper_data != 'FROM') {
1640 $from_clause .= $arr[$i]['data'] . $sep;
1642 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1643 $group_by_clause .= $arr[$i]['data'] . $sep;
1645 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1646 // add a space only before ASC or DESC
1647 // not around the dot between dbname and tablename
1648 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1649 $order_by_clause .= $sep;
1651 $order_by_clause .= $arr[$i]['data'];
1653 if ($in_having && $upper_data != 'HAVING') {
1654 $having_clause .= $arr[$i]['data'] . $sep;
1656 if ($in_where && $upper_data != 'WHERE') {
1657 $where_clause .= $arr[$i]['data'] . $sep;
1659 if (($arr[$i]['type'] == 'quote_backtick')
1660 || ($arr[$i]['type'] == 'alpha_identifier')) {
1661 $where_clause_identifiers[] = $arr[$i]['data'];
1665 if (isset($subresult['queryflags']['select_from'])
1666 && $subresult['queryflags']['select_from'] == 1
1667 && !$seen_order) {
1668 $unsorted_query .= $arr[$i]['data'];
1670 if ($arr[$i]['type'] != 'punct_bracket_open_round'
1671 && $arr[$i]['type'] != 'punct_bracket_close_round'
1672 && $arr[$i]['type'] != 'punct') {
1673 $unsorted_query .= $sep;
1677 // clear $upper_data for next iteration
1678 $upper_data='';
1680 if ($collect_section_before_limit && $arr[$i]['type'] != 'punct_queryend') {
1681 $section_before_limit .= $arr[$i]['data'] . $sep;
1682 } else {
1683 $section_after_limit .= $arr[$i]['data'] . $sep;
1687 } // end for $i (loop #2)
1690 // -----------------------------------------------------
1691 // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
1692 // (for now, check only the first query)
1693 // (for now, identifiers are assumed to be backquoted)
1695 // If we find that we are dealing with a CREATE TABLE query,
1696 // we look for the next punct_bracket_open_round, which
1697 // introduces the fields list. Then, when we find a
1698 // quote_backtick, it must be a field, so we put it into
1699 // the create_table_fields array. Even if this field is
1700 // not a timestamp, it will be useful when logic has been
1701 // added for complete field attributes analysis.
1703 $seen_foreign = FALSE;
1704 $seen_references = FALSE;
1705 $seen_constraint = FALSE;
1706 $foreign_key_number = -1;
1707 $seen_create_table = FALSE;
1708 $seen_create = FALSE;
1709 $in_create_table_fields = FALSE;
1710 $brackets_level = 0;
1711 $in_timestamp_options = FALSE;
1712 $seen_default = FALSE;
1714 for ($i = 0; $i < $size; $i++) {
1715 // DEBUG echo "Loop 3 <b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
1717 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1718 $upper_data = strtoupper($arr[$i]['data']);
1720 if ($upper_data == 'NOT' && $in_timestamp_options) {
1721 $create_table_fields[$current_identifier]['timestamp_not_null'] = TRUE;
1725 if ($upper_data == 'CREATE') {
1726 $seen_create = TRUE;
1729 if ($upper_data == 'TABLE' && $seen_create) {
1730 $seen_create_table = TRUE;
1731 $create_table_fields = array();
1734 if ($upper_data == 'CURRENT_TIMESTAMP') {
1735 if ($in_timestamp_options) {
1736 if ($seen_default) {
1737 $create_table_fields[$current_identifier]['default_current_timestamp'] = TRUE;
1742 if ($upper_data == 'CONSTRAINT') {
1743 $foreign_key_number++;
1744 $seen_foreign = FALSE;
1745 $seen_references = FALSE;
1746 $seen_constraint = TRUE;
1748 if ($upper_data == 'FOREIGN') {
1749 $seen_foreign = TRUE;
1750 $seen_references = FALSE;
1751 $seen_constraint = FALSE;
1753 if ($upper_data == 'REFERENCES') {
1754 $seen_foreign = FALSE;
1755 $seen_references = TRUE;
1756 $seen_constraint = FALSE;
1760 // Cases covered:
1762 // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1763 // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1765 // but we set ['on_delete'] or ['on_cascade'] to
1766 // CASCADE | SET_NULL | NO_ACTION | RESTRICT
1768 // ON UPDATE CURRENT_TIMESTAMP
1770 if ($upper_data == 'ON') {
1771 if (isset($arr[$i+1]) && $arr[$i+1]['type'] == 'alpha_reservedWord') {
1772 $second_upper_data = strtoupper($arr[$i+1]['data']);
1773 if ($second_upper_data == 'DELETE') {
1774 $clause = 'on_delete';
1776 if ($second_upper_data == 'UPDATE') {
1777 $clause = 'on_update';
1779 if (isset($clause)
1780 && ($arr[$i+2]['type'] == 'alpha_reservedWord'
1782 // ugly workaround because currently, NO is not
1783 // in the list of reserved words in sqlparser.data
1784 // (we got a bug report about not being able to use
1785 // 'no' as an identifier)
1786 || ($arr[$i+2]['type'] == 'alpha_identifier'
1787 && strtoupper($arr[$i+2]['data'])=='NO'))
1789 $third_upper_data = strtoupper($arr[$i+2]['data']);
1790 if ($third_upper_data == 'CASCADE'
1791 || $third_upper_data == 'RESTRICT') {
1792 $value = $third_upper_data;
1793 } elseif ($third_upper_data == 'SET'
1794 || $third_upper_data == 'NO') {
1795 if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
1796 $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
1798 } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
1799 if ($clause == 'on_update'
1800 && $in_timestamp_options) {
1801 $create_table_fields[$current_identifier]['on_update_current_timestamp'] = TRUE;
1802 $seen_default = FALSE;
1805 } else {
1806 $value = '';
1808 if (!empty($value)) {
1809 $foreign[$foreign_key_number][$clause] = $value;
1811 unset($clause);
1812 } // endif (isset($clause))
1816 } // end of reserved words analysis
1819 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1820 $brackets_level++;
1821 if ($seen_create_table && $brackets_level == 1) {
1822 $in_create_table_fields = TRUE;
1827 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1828 $brackets_level--;
1829 if ($seen_references) {
1830 $seen_references = FALSE;
1832 if ($seen_create_table && $brackets_level == 0) {
1833 $in_create_table_fields = FALSE;
1837 if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
1838 $upper_data = strtoupper($arr[$i]['data']);
1839 if ($seen_create_table && $in_create_table_fields) {
1840 if ($upper_data == 'DEFAULT') {
1841 $seen_default = TRUE;
1847 * @see @todo 2005-10-16 note: the "or" part here is a workaround for a bug
1849 if (($arr[$i]['type'] == 'alpha_columnType') || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)) {
1850 $upper_data = strtoupper($arr[$i]['data']);
1851 if ($seen_create_table && $in_create_table_fields && isset($current_identifier)) {
1852 $create_table_fields[$current_identifier]['type'] = $upper_data;
1853 if ($upper_data == 'TIMESTAMP') {
1854 $arr[$i]['type'] = 'alpha_columnType';
1855 $in_timestamp_options = TRUE;
1856 } else {
1857 $in_timestamp_options = FALSE;
1858 if ($upper_data == 'CHAR') {
1859 $arr[$i]['type'] = 'alpha_columnType';
1866 if ($arr[$i]['type'] == 'quote_backtick' || $arr[$i]['type'] == 'alpha_identifier') {
1868 if ($arr[$i]['type'] == 'quote_backtick') {
1869 // remove backquotes
1870 $identifier = PMA_unQuote($arr[$i]['data']);
1871 } else {
1872 $identifier = $arr[$i]['data'];
1875 if ($seen_create_table && $in_create_table_fields) {
1876 $current_identifier = $identifier;
1877 // warning: we set this one even for non TIMESTAMP type
1878 $create_table_fields[$current_identifier]['timestamp_not_null'] = FALSE;
1881 if ($seen_constraint) {
1882 $foreign[$foreign_key_number]['constraint'] = $identifier;
1885 if ($seen_foreign && $brackets_level > 0) {
1886 $foreign[$foreign_key_number]['index_list'][] = $identifier;
1889 if ($seen_references) {
1890 // here, the first bracket level corresponds to the
1891 // bracket of CREATE TABLE
1892 // so if we are on level 2, it must be the index list
1893 // of the foreign key REFERENCES
1894 if ($brackets_level > 1) {
1895 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1896 } else {
1897 // for MySQL 4.0.18, identifier is
1898 // `table` or `db`.`table`
1899 // the first pass will pick the db name
1900 // the next pass will execute the else and pick the
1901 // db name in $db_table[0]
1902 if ($arr[$i+1]['type'] == 'punct_qualifier') {
1903 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
1904 } else {
1905 // for MySQL 4.0.16, identifier is
1906 // `table` or `db.table`
1907 $db_table = explode('.', $identifier);
1908 if (isset($db_table[1])) {
1909 $foreign[$foreign_key_number]['ref_db_name'] = $db_table[0];
1910 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[1];
1911 } else {
1912 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[0];
1918 } // end for $i (loop #3)
1921 // Fill the $subresult array
1923 if (isset($create_table_fields)) {
1924 $subresult['create_table_fields'] = $create_table_fields;
1927 if (isset($foreign)) {
1928 $subresult['foreign_keys'] = $foreign;
1931 if (isset($select_expr_clause)) {
1932 $subresult['select_expr_clause'] = $select_expr_clause;
1934 if (isset($from_clause)) {
1935 $subresult['from_clause'] = $from_clause;
1937 if (isset($group_by_clause)) {
1938 $subresult['group_by_clause'] = $group_by_clause;
1940 if (isset($order_by_clause)) {
1941 $subresult['order_by_clause'] = $order_by_clause;
1943 if (isset($having_clause)) {
1944 $subresult['having_clause'] = $having_clause;
1946 if (isset($where_clause)) {
1947 $subresult['where_clause'] = $where_clause;
1949 if (isset($unsorted_query) && !empty($unsorted_query)) {
1950 $subresult['unsorted_query'] = $unsorted_query;
1952 if (isset($where_clause_identifiers)) {
1953 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
1956 if (isset($position_of_first_select)) {
1957 $subresult['position_of_first_select'] = $position_of_first_select;
1958 $subresult['section_before_limit'] = $section_before_limit;
1959 $subresult['section_after_limit'] = $section_after_limit;
1962 // They are naughty and didn't have a trailing semi-colon,
1963 // then still handle it properly
1964 if ($subresult['querytype'] != '') {
1965 $result[] = $subresult;
1967 return $result;
1968 } // end of the "PMA_SQP_analyze()" function
1972 * Colorizes SQL queries html formatted
1974 * @todo check why adding a "\n" after the </span> would cause extra blanks
1975 * to be displayed: SELECT p . person_name
1976 * @param array The SQL queries html formatted
1978 * @return array The colorized SQL queries
1980 * @access public
1982 function PMA_SQP_formatHtml_colorize($arr)
1984 $i = $GLOBALS['PMA_strpos']($arr['type'], '_');
1985 $class = '';
1986 if ($i > 0) {
1987 $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
1990 $class .= 'syntax_' . $arr['type'];
1992 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
1993 } // end of the "PMA_SQP_formatHtml_colorize()" function
1997 * Formats SQL queries to html
1999 * @param array The SQL queries
2000 * @param string mode
2001 * @param integer starting token
2002 * @param integer number of tokens to format, -1 = all
2004 * @return string The formatted SQL queries
2006 * @access public
2008 function PMA_SQP_formatHtml($arr, $mode='color', $start_token=0,
2009 $number_of_tokens=-1)
2011 //DEBUG echo 'in Format<pre>'; print_r($arr); echo '</pre>';
2012 // then check for an array
2013 if (!is_array($arr)) {
2014 return htmlspecialchars($arr);
2016 // first check for the SQL parser having hit an error
2017 if (PMA_SQP_isError()) {
2018 return htmlspecialchars($arr['raw']);
2020 // else do it properly
2021 switch ($mode) {
2022 case 'color':
2023 $str = '<span class="syntax">';
2024 $html_line_break = '<br />';
2025 break;
2026 case 'query_only':
2027 $str = '';
2028 $html_line_break = "\n";
2029 break;
2030 case 'text':
2031 $str = '';
2032 $html_line_break = '<br />';
2033 break;
2034 } // end switch
2035 $indent = 0;
2036 $bracketlevel = 0;
2037 $functionlevel = 0;
2038 $infunction = FALSE;
2039 $space_punct_listsep = ' ';
2040 $space_punct_listsep_function_name = ' ';
2041 // $space_alpha_reserved_word = '<br />'."\n";
2042 $space_alpha_reserved_word = ' ';
2044 $keywords_with_brackets_1before = array(
2045 'INDEX',
2046 'KEY',
2047 'ON',
2048 'USING'
2050 $keywords_with_brackets_1before_cnt = 4;
2052 $keywords_with_brackets_2before = array(
2053 'IGNORE',
2054 'INDEX',
2055 'INTO',
2056 'KEY',
2057 'PRIMARY',
2058 'PROCEDURE',
2059 'REFERENCES',
2060 'UNIQUE',
2061 'USE'
2063 // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
2064 $keywords_with_brackets_2before_cnt = 9;
2066 // These reserved words do NOT get a newline placed near them.
2067 $keywords_no_newline = array(
2068 'AS',
2069 'ASC',
2070 'DESC',
2071 'DISTINCT',
2072 'DUPLICATE',
2073 'HOUR',
2074 'INTERVAL',
2075 'IS',
2076 'LIKE',
2077 'NOT',
2078 'NULL',
2079 'ON',
2080 'REGEXP'
2082 $keywords_no_newline_cnt = 12;
2084 // These reserved words introduce a privilege list
2085 $keywords_priv_list = array(
2086 'GRANT',
2087 'REVOKE'
2089 $keywords_priv_list_cnt = 2;
2091 if ($number_of_tokens == -1) {
2092 $arraysize = $arr['len'];
2093 } else {
2094 $arraysize = $number_of_tokens;
2096 $typearr = array();
2097 if ($arraysize >= 0) {
2098 $typearr[0] = '';
2099 $typearr[1] = '';
2100 $typearr[2] = '';
2101 //$typearr[3] = $arr[0]['type'];
2102 $typearr[3] = $arr[$start_token]['type'];
2105 $in_priv_list = FALSE;
2106 for ($i = $start_token; $i < $arraysize; $i++) {
2107 // DEBUG echo "Loop format <b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
2108 $before = '';
2109 $after = '';
2110 $indent = 0;
2111 // array_shift($typearr);
2113 0 prev2
2114 1 prev
2115 2 current
2116 3 next
2118 if (($i + 1) < $arraysize) {
2119 // array_push($typearr, $arr[$i + 1]['type']);
2120 $typearr[4] = $arr[$i + 1]['type'];
2121 } else {
2122 //array_push($typearr, null);
2123 $typearr[4] = '';
2126 for ($j=0; $j<4; $j++) {
2127 $typearr[$j] = $typearr[$j + 1];
2130 switch ($typearr[2]) {
2131 case 'white_newline':
2132 $before = '';
2133 break;
2134 case 'punct_bracket_open_round':
2135 $bracketlevel++;
2136 $infunction = FALSE;
2137 // Make sure this array is sorted!
2138 if (($typearr[1] == 'alpha_functionName') || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
2139 || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex') || ($typearr[3] == 'digit_float')
2140 || (($typearr[0] == 'alpha_reservedWord')
2141 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
2142 || (($typearr[1] == 'alpha_reservedWord')
2143 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
2145 $functionlevel++;
2146 $infunction = TRUE;
2147 $after .= ' ';
2148 } else {
2149 $indent++;
2150 $after .= ($mode != 'query_only' ? '<div class="syntax_indent' . $indent . '">' : ' ');
2152 break;
2153 case 'alpha_identifier':
2154 if (($typearr[1] == 'punct_qualifier') || ($typearr[3] == 'punct_qualifier')) {
2155 $after = '';
2156 $before = '';
2158 if (($typearr[3] == 'alpha_columnType') || ($typearr[3] == 'alpha_identifier')) {
2159 $after .= ' ';
2161 break;
2162 case 'punct_user':
2163 case 'punct_qualifier':
2164 $before = '';
2165 $after = '';
2166 break;
2167 case 'punct_listsep':
2168 if ($infunction == TRUE) {
2169 $after .= $space_punct_listsep_function_name;
2170 } else {
2171 $after .= $space_punct_listsep;
2173 break;
2174 case 'punct_queryend':
2175 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi') && $typearr[3] != 'comment_c') {
2176 $after .= $html_line_break;
2177 $after .= $html_line_break;
2179 $space_punct_listsep = ' ';
2180 $space_punct_listsep_function_name = ' ';
2181 $space_alpha_reserved_word = ' ';
2182 $in_priv_list = FALSE;
2183 break;
2184 case 'comment_mysql':
2185 case 'comment_ansi':
2186 $after .= $html_line_break;
2187 break;
2188 case 'punct':
2189 $before .= ' ';
2190 // workaround for
2191 // select * from mytable limit 0,-1
2192 // (a side effect of this workaround is that
2193 // select 20 - 9
2194 // becomes
2195 // select 20 -9
2196 // )
2197 if ($typearr[3] != 'digit_integer') {
2198 $after .= ' ';
2200 break;
2201 case 'punct_bracket_close_round':
2202 $bracketlevel--;
2203 if ($infunction == TRUE) {
2204 $functionlevel--;
2205 $after .= ' ';
2206 $before .= ' ';
2207 } else {
2208 $indent--;
2209 $before .= ($mode != 'query_only' ? '</div>' : ' ');
2211 $infunction = ($functionlevel > 0) ? TRUE : FALSE;
2212 break;
2213 case 'alpha_columnType':
2214 if ($typearr[3] == 'alpha_columnAttrib') {
2215 $after .= ' ';
2217 if ($typearr[1] == 'alpha_columnType') {
2218 $before .= ' ';
2220 break;
2221 case 'alpha_columnAttrib':
2223 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
2224 // COLLATE LATIN1_GENERAL_CI DEFAULT
2225 if ($typearr[1] == 'alpha_identifier' || $typearr[1] == 'alpha_charset') {
2226 $before .= ' ';
2228 if (($typearr[3] == 'alpha_columnAttrib') || ($typearr[3] == 'quote_single') || ($typearr[3] == 'digit_integer')) {
2229 $after .= ' ';
2231 // workaround for
2232 // AUTO_INCREMENT = 31DEFAULT_CHARSET = utf-8
2234 if ($typearr[2] == 'alpha_columnAttrib' && $typearr[3] == 'alpha_reservedWord') {
2235 $before .= ' ';
2237 // workaround for
2238 // select * from mysql.user where binary user="root"
2239 // binary is marked as alpha_columnAttrib
2240 // but should be marked as a reserved word
2241 if (strtoupper($arr[$i]['data']) == 'BINARY'
2242 && $typearr[3] == 'alpha_identifier') {
2243 $after .= ' ';
2245 break;
2246 case 'alpha_reservedWord':
2247 // do not uppercase the reserved word if we are calling
2248 // this function in query_only mode, because we need
2249 // the original query (otherwise we get problems with
2250 // semi-reserved words like "storage" which is legal
2251 // as an identifier name)
2253 if ($mode != 'query_only') {
2254 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
2257 if ((($typearr[1] != 'alpha_reservedWord')
2258 || (($typearr[1] == 'alpha_reservedWord')
2259 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
2260 && ($typearr[1] != 'punct_level_plus')
2261 && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
2262 // do not put a space before the first token, because
2263 // we use a lot of eregi() checking for the first
2264 // reserved word at beginning of query
2265 // so do not put a newline before
2267 // also we must not be inside a privilege list
2268 if ($i > 0) {
2269 // the alpha_identifier exception is there to
2270 // catch cases like
2271 // GRANT SELECT ON mydb.mytable TO myuser@localhost
2272 // (else, we get mydb.mytableTO)
2274 // the quote_single exception is there to
2275 // catch cases like
2276 // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
2278 * @todo fix all cases and find why this happens
2281 if (!$in_priv_list || $typearr[1] == 'alpha_identifier' || $typearr[1] == 'quote_single' || $typearr[1] == 'white_newline') {
2282 $before .= $space_alpha_reserved_word;
2284 } else {
2285 // on first keyword, check if it introduces a
2286 // privilege list
2287 if (PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_priv_list, $keywords_priv_list_cnt)) {
2288 $in_priv_list = TRUE;
2291 } else {
2292 $before .= ' ';
2295 switch ($arr[$i]['data']) {
2296 case 'CREATE':
2297 if (!$in_priv_list) {
2298 $space_punct_listsep = $html_line_break;
2299 $space_alpha_reserved_word = ' ';
2301 break;
2302 case 'EXPLAIN':
2303 case 'DESCRIBE':
2304 case 'SET':
2305 case 'ALTER':
2306 case 'DELETE':
2307 case 'SHOW':
2308 case 'DROP':
2309 case 'UPDATE':
2310 case 'TRUNCATE':
2311 case 'ANALYZE':
2312 case 'ANALYSE':
2313 if (!$in_priv_list) {
2314 $space_punct_listsep = $html_line_break;
2315 $space_alpha_reserved_word = ' ';
2317 break;
2318 case 'INSERT':
2319 case 'REPLACE':
2320 if (!$in_priv_list) {
2321 $space_punct_listsep = $html_line_break;
2322 $space_alpha_reserved_word = $html_line_break;
2324 break;
2325 case 'VALUES':
2326 $space_punct_listsep = ' ';
2327 $space_alpha_reserved_word = $html_line_break;
2328 break;
2329 case 'SELECT':
2330 $space_punct_listsep = ' ';
2331 $space_alpha_reserved_word = $html_line_break;
2332 break;
2333 default:
2334 break;
2335 } // end switch ($arr[$i]['data'])
2337 $after .= ' ';
2338 break;
2339 case 'digit_integer':
2340 case 'digit_float':
2341 case 'digit_hex':
2343 * @todo could there be other types preceding a digit?
2345 if ($typearr[1] == 'alpha_reservedWord') {
2346 $after .= ' ';
2348 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2349 $after .= ' ';
2351 if ($typearr[1] == 'alpha_columnAttrib') {
2352 $before .= ' ';
2354 break;
2355 case 'alpha_variable':
2356 $after = ' ';
2357 break;
2358 case 'quote_double':
2359 case 'quote_single':
2360 // workaround: for the query
2361 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
2362 // the @ is incorrectly marked as alpha_variable
2363 // in the parser, and here, the '%' gets a blank before,
2364 // which is a syntax error
2365 if ($typearr[1] != 'punct_user') {
2366 $before .= ' ';
2368 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2369 $after .= ' ';
2371 break;
2372 case 'quote_backtick':
2373 if ($typearr[3] != 'punct_qualifier' && $typearr[3] != 'alpha_variable') {
2374 $after .= ' ';
2376 if ($typearr[1] != 'punct_qualifier' && $typearr[1] != 'alpha_variable') {
2377 $before .= ' ';
2379 break;
2380 default:
2381 break;
2382 } // end switch ($typearr[2])
2385 if ($typearr[3] != 'punct_qualifier') {
2386 $after .= ' ';
2388 $after .= "\n";
2390 $str .= $before . ($mode=='color' ? PMA_SQP_formatHTML_colorize($arr[$i]) : $arr[$i]['data']). $after;
2391 } // end for
2392 if ($mode=='color') {
2393 $str .= '</span>';
2396 return $str;
2397 } // end of the "PMA_SQP_formatHtml()" function
2401 * Builds a CSS rule used for html formatted SQL queries
2403 * @param string The class name
2404 * @param string The property name
2405 * @param string The property value
2407 * @return string The CSS rule
2409 * @access public
2411 * @see PMA_SQP_buildCssData()
2413 function PMA_SQP_buildCssRule($classname, $property, $value)
2415 $str = '.' . $classname . ' {';
2416 if ($value != '') {
2417 $str .= $property . ': ' . $value . ';';
2419 $str .= '}' . "\n";
2421 return $str;
2422 } // end of the "PMA_SQP_buildCssRule()" function
2426 * Builds CSS rules used for html formatted SQL queries
2428 * @return string The CSS rules set
2430 * @access public
2432 * @global array The current PMA configuration
2434 * @see PMA_SQP_buildCssRule()
2436 function PMA_SQP_buildCssData()
2438 global $cfg;
2440 $css_string = '';
2441 foreach ($cfg['SQP']['fmtColor'] AS $key => $col) {
2442 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
2445 for ($i = 0; $i < 8; $i++) {
2446 $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
2449 return $css_string;
2450 } // end of the "PMA_SQP_buildCssData()" function
2452 if (! defined('PMA_MINIMUM_COMMON')) {
2454 * Gets SQL queries with no format
2456 * @param array The SQL queries list
2458 * @return string The SQL queries with no format
2460 * @access public
2462 function PMA_SQP_formatNone($arr)
2464 $formatted_sql = htmlspecialchars($arr['raw']);
2465 $formatted_sql = preg_replace("@((\015\012)|(\015)|(\012)){3,}@", "\n\n", $formatted_sql);
2467 return $formatted_sql;
2468 } // end of the "PMA_SQP_formatNone()" function
2472 * Gets SQL queries in text format
2474 * @todo WRITE THIS!
2475 * @param array The SQL queries list
2477 * @return string The SQL queries in text format
2479 * @access public
2481 function PMA_SQP_formatText($arr)
2483 return PMA_SQP_formatNone($arr);
2484 } // end of the "PMA_SQP_formatText()" function
2485 } // end if: minimal common.lib needed?