bug #2027102 quotes around lang missing
[phpmyadmin/last10db.git] / libraries / sqlparser.lib.php
blobdef36f9780c4fc4331cce070da4589a87f6402ab
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$
32 if (! defined('PHPMYADMIN')) {
33 exit;
36 /**
37 * Minimum inclusion? (i.e. for the stylesheet builder)
39 if (! defined('PMA_MINIMUM_COMMON')) {
40 /**
41 * Include the string library as we use it heavily
43 require_once './libraries/string.lib.php';
45 /**
46 * Include data for the SQL Parser
48 require_once './libraries/sqlparser.data.php';
49 require_once './libraries/mysql_charsets.lib.php';
50 if (!isset($mysql_charsets)) {
51 $mysql_charsets = array();
52 $mysql_charsets_count = 0;
53 $mysql_collations_flat = array();
54 $mysql_collations_count = 0;
57 if (!defined('DEBUG_TIMING')) {
58 // currently we don't need the $pos (token position in query)
59 // for other purposes than LIMIT clause verification,
60 // so many calls to this function do not include the 4th parameter
61 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
63 $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos);
64 $arrsize++;
65 } // end of the "PMA_SQP_arrayAdd()" function
66 } else {
67 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
69 global $timer;
71 $t = $timer;
72 $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos, 'time' => $t);
73 $timer = microtime();
74 $arrsize++;
75 } // end of the "PMA_SQP_arrayAdd()" function
76 } // end if... else...
79 /**
80 * Reset the error variable for the SQL parser
82 * @access public
84 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
85 function PMA_SQP_resetError()
87 global $SQP_errorString;
88 $SQP_errorString = '';
89 unset($SQP_errorString);
92 /**
93 * Get the contents of the error variable for the SQL parser
95 * @return string Error string from SQL parser
97 * @access public
99 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
100 function PMA_SQP_getErrorString()
102 global $SQP_errorString;
103 return isset($SQP_errorString) ? $SQP_errorString : '';
107 * Check if the SQL parser hit an error
109 * @return boolean error state
111 * @access public
113 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
114 function PMA_SQP_isError()
116 global $SQP_errorString;
117 return isset($SQP_errorString) && !empty($SQP_errorString);
121 * Set an error message for the system
123 * @param string The error message
124 * @param string The failing SQL query
126 * @access private
127 * @scope SQL Parser internal
129 // Revised, Robbat2 - 13 Janurary 2003, 2:59PM
130 function PMA_SQP_throwError($message, $sql)
133 global $SQP_errorString;
134 $SQP_errorString = '<p>'.$GLOBALS['strSQLParserUserError'] . '</p>' . "\n"
135 . '<pre>' . "\n"
136 . 'ERROR: ' . $message . "\n"
137 . 'SQL: ' . htmlspecialchars($sql) . "\n"
138 . '</pre>' . "\n";
140 } // end of the "PMA_SQP_throwError()" function
144 * Do display the bug report
146 * @param string The error message
147 * @param string The failing SQL query
149 * @access public
151 function PMA_SQP_bug($message, $sql)
153 global $SQP_errorString;
154 $debugstr = 'ERROR: ' . $message . "\n";
155 $debugstr .= 'SVN: $Id$' . "\n";
156 $debugstr .= 'MySQL: '.PMA_MYSQL_STR_VERSION . "\n";
157 $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS . ' ' . PMA_USR_BROWSER_AGENT . ' ' . PMA_USR_BROWSER_VER . "\n";
158 $debugstr .= 'PMA: ' . PMA_VERSION . "\n";
159 $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION . ' ' . PHP_OS . "\n";
160 $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
161 $debugstr .= 'SQL: ' . htmlspecialchars($sql);
163 $encodedstr = $debugstr;
164 if (@function_exists('gzcompress')) {
165 $encodedstr = gzcompress($debugstr, 9);
167 $encodedstr = preg_replace("/(\015\012)|(\015)|(\012)/", '<br />' . "\n", chunk_split(base64_encode($encodedstr)));
169 $SQP_errorString .= $GLOBALS['strSQLParserBugMessage'] . '<br />' . "\n"
170 . '----' . $GLOBALS['strBeginCut'] . '----' . '<br />' . "\n"
171 . $encodedstr . "\n"
172 . '----' . $GLOBALS['strEndCut'] . '----' . '<br />' . "\n";
174 $SQP_errorString .= '----' . $GLOBALS['strBeginRaw'] . '----<br />' . "\n"
175 . '<pre>' . "\n"
176 . $debugstr
177 . '</pre>' . "\n"
178 . '----' . $GLOBALS['strEndRaw'] . '----<br />' . "\n";
180 } // end of the "PMA_SQP_bug()" function
184 * Parses the SQL queries
186 * @param string The SQL query list
188 * @return mixed Most of times, nothing...
190 * @global array The current PMA configuration
191 * @global array MySQL column attributes
192 * @global array MySQL reserved words
193 * @global array MySQL column types
194 * @global array MySQL function names
195 * @global integer MySQL column attributes count
196 * @global integer MySQL reserved words count
197 * @global integer MySQL column types count
198 * @global integer MySQL function names count
199 * @global array List of available character sets
200 * @global array List of available collations
201 * @global integer Character sets count
202 * @global integer Collations count
204 * @access public
206 function PMA_SQP_parse($sql)
208 global $cfg;
209 global $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word, $PMA_SQPdata_column_type, $PMA_SQPdata_function_name,
210 $PMA_SQPdata_column_attrib_cnt, $PMA_SQPdata_reserved_word_cnt, $PMA_SQPdata_column_type_cnt, $PMA_SQPdata_function_name_cnt;
211 global $mysql_charsets, $mysql_collations_flat, $mysql_charsets_count, $mysql_collations_count;
212 global $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt;
214 // rabus: Convert all line feeds to Unix style
215 $sql = str_replace("\r\n", "\n", $sql);
216 $sql = str_replace("\r", "\n", $sql);
218 $len = PMA_strlen($sql);
219 if ($len == 0) {
220 return array();
223 $sql_array = array();
224 $sql_array['raw'] = $sql;
225 $count1 = 0;
226 $count2 = 0;
227 $punct_queryend = ';';
228 $punct_qualifier = '.';
229 $punct_listsep = ',';
230 $punct_level_plus = '(';
231 $punct_level_minus = ')';
232 $punct_user = '@';
233 $digit_floatdecimal = '.';
234 $digit_hexset = 'x';
235 $bracket_list = '()[]{}';
236 $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
237 $allpunct_list_pair = array (
238 0 => '!=',
239 1 => '&&',
240 2 => ':=',
241 3 => '<<',
242 4 => '<=',
243 5 => '<=>',
244 6 => '<>',
245 7 => '>=',
246 8 => '>>',
247 9 => '||'
249 $allpunct_list_pair_size = 10; //count($allpunct_list_pair);
250 $quote_list = '\'"`';
251 $arraysize = 0;
253 $previous_was_space = false;
254 $this_was_space = false;
255 $previous_was_bracket = false;
256 $this_was_bracket = false;
257 $previous_was_punct = false;
258 $this_was_punct = false;
259 $previous_was_listsep = false;
260 $this_was_listsep = false;
261 $previous_was_quote = false;
262 $this_was_quote = false;
264 while ($count2 < $len) {
265 $c = PMA_substr($sql, $count2, 1);
266 $count1 = $count2;
268 $previous_was_space = $this_was_space;
269 $this_was_space = false;
270 $previous_was_bracket = $this_was_bracket;
271 $this_was_bracket = false;
272 $previous_was_punct = $this_was_punct;
273 $this_was_punct = false;
274 $previous_was_listsep = $this_was_listsep;
275 $this_was_listsep = false;
276 $previous_was_quote = $this_was_quote;
277 $this_was_quote = false;
279 if (($c == "\n")) {
280 $this_was_space = true;
281 $count2++;
282 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
283 continue;
286 // Checks for white space
287 if (PMA_STR_isSpace($c)) {
288 $this_was_space = true;
289 $count2++;
290 continue;
293 // Checks for comment lines.
294 // MySQL style #
295 // C style /* */
296 // ANSI style --
297 if (($c == '#')
298 || (($count2 + 1 < $len) && ($c == '/') && (PMA_substr($sql, $count2 + 1, 1) == '*'))
299 || (($count2 + 2 == $len) && ($c == '-') && (PMA_substr($sql, $count2 + 1, 1) == '-'))
300 || (($count2 + 2 < $len) && ($c == '-') && (PMA_substr($sql, $count2 + 1, 1) == '-') && ((PMA_substr($sql, $count2 + 2, 1) <= ' ')))) {
301 $count2++;
302 $pos = 0;
303 $type = 'bad';
304 switch ($c) {
305 case '#':
306 $type = 'mysql';
307 case '-':
308 $type = 'ansi';
309 $pos = $GLOBALS['PMA_strpos']($sql, "\n", $count2);
310 break;
311 case '/':
312 $type = 'c';
313 $pos = $GLOBALS['PMA_strpos']($sql, '*/', $count2);
314 $pos += 2;
315 break;
316 default:
317 break;
318 } // end switch
319 $count2 = ($pos < $count2) ? $len : $pos;
320 $str = PMA_substr($sql, $count1, $count2 - $count1);
321 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
322 continue;
323 } // end if
325 // Checks for something inside quotation marks
326 if (PMA_STR_strInStr($c, $quote_list)) {
327 $startquotepos = $count2;
328 $quotetype = $c;
329 $count2++;
330 $escaped = FALSE;
331 $escaped_escaped = FALSE;
332 $pos = $count2;
333 $oldpos = 0;
334 do {
335 $oldpos = $pos;
336 $pos = $GLOBALS['PMA_strpos'](' ' . $sql, $quotetype, $oldpos + 1) - 1;
337 // ($pos === FALSE)
338 if ($pos < 0) {
339 $debugstr = $GLOBALS['strSQPBugUnclosedQuote'] . ' @ ' . $startquotepos. "\n"
340 . 'STR: ' . htmlspecialchars($quotetype);
341 PMA_SQP_throwError($debugstr, $sql);
342 return $sql;
345 // If the quote is the first character, it can't be
346 // escaped, so don't do the rest of the code
347 if ($pos == 0) {
348 break;
351 // Checks for MySQL escaping using a \
352 // And checks for ANSI escaping using the $quotetype character
353 if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos)) {
354 $pos ++;
355 continue;
356 } elseif (($pos + 1 < $len) && (PMA_substr($sql, $pos, 1) == $quotetype) && (PMA_substr($sql, $pos + 1, 1) == $quotetype)) {
357 $pos = $pos + 2;
358 continue;
359 } else {
360 break;
362 } while ($len > $pos); // end do
364 $count2 = $pos;
365 $count2++;
366 $type = 'quote_';
367 switch ($quotetype) {
368 case '\'':
369 $type .= 'single';
370 $this_was_quote = true;
371 break;
372 case '"':
373 $type .= 'double';
374 $this_was_quote = true;
375 break;
376 case '`':
377 $type .= 'backtick';
378 $this_was_quote = true;
379 break;
380 default:
381 break;
382 } // end switch
383 $data = PMA_substr($sql, $count1, $count2 - $count1);
384 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
385 continue;
388 // Checks for brackets
389 if (PMA_STR_strInStr($c, $bracket_list)) {
390 // All bracket tokens are only one item long
391 $this_was_bracket = true;
392 $count2++;
393 $type_type = '';
394 if (PMA_STR_strInStr($c, '([{')) {
395 $type_type = 'open';
396 } else {
397 $type_type = 'close';
400 $type_style = '';
401 if (PMA_STR_strInStr($c, '()')) {
402 $type_style = 'round';
403 } elseif (PMA_STR_strInStr($c, '[]')) {
404 $type_style = 'square';
405 } else {
406 $type_style = 'curly';
409 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
410 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
411 continue;
414 /* DEBUG
415 echo '<pre>1';
416 var_dump(PMA_STR_isSqlIdentifier($c, false));
417 var_dump($c == '@');
418 var_dump($c == '.');
419 var_dump(PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1)));
420 var_dump($previous_was_space);
421 var_dump($previous_was_bracket);
422 var_dump($previous_was_listsep);
423 echo '</pre>';
426 // Checks for identifier (alpha or numeric)
427 if (PMA_STR_isSqlIdentifier($c, false)
428 || $c == '@'
429 || ($c == '.'
430 && PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1))
431 && ($previous_was_space || $previous_was_bracket || $previous_was_listsep))) {
433 /* DEBUG
434 echo PMA_substr($sql, $count2);
435 echo '<hr />';
438 $count2++;
441 * @todo a @ can also be present in expressions like
442 * FROM 'user'@'%' or TO 'user'@'%'
443 * in this case, the @ is wrongly marked as alpha_variable
445 $is_identifier = $previous_was_punct;
446 $is_sql_variable = $c == '@' && ! $previous_was_quote;
447 $is_user = $c == '@' && $previous_was_quote;
448 $is_digit = !$is_identifier && !$is_sql_variable && PMA_STR_isDigit($c);
449 $is_hex_digit = $is_digit && $c == '0' && $count2 < $len && PMA_substr($sql, $count2, 1) == 'x';
450 $is_float_digit = $c == '.';
451 $is_float_digit_exponent = FALSE;
453 /* DEBUG
454 echo '<pre>2';
455 var_dump($is_identifier);
456 var_dump($is_sql_variable);
457 var_dump($is_digit);
458 var_dump($is_float_digit);
459 echo '</pre>';
462 // Nijel: Fast skip is especially needed for huge BLOB data, requires PHP at least 4.3.0:
463 if (PMA_PHP_INT_VERSION >= 40300) {
464 if ($is_hex_digit) {
465 $count2++;
466 $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
467 if ($pos > $count2) {
468 $count2 = $pos;
470 unset($pos);
471 } elseif ($is_digit) {
472 $pos = strspn($sql, '0123456789', $count2);
473 if ($pos > $count2) {
474 $count2 = $pos;
476 unset($pos);
480 while (($count2 < $len) && PMA_STR_isSqlIdentifier(PMA_substr($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
481 $c2 = PMA_substr($sql, $count2, 1);
482 if ($is_sql_variable && ($c2 == '.')) {
483 $count2++;
484 continue;
486 if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
487 $count2++;
488 if (!$is_float_digit) {
489 $is_float_digit = TRUE;
490 continue;
491 } else {
492 $debugstr = $GLOBALS['strSQPBugInvalidIdentifer'] . ' @ ' . ($count1+1) . "\n"
493 . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
494 PMA_SQP_throwError($debugstr, $sql);
495 return $sql;
498 if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
499 if (!$is_float_digit_exponent) {
500 $is_float_digit_exponent = TRUE;
501 $is_float_digit = TRUE;
502 $count2++;
503 continue;
504 } else {
505 $is_digit = FALSE;
506 $is_float_digit = FALSE;
509 if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && PMA_STR_isDigit($c2))) {
510 $count2++;
511 continue;
512 } else {
513 $is_digit = FALSE;
514 $is_hex_digit = FALSE;
517 $count2++;
518 } // end while
520 $l = $count2 - $count1;
521 $str = PMA_substr($sql, $count1, $l);
523 $type = '';
524 if ($is_digit || $is_float_digit || $is_hex_digit) {
525 $type = 'digit';
526 if ($is_float_digit) {
527 $type .= '_float';
528 } elseif ($is_hex_digit) {
529 $type .= '_hex';
530 } else {
531 $type .= '_integer';
533 } elseif ($is_user) {
534 $type = 'punct_user';
535 } elseif ($is_sql_variable != FALSE) {
536 $type = 'alpha_variable';
537 } else {
538 $type = 'alpha';
539 } // end if... else....
540 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize, $count2);
542 continue;
545 // Checks for punct
546 if (PMA_STR_strInStr($c, $allpunct_list)) {
547 while (($count2 < $len) && PMA_STR_strInStr(PMA_substr($sql, $count2, 1), $allpunct_list)) {
548 $count2++;
550 $l = $count2 - $count1;
551 if ($l == 1) {
552 $punct_data = $c;
553 } else {
554 $punct_data = PMA_substr($sql, $count1, $l);
557 // Special case, sometimes, althought two characters are
558 // adjectent directly, they ACTUALLY need to be seperate
559 /* DEBUG
560 echo '<pre>';
561 var_dump($l);
562 var_dump($punct_data);
563 echo '</pre>';
566 if ($l == 1) {
567 $t_suffix = '';
568 switch ($punct_data) {
569 case $punct_queryend:
570 $t_suffix = '_queryend';
571 break;
572 case $punct_qualifier:
573 $t_suffix = '_qualifier';
574 $this_was_punct = true;
575 break;
576 case $punct_listsep:
577 $this_was_listsep = true;
578 $t_suffix = '_listsep';
579 break;
580 default:
581 break;
583 PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
584 } elseif (PMA_STR_binarySearchInArr($punct_data, $allpunct_list_pair, $allpunct_list_pair_size)) {
585 // Ok, we have one of the valid combined punct expressions
586 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
587 } else {
588 // Bad luck, lets split it up more
589 $first = $punct_data[0];
590 $first2 = $punct_data[0] . $punct_data[1];
591 $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
592 $last = $punct_data[$l - 1];
593 if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
594 $count2 = $count1 + 1;
595 $punct_data = $first;
596 } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || PMA_substr($sql, $count2, 1) <= ' '))) {
597 $count2 -= 2;
598 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
599 } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
600 $count2--;
601 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
603 * @todo for negation operator, split in 2 tokens ?
604 * "select x&~1 from t"
605 * becomes "select x & ~ 1 from t" ?
608 } elseif ($last != '~') {
609 $debugstr = $GLOBALS['strSQPBugUnknownPunctuation'] . ' @ ' . ($count1+1) . "\n"
610 . 'STR: ' . htmlspecialchars($punct_data);
611 PMA_SQP_throwError($debugstr, $sql);
612 return $sql;
614 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
615 continue;
616 } // end if... elseif... else
617 continue;
620 // DEBUG
621 $count2++;
623 $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
624 . 'STR: ' . PMA_substr($sql, $count1, $count2 - $count1) . "\n";
625 PMA_SQP_bug($debugstr, $sql);
626 return $sql;
628 } // end while ($count2 < $len)
631 echo '<pre>';
632 print_r($sql_array);
633 echo '</pre>';
636 if ($arraysize > 0) {
637 $t_next = $sql_array[0]['type'];
638 $t_prev = '';
639 $t_bef_prev = '';
640 $t_cur = '';
641 $d_next = $sql_array[0]['data'];
642 $d_prev = '';
643 $d_bef_prev = '';
644 $d_cur = '';
645 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
646 $d_prev_upper = '';
647 $d_bef_prev_upper = '';
648 $d_cur_upper = '';
651 for ($i = 0; $i < $arraysize; $i++) {
652 $t_bef_prev = $t_prev;
653 $t_prev = $t_cur;
654 $t_cur = $t_next;
655 $d_bef_prev = $d_prev;
656 $d_prev = $d_cur;
657 $d_cur = $d_next;
658 $d_bef_prev_upper = $d_prev_upper;
659 $d_prev_upper = $d_cur_upper;
660 $d_cur_upper = $d_next_upper;
661 if (($i + 1) < $arraysize) {
662 $t_next = $sql_array[$i + 1]['type'];
663 $d_next = $sql_array[$i + 1]['data'];
664 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
665 } else {
666 $t_next = '';
667 $d_next = '';
668 $d_next_upper = '';
671 //DEBUG echo "[prev: <b>".$d_prev."</b> ".$t_prev."][cur: <b>".$d_cur."</b> ".$t_cur."][next: <b>".$d_next."</b> ".$t_next."]<br />";
673 if ($t_cur == 'alpha') {
674 $t_suffix = '_identifier';
675 if (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
676 $t_suffix = '_identifier';
677 } elseif (($t_next == 'punct_bracket_open_round')
678 && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_function_name, $PMA_SQPdata_function_name_cnt)) {
680 * @todo 2005-10-16: in the case of a CREATE TABLE containing
681 * a TIMESTAMP, since TIMESTAMP() is also a function, it's
682 * found here and the token is wrongly marked as alpha_functionName.
683 * But we compensate for this when analysing for timestamp_not_null
684 * later in this script.
686 * Same applies to CHAR vs. CHAR() function.
688 $t_suffix = '_functionName';
689 /* There are functions which might be as well column types */
690 if (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt)) {
692 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt)) {
693 $t_suffix = '_columnType';
696 * Temporary fix for BUG #621357
698 * @todo FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
700 if ($d_cur_upper == 'SET' && $t_next != 'punct_bracket_open_round') {
701 $t_suffix = '_reservedWord';
703 //END OF TEMPORARY FIX
705 // CHARACTER is a synonym for CHAR, but can also be meant as
706 // CHARACTER SET. In this case, we have a reserved word.
707 if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
708 $t_suffix = '_reservedWord';
711 // experimental
712 // current is a column type, so previous must not be
713 // a reserved word but an identifier
714 // CREATE TABLE SG_Persons (first varchar(64))
716 //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
717 // $sql_array[$i-1]['type'] = 'alpha_identifier';
720 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_reserved_word, $PMA_SQPdata_reserved_word_cnt)) {
721 $t_suffix = '_reservedWord';
722 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_attrib, $PMA_SQPdata_column_attrib_cnt)) {
723 $t_suffix = '_columnAttrib';
724 // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
725 // it should be regarded as a reserved word.
726 if ($d_cur_upper == 'INNODB' && $d_prev_upper == 'SHOW' && $d_next_upper == 'STATUS') {
727 $t_suffix = '_reservedWord';
730 if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
731 $t_suffix = '_reservedWord';
733 // Binary as character set
734 if ($d_cur_upper == 'BINARY' && (
735 ($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
736 || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
737 || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
738 || $d_prev_upper == 'CHARSET'
739 ) && PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, count($mysql_charsets))) {
740 $t_suffix = '_charset';
742 } elseif (PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, $mysql_charsets_count)
743 || PMA_STR_binarySearchInArr($d_cur, $mysql_collations_flat, $mysql_collations_count)
744 || ($d_cur{0} == '_' && PMA_STR_binarySearchInArr(substr($d_cur, 1), $mysql_charsets, $mysql_charsets_count))) {
745 $t_suffix = '_charset';
746 } else {
747 // Do nothing
749 // check if present in the list of forbidden words
750 if ($t_suffix == '_reservedWord' && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt)) {
751 $sql_array[$i]['forbidden'] = TRUE;
752 } else {
753 $sql_array[$i]['forbidden'] = FALSE;
755 $sql_array[$i]['type'] .= $t_suffix;
757 } // end for
759 // Stores the size of the array inside the array, as count() is a slow
760 // operation.
761 $sql_array['len'] = $arraysize;
763 // DEBUG echo 'After parsing<pre>'; print_r($sql_array); echo '</pre>';
764 // Sends the data back
765 return $sql_array;
766 } // end of the "PMA_SQP_parse()" function
769 * Checks for token types being what we want...
771 * @param string String of type that we have
772 * @param string String of type that we want
774 * @return boolean result of check
776 * @access private
778 function PMA_SQP_typeCheck($toCheck, $whatWeWant)
780 $typeSeperator = '_';
781 if (strcmp($whatWeWant, $toCheck) == 0) {
782 return TRUE;
783 } else {
784 if (strpos($whatWeWant, $typeSeperator) === FALSE) {
785 return strncmp($whatWeWant, $toCheck, strpos($toCheck, $typeSeperator)) == 0;
786 } else {
787 return FALSE;
794 * Analyzes SQL queries
796 * @param array The SQL queries
798 * @return array The analyzed SQL queries
800 * @access public
802 function PMA_SQP_analyze($arr)
804 if ($arr == array()) {
805 return array();
807 $result = array();
808 $size = $arr['len'];
809 $subresult = array(
810 'querytype' => '',
811 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
812 'position_of_first_select' => '', // the array index
813 'from_clause'=> '',
814 'group_by_clause'=> '',
815 'order_by_clause'=> '',
816 'having_clause' => '',
817 'limit_clause' => '',
818 'where_clause' => '',
819 'where_clause_identifiers' => array(),
820 'unsorted_query' => '',
821 'queryflags' => array(),
822 'select_expr' => array(),
823 'table_ref' => array(),
824 'foreign_keys' => array(),
825 'create_table_fields' => array()
827 $subresult_empty = $subresult;
828 $seek_queryend = FALSE;
829 $seen_end_of_table_ref = FALSE;
830 $number_of_brackets_in_extract = 0;
831 $number_of_brackets_in_group_concat = 0;
833 $number_of_brackets = 0;
834 $in_subquery = false;
835 $seen_subquery = false;
837 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
838 // we must not use CURDATE as a table_ref
839 // so we track wether we are in the EXTRACT()
840 $in_extract = FALSE;
842 // for GROUP_CONCAT(...)
843 $in_group_concat = FALSE;
845 /* Description of analyzer results by lem9
847 * db, table, column, alias
848 * ------------------------
850 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
852 * The SELECT syntax (simplified) is
854 * SELECT
855 * select_expression,...
856 * [FROM [table_references]
859 * ['select_expr'] is filled with each expression, the key represents the
860 * expression position in the list (0-based) (so we don't lose track of
861 * multiple occurences of the same column).
863 * ['table_ref'] is filled with each table ref, same thing for the key.
865 * I create all sub-values empty, even if they are
866 * not present (for example no select_expression alias).
868 * There is a debug section at the end of loop #1, if you want to
869 * see the exact contents of select_expr and table_ref
871 * queryflags
872 * ----------
874 * In $subresult, array 'queryflags' is filled, according to what we
875 * find in the query.
877 * Currently, those are generated:
879 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
880 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
881 * ['queryflags']['distinct'] = 1; for a DISTINCT
882 * ['queryflags']['union'] = 1; for a UNION
883 * ['queryflags']['join'] = 1; for a JOIN
884 * ['queryflags']['offset'] = 1; for the presence of OFFSET
885 * ['queryflags']['procedure'] = 1; for the presence of PROCEDURE
887 * query clauses
888 * -------------
890 * The select is splitted in those clauses:
891 * ['select_expr_clause']
892 * ['from_clause']
893 * ['group_by_clause']
894 * ['order_by_clause']
895 * ['having_clause']
896 * ['where_clause']
897 * ['limit_clause']
899 * The identifiers of the WHERE clause are put into the array
900 * ['where_clause_identifier']
902 * For a SELECT, the whole query without the ORDER BY clause is put into
903 * ['unsorted_query']
905 * foreign keys
906 * ------------
907 * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
908 * analyzed and ['foreign_keys'] is an array filled with
909 * the constraint name, the index list,
910 * the REFERENCES table name and REFERENCES index list,
911 * and ON UPDATE | ON DELETE clauses
913 * position_of_first_select
914 * ------------------------
916 * The array index of the first SELECT we find. Will be used to
917 * insert a SQL_CALC_FOUND_ROWS.
919 * create_table_fields
920 * -------------------
922 * For now, mostly used to detect the DEFAULT CURRENT_TIMESTAMP and
923 * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
924 * An array, each element is the identifier name.
925 * Note that for now, the timestamp_not_null element is created
926 * even for non-TIMESTAMP fields.
928 * Sub-elements: ['type'] which contains the column type
929 * optional (currently they are never false but can be absent):
930 * ['default_current_timestamp'] boolean
931 * ['on_update_current_timestamp'] boolean
932 * ['timestamp_not_null'] boolean
934 * section_before_limit, section_after_limit
935 * -----------------------------------------
937 * Marks the point of the query where we can insert a LIMIT clause;
938 * so the section_before_limit will contain the left part before
939 * a possible LIMIT clause
942 * End of description of analyzer results
945 // must be sorted
946 // TODO: current logic checks for only one word, so I put only the
947 // first word of the reserved expressions that end a table ref;
948 // maybe this is not ok (the first word might mean something else)
949 // $words_ending_table_ref = array(
950 // 'FOR UPDATE',
951 // 'GROUP BY',
952 // 'HAVING',
953 // 'LIMIT',
954 // 'LOCK IN SHARE MODE',
955 // 'ORDER BY',
956 // 'PROCEDURE',
957 // 'UNION',
958 // 'WHERE'
959 // );
960 $words_ending_table_ref = array(
961 'FOR',
962 'GROUP',
963 'HAVING',
964 'LIMIT',
965 'LOCK',
966 'ORDER',
967 'PROCEDURE',
968 'UNION',
969 'WHERE'
971 $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
973 $words_ending_clauses = array(
974 'FOR',
975 'LIMIT',
976 'LOCK',
977 'PROCEDURE',
978 'UNION'
980 $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
985 // must be sorted
986 $supported_query_types = array(
987 'SELECT'
989 // Support for these additional query types will come later on.
990 'DELETE',
991 'INSERT',
992 'REPLACE',
993 'TRUNCATE',
994 'UPDATE'
995 'EXPLAIN',
996 'DESCRIBE',
997 'SHOW',
998 'CREATE',
999 'SET',
1000 'ALTER'
1003 $supported_query_types_cnt = count($supported_query_types);
1005 // loop #1 for each token: select_expr, table_ref for SELECT
1007 for ($i = 0; $i < $size; $i++) {
1008 //DEBUG echo "Loop1 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
1010 // High speed seek for locating the end of the current query
1011 if ($seek_queryend == TRUE) {
1012 if ($arr[$i]['type'] == 'punct_queryend') {
1013 $seek_queryend = FALSE;
1014 } else {
1015 continue;
1016 } // end if (type == punct_queryend)
1017 } // end if ($seek_queryend)
1020 * Note: do not split if this is a punct_queryend for the first and only query
1021 * @todo when we find a UNION, should we split in another subresult?
1023 if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
1024 $result[] = $subresult;
1025 $subresult = $subresult_empty;
1026 continue;
1027 } // end if (type == punct_queryend)
1029 // ==============================================================
1030 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1031 $number_of_brackets++;
1032 if ($in_extract) {
1033 $number_of_brackets_in_extract++;
1035 if ($in_group_concat) {
1036 $number_of_brackets_in_group_concat++;
1039 // ==============================================================
1040 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1041 $number_of_brackets--;
1042 if ($number_of_brackets == 0) {
1043 $in_subquery = false;
1045 if ($in_extract) {
1046 $number_of_brackets_in_extract--;
1047 if ($number_of_brackets_in_extract == 0) {
1048 $in_extract = FALSE;
1051 if ($in_group_concat) {
1052 $number_of_brackets_in_group_concat--;
1053 if ($number_of_brackets_in_group_concat == 0) {
1054 $in_group_concat = FALSE;
1059 if ($in_subquery) {
1061 * skip the subquery to avoid setting
1062 * select_expr or table_ref with the contents
1063 * of this subquery; this is to avoid a bug when
1064 * trying to edit the results of
1065 * select * from child where not exists (select id from
1066 * parent where child.parent_id = parent.id);
1068 continue;
1070 // ==============================================================
1071 if ($arr[$i]['type'] == 'alpha_functionName') {
1072 $upper_data = strtoupper($arr[$i]['data']);
1073 if ($upper_data =='EXTRACT') {
1074 $in_extract = TRUE;
1075 $number_of_brackets_in_extract = 0;
1077 if ($upper_data =='GROUP_CONCAT') {
1078 $in_group_concat = TRUE;
1079 $number_of_brackets_in_group_concat = 0;
1083 // ==============================================================
1084 if ($arr[$i]['type'] == 'alpha_reservedWord'
1085 // && $arr[$i]['forbidden'] == FALSE) {
1087 // We don't know what type of query yet, so run this
1088 if ($subresult['querytype'] == '') {
1089 $subresult['querytype'] = strtoupper($arr[$i]['data']);
1090 } // end if (querytype was empty)
1092 // Check if we support this type of query
1093 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
1094 // Skip ahead to the next one if we don't
1095 $seek_queryend = TRUE;
1096 continue;
1097 } // end if (query not supported)
1099 // upper once
1100 $upper_data = strtoupper($arr[$i]['data']);
1102 * @todo reset for each query?
1105 if ($upper_data == 'SELECT') {
1106 if ($number_of_brackets > 0) {
1107 $in_subquery = true;
1108 $seen_subquery = true;
1109 // this is a subquery so do not analyze inside it
1110 continue;
1112 $seen_from = FALSE;
1113 $previous_was_identifier = FALSE;
1114 $current_select_expr = -1;
1115 $seen_end_of_table_ref = FALSE;
1116 } // end if (data == SELECT)
1118 if ($upper_data =='FROM' && !$in_extract) {
1119 $current_table_ref = -1;
1120 $seen_from = TRUE;
1121 $previous_was_identifier = FALSE;
1122 $save_table_ref = TRUE;
1123 } // end if (data == FROM)
1125 // here, do not 'continue' the loop, as we have more work for
1126 // reserved words below
1127 } // end if (type == alpha_reservedWord)
1129 // ==============================
1130 if ($arr[$i]['type'] == 'quote_backtick'
1131 || $arr[$i]['type'] == 'quote_double'
1132 || $arr[$i]['type'] == 'quote_single'
1133 || $arr[$i]['type'] == 'alpha_identifier'
1134 || ($arr[$i]['type'] == 'alpha_reservedWord'
1135 && $arr[$i]['forbidden'] == FALSE)) {
1137 switch ($arr[$i]['type']) {
1138 case 'alpha_identifier':
1139 case 'alpha_reservedWord':
1141 * this is not a real reservedWord, because it's not
1142 * present in the list of forbidden words, for example
1143 * "storage" which can be used as an identifier
1145 * @todo avoid the pretty printing in color in this case
1147 $identifier = $arr[$i]['data'];
1148 break;
1150 case 'quote_backtick':
1151 case 'quote_double':
1152 case 'quote_single':
1153 $identifier = PMA_unQuote($arr[$i]['data']);
1154 break;
1155 } // end switch
1157 if ($subresult['querytype'] == 'SELECT'
1158 && ! $in_group_concat
1159 && ! ($seen_subquery && $arr[$i - 1]['type'] == 'punct_bracket_close_round')) {
1160 if (!$seen_from) {
1161 if ($previous_was_identifier && isset($chain)) {
1162 // found alias for this select_expr, save it
1163 // but only if we got something in $chain
1164 // (for example, SELECT COUNT(*) AS cnt
1165 // puts nothing in $chain, so we avoid
1166 // setting the alias)
1167 $alias_for_select_expr = $identifier;
1168 } else {
1169 $chain[] = $identifier;
1170 $previous_was_identifier = TRUE;
1172 } // end if !$previous_was_identifier
1173 } else {
1174 // ($seen_from)
1175 if ($save_table_ref && !$seen_end_of_table_ref) {
1176 if ($previous_was_identifier) {
1177 // found alias for table ref
1178 // save it for later
1179 $alias_for_table_ref = $identifier;
1180 } else {
1181 $chain[] = $identifier;
1182 $previous_was_identifier = TRUE;
1184 } // end if ($previous_was_identifier)
1185 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
1186 } // end if (!$seen_from)
1187 } // end if (querytype SELECT)
1188 } // end if (quote_backtick or double quote or alpha_identifier)
1190 // ===================================
1191 if ($arr[$i]['type'] == 'punct_qualifier') {
1192 // to be able to detect an identifier following another
1193 $previous_was_identifier = FALSE;
1194 continue;
1195 } // end if (punct_qualifier)
1198 * @todo check if 3 identifiers following one another -> error
1201 // s a v e a s e l e c t e x p r
1202 // finding a list separator or FROM
1203 // means that we must save the current chain of identifiers
1204 // into a select expression
1206 // for now, we only save a select expression if it contains
1207 // at least one identifier, as we are interested in checking
1208 // the columns and table names, so in "select * from persons",
1209 // the "*" is not saved
1211 if (isset($chain) && !$seen_end_of_table_ref
1212 && ((!$seen_from && $arr[$i]['type'] == 'punct_listsep')
1213 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM'))) {
1214 $size_chain = count($chain);
1215 $current_select_expr++;
1216 $subresult['select_expr'][$current_select_expr] = array(
1217 'expr' => '',
1218 'alias' => '',
1219 'db' => '',
1220 'table_name' => '',
1221 'table_true_name' => '',
1222 'column' => ''
1225 if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
1226 // we had found an alias for this select expression
1227 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
1228 unset($alias_for_select_expr);
1230 // there is at least a column
1231 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
1232 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
1234 // maybe a table
1235 if ($size_chain > 1) {
1236 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
1237 // we assume for now that this is also the true name
1238 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
1239 $subresult['select_expr'][$current_select_expr]['expr']
1240 = $subresult['select_expr'][$current_select_expr]['table_name']
1241 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1242 } // end if ($size_chain > 1)
1244 // maybe a db
1245 if ($size_chain > 2) {
1246 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
1247 $subresult['select_expr'][$current_select_expr]['expr']
1248 = $subresult['select_expr'][$current_select_expr]['db']
1249 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1250 } // end if ($size_chain > 2)
1251 unset($chain);
1254 * @todo explain this:
1256 if (($arr[$i]['type'] == 'alpha_reservedWord')
1257 && ($upper_data != 'FROM')) {
1258 $previous_was_identifier = TRUE;
1261 } // end if (save a select expr)
1264 //======================================
1265 // s a v e a t a b l e r e f
1266 //======================================
1268 // maybe we just saw the end of table refs
1269 // but the last table ref has to be saved
1270 // or we are at the last token
1271 // or we just got a reserved word
1273 * @todo there could be another query after this one
1276 if (isset($chain) && $seen_from && $save_table_ref
1277 && ($arr[$i]['type'] == 'punct_listsep'
1278 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
1279 || $seen_end_of_table_ref
1280 || $i==$size-1)) {
1282 $size_chain = count($chain);
1283 $current_table_ref++;
1284 $subresult['table_ref'][$current_table_ref] = array(
1285 'expr' => '',
1286 'db' => '',
1287 'table_name' => '',
1288 'table_alias' => '',
1289 'table_true_name' => ''
1291 if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
1292 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1293 unset($alias_for_table_ref);
1295 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1296 // we assume for now that this is also the true name
1297 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1298 $subresult['table_ref'][$current_table_ref]['expr']
1299 = $subresult['table_ref'][$current_table_ref]['table_name'];
1300 // maybe a db
1301 if ($size_chain > 1) {
1302 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1303 $subresult['table_ref'][$current_table_ref]['expr']
1304 = $subresult['table_ref'][$current_table_ref]['db']
1305 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1306 } // end if ($size_chain > 1)
1308 // add the table alias into the whole expression
1309 $subresult['table_ref'][$current_table_ref]['expr']
1310 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1312 unset($chain);
1313 $previous_was_identifier = TRUE;
1314 //continue;
1316 } // end if (save a table ref)
1319 // when we have found all table refs,
1320 // for each table_ref alias, put the true name of the table
1321 // in the corresponding select expressions
1323 if (isset($current_table_ref) && ($seen_end_of_table_ref || $i == $size-1) && $subresult != $subresult_empty) {
1324 for ($tr=0; $tr <= $current_table_ref; $tr++) {
1325 $alias = $subresult['table_ref'][$tr]['table_alias'];
1326 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1327 for ($se=0; $se <= $current_select_expr; $se++) {
1328 if (isset($alias) && strlen($alias) && $subresult['select_expr'][$se]['table_true_name']
1329 == $alias) {
1330 $subresult['select_expr'][$se]['table_true_name']
1331 = $truename;
1332 } // end if (found the alias)
1333 } // end for (select expressions)
1335 } // end for (table refs)
1336 } // end if (set the true names)
1339 // e n d i n g l o o p #1
1340 // set the $previous_was_identifier to FALSE if the current
1341 // token is not an identifier
1342 if (($arr[$i]['type'] != 'alpha_identifier')
1343 && ($arr[$i]['type'] != 'quote_double')
1344 && ($arr[$i]['type'] != 'quote_single')
1345 && ($arr[$i]['type'] != 'quote_backtick')) {
1346 $previous_was_identifier = FALSE;
1347 } // end if
1349 // however, if we are on AS, we must keep the $previous_was_identifier
1350 if (($arr[$i]['type'] == 'alpha_reservedWord')
1351 && ($upper_data == 'AS')) {
1352 $previous_was_identifier = TRUE;
1355 if (($arr[$i]['type'] == 'alpha_reservedWord')
1356 && ($upper_data =='ON' || $upper_data =='USING')) {
1357 $save_table_ref = FALSE;
1358 } // end if (data == ON)
1360 if (($arr[$i]['type'] == 'alpha_reservedWord')
1361 && ($upper_data =='JOIN' || $upper_data =='FROM')) {
1362 $save_table_ref = TRUE;
1363 } // end if (data == JOIN)
1366 * no need to check the end of table ref if we already did
1368 * @todo maybe add "&& $seen_from"
1370 if (!$seen_end_of_table_ref) {
1371 // if this is the last token, it implies that we have
1372 // seen the end of table references
1373 // Check for the end of table references
1375 // Note: if we are analyzing a GROUP_CONCAT clause,
1376 // we might find a word that seems to indicate that
1377 // we have found the end of table refs (like ORDER)
1378 // but it's a modifier of the GROUP_CONCAT so
1379 // it's not the real end of table refs
1380 if (($i == $size-1)
1381 || ($arr[$i]['type'] == 'alpha_reservedWord'
1382 && !$in_group_concat
1383 && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
1384 $seen_end_of_table_ref = TRUE;
1385 // to be able to save the last table ref, but do not
1386 // set it true if we found a word like "ON" that has
1387 // already set it to false
1388 if (isset($save_table_ref) && $save_table_ref != FALSE) {
1389 $save_table_ref = TRUE;
1390 } //end if
1392 } // end if (check for end of table ref)
1393 } //end if (!$seen_end_of_table_ref)
1395 if ($seen_end_of_table_ref) {
1396 $save_table_ref = FALSE;
1397 } // end if
1399 } // end for $i (loop #1)
1401 //DEBUG
1403 if (isset($current_select_expr)) {
1404 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1405 echo "<br />";
1406 reset ($subresult['select_expr'][$trace]);
1407 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1408 echo "sel expr $trace $key => $val<br />\n";
1412 if (isset($current_table_ref)) {
1413 echo "current_table_ref = " . $current_table_ref . "<br>";
1414 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1416 echo "<br />";
1417 reset ($subresult['table_ref'][$trace]);
1418 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1419 echo "table ref $trace $key => $val<br />\n";
1423 // -------------------------------------------------------
1426 // loop #2: - queryflags
1427 // - querytype (for queries != 'SELECT')
1428 // - section_before_limit, section_after_limit
1430 // we will also need this queryflag in loop 2
1431 // so set it here
1432 if (isset($current_table_ref) && $current_table_ref > -1) {
1433 $subresult['queryflags']['select_from'] = 1;
1436 $section_before_limit = '';
1437 $section_after_limit = ''; // truly the section after the limit clause
1438 $seen_reserved_word = FALSE;
1439 $seen_group = FALSE;
1440 $seen_order = FALSE;
1441 $seen_order_by = FALSE;
1442 $in_group_by = FALSE; // true when we are inside the GROUP BY clause
1443 $in_order_by = FALSE; // true when we are inside the ORDER BY clause
1444 $in_having = FALSE; // true when we are inside the HAVING clause
1445 $in_select_expr = FALSE; // true when we are inside the select expr clause
1446 $in_where = FALSE; // true when we are inside the WHERE clause
1447 $seen_limit = FALSE; // true if we have seen a LIMIT clause
1448 $in_limit = FALSE; // true when we are inside the LIMIT clause
1449 $after_limit = FALSE; // true when we are after the LIMIT clause
1450 $in_from = FALSE; // true when we are in the FROM clause
1451 $in_group_concat = FALSE;
1452 $first_reserved_word = '';
1453 $current_identifier = '';
1454 $unsorted_query = $arr['raw']; // in case there is no ORDER BY
1456 for ($i = 0; $i < $size; $i++) {
1457 //DEBUG echo "Loop2 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
1459 // need_confirm
1461 // check for reserved words that will have to generate
1462 // a confirmation request later in sql.php
1463 // the cases are:
1464 // DROP TABLE
1465 // DROP DATABASE
1466 // ALTER TABLE... DROP
1467 // DELETE FROM...
1469 // this code is not used for confirmations coming from functions.js
1471 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1472 $upper_data = strtoupper($arr[$i]['data']);
1473 if (!$seen_reserved_word) {
1474 $first_reserved_word = $upper_data;
1475 $subresult['querytype'] = $upper_data;
1476 $seen_reserved_word = TRUE;
1478 // if the first reserved word is DROP or DELETE,
1479 // we know this is a query that needs to be confirmed
1480 if ($first_reserved_word=='DROP'
1481 || $first_reserved_word == 'DELETE'
1482 || $first_reserved_word == 'TRUNCATE') {
1483 $subresult['queryflags']['need_confirm'] = 1;
1486 if ($first_reserved_word=='SELECT'){
1487 $position_of_first_select = $i;
1490 } else {
1491 if ($upper_data == 'DROP' && $first_reserved_word == 'ALTER') {
1492 $subresult['queryflags']['need_confirm'] = 1;
1496 if ($upper_data == 'LIMIT') {
1497 $section_before_limit = substr($arr['raw'], 0, $arr[$i]['pos'] - 5);
1498 $in_limit = TRUE;
1499 $seen_limit = TRUE;
1500 $limit_clause = '';
1501 $in_order_by = FALSE; // @todo maybe others to set FALSE
1504 if ($upper_data == 'PROCEDURE') {
1505 $subresult['queryflags']['procedure'] = 1;
1506 $in_limit = FALSE;
1507 $after_limit = TRUE;
1510 * @todo set also to FALSE if we find FOR UPDATE or LOCK IN SHARE MODE
1512 if ($upper_data == 'SELECT') {
1513 $in_select_expr = TRUE;
1514 $select_expr_clause = '';
1516 if ($upper_data == 'DISTINCT' && !$in_group_concat) {
1517 $subresult['queryflags']['distinct'] = 1;
1520 if ($upper_data == 'UNION') {
1521 $subresult['queryflags']['union'] = 1;
1524 if ($upper_data == 'JOIN') {
1525 $subresult['queryflags']['join'] = 1;
1528 if ($upper_data == 'OFFSET') {
1529 $subresult['queryflags']['offset'] = 1;
1532 // if this is a real SELECT...FROM
1533 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1534 $in_from = TRUE;
1535 $from_clause = '';
1536 $in_select_expr = FALSE;
1540 // (we could have less resetting of variables to FALSE
1541 // if we trust that the query respects the standard
1542 // MySQL order for clauses)
1544 // we use $seen_group and $seen_order because we are looking
1545 // for the BY
1546 if ($upper_data == 'GROUP') {
1547 $seen_group = TRUE;
1548 $seen_order = FALSE;
1549 $in_having = FALSE;
1550 $in_order_by = FALSE;
1551 $in_where = FALSE;
1552 $in_select_expr = FALSE;
1553 $in_from = FALSE;
1555 if ($upper_data == 'ORDER' && !$in_group_concat) {
1556 $seen_order = TRUE;
1557 $seen_group = FALSE;
1558 $in_having = FALSE;
1559 $in_group_by = FALSE;
1560 $in_where = FALSE;
1561 $in_select_expr = FALSE;
1562 $in_from = FALSE;
1564 if ($upper_data == 'HAVING') {
1565 $in_having = TRUE;
1566 $having_clause = '';
1567 $seen_group = FALSE;
1568 $seen_order = FALSE;
1569 $in_group_by = FALSE;
1570 $in_order_by = FALSE;
1571 $in_where = FALSE;
1572 $in_select_expr = FALSE;
1573 $in_from = FALSE;
1576 if ($upper_data == 'WHERE') {
1577 $in_where = TRUE;
1578 $where_clause = '';
1579 $where_clause_identifiers = array();
1580 $seen_group = FALSE;
1581 $seen_order = FALSE;
1582 $in_group_by = FALSE;
1583 $in_order_by = FALSE;
1584 $in_having = FALSE;
1585 $in_select_expr = FALSE;
1586 $in_from = FALSE;
1589 if ($upper_data == 'BY') {
1590 if ($seen_group) {
1591 $in_group_by = TRUE;
1592 $group_by_clause = '';
1594 if ($seen_order) {
1595 $seen_order_by = TRUE;
1596 // here we assume that the ORDER BY keywords took
1597 // exactly 8 characters
1598 $unsorted_query = substr($arr['raw'], 0, $arr[$i]['pos'] - 8);
1599 $in_order_by = TRUE;
1600 $order_by_clause = '';
1604 // if we find one of the words that could end the clause
1605 if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
1607 $in_group_by = FALSE;
1608 $in_order_by = FALSE;
1609 $in_having = FALSE;
1610 $in_where = FALSE;
1611 $in_select_expr = FALSE;
1612 $in_from = FALSE;
1615 } // endif (reservedWord)
1618 // do not add a space after a function name
1620 * @todo can we combine loop 2 and loop 1? some code is repeated here...
1623 $sep = ' ';
1624 if ($arr[$i]['type'] == 'alpha_functionName') {
1625 $sep='';
1626 $upper_data = strtoupper($arr[$i]['data']);
1627 if ($upper_data =='GROUP_CONCAT') {
1628 $in_group_concat = TRUE;
1629 $number_of_brackets_in_group_concat = 0;
1633 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1634 if ($in_group_concat) {
1635 $number_of_brackets_in_group_concat++;
1638 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1639 if ($in_group_concat) {
1640 $number_of_brackets_in_group_concat--;
1641 if ($number_of_brackets_in_group_concat == 0) {
1642 $in_group_concat = FALSE;
1647 // do not add a space after an identifier if followed by a dot
1648 if ($arr[$i]['type'] == 'alpha_identifier' && $i < $size - 1 && $arr[$i + 1]['data'] == '.') {
1649 $sep = '';
1652 // do not add a space after a dot if followed by an identifier
1653 if ($arr[$i]['data'] == '.' && $i < $size - 1 && $arr[$i + 1]['type'] == 'alpha_identifier') {
1654 $sep = '';
1657 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1658 $select_expr_clause .= $arr[$i]['data'] . $sep;
1660 if ($in_from && $upper_data != 'FROM') {
1661 $from_clause .= $arr[$i]['data'] . $sep;
1663 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1664 $group_by_clause .= $arr[$i]['data'] . $sep;
1666 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1667 // add a space only before ASC or DESC
1668 // not around the dot between dbname and tablename
1669 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1670 $order_by_clause .= $sep;
1672 $order_by_clause .= $arr[$i]['data'];
1674 if ($in_having && $upper_data != 'HAVING') {
1675 $having_clause .= $arr[$i]['data'] . $sep;
1677 if ($in_where && $upper_data != 'WHERE') {
1678 $where_clause .= $arr[$i]['data'] . $sep;
1680 if (($arr[$i]['type'] == 'quote_backtick')
1681 || ($arr[$i]['type'] == 'alpha_identifier')) {
1682 $where_clause_identifiers[] = $arr[$i]['data'];
1686 // to grab the rest of the query after the ORDER BY clause
1687 if (isset($subresult['queryflags']['select_from'])
1688 && $subresult['queryflags']['select_from'] == 1
1689 && ! $in_order_by
1690 && $seen_order_by
1691 && $upper_data != 'BY') {
1692 $unsorted_query .= $arr[$i]['data'];
1693 if ($arr[$i]['type'] != 'punct_bracket_open_round'
1694 && $arr[$i]['type'] != 'punct_bracket_close_round'
1695 && $arr[$i]['type'] != 'punct') {
1696 $unsorted_query .= $sep;
1700 if ($in_limit) {
1701 if ($upper_data == 'OFFSET') {
1702 $limit_clause .= $sep;
1704 $limit_clause .= $arr[$i]['data'];
1705 if ($upper_data == 'LIMIT' || $upper_data == 'OFFSET') {
1706 $limit_clause .= $sep;
1709 if ($after_limit && $seen_limit) {
1710 $section_after_limit .= $arr[$i]['data'] . $sep;
1713 // clear $upper_data for next iteration
1714 $upper_data='';
1716 } // end for $i (loop #2)
1717 if (empty($section_before_limit)) {
1718 $section_before_limit = $arr['raw'];
1721 // -----------------------------------------------------
1722 // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
1723 // (for now, check only the first query)
1724 // (for now, identifiers are assumed to be backquoted)
1726 // If we find that we are dealing with a CREATE TABLE query,
1727 // we look for the next punct_bracket_open_round, which
1728 // introduces the fields list. Then, when we find a
1729 // quote_backtick, it must be a field, so we put it into
1730 // the create_table_fields array. Even if this field is
1731 // not a timestamp, it will be useful when logic has been
1732 // added for complete field attributes analysis.
1734 $seen_foreign = FALSE;
1735 $seen_references = FALSE;
1736 $seen_constraint = FALSE;
1737 $foreign_key_number = -1;
1738 $seen_create_table = FALSE;
1739 $seen_create = FALSE;
1740 $in_create_table_fields = FALSE;
1741 $brackets_level = 0;
1742 $in_timestamp_options = FALSE;
1743 $seen_default = FALSE;
1745 for ($i = 0; $i < $size; $i++) {
1746 // DEBUG echo "Loop 3 <b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
1748 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1749 $upper_data = strtoupper($arr[$i]['data']);
1751 if ($upper_data == 'NOT' && $in_timestamp_options) {
1752 $create_table_fields[$current_identifier]['timestamp_not_null'] = TRUE;
1756 if ($upper_data == 'CREATE') {
1757 $seen_create = TRUE;
1760 if ($upper_data == 'TABLE' && $seen_create) {
1761 $seen_create_table = TRUE;
1762 $create_table_fields = array();
1765 if ($upper_data == 'CURRENT_TIMESTAMP') {
1766 if ($in_timestamp_options) {
1767 if ($seen_default) {
1768 $create_table_fields[$current_identifier]['default_current_timestamp'] = TRUE;
1773 if ($upper_data == 'CONSTRAINT') {
1774 $foreign_key_number++;
1775 $seen_foreign = FALSE;
1776 $seen_references = FALSE;
1777 $seen_constraint = TRUE;
1779 if ($upper_data == 'FOREIGN') {
1780 $seen_foreign = TRUE;
1781 $seen_references = FALSE;
1782 $seen_constraint = FALSE;
1784 if ($upper_data == 'REFERENCES') {
1785 $seen_foreign = FALSE;
1786 $seen_references = TRUE;
1787 $seen_constraint = FALSE;
1791 // Cases covered:
1793 // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1794 // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1796 // but we set ['on_delete'] or ['on_cascade'] to
1797 // CASCADE | SET_NULL | NO_ACTION | RESTRICT
1799 // ON UPDATE CURRENT_TIMESTAMP
1801 if ($upper_data == 'ON') {
1802 if (isset($arr[$i+1]) && $arr[$i+1]['type'] == 'alpha_reservedWord') {
1803 $second_upper_data = strtoupper($arr[$i+1]['data']);
1804 if ($second_upper_data == 'DELETE') {
1805 $clause = 'on_delete';
1807 if ($second_upper_data == 'UPDATE') {
1808 $clause = 'on_update';
1810 if (isset($clause)
1811 && ($arr[$i+2]['type'] == 'alpha_reservedWord'
1813 // ugly workaround because currently, NO is not
1814 // in the list of reserved words in sqlparser.data
1815 // (we got a bug report about not being able to use
1816 // 'no' as an identifier)
1817 || ($arr[$i+2]['type'] == 'alpha_identifier'
1818 && strtoupper($arr[$i+2]['data'])=='NO'))
1820 $third_upper_data = strtoupper($arr[$i+2]['data']);
1821 if ($third_upper_data == 'CASCADE'
1822 || $third_upper_data == 'RESTRICT') {
1823 $value = $third_upper_data;
1824 } elseif ($third_upper_data == 'SET'
1825 || $third_upper_data == 'NO') {
1826 if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
1827 $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
1829 } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
1830 if ($clause == 'on_update'
1831 && $in_timestamp_options) {
1832 $create_table_fields[$current_identifier]['on_update_current_timestamp'] = TRUE;
1833 $seen_default = FALSE;
1836 } else {
1837 $value = '';
1839 if (!empty($value)) {
1840 $foreign[$foreign_key_number][$clause] = $value;
1842 unset($clause);
1843 } // endif (isset($clause))
1847 } // end of reserved words analysis
1850 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1851 $brackets_level++;
1852 if ($seen_create_table && $brackets_level == 1) {
1853 $in_create_table_fields = TRUE;
1858 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1859 $brackets_level--;
1860 if ($seen_references) {
1861 $seen_references = FALSE;
1863 if ($seen_create_table && $brackets_level == 0) {
1864 $in_create_table_fields = FALSE;
1868 if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
1869 $upper_data = strtoupper($arr[$i]['data']);
1870 if ($seen_create_table && $in_create_table_fields) {
1871 if ($upper_data == 'DEFAULT') {
1872 $seen_default = TRUE;
1878 * @see @todo 2005-10-16 note: the "or" part here is a workaround for a bug
1880 if (($arr[$i]['type'] == 'alpha_columnType') || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)) {
1881 $upper_data = strtoupper($arr[$i]['data']);
1882 if ($seen_create_table && $in_create_table_fields && isset($current_identifier)) {
1883 $create_table_fields[$current_identifier]['type'] = $upper_data;
1884 if ($upper_data == 'TIMESTAMP') {
1885 $arr[$i]['type'] = 'alpha_columnType';
1886 $in_timestamp_options = TRUE;
1887 } else {
1888 $in_timestamp_options = FALSE;
1889 if ($upper_data == 'CHAR') {
1890 $arr[$i]['type'] = 'alpha_columnType';
1897 if ($arr[$i]['type'] == 'quote_backtick' || $arr[$i]['type'] == 'alpha_identifier') {
1899 if ($arr[$i]['type'] == 'quote_backtick') {
1900 // remove backquotes
1901 $identifier = PMA_unQuote($arr[$i]['data']);
1902 } else {
1903 $identifier = $arr[$i]['data'];
1906 if ($seen_create_table && $in_create_table_fields) {
1907 $current_identifier = $identifier;
1908 // warning: we set this one even for non TIMESTAMP type
1909 $create_table_fields[$current_identifier]['timestamp_not_null'] = FALSE;
1912 if ($seen_constraint) {
1913 $foreign[$foreign_key_number]['constraint'] = $identifier;
1916 if ($seen_foreign && $brackets_level > 0) {
1917 $foreign[$foreign_key_number]['index_list'][] = $identifier;
1920 if ($seen_references) {
1921 // here, the first bracket level corresponds to the
1922 // bracket of CREATE TABLE
1923 // so if we are on level 2, it must be the index list
1924 // of the foreign key REFERENCES
1925 if ($brackets_level > 1) {
1926 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1927 } else {
1928 // for MySQL 4.0.18, identifier is
1929 // `table` or `db`.`table`
1930 // the first pass will pick the db name
1931 // the next pass will execute the else and pick the
1932 // db name in $db_table[0]
1933 if ($arr[$i+1]['type'] == 'punct_qualifier') {
1934 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
1935 } else {
1936 // for MySQL 4.0.16, identifier is
1937 // `table` or `db.table`
1938 $db_table = explode('.', $identifier);
1939 if (isset($db_table[1])) {
1940 $foreign[$foreign_key_number]['ref_db_name'] = $db_table[0];
1941 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[1];
1942 } else {
1943 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[0];
1949 } // end for $i (loop #3)
1952 // Fill the $subresult array
1954 if (isset($create_table_fields)) {
1955 $subresult['create_table_fields'] = $create_table_fields;
1958 if (isset($foreign)) {
1959 $subresult['foreign_keys'] = $foreign;
1962 if (isset($select_expr_clause)) {
1963 $subresult['select_expr_clause'] = $select_expr_clause;
1965 if (isset($from_clause)) {
1966 $subresult['from_clause'] = $from_clause;
1968 if (isset($group_by_clause)) {
1969 $subresult['group_by_clause'] = $group_by_clause;
1971 if (isset($order_by_clause)) {
1972 $subresult['order_by_clause'] = $order_by_clause;
1974 if (isset($having_clause)) {
1975 $subresult['having_clause'] = $having_clause;
1977 if (isset($limit_clause)) {
1978 $subresult['limit_clause'] = $limit_clause;
1980 if (isset($where_clause)) {
1981 $subresult['where_clause'] = $where_clause;
1983 if (isset($unsorted_query) && !empty($unsorted_query)) {
1984 $subresult['unsorted_query'] = $unsorted_query;
1986 if (isset($where_clause_identifiers)) {
1987 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
1990 if (isset($position_of_first_select)) {
1991 $subresult['position_of_first_select'] = $position_of_first_select;
1992 $subresult['section_before_limit'] = $section_before_limit;
1993 $subresult['section_after_limit'] = $section_after_limit;
1996 // They are naughty and didn't have a trailing semi-colon,
1997 // then still handle it properly
1998 if ($subresult['querytype'] != '') {
1999 $result[] = $subresult;
2001 return $result;
2002 } // end of the "PMA_SQP_analyze()" function
2006 * Colorizes SQL queries html formatted
2008 * @todo check why adding a "\n" after the </span> would cause extra blanks
2009 * to be displayed: SELECT p . person_name
2010 * @param array The SQL queries html formatted
2012 * @return array The colorized SQL queries
2014 * @access public
2016 function PMA_SQP_formatHtml_colorize($arr)
2018 $i = $GLOBALS['PMA_strpos']($arr['type'], '_');
2019 $class = '';
2020 if ($i > 0) {
2021 $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
2024 $class .= 'syntax_' . $arr['type'];
2026 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
2027 } // end of the "PMA_SQP_formatHtml_colorize()" function
2031 * Formats SQL queries to html
2033 * @param array The SQL queries
2034 * @param string mode
2035 * @param integer starting token
2036 * @param integer number of tokens to format, -1 = all
2038 * @return string The formatted SQL queries
2040 * @access public
2042 function PMA_SQP_formatHtml($arr, $mode='color', $start_token=0,
2043 $number_of_tokens=-1)
2045 //DEBUG echo 'in Format<pre>'; print_r($arr); echo '</pre>';
2046 // then check for an array
2047 if (!is_array($arr)) {
2048 return htmlspecialchars($arr);
2050 // first check for the SQL parser having hit an error
2051 if (PMA_SQP_isError()) {
2052 return htmlspecialchars($arr['raw']);
2054 // else do it properly
2055 switch ($mode) {
2056 case 'color':
2057 $str = '<span class="syntax">';
2058 $html_line_break = '<br />';
2059 break;
2060 case 'query_only':
2061 $str = '';
2062 $html_line_break = "\n";
2063 break;
2064 case 'text':
2065 $str = '';
2066 $html_line_break = '<br />';
2067 break;
2068 } // end switch
2069 $indent = 0;
2070 $bracketlevel = 0;
2071 $functionlevel = 0;
2072 $infunction = FALSE;
2073 $space_punct_listsep = ' ';
2074 $space_punct_listsep_function_name = ' ';
2075 // $space_alpha_reserved_word = '<br />'."\n";
2076 $space_alpha_reserved_word = ' ';
2078 $keywords_with_brackets_1before = array(
2079 'INDEX',
2080 'KEY',
2081 'ON',
2082 'USING'
2084 $keywords_with_brackets_1before_cnt = 4;
2086 $keywords_with_brackets_2before = array(
2087 'IGNORE',
2088 'INDEX',
2089 'INTO',
2090 'KEY',
2091 'PRIMARY',
2092 'PROCEDURE',
2093 'REFERENCES',
2094 'UNIQUE',
2095 'USE'
2097 // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
2098 $keywords_with_brackets_2before_cnt = 9;
2100 // These reserved words do NOT get a newline placed near them.
2101 $keywords_no_newline = array(
2102 'AS',
2103 'ASC',
2104 'DESC',
2105 'DISTINCT',
2106 'DUPLICATE',
2107 'HOUR',
2108 'INTERVAL',
2109 'IS',
2110 'LIKE',
2111 'NOT',
2112 'NULL',
2113 'ON',
2114 'REGEXP'
2116 $keywords_no_newline_cnt = 12;
2118 // These reserved words introduce a privilege list
2119 $keywords_priv_list = array(
2120 'GRANT',
2121 'REVOKE'
2123 $keywords_priv_list_cnt = 2;
2125 if ($number_of_tokens == -1) {
2126 $arraysize = $arr['len'];
2127 } else {
2128 $arraysize = $number_of_tokens;
2130 $typearr = array();
2131 if ($arraysize >= 0) {
2132 $typearr[0] = '';
2133 $typearr[1] = '';
2134 $typearr[2] = '';
2135 //$typearr[3] = $arr[0]['type'];
2136 $typearr[3] = $arr[$start_token]['type'];
2139 $in_priv_list = FALSE;
2140 for ($i = $start_token; $i < $arraysize; $i++) {
2141 // DEBUG echo "Loop format <b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
2142 $before = '';
2143 $after = '';
2144 $indent = 0;
2145 // array_shift($typearr);
2147 0 prev2
2148 1 prev
2149 2 current
2150 3 next
2152 if (($i + 1) < $arraysize) {
2153 // array_push($typearr, $arr[$i + 1]['type']);
2154 $typearr[4] = $arr[$i + 1]['type'];
2155 } else {
2156 //array_push($typearr, null);
2157 $typearr[4] = '';
2160 for ($j=0; $j<4; $j++) {
2161 $typearr[$j] = $typearr[$j + 1];
2164 switch ($typearr[2]) {
2165 case 'white_newline':
2166 $before = '';
2167 break;
2168 case 'punct_bracket_open_round':
2169 $bracketlevel++;
2170 $infunction = FALSE;
2171 // Make sure this array is sorted!
2172 if (($typearr[1] == 'alpha_functionName') || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
2173 || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex') || ($typearr[3] == 'digit_float')
2174 || (($typearr[0] == 'alpha_reservedWord')
2175 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
2176 || (($typearr[1] == 'alpha_reservedWord')
2177 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
2179 $functionlevel++;
2180 $infunction = TRUE;
2181 $after .= ' ';
2182 } else {
2183 $indent++;
2184 $after .= ($mode != 'query_only' ? '<div class="syntax_indent' . $indent . '">' : ' ');
2186 break;
2187 case 'alpha_identifier':
2188 if (($typearr[1] == 'punct_qualifier') || ($typearr[3] == 'punct_qualifier')) {
2189 $after = '';
2190 $before = '';
2192 if (($typearr[3] == 'alpha_columnType') || ($typearr[3] == 'alpha_identifier')) {
2193 $after .= ' ';
2195 break;
2196 case 'punct_user':
2197 case 'punct_qualifier':
2198 $before = '';
2199 $after = '';
2200 break;
2201 case 'punct_listsep':
2202 if ($infunction == TRUE) {
2203 $after .= $space_punct_listsep_function_name;
2204 } else {
2205 $after .= $space_punct_listsep;
2207 break;
2208 case 'punct_queryend':
2209 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi') && $typearr[3] != 'comment_c') {
2210 $after .= $html_line_break;
2211 $after .= $html_line_break;
2213 $space_punct_listsep = ' ';
2214 $space_punct_listsep_function_name = ' ';
2215 $space_alpha_reserved_word = ' ';
2216 $in_priv_list = FALSE;
2217 break;
2218 case 'comment_mysql':
2219 case 'comment_ansi':
2220 $after .= $html_line_break;
2221 break;
2222 case 'punct':
2223 $before .= ' ';
2224 // workaround for
2225 // select * from mytable limit 0,-1
2226 // (a side effect of this workaround is that
2227 // select 20 - 9
2228 // becomes
2229 // select 20 -9
2230 // )
2231 if ($typearr[3] != 'digit_integer') {
2232 $after .= ' ';
2234 break;
2235 case 'punct_bracket_close_round':
2236 $bracketlevel--;
2237 if ($infunction == TRUE) {
2238 $functionlevel--;
2239 $after .= ' ';
2240 $before .= ' ';
2241 } else {
2242 $indent--;
2243 $before .= ($mode != 'query_only' ? '</div>' : ' ');
2245 $infunction = ($functionlevel > 0) ? TRUE : FALSE;
2246 break;
2247 case 'alpha_columnType':
2248 if ($typearr[3] == 'alpha_columnAttrib') {
2249 $after .= ' ';
2251 if ($typearr[1] == 'alpha_columnType') {
2252 $before .= ' ';
2254 break;
2255 case 'alpha_columnAttrib':
2257 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
2258 // COLLATE LATIN1_GENERAL_CI DEFAULT
2259 if ($typearr[1] == 'alpha_identifier' || $typearr[1] == 'alpha_charset') {
2260 $before .= ' ';
2262 if (($typearr[3] == 'alpha_columnAttrib') || ($typearr[3] == 'quote_single') || ($typearr[3] == 'digit_integer')) {
2263 $after .= ' ';
2265 // workaround for
2266 // AUTO_INCREMENT = 31DEFAULT_CHARSET = utf-8
2268 if ($typearr[2] == 'alpha_columnAttrib' && $typearr[3] == 'alpha_reservedWord') {
2269 $before .= ' ';
2271 // workaround for
2272 // select * from mysql.user where binary user="root"
2273 // binary is marked as alpha_columnAttrib
2274 // but should be marked as a reserved word
2275 if (strtoupper($arr[$i]['data']) == 'BINARY'
2276 && $typearr[3] == 'alpha_identifier') {
2277 $after .= ' ';
2279 break;
2280 case 'alpha_reservedWord':
2281 // do not uppercase the reserved word if we are calling
2282 // this function in query_only mode, because we need
2283 // the original query (otherwise we get problems with
2284 // semi-reserved words like "storage" which is legal
2285 // as an identifier name)
2287 if ($mode != 'query_only') {
2288 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
2291 if ((($typearr[1] != 'alpha_reservedWord')
2292 || (($typearr[1] == 'alpha_reservedWord')
2293 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
2294 && ($typearr[1] != 'punct_level_plus')
2295 && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
2296 // do not put a space before the first token, because
2297 // we use a lot of eregi() checking for the first
2298 // reserved word at beginning of query
2299 // so do not put a newline before
2301 // also we must not be inside a privilege list
2302 if ($i > 0) {
2303 // the alpha_identifier exception is there to
2304 // catch cases like
2305 // GRANT SELECT ON mydb.mytable TO myuser@localhost
2306 // (else, we get mydb.mytableTO)
2308 // the quote_single exception is there to
2309 // catch cases like
2310 // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
2312 * @todo fix all cases and find why this happens
2315 if (!$in_priv_list || $typearr[1] == 'alpha_identifier' || $typearr[1] == 'quote_single' || $typearr[1] == 'white_newline') {
2316 $before .= $space_alpha_reserved_word;
2318 } else {
2319 // on first keyword, check if it introduces a
2320 // privilege list
2321 if (PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_priv_list, $keywords_priv_list_cnt)) {
2322 $in_priv_list = TRUE;
2325 } else {
2326 $before .= ' ';
2329 switch ($arr[$i]['data']) {
2330 case 'CREATE':
2331 if (!$in_priv_list) {
2332 $space_punct_listsep = $html_line_break;
2333 $space_alpha_reserved_word = ' ';
2335 break;
2336 case 'EXPLAIN':
2337 case 'DESCRIBE':
2338 case 'SET':
2339 case 'ALTER':
2340 case 'DELETE':
2341 case 'SHOW':
2342 case 'DROP':
2343 case 'UPDATE':
2344 case 'TRUNCATE':
2345 case 'ANALYZE':
2346 case 'ANALYSE':
2347 if (!$in_priv_list) {
2348 $space_punct_listsep = $html_line_break;
2349 $space_alpha_reserved_word = ' ';
2351 break;
2352 case 'INSERT':
2353 case 'REPLACE':
2354 if (!$in_priv_list) {
2355 $space_punct_listsep = $html_line_break;
2356 $space_alpha_reserved_word = $html_line_break;
2358 break;
2359 case 'VALUES':
2360 $space_punct_listsep = ' ';
2361 $space_alpha_reserved_word = $html_line_break;
2362 break;
2363 case 'SELECT':
2364 $space_punct_listsep = ' ';
2365 $space_alpha_reserved_word = $html_line_break;
2366 break;
2367 default:
2368 break;
2369 } // end switch ($arr[$i]['data'])
2371 $after .= ' ';
2372 break;
2373 case 'digit_integer':
2374 case 'digit_float':
2375 case 'digit_hex':
2377 * @todo could there be other types preceding a digit?
2379 if ($typearr[1] == 'alpha_reservedWord') {
2380 $after .= ' ';
2382 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2383 $after .= ' ';
2385 if ($typearr[1] == 'alpha_columnAttrib') {
2386 $before .= ' ';
2388 break;
2389 case 'alpha_variable':
2390 $after = ' ';
2391 break;
2392 case 'quote_double':
2393 case 'quote_single':
2394 // workaround: for the query
2395 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
2396 // the @ is incorrectly marked as alpha_variable
2397 // in the parser, and here, the '%' gets a blank before,
2398 // which is a syntax error
2399 if ($typearr[1] != 'punct_user') {
2400 $before .= ' ';
2402 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2403 $after .= ' ';
2405 break;
2406 case 'quote_backtick':
2407 // here we check for punct_user to handle correctly
2408 // DEFINER = `username`@`%`
2409 // where @ is the punct_user and `%` is the quote_backtick
2410 if ($typearr[3] != 'punct_qualifier' && $typearr[3] != 'alpha_variable' && $typearr[3] != 'punct_user') {
2411 $after .= ' ';
2413 if ($typearr[1] != 'punct_qualifier' && $typearr[1] != 'alpha_variable' && $typearr[1] != 'punct_user') {
2414 $before .= ' ';
2416 break;
2417 default:
2418 break;
2419 } // end switch ($typearr[2])
2422 if ($typearr[3] != 'punct_qualifier') {
2423 $after .= ' ';
2425 $after .= "\n";
2427 $str .= $before . ($mode=='color' ? PMA_SQP_formatHTML_colorize($arr[$i]) : $arr[$i]['data']). $after;
2428 } // end for
2429 if ($mode=='color') {
2430 $str .= '</span>';
2433 return $str;
2434 } // end of the "PMA_SQP_formatHtml()" function
2438 * Builds a CSS rule used for html formatted SQL queries
2440 * @param string The class name
2441 * @param string The property name
2442 * @param string The property value
2444 * @return string The CSS rule
2446 * @access public
2448 * @see PMA_SQP_buildCssData()
2450 function PMA_SQP_buildCssRule($classname, $property, $value)
2452 $str = '.' . $classname . ' {';
2453 if ($value != '') {
2454 $str .= $property . ': ' . $value . ';';
2456 $str .= '}' . "\n";
2458 return $str;
2459 } // end of the "PMA_SQP_buildCssRule()" function
2463 * Builds CSS rules used for html formatted SQL queries
2465 * @return string The CSS rules set
2467 * @access public
2469 * @global array The current PMA configuration
2471 * @see PMA_SQP_buildCssRule()
2473 function PMA_SQP_buildCssData()
2475 global $cfg;
2477 $css_string = '';
2478 foreach ($cfg['SQP']['fmtColor'] AS $key => $col) {
2479 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
2482 for ($i = 0; $i < 8; $i++) {
2483 $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
2486 return $css_string;
2487 } // end of the "PMA_SQP_buildCssData()" function
2489 if (! defined('PMA_MINIMUM_COMMON')) {
2491 * Gets SQL queries with no format
2493 * @param array The SQL queries list
2495 * @return string The SQL queries with no format
2497 * @access public
2499 function PMA_SQP_formatNone($arr)
2501 $formatted_sql = htmlspecialchars($arr['raw']);
2502 $formatted_sql = preg_replace("@((\015\012)|(\015)|(\012)){3,}@", "\n\n", $formatted_sql);
2504 return $formatted_sql;
2505 } // end of the "PMA_SQP_formatNone()" function
2509 * Gets SQL queries in text format
2511 * @todo WRITE THIS!
2512 * @param array The SQL queries list
2514 * @return string The SQL queries in text format
2516 * @access public
2518 function PMA_SQP_formatText($arr)
2520 return PMA_SQP_formatNone($arr);
2521 } // end of the "PMA_SQP_formatText()" function
2522 } // end if: minimal common.lib needed?