Fix for the Open in New Window in Patient/Client->Patients search gui, take 2.
[openemr.git] / phpmyadmin / libraries / sqlparser.lib.php
blob488cde211babb3ede3be70abfdbd4a74d13f2d8c
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;
836 $seen_from = false;
838 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
839 // we must not use CURDATE as a table_ref
840 // so we track wether we are in the EXTRACT()
841 $in_extract = FALSE;
843 // for GROUP_CONCAT(...)
844 $in_group_concat = FALSE;
846 /* Description of analyzer results by lem9
848 * db, table, column, alias
849 * ------------------------
851 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
853 * The SELECT syntax (simplified) is
855 * SELECT
856 * select_expression,...
857 * [FROM [table_references]
860 * ['select_expr'] is filled with each expression, the key represents the
861 * expression position in the list (0-based) (so we don't lose track of
862 * multiple occurences of the same column).
864 * ['table_ref'] is filled with each table ref, same thing for the key.
866 * I create all sub-values empty, even if they are
867 * not present (for example no select_expression alias).
869 * There is a debug section at the end of loop #1, if you want to
870 * see the exact contents of select_expr and table_ref
872 * queryflags
873 * ----------
875 * In $subresult, array 'queryflags' is filled, according to what we
876 * find in the query.
878 * Currently, those are generated:
880 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
881 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
882 * ['queryflags']['distinct'] = 1; for a DISTINCT
883 * ['queryflags']['union'] = 1; for a UNION
884 * ['queryflags']['join'] = 1; for a JOIN
885 * ['queryflags']['offset'] = 1; for the presence of OFFSET
886 * ['queryflags']['procedure'] = 1; for the presence of PROCEDURE
888 * query clauses
889 * -------------
891 * The select is splitted in those clauses:
892 * ['select_expr_clause']
893 * ['from_clause']
894 * ['group_by_clause']
895 * ['order_by_clause']
896 * ['having_clause']
897 * ['where_clause']
898 * ['limit_clause']
900 * The identifiers of the WHERE clause are put into the array
901 * ['where_clause_identifier']
903 * For a SELECT, the whole query without the ORDER BY clause is put into
904 * ['unsorted_query']
906 * foreign keys
907 * ------------
908 * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
909 * analyzed and ['foreign_keys'] is an array filled with
910 * the constraint name, the index list,
911 * the REFERENCES table name and REFERENCES index list,
912 * and ON UPDATE | ON DELETE clauses
914 * position_of_first_select
915 * ------------------------
917 * The array index of the first SELECT we find. Will be used to
918 * insert a SQL_CALC_FOUND_ROWS.
920 * create_table_fields
921 * -------------------
923 * For now, mostly used to detect the DEFAULT CURRENT_TIMESTAMP and
924 * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
925 * An array, each element is the identifier name.
926 * Note that for now, the timestamp_not_null element is created
927 * even for non-TIMESTAMP fields.
929 * Sub-elements: ['type'] which contains the column type
930 * optional (currently they are never false but can be absent):
931 * ['default_current_timestamp'] boolean
932 * ['on_update_current_timestamp'] boolean
933 * ['timestamp_not_null'] boolean
935 * section_before_limit, section_after_limit
936 * -----------------------------------------
938 * Marks the point of the query where we can insert a LIMIT clause;
939 * so the section_before_limit will contain the left part before
940 * a possible LIMIT clause
943 * End of description of analyzer results
946 // must be sorted
947 // TODO: current logic checks for only one word, so I put only the
948 // first word of the reserved expressions that end a table ref;
949 // maybe this is not ok (the first word might mean something else)
950 // $words_ending_table_ref = array(
951 // 'FOR UPDATE',
952 // 'GROUP BY',
953 // 'HAVING',
954 // 'LIMIT',
955 // 'LOCK IN SHARE MODE',
956 // 'ORDER BY',
957 // 'PROCEDURE',
958 // 'UNION',
959 // 'WHERE'
960 // );
961 $words_ending_table_ref = array(
962 'FOR',
963 'GROUP',
964 'HAVING',
965 'LIMIT',
966 'LOCK',
967 'ORDER',
968 'PROCEDURE',
969 'UNION',
970 'WHERE'
972 $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
974 $words_ending_clauses = array(
975 'FOR',
976 'LIMIT',
977 'LOCK',
978 'PROCEDURE',
979 'UNION'
981 $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
986 // must be sorted
987 $supported_query_types = array(
988 'SELECT'
990 // Support for these additional query types will come later on.
991 'DELETE',
992 'INSERT',
993 'REPLACE',
994 'TRUNCATE',
995 'UPDATE'
996 'EXPLAIN',
997 'DESCRIBE',
998 'SHOW',
999 'CREATE',
1000 'SET',
1001 'ALTER'
1004 $supported_query_types_cnt = count($supported_query_types);
1006 // loop #1 for each token: select_expr, table_ref for SELECT
1008 for ($i = 0; $i < $size; $i++) {
1009 //DEBUG echo "Loop1 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
1011 // High speed seek for locating the end of the current query
1012 if ($seek_queryend == TRUE) {
1013 if ($arr[$i]['type'] == 'punct_queryend') {
1014 $seek_queryend = FALSE;
1015 } else {
1016 continue;
1017 } // end if (type == punct_queryend)
1018 } // end if ($seek_queryend)
1021 * Note: do not split if this is a punct_queryend for the first and only query
1022 * @todo when we find a UNION, should we split in another subresult?
1024 if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
1025 $result[] = $subresult;
1026 $subresult = $subresult_empty;
1027 continue;
1028 } // end if (type == punct_queryend)
1030 // ==============================================================
1031 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1032 $number_of_brackets++;
1033 if ($in_extract) {
1034 $number_of_brackets_in_extract++;
1036 if ($in_group_concat) {
1037 $number_of_brackets_in_group_concat++;
1040 // ==============================================================
1041 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1042 $number_of_brackets--;
1043 if ($number_of_brackets == 0) {
1044 $in_subquery = false;
1046 if ($in_extract) {
1047 $number_of_brackets_in_extract--;
1048 if ($number_of_brackets_in_extract == 0) {
1049 $in_extract = FALSE;
1052 if ($in_group_concat) {
1053 $number_of_brackets_in_group_concat--;
1054 if ($number_of_brackets_in_group_concat == 0) {
1055 $in_group_concat = FALSE;
1060 if ($in_subquery) {
1062 * skip the subquery to avoid setting
1063 * select_expr or table_ref with the contents
1064 * of this subquery; this is to avoid a bug when
1065 * trying to edit the results of
1066 * select * from child where not exists (select id from
1067 * parent where child.parent_id = parent.id);
1069 continue;
1071 // ==============================================================
1072 if ($arr[$i]['type'] == 'alpha_functionName') {
1073 $upper_data = strtoupper($arr[$i]['data']);
1074 if ($upper_data =='EXTRACT') {
1075 $in_extract = TRUE;
1076 $number_of_brackets_in_extract = 0;
1078 if ($upper_data =='GROUP_CONCAT') {
1079 $in_group_concat = TRUE;
1080 $number_of_brackets_in_group_concat = 0;
1084 // ==============================================================
1085 if ($arr[$i]['type'] == 'alpha_reservedWord'
1086 // && $arr[$i]['forbidden'] == FALSE) {
1088 // We don't know what type of query yet, so run this
1089 if ($subresult['querytype'] == '') {
1090 $subresult['querytype'] = strtoupper($arr[$i]['data']);
1091 } // end if (querytype was empty)
1093 // Check if we support this type of query
1094 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
1095 // Skip ahead to the next one if we don't
1096 $seek_queryend = TRUE;
1097 continue;
1098 } // end if (query not supported)
1100 // upper once
1101 $upper_data = strtoupper($arr[$i]['data']);
1103 * @todo reset for each query?
1106 if ($upper_data == 'SELECT') {
1107 if ($number_of_brackets > 0) {
1108 $in_subquery = true;
1109 $seen_subquery = true;
1110 // this is a subquery so do not analyze inside it
1111 continue;
1113 $seen_from = FALSE;
1114 $previous_was_identifier = FALSE;
1115 $current_select_expr = -1;
1116 $seen_end_of_table_ref = FALSE;
1117 } // end if (data == SELECT)
1119 if ($upper_data =='FROM' && !$in_extract) {
1120 $current_table_ref = -1;
1121 $seen_from = TRUE;
1122 $previous_was_identifier = FALSE;
1123 $save_table_ref = TRUE;
1124 } // end if (data == FROM)
1126 // here, do not 'continue' the loop, as we have more work for
1127 // reserved words below
1128 } // end if (type == alpha_reservedWord)
1130 // ==============================
1131 if ($arr[$i]['type'] == 'quote_backtick'
1132 || $arr[$i]['type'] == 'quote_double'
1133 || $arr[$i]['type'] == 'quote_single'
1134 || $arr[$i]['type'] == 'alpha_identifier'
1135 || ($arr[$i]['type'] == 'alpha_reservedWord'
1136 && $arr[$i]['forbidden'] == FALSE)) {
1138 switch ($arr[$i]['type']) {
1139 case 'alpha_identifier':
1140 case 'alpha_reservedWord':
1142 * this is not a real reservedWord, because it's not
1143 * present in the list of forbidden words, for example
1144 * "storage" which can be used as an identifier
1146 * @todo avoid the pretty printing in color in this case
1148 $identifier = $arr[$i]['data'];
1149 break;
1151 case 'quote_backtick':
1152 case 'quote_double':
1153 case 'quote_single':
1154 $identifier = PMA_unQuote($arr[$i]['data']);
1155 break;
1156 } // end switch
1158 if ($subresult['querytype'] == 'SELECT'
1159 && ! $in_group_concat
1160 && ! ($seen_subquery && $arr[$i - 1]['type'] == 'punct_bracket_close_round')) {
1161 if (!$seen_from) {
1162 if ($previous_was_identifier && isset($chain)) {
1163 // found alias for this select_expr, save it
1164 // but only if we got something in $chain
1165 // (for example, SELECT COUNT(*) AS cnt
1166 // puts nothing in $chain, so we avoid
1167 // setting the alias)
1168 $alias_for_select_expr = $identifier;
1169 } else {
1170 $chain[] = $identifier;
1171 $previous_was_identifier = TRUE;
1173 } // end if !$previous_was_identifier
1174 } else {
1175 // ($seen_from)
1176 if ($save_table_ref && !$seen_end_of_table_ref) {
1177 if ($previous_was_identifier) {
1178 // found alias for table ref
1179 // save it for later
1180 $alias_for_table_ref = $identifier;
1181 } else {
1182 $chain[] = $identifier;
1183 $previous_was_identifier = TRUE;
1185 } // end if ($previous_was_identifier)
1186 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
1187 } // end if (!$seen_from)
1188 } // end if (querytype SELECT)
1189 } // end if (quote_backtick or double quote or alpha_identifier)
1191 // ===================================
1192 if ($arr[$i]['type'] == 'punct_qualifier') {
1193 // to be able to detect an identifier following another
1194 $previous_was_identifier = FALSE;
1195 continue;
1196 } // end if (punct_qualifier)
1199 * @todo check if 3 identifiers following one another -> error
1202 // s a v e a s e l e c t e x p r
1203 // finding a list separator or FROM
1204 // means that we must save the current chain of identifiers
1205 // into a select expression
1207 // for now, we only save a select expression if it contains
1208 // at least one identifier, as we are interested in checking
1209 // the columns and table names, so in "select * from persons",
1210 // the "*" is not saved
1212 if (isset($chain) && !$seen_end_of_table_ref
1213 && ((!$seen_from && $arr[$i]['type'] == 'punct_listsep')
1214 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM'))) {
1215 $size_chain = count($chain);
1216 $current_select_expr++;
1217 $subresult['select_expr'][$current_select_expr] = array(
1218 'expr' => '',
1219 'alias' => '',
1220 'db' => '',
1221 'table_name' => '',
1222 'table_true_name' => '',
1223 'column' => ''
1226 if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
1227 // we had found an alias for this select expression
1228 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
1229 unset($alias_for_select_expr);
1231 // there is at least a column
1232 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
1233 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
1235 // maybe a table
1236 if ($size_chain > 1) {
1237 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
1238 // we assume for now that this is also the true name
1239 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
1240 $subresult['select_expr'][$current_select_expr]['expr']
1241 = $subresult['select_expr'][$current_select_expr]['table_name']
1242 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1243 } // end if ($size_chain > 1)
1245 // maybe a db
1246 if ($size_chain > 2) {
1247 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
1248 $subresult['select_expr'][$current_select_expr]['expr']
1249 = $subresult['select_expr'][$current_select_expr]['db']
1250 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1251 } // end if ($size_chain > 2)
1252 unset($chain);
1255 * @todo explain this:
1257 if (($arr[$i]['type'] == 'alpha_reservedWord')
1258 && ($upper_data != 'FROM')) {
1259 $previous_was_identifier = TRUE;
1262 } // end if (save a select expr)
1265 //======================================
1266 // s a v e a t a b l e r e f
1267 //======================================
1269 // maybe we just saw the end of table refs
1270 // but the last table ref has to be saved
1271 // or we are at the last token
1272 // or we just got a reserved word
1274 * @todo there could be another query after this one
1277 if (isset($chain) && $seen_from && $save_table_ref
1278 && ($arr[$i]['type'] == 'punct_listsep'
1279 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
1280 || $seen_end_of_table_ref
1281 || $i==$size-1)) {
1283 $size_chain = count($chain);
1284 $current_table_ref++;
1285 $subresult['table_ref'][$current_table_ref] = array(
1286 'expr' => '',
1287 'db' => '',
1288 'table_name' => '',
1289 'table_alias' => '',
1290 'table_true_name' => ''
1292 if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
1293 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1294 unset($alias_for_table_ref);
1296 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1297 // we assume for now that this is also the true name
1298 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1299 $subresult['table_ref'][$current_table_ref]['expr']
1300 = $subresult['table_ref'][$current_table_ref]['table_name'];
1301 // maybe a db
1302 if ($size_chain > 1) {
1303 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1304 $subresult['table_ref'][$current_table_ref]['expr']
1305 = $subresult['table_ref'][$current_table_ref]['db']
1306 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1307 } // end if ($size_chain > 1)
1309 // add the table alias into the whole expression
1310 $subresult['table_ref'][$current_table_ref]['expr']
1311 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1313 unset($chain);
1314 $previous_was_identifier = TRUE;
1315 //continue;
1317 } // end if (save a table ref)
1320 // when we have found all table refs,
1321 // for each table_ref alias, put the true name of the table
1322 // in the corresponding select expressions
1324 if (isset($current_table_ref) && ($seen_end_of_table_ref || $i == $size-1) && $subresult != $subresult_empty) {
1325 for ($tr=0; $tr <= $current_table_ref; $tr++) {
1326 $alias = $subresult['table_ref'][$tr]['table_alias'];
1327 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1328 for ($se=0; $se <= $current_select_expr; $se++) {
1329 if (isset($alias) && strlen($alias) && $subresult['select_expr'][$se]['table_true_name']
1330 == $alias) {
1331 $subresult['select_expr'][$se]['table_true_name']
1332 = $truename;
1333 } // end if (found the alias)
1334 } // end for (select expressions)
1336 } // end for (table refs)
1337 } // end if (set the true names)
1340 // e n d i n g l o o p #1
1341 // set the $previous_was_identifier to FALSE if the current
1342 // token is not an identifier
1343 if (($arr[$i]['type'] != 'alpha_identifier')
1344 && ($arr[$i]['type'] != 'quote_double')
1345 && ($arr[$i]['type'] != 'quote_single')
1346 && ($arr[$i]['type'] != 'quote_backtick')) {
1347 $previous_was_identifier = FALSE;
1348 } // end if
1350 // however, if we are on AS, we must keep the $previous_was_identifier
1351 if (($arr[$i]['type'] == 'alpha_reservedWord')
1352 && ($upper_data == 'AS')) {
1353 $previous_was_identifier = TRUE;
1356 if (($arr[$i]['type'] == 'alpha_reservedWord')
1357 && ($upper_data =='ON' || $upper_data =='USING')) {
1358 $save_table_ref = FALSE;
1359 } // end if (data == ON)
1361 if (($arr[$i]['type'] == 'alpha_reservedWord')
1362 && ($upper_data =='JOIN' || $upper_data =='FROM')) {
1363 $save_table_ref = TRUE;
1364 } // end if (data == JOIN)
1367 * no need to check the end of table ref if we already did
1369 * @todo maybe add "&& $seen_from"
1371 if (!$seen_end_of_table_ref) {
1372 // if this is the last token, it implies that we have
1373 // seen the end of table references
1374 // Check for the end of table references
1376 // Note: if we are analyzing a GROUP_CONCAT clause,
1377 // we might find a word that seems to indicate that
1378 // we have found the end of table refs (like ORDER)
1379 // but it's a modifier of the GROUP_CONCAT so
1380 // it's not the real end of table refs
1381 if (($i == $size-1)
1382 || ($arr[$i]['type'] == 'alpha_reservedWord'
1383 && !$in_group_concat
1384 && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
1385 $seen_end_of_table_ref = TRUE;
1386 // to be able to save the last table ref, but do not
1387 // set it true if we found a word like "ON" that has
1388 // already set it to false
1389 if (isset($save_table_ref) && $save_table_ref != FALSE) {
1390 $save_table_ref = TRUE;
1391 } //end if
1393 } // end if (check for end of table ref)
1394 } //end if (!$seen_end_of_table_ref)
1396 if ($seen_end_of_table_ref) {
1397 $save_table_ref = FALSE;
1398 } // end if
1400 } // end for $i (loop #1)
1402 //DEBUG
1404 if (isset($current_select_expr)) {
1405 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1406 echo "<br />";
1407 reset ($subresult['select_expr'][$trace]);
1408 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1409 echo "sel expr $trace $key => $val<br />\n";
1413 if (isset($current_table_ref)) {
1414 echo "current_table_ref = " . $current_table_ref . "<br>";
1415 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1417 echo "<br />";
1418 reset ($subresult['table_ref'][$trace]);
1419 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1420 echo "table ref $trace $key => $val<br />\n";
1424 // -------------------------------------------------------
1427 // loop #2: - queryflags
1428 // - querytype (for queries != 'SELECT')
1429 // - section_before_limit, section_after_limit
1431 // we will also need this queryflag in loop 2
1432 // so set it here
1433 if (isset($current_table_ref) && $current_table_ref > -1) {
1434 $subresult['queryflags']['select_from'] = 1;
1437 $section_before_limit = '';
1438 $section_after_limit = ''; // truly the section after the limit clause
1439 $seen_reserved_word = FALSE;
1440 $seen_group = FALSE;
1441 $seen_order = FALSE;
1442 $seen_order_by = FALSE;
1443 $in_group_by = FALSE; // true when we are inside the GROUP BY clause
1444 $in_order_by = FALSE; // true when we are inside the ORDER BY clause
1445 $in_having = FALSE; // true when we are inside the HAVING clause
1446 $in_select_expr = FALSE; // true when we are inside the select expr clause
1447 $in_where = FALSE; // true when we are inside the WHERE clause
1448 $seen_limit = FALSE; // true if we have seen a LIMIT clause
1449 $in_limit = FALSE; // true when we are inside the LIMIT clause
1450 $after_limit = FALSE; // true when we are after the LIMIT clause
1451 $in_from = FALSE; // true when we are in the FROM clause
1452 $in_group_concat = FALSE;
1453 $first_reserved_word = '';
1454 $current_identifier = '';
1455 $unsorted_query = $arr['raw']; // in case there is no ORDER BY
1457 for ($i = 0; $i < $size; $i++) {
1458 //DEBUG echo "Loop2 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
1460 // need_confirm
1462 // check for reserved words that will have to generate
1463 // a confirmation request later in sql.php
1464 // the cases are:
1465 // DROP TABLE
1466 // DROP DATABASE
1467 // ALTER TABLE... DROP
1468 // DELETE FROM...
1470 // this code is not used for confirmations coming from functions.js
1472 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1473 $upper_data = strtoupper($arr[$i]['data']);
1474 if (!$seen_reserved_word) {
1475 $first_reserved_word = $upper_data;
1476 $subresult['querytype'] = $upper_data;
1477 $seen_reserved_word = TRUE;
1479 // if the first reserved word is DROP or DELETE,
1480 // we know this is a query that needs to be confirmed
1481 if ($first_reserved_word=='DROP'
1482 || $first_reserved_word == 'DELETE'
1483 || $first_reserved_word == 'TRUNCATE') {
1484 $subresult['queryflags']['need_confirm'] = 1;
1487 if ($first_reserved_word=='SELECT'){
1488 $position_of_first_select = $i;
1491 } else {
1492 if ($upper_data == 'DROP' && $first_reserved_word == 'ALTER') {
1493 $subresult['queryflags']['need_confirm'] = 1;
1497 if ($upper_data == 'LIMIT') {
1498 $section_before_limit = substr($arr['raw'], 0, $arr[$i]['pos'] - 5);
1499 $in_limit = TRUE;
1500 $seen_limit = TRUE;
1501 $limit_clause = '';
1502 $in_order_by = FALSE; // @todo maybe others to set FALSE
1505 if ($upper_data == 'PROCEDURE') {
1506 $subresult['queryflags']['procedure'] = 1;
1507 $in_limit = FALSE;
1508 $after_limit = TRUE;
1511 * @todo set also to FALSE if we find FOR UPDATE or LOCK IN SHARE MODE
1513 if ($upper_data == 'SELECT') {
1514 $in_select_expr = TRUE;
1515 $select_expr_clause = '';
1517 if ($upper_data == 'DISTINCT' && !$in_group_concat) {
1518 $subresult['queryflags']['distinct'] = 1;
1521 if ($upper_data == 'UNION') {
1522 $subresult['queryflags']['union'] = 1;
1525 if ($upper_data == 'JOIN') {
1526 $subresult['queryflags']['join'] = 1;
1529 if ($upper_data == 'OFFSET') {
1530 $subresult['queryflags']['offset'] = 1;
1533 // if this is a real SELECT...FROM
1534 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1535 $in_from = TRUE;
1536 $from_clause = '';
1537 $in_select_expr = FALSE;
1541 // (we could have less resetting of variables to FALSE
1542 // if we trust that the query respects the standard
1543 // MySQL order for clauses)
1545 // we use $seen_group and $seen_order because we are looking
1546 // for the BY
1547 if ($upper_data == 'GROUP') {
1548 $seen_group = TRUE;
1549 $seen_order = FALSE;
1550 $in_having = FALSE;
1551 $in_order_by = FALSE;
1552 $in_where = FALSE;
1553 $in_select_expr = FALSE;
1554 $in_from = FALSE;
1556 if ($upper_data == 'ORDER' && !$in_group_concat) {
1557 $seen_order = TRUE;
1558 $seen_group = FALSE;
1559 $in_having = FALSE;
1560 $in_group_by = FALSE;
1561 $in_where = FALSE;
1562 $in_select_expr = FALSE;
1563 $in_from = FALSE;
1565 if ($upper_data == 'HAVING') {
1566 $in_having = TRUE;
1567 $having_clause = '';
1568 $seen_group = FALSE;
1569 $seen_order = FALSE;
1570 $in_group_by = FALSE;
1571 $in_order_by = FALSE;
1572 $in_where = FALSE;
1573 $in_select_expr = FALSE;
1574 $in_from = FALSE;
1577 if ($upper_data == 'WHERE') {
1578 $in_where = TRUE;
1579 $where_clause = '';
1580 $where_clause_identifiers = array();
1581 $seen_group = FALSE;
1582 $seen_order = FALSE;
1583 $in_group_by = FALSE;
1584 $in_order_by = FALSE;
1585 $in_having = FALSE;
1586 $in_select_expr = FALSE;
1587 $in_from = FALSE;
1590 if ($upper_data == 'BY') {
1591 if ($seen_group) {
1592 $in_group_by = TRUE;
1593 $group_by_clause = '';
1595 if ($seen_order) {
1596 $seen_order_by = TRUE;
1597 // here we assume that the ORDER BY keywords took
1598 // exactly 8 characters
1599 $unsorted_query = substr($arr['raw'], 0, $arr[$i]['pos'] - 8);
1600 $in_order_by = TRUE;
1601 $order_by_clause = '';
1605 // if we find one of the words that could end the clause
1606 if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
1608 $in_group_by = FALSE;
1609 $in_order_by = FALSE;
1610 $in_having = FALSE;
1611 $in_where = FALSE;
1612 $in_select_expr = FALSE;
1613 $in_from = FALSE;
1616 } // endif (reservedWord)
1619 // do not add a space after a function name
1621 * @todo can we combine loop 2 and loop 1? some code is repeated here...
1624 $sep = ' ';
1625 if ($arr[$i]['type'] == 'alpha_functionName') {
1626 $sep='';
1627 $upper_data = strtoupper($arr[$i]['data']);
1628 if ($upper_data =='GROUP_CONCAT') {
1629 $in_group_concat = TRUE;
1630 $number_of_brackets_in_group_concat = 0;
1634 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1635 if ($in_group_concat) {
1636 $number_of_brackets_in_group_concat++;
1639 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1640 if ($in_group_concat) {
1641 $number_of_brackets_in_group_concat--;
1642 if ($number_of_brackets_in_group_concat == 0) {
1643 $in_group_concat = FALSE;
1648 // do not add a space after an identifier if followed by a dot
1649 if ($arr[$i]['type'] == 'alpha_identifier' && $i < $size - 1 && $arr[$i + 1]['data'] == '.') {
1650 $sep = '';
1653 // do not add a space after a dot if followed by an identifier
1654 if ($arr[$i]['data'] == '.' && $i < $size - 1 && $arr[$i + 1]['type'] == 'alpha_identifier') {
1655 $sep = '';
1658 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1659 $select_expr_clause .= $arr[$i]['data'] . $sep;
1661 if ($in_from && $upper_data != 'FROM') {
1662 $from_clause .= $arr[$i]['data'] . $sep;
1664 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1665 $group_by_clause .= $arr[$i]['data'] . $sep;
1667 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1668 // add a space only before ASC or DESC
1669 // not around the dot between dbname and tablename
1670 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1671 $order_by_clause .= $sep;
1673 $order_by_clause .= $arr[$i]['data'];
1675 if ($in_having && $upper_data != 'HAVING') {
1676 $having_clause .= $arr[$i]['data'] . $sep;
1678 if ($in_where && $upper_data != 'WHERE') {
1679 $where_clause .= $arr[$i]['data'] . $sep;
1681 if (($arr[$i]['type'] == 'quote_backtick')
1682 || ($arr[$i]['type'] == 'alpha_identifier')) {
1683 $where_clause_identifiers[] = $arr[$i]['data'];
1687 // to grab the rest of the query after the ORDER BY clause
1688 if (isset($subresult['queryflags']['select_from'])
1689 && $subresult['queryflags']['select_from'] == 1
1690 && ! $in_order_by
1691 && $seen_order_by
1692 && $upper_data != 'BY') {
1693 $unsorted_query .= $arr[$i]['data'];
1694 if ($arr[$i]['type'] != 'punct_bracket_open_round'
1695 && $arr[$i]['type'] != 'punct_bracket_close_round'
1696 && $arr[$i]['type'] != 'punct') {
1697 $unsorted_query .= $sep;
1701 if ($in_limit) {
1702 if ($upper_data == 'OFFSET') {
1703 $limit_clause .= $sep;
1705 $limit_clause .= $arr[$i]['data'];
1706 if ($upper_data == 'LIMIT' || $upper_data == 'OFFSET') {
1707 $limit_clause .= $sep;
1710 if ($after_limit && $seen_limit) {
1711 $section_after_limit .= $arr[$i]['data'] . $sep;
1714 // clear $upper_data for next iteration
1715 $upper_data='';
1717 } // end for $i (loop #2)
1718 if (empty($section_before_limit)) {
1719 $section_before_limit = $arr['raw'];
1722 // -----------------------------------------------------
1723 // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
1724 // (for now, check only the first query)
1725 // (for now, identifiers are assumed to be backquoted)
1727 // If we find that we are dealing with a CREATE TABLE query,
1728 // we look for the next punct_bracket_open_round, which
1729 // introduces the fields list. Then, when we find a
1730 // quote_backtick, it must be a field, so we put it into
1731 // the create_table_fields array. Even if this field is
1732 // not a timestamp, it will be useful when logic has been
1733 // added for complete field attributes analysis.
1735 $seen_foreign = FALSE;
1736 $seen_references = FALSE;
1737 $seen_constraint = FALSE;
1738 $foreign_key_number = -1;
1739 $seen_create_table = FALSE;
1740 $seen_create = FALSE;
1741 $in_create_table_fields = FALSE;
1742 $brackets_level = 0;
1743 $in_timestamp_options = FALSE;
1744 $seen_default = FALSE;
1746 for ($i = 0; $i < $size; $i++) {
1747 // DEBUG echo "Loop 3 <b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
1749 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1750 $upper_data = strtoupper($arr[$i]['data']);
1752 if ($upper_data == 'NOT' && $in_timestamp_options) {
1753 $create_table_fields[$current_identifier]['timestamp_not_null'] = TRUE;
1757 if ($upper_data == 'CREATE') {
1758 $seen_create = TRUE;
1761 if ($upper_data == 'TABLE' && $seen_create) {
1762 $seen_create_table = TRUE;
1763 $create_table_fields = array();
1766 if ($upper_data == 'CURRENT_TIMESTAMP') {
1767 if ($in_timestamp_options) {
1768 if ($seen_default) {
1769 $create_table_fields[$current_identifier]['default_current_timestamp'] = TRUE;
1774 if ($upper_data == 'CONSTRAINT') {
1775 $foreign_key_number++;
1776 $seen_foreign = FALSE;
1777 $seen_references = FALSE;
1778 $seen_constraint = TRUE;
1780 if ($upper_data == 'FOREIGN') {
1781 $seen_foreign = TRUE;
1782 $seen_references = FALSE;
1783 $seen_constraint = FALSE;
1785 if ($upper_data == 'REFERENCES') {
1786 $seen_foreign = FALSE;
1787 $seen_references = TRUE;
1788 $seen_constraint = FALSE;
1792 // Cases covered:
1794 // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1795 // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1797 // but we set ['on_delete'] or ['on_cascade'] to
1798 // CASCADE | SET_NULL | NO_ACTION | RESTRICT
1800 // ON UPDATE CURRENT_TIMESTAMP
1802 if ($upper_data == 'ON') {
1803 if (isset($arr[$i+1]) && $arr[$i+1]['type'] == 'alpha_reservedWord') {
1804 $second_upper_data = strtoupper($arr[$i+1]['data']);
1805 if ($second_upper_data == 'DELETE') {
1806 $clause = 'on_delete';
1808 if ($second_upper_data == 'UPDATE') {
1809 $clause = 'on_update';
1811 if (isset($clause)
1812 && ($arr[$i+2]['type'] == 'alpha_reservedWord'
1814 // ugly workaround because currently, NO is not
1815 // in the list of reserved words in sqlparser.data
1816 // (we got a bug report about not being able to use
1817 // 'no' as an identifier)
1818 || ($arr[$i+2]['type'] == 'alpha_identifier'
1819 && strtoupper($arr[$i+2]['data'])=='NO'))
1821 $third_upper_data = strtoupper($arr[$i+2]['data']);
1822 if ($third_upper_data == 'CASCADE'
1823 || $third_upper_data == 'RESTRICT') {
1824 $value = $third_upper_data;
1825 } elseif ($third_upper_data == 'SET'
1826 || $third_upper_data == 'NO') {
1827 if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
1828 $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
1830 } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
1831 if ($clause == 'on_update'
1832 && $in_timestamp_options) {
1833 $create_table_fields[$current_identifier]['on_update_current_timestamp'] = TRUE;
1834 $seen_default = FALSE;
1837 } else {
1838 $value = '';
1840 if (!empty($value)) {
1841 $foreign[$foreign_key_number][$clause] = $value;
1843 unset($clause);
1844 } // endif (isset($clause))
1848 } // end of reserved words analysis
1851 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1852 $brackets_level++;
1853 if ($seen_create_table && $brackets_level == 1) {
1854 $in_create_table_fields = TRUE;
1859 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1860 $brackets_level--;
1861 if ($seen_references) {
1862 $seen_references = FALSE;
1864 if ($seen_create_table && $brackets_level == 0) {
1865 $in_create_table_fields = FALSE;
1869 if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
1870 $upper_data = strtoupper($arr[$i]['data']);
1871 if ($seen_create_table && $in_create_table_fields) {
1872 if ($upper_data == 'DEFAULT') {
1873 $seen_default = TRUE;
1879 * @see @todo 2005-10-16 note: the "or" part here is a workaround for a bug
1881 if (($arr[$i]['type'] == 'alpha_columnType') || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)) {
1882 $upper_data = strtoupper($arr[$i]['data']);
1883 if ($seen_create_table && $in_create_table_fields && isset($current_identifier)) {
1884 $create_table_fields[$current_identifier]['type'] = $upper_data;
1885 if ($upper_data == 'TIMESTAMP') {
1886 $arr[$i]['type'] = 'alpha_columnType';
1887 $in_timestamp_options = TRUE;
1888 } else {
1889 $in_timestamp_options = FALSE;
1890 if ($upper_data == 'CHAR') {
1891 $arr[$i]['type'] = 'alpha_columnType';
1898 if ($arr[$i]['type'] == 'quote_backtick' || $arr[$i]['type'] == 'alpha_identifier') {
1900 if ($arr[$i]['type'] == 'quote_backtick') {
1901 // remove backquotes
1902 $identifier = PMA_unQuote($arr[$i]['data']);
1903 } else {
1904 $identifier = $arr[$i]['data'];
1907 if ($seen_create_table && $in_create_table_fields) {
1908 $current_identifier = $identifier;
1909 // warning: we set this one even for non TIMESTAMP type
1910 $create_table_fields[$current_identifier]['timestamp_not_null'] = FALSE;
1913 if ($seen_constraint) {
1914 $foreign[$foreign_key_number]['constraint'] = $identifier;
1917 if ($seen_foreign && $brackets_level > 0) {
1918 $foreign[$foreign_key_number]['index_list'][] = $identifier;
1921 if ($seen_references) {
1922 // here, the first bracket level corresponds to the
1923 // bracket of CREATE TABLE
1924 // so if we are on level 2, it must be the index list
1925 // of the foreign key REFERENCES
1926 if ($brackets_level > 1) {
1927 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1928 } else {
1929 // for MySQL 4.0.18, identifier is
1930 // `table` or `db`.`table`
1931 // the first pass will pick the db name
1932 // the next pass will execute the else and pick the
1933 // db name in $db_table[0]
1934 if ($arr[$i+1]['type'] == 'punct_qualifier') {
1935 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
1936 } else {
1937 // for MySQL 4.0.16, identifier is
1938 // `table` or `db.table`
1939 $db_table = explode('.', $identifier);
1940 if (isset($db_table[1])) {
1941 $foreign[$foreign_key_number]['ref_db_name'] = $db_table[0];
1942 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[1];
1943 } else {
1944 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[0];
1950 } // end for $i (loop #3)
1953 // Fill the $subresult array
1955 if (isset($create_table_fields)) {
1956 $subresult['create_table_fields'] = $create_table_fields;
1959 if (isset($foreign)) {
1960 $subresult['foreign_keys'] = $foreign;
1963 if (isset($select_expr_clause)) {
1964 $subresult['select_expr_clause'] = $select_expr_clause;
1966 if (isset($from_clause)) {
1967 $subresult['from_clause'] = $from_clause;
1969 if (isset($group_by_clause)) {
1970 $subresult['group_by_clause'] = $group_by_clause;
1972 if (isset($order_by_clause)) {
1973 $subresult['order_by_clause'] = $order_by_clause;
1975 if (isset($having_clause)) {
1976 $subresult['having_clause'] = $having_clause;
1978 if (isset($limit_clause)) {
1979 $subresult['limit_clause'] = $limit_clause;
1981 if (isset($where_clause)) {
1982 $subresult['where_clause'] = $where_clause;
1984 if (isset($unsorted_query) && !empty($unsorted_query)) {
1985 $subresult['unsorted_query'] = $unsorted_query;
1987 if (isset($where_clause_identifiers)) {
1988 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
1991 if (isset($position_of_first_select)) {
1992 $subresult['position_of_first_select'] = $position_of_first_select;
1993 $subresult['section_before_limit'] = $section_before_limit;
1994 $subresult['section_after_limit'] = $section_after_limit;
1997 // They are naughty and didn't have a trailing semi-colon,
1998 // then still handle it properly
1999 if ($subresult['querytype'] != '') {
2000 $result[] = $subresult;
2002 return $result;
2003 } // end of the "PMA_SQP_analyze()" function
2007 * Colorizes SQL queries html formatted
2009 * @todo check why adding a "\n" after the </span> would cause extra blanks
2010 * to be displayed: SELECT p . person_name
2011 * @param array The SQL queries html formatted
2013 * @return array The colorized SQL queries
2015 * @access public
2017 function PMA_SQP_formatHtml_colorize($arr)
2019 $i = $GLOBALS['PMA_strpos']($arr['type'], '_');
2020 $class = '';
2021 if ($i > 0) {
2022 $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
2025 $class .= 'syntax_' . $arr['type'];
2027 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
2028 } // end of the "PMA_SQP_formatHtml_colorize()" function
2032 * Formats SQL queries to html
2034 * @param array The SQL queries
2035 * @param string mode
2036 * @param integer starting token
2037 * @param integer number of tokens to format, -1 = all
2039 * @return string The formatted SQL queries
2041 * @access public
2043 function PMA_SQP_formatHtml($arr, $mode='color', $start_token=0,
2044 $number_of_tokens=-1)
2046 //DEBUG echo 'in Format<pre>'; print_r($arr); echo '</pre>';
2047 // then check for an array
2048 if (!is_array($arr)) {
2049 return htmlspecialchars($arr);
2051 // first check for the SQL parser having hit an error
2052 if (PMA_SQP_isError()) {
2053 return htmlspecialchars($arr['raw']);
2055 // else do it properly
2056 switch ($mode) {
2057 case 'color':
2058 $str = '<span class="syntax">';
2059 $html_line_break = '<br />';
2060 break;
2061 case 'query_only':
2062 $str = '';
2063 $html_line_break = "\n";
2064 break;
2065 case 'text':
2066 $str = '';
2067 $html_line_break = '<br />';
2068 break;
2069 } // end switch
2070 $indent = 0;
2071 $bracketlevel = 0;
2072 $functionlevel = 0;
2073 $infunction = FALSE;
2074 $space_punct_listsep = ' ';
2075 $space_punct_listsep_function_name = ' ';
2076 // $space_alpha_reserved_word = '<br />'."\n";
2077 $space_alpha_reserved_word = ' ';
2079 $keywords_with_brackets_1before = array(
2080 'INDEX',
2081 'KEY',
2082 'ON',
2083 'USING'
2085 $keywords_with_brackets_1before_cnt = 4;
2087 $keywords_with_brackets_2before = array(
2088 'IGNORE',
2089 'INDEX',
2090 'INTO',
2091 'KEY',
2092 'PRIMARY',
2093 'PROCEDURE',
2094 'REFERENCES',
2095 'UNIQUE',
2096 'USE'
2098 // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
2099 $keywords_with_brackets_2before_cnt = 9;
2101 // These reserved words do NOT get a newline placed near them.
2102 $keywords_no_newline = array(
2103 'AS',
2104 'ASC',
2105 'DESC',
2106 'DISTINCT',
2107 'DUPLICATE',
2108 'HOUR',
2109 'INTERVAL',
2110 'IS',
2111 'LIKE',
2112 'NOT',
2113 'NULL',
2114 'ON',
2115 'REGEXP'
2117 $keywords_no_newline_cnt = 12;
2119 // These reserved words introduce a privilege list
2120 $keywords_priv_list = array(
2121 'GRANT',
2122 'REVOKE'
2124 $keywords_priv_list_cnt = 2;
2126 if ($number_of_tokens == -1) {
2127 $arraysize = $arr['len'];
2128 } else {
2129 $arraysize = $number_of_tokens;
2131 $typearr = array();
2132 if ($arraysize >= 0) {
2133 $typearr[0] = '';
2134 $typearr[1] = '';
2135 $typearr[2] = '';
2136 //$typearr[3] = $arr[0]['type'];
2137 $typearr[3] = $arr[$start_token]['type'];
2140 $in_priv_list = FALSE;
2141 for ($i = $start_token; $i < $arraysize; $i++) {
2142 // DEBUG echo "Loop format <b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
2143 $before = '';
2144 $after = '';
2145 $indent = 0;
2146 // array_shift($typearr);
2148 0 prev2
2149 1 prev
2150 2 current
2151 3 next
2153 if (($i + 1) < $arraysize) {
2154 // array_push($typearr, $arr[$i + 1]['type']);
2155 $typearr[4] = $arr[$i + 1]['type'];
2156 } else {
2157 //array_push($typearr, null);
2158 $typearr[4] = '';
2161 for ($j=0; $j<4; $j++) {
2162 $typearr[$j] = $typearr[$j + 1];
2165 switch ($typearr[2]) {
2166 case 'white_newline':
2167 $before = '';
2168 break;
2169 case 'punct_bracket_open_round':
2170 $bracketlevel++;
2171 $infunction = FALSE;
2172 // Make sure this array is sorted!
2173 if (($typearr[1] == 'alpha_functionName') || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
2174 || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex') || ($typearr[3] == 'digit_float')
2175 || (($typearr[0] == 'alpha_reservedWord')
2176 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
2177 || (($typearr[1] == 'alpha_reservedWord')
2178 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
2180 $functionlevel++;
2181 $infunction = TRUE;
2182 $after .= ' ';
2183 } else {
2184 $indent++;
2185 $after .= ($mode != 'query_only' ? '<div class="syntax_indent' . $indent . '">' : ' ');
2187 break;
2188 case 'alpha_identifier':
2189 if (($typearr[1] == 'punct_qualifier') || ($typearr[3] == 'punct_qualifier')) {
2190 $after = '';
2191 $before = '';
2193 if (($typearr[3] == 'alpha_columnType') || ($typearr[3] == 'alpha_identifier')) {
2194 $after .= ' ';
2196 break;
2197 case 'punct_user':
2198 case 'punct_qualifier':
2199 $before = '';
2200 $after = '';
2201 break;
2202 case 'punct_listsep':
2203 if ($infunction == TRUE) {
2204 $after .= $space_punct_listsep_function_name;
2205 } else {
2206 $after .= $space_punct_listsep;
2208 break;
2209 case 'punct_queryend':
2210 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi') && $typearr[3] != 'comment_c') {
2211 $after .= $html_line_break;
2212 $after .= $html_line_break;
2214 $space_punct_listsep = ' ';
2215 $space_punct_listsep_function_name = ' ';
2216 $space_alpha_reserved_word = ' ';
2217 $in_priv_list = FALSE;
2218 break;
2219 case 'comment_mysql':
2220 case 'comment_ansi':
2221 $after .= $html_line_break;
2222 break;
2223 case 'punct':
2224 $before .= ' ';
2225 // workaround for
2226 // select * from mytable limit 0,-1
2227 // (a side effect of this workaround is that
2228 // select 20 - 9
2229 // becomes
2230 // select 20 -9
2231 // )
2232 if ($typearr[3] != 'digit_integer') {
2233 $after .= ' ';
2235 break;
2236 case 'punct_bracket_close_round':
2237 $bracketlevel--;
2238 if ($infunction == TRUE) {
2239 $functionlevel--;
2240 $after .= ' ';
2241 $before .= ' ';
2242 } else {
2243 $indent--;
2244 $before .= ($mode != 'query_only' ? '</div>' : ' ');
2246 $infunction = ($functionlevel > 0) ? TRUE : FALSE;
2247 break;
2248 case 'alpha_columnType':
2249 if ($typearr[3] == 'alpha_columnAttrib') {
2250 $after .= ' ';
2252 if ($typearr[1] == 'alpha_columnType') {
2253 $before .= ' ';
2255 break;
2256 case 'alpha_columnAttrib':
2258 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
2259 // COLLATE LATIN1_GENERAL_CI DEFAULT
2260 if ($typearr[1] == 'alpha_identifier' || $typearr[1] == 'alpha_charset') {
2261 $before .= ' ';
2263 if (($typearr[3] == 'alpha_columnAttrib') || ($typearr[3] == 'quote_single') || ($typearr[3] == 'digit_integer')) {
2264 $after .= ' ';
2266 // workaround for
2267 // AUTO_INCREMENT = 31DEFAULT_CHARSET = utf-8
2269 if ($typearr[2] == 'alpha_columnAttrib' && $typearr[3] == 'alpha_reservedWord') {
2270 $before .= ' ';
2272 // workaround for
2273 // select * from mysql.user where binary user="root"
2274 // binary is marked as alpha_columnAttrib
2275 // but should be marked as a reserved word
2276 if (strtoupper($arr[$i]['data']) == 'BINARY'
2277 && $typearr[3] == 'alpha_identifier') {
2278 $after .= ' ';
2280 break;
2281 case 'alpha_reservedWord':
2282 // do not uppercase the reserved word if we are calling
2283 // this function in query_only mode, because we need
2284 // the original query (otherwise we get problems with
2285 // semi-reserved words like "storage" which is legal
2286 // as an identifier name)
2288 if ($mode != 'query_only') {
2289 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
2292 if ((($typearr[1] != 'alpha_reservedWord')
2293 || (($typearr[1] == 'alpha_reservedWord')
2294 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
2295 && ($typearr[1] != 'punct_level_plus')
2296 && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
2297 // do not put a space before the first token, because
2298 // we use a lot of eregi() checking for the first
2299 // reserved word at beginning of query
2300 // so do not put a newline before
2302 // also we must not be inside a privilege list
2303 if ($i > 0) {
2304 // the alpha_identifier exception is there to
2305 // catch cases like
2306 // GRANT SELECT ON mydb.mytable TO myuser@localhost
2307 // (else, we get mydb.mytableTO)
2309 // the quote_single exception is there to
2310 // catch cases like
2311 // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
2313 * @todo fix all cases and find why this happens
2316 if (!$in_priv_list || $typearr[1] == 'alpha_identifier' || $typearr[1] == 'quote_single' || $typearr[1] == 'white_newline') {
2317 $before .= $space_alpha_reserved_word;
2319 } else {
2320 // on first keyword, check if it introduces a
2321 // privilege list
2322 if (PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_priv_list, $keywords_priv_list_cnt)) {
2323 $in_priv_list = TRUE;
2326 } else {
2327 $before .= ' ';
2330 switch ($arr[$i]['data']) {
2331 case 'CREATE':
2332 if (!$in_priv_list) {
2333 $space_punct_listsep = $html_line_break;
2334 $space_alpha_reserved_word = ' ';
2336 break;
2337 case 'EXPLAIN':
2338 case 'DESCRIBE':
2339 case 'SET':
2340 case 'ALTER':
2341 case 'DELETE':
2342 case 'SHOW':
2343 case 'DROP':
2344 case 'UPDATE':
2345 case 'TRUNCATE':
2346 case 'ANALYZE':
2347 case 'ANALYSE':
2348 if (!$in_priv_list) {
2349 $space_punct_listsep = $html_line_break;
2350 $space_alpha_reserved_word = ' ';
2352 break;
2353 case 'INSERT':
2354 case 'REPLACE':
2355 if (!$in_priv_list) {
2356 $space_punct_listsep = $html_line_break;
2357 $space_alpha_reserved_word = $html_line_break;
2359 break;
2360 case 'VALUES':
2361 $space_punct_listsep = ' ';
2362 $space_alpha_reserved_word = $html_line_break;
2363 break;
2364 case 'SELECT':
2365 $space_punct_listsep = ' ';
2366 $space_alpha_reserved_word = $html_line_break;
2367 break;
2368 default:
2369 break;
2370 } // end switch ($arr[$i]['data'])
2372 $after .= ' ';
2373 break;
2374 case 'digit_integer':
2375 case 'digit_float':
2376 case 'digit_hex':
2378 * @todo could there be other types preceding a digit?
2380 if ($typearr[1] == 'alpha_reservedWord') {
2381 $after .= ' ';
2383 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2384 $after .= ' ';
2386 if ($typearr[1] == 'alpha_columnAttrib') {
2387 $before .= ' ';
2389 break;
2390 case 'alpha_variable':
2391 $after = ' ';
2392 break;
2393 case 'quote_double':
2394 case 'quote_single':
2395 // workaround: for the query
2396 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
2397 // the @ is incorrectly marked as alpha_variable
2398 // in the parser, and here, the '%' gets a blank before,
2399 // which is a syntax error
2400 if ($typearr[1] != 'punct_user') {
2401 $before .= ' ';
2403 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2404 $after .= ' ';
2406 break;
2407 case 'quote_backtick':
2408 // here we check for punct_user to handle correctly
2409 // DEFINER = `username`@`%`
2410 // where @ is the punct_user and `%` is the quote_backtick
2411 if ($typearr[3] != 'punct_qualifier' && $typearr[3] != 'alpha_variable' && $typearr[3] != 'punct_user') {
2412 $after .= ' ';
2414 if ($typearr[1] != 'punct_qualifier' && $typearr[1] != 'alpha_variable' && $typearr[1] != 'punct_user') {
2415 $before .= ' ';
2417 break;
2418 default:
2419 break;
2420 } // end switch ($typearr[2])
2423 if ($typearr[3] != 'punct_qualifier') {
2424 $after .= ' ';
2426 $after .= "\n";
2428 $str .= $before . ($mode=='color' ? PMA_SQP_formatHTML_colorize($arr[$i]) : $arr[$i]['data']). $after;
2429 } // end for
2430 if ($mode=='color') {
2431 $str .= '</span>';
2434 return $str;
2435 } // end of the "PMA_SQP_formatHtml()" function
2439 * Builds a CSS rule used for html formatted SQL queries
2441 * @param string The class name
2442 * @param string The property name
2443 * @param string The property value
2445 * @return string The CSS rule
2447 * @access public
2449 * @see PMA_SQP_buildCssData()
2451 function PMA_SQP_buildCssRule($classname, $property, $value)
2453 $str = '.' . $classname . ' {';
2454 if ($value != '') {
2455 $str .= $property . ': ' . $value . ';';
2457 $str .= '}' . "\n";
2459 return $str;
2460 } // end of the "PMA_SQP_buildCssRule()" function
2464 * Builds CSS rules used for html formatted SQL queries
2466 * @return string The CSS rules set
2468 * @access public
2470 * @global array The current PMA configuration
2472 * @see PMA_SQP_buildCssRule()
2474 function PMA_SQP_buildCssData()
2476 global $cfg;
2478 $css_string = '';
2479 foreach ($cfg['SQP']['fmtColor'] AS $key => $col) {
2480 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
2483 for ($i = 0; $i < 8; $i++) {
2484 $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
2487 return $css_string;
2488 } // end of the "PMA_SQP_buildCssData()" function
2490 if (! defined('PMA_MINIMUM_COMMON')) {
2492 * Gets SQL queries with no format
2494 * @param array The SQL queries list
2496 * @return string The SQL queries with no format
2498 * @access public
2500 function PMA_SQP_formatNone($arr)
2502 $formatted_sql = htmlspecialchars($arr['raw']);
2503 $formatted_sql = preg_replace("@((\015\012)|(\015)|(\012)){3,}@", "\n\n", $formatted_sql);
2505 return $formatted_sql;
2506 } // end of the "PMA_SQP_formatNone()" function
2510 * Gets SQL queries in text format
2512 * @todo WRITE THIS!
2513 * @param array The SQL queries list
2515 * @return string The SQL queries in text format
2517 * @access public
2519 function PMA_SQP_formatText($arr)
2521 return PMA_SQP_formatNone($arr);
2522 } // end of the "PMA_SQP_formatText()" function
2523 } // end if: minimal common.lib needed?