2.9.2-rc1
[phpmyadmin/arisferyanto.git] / libraries / sqlparser.lib.php
blobca98e2baf86123ebd1e32476cc780fe538496622
1 <?php
2 /* $Id$ */
3 // vim: expandtab sw=4 ts=4 sts=4:
5 /** SQL Parser Functions for phpMyAdmin
7 * Copyright 2002 Robin Johnson <robbat2@users.sourceforge.net>
8 * http://www.orbis-terrarum.net/?l=people.robbat2
10 * These functions define an SQL parser system, capable of understanding and
11 * extracting data from a MySQL type SQL query.
13 * The basic procedure for using the new SQL parser:
14 * On any page that needs to extract data from a query or to pretty-print a
15 * query, you need code like this up at the top:
17 * ($sql contains the query)
18 * $parsed_sql = PMA_SQP_parse($sql);
20 * If you want to extract data from it then, you just need to run
21 * $sql_info = PMA_SQP_analyze($parsed_sql);
23 * lem9: See comments in PMA_SQP_analyze for the returned info
24 * from the analyzer.
26 * If you want a pretty-printed version of the query, do:
27 * $string = PMA_SQP_formatHtml($parsed_sql);
28 * (note that that you need to have syntax.css.php included somehow in your
29 * page for it to work, I recommend '<link rel="stylesheet" type="text/css"
30 * href="syntax.css.php" />' at the moment.)
34 /**
35 * Minimum inclusion? (i.e. for the stylesheet builder)
37 if ( ! defined( 'PMA_MINIMUM_COMMON' ) ) {
38 /**
39 * Include the string library as we use it heavily
41 require_once('./libraries/string.lib.php');
43 /**
44 * Include data for the SQL Parser
46 require_once('./libraries/sqlparser.data.php');
47 require_once('./libraries/mysql_charsets.lib.php');
48 if (!isset($mysql_charsets)) {
49 $mysql_charsets = array();
50 $mysql_charsets_count = 0;
51 $mysql_collations_flat = array();
52 $mysql_collations_count = 0;
55 if (!defined('DEBUG_TIMING')) {
56 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize)
58 $arr[] = array('type' => $type, 'data' => $data);
59 $arrsize++;
60 } // end of the "PMA_SQP_arrayAdd()" function
61 } else {
62 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize)
64 global $timer;
66 $t = $timer;
67 $arr[] = array('type' => $type, 'data' => $data, 'time' => $t);
68 $timer = microtime();
69 $arrsize++;
70 } // end of the "PMA_SQP_arrayAdd()" function
71 } // end if... else...
74 /**
75 * Reset the error variable for the SQL parser
77 * @access public
79 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
80 function PMA_SQP_resetError()
82 global $SQP_errorString;
83 $SQP_errorString = '';
84 unset($SQP_errorString);
87 /**
88 * Get the contents of the error variable for the SQL parser
90 * @return string Error string from SQL parser
92 * @access public
94 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
95 function PMA_SQP_getErrorString()
97 global $SQP_errorString;
98 return isset($SQP_errorString) ? $SQP_errorString : '';
102 * Check if the SQL parser hit an error
104 * @return boolean error state
106 * @access public
108 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
109 function PMA_SQP_isError()
111 global $SQP_errorString;
112 return isset($SQP_errorString) && !empty($SQP_errorString);
116 * Set an error message for the system
118 * @param string The error message
119 * @param string The failing SQL query
121 * @access private
122 * @scope SQL Parser internal
124 // Revised, Robbat2 - 13 Janurary 2003, 2:59PM
125 function PMA_SQP_throwError($message, $sql)
128 global $SQP_errorString;
129 $SQP_errorString = '<p>'.$GLOBALS['strSQLParserUserError'] . '</p>' . "\n"
130 . '<pre>' . "\n"
131 . 'ERROR: ' . $message . "\n"
132 . 'SQL: ' . htmlspecialchars($sql) . "\n"
133 . '</pre>' . "\n";
135 } // end of the "PMA_SQP_throwError()" function
139 * Do display the bug report
141 * @param string The error message
142 * @param string The failing SQL query
144 * @access public
146 function PMA_SQP_bug($message, $sql)
148 global $SQP_errorString;
149 $debugstr = 'ERROR: ' . $message . "\n";
150 $debugstr .= 'CVS: $Id$' . "\n";
151 $debugstr .= 'MySQL: '.PMA_MYSQL_STR_VERSION . "\n";
152 $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS . ' ' . PMA_USR_BROWSER_AGENT . ' ' . PMA_USR_BROWSER_VER . "\n";
153 $debugstr .= 'PMA: ' . PMA_VERSION . "\n";
154 $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION . ' ' . PHP_OS . "\n";
155 $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
156 $debugstr .= 'SQL: ' . htmlspecialchars($sql);
158 $encodedstr = $debugstr;
159 if (@function_exists('gzcompress')) {
160 $encodedstr = gzcompress($debugstr, 9);
162 $encodedstr = preg_replace("/(\015\012)|(\015)|(\012)/", '<br />' . "\n", chunk_split(base64_encode($encodedstr)));
164 $SQP_errorString .= $GLOBALS['strSQLParserBugMessage'] . '<br />' . "\n"
165 . '----' . $GLOBALS['strBeginCut'] . '----' . '<br />' . "\n"
166 . $encodedstr . "\n"
167 . '----' . $GLOBALS['strEndCut'] . '----' . '<br />' . "\n";
169 $SQP_errorString .= '----' . $GLOBALS['strBeginRaw'] . '----<br />' . "\n"
170 . '<pre>' . "\n"
171 . $debugstr
172 . '</pre>' . "\n"
173 . '----' . $GLOBALS['strEndRaw'] . '----<br />' . "\n";
175 } // end of the "PMA_SQP_bug()" function
179 * Parses the SQL queries
181 * @param string The SQL query list
183 * @return mixed Most of times, nothing...
185 * @global array The current PMA configuration
186 * @global array MySQL column attributes
187 * @global array MySQL reserved words
188 * @global array MySQL column types
189 * @global array MySQL function names
190 * @global integer MySQL column attributes count
191 * @global integer MySQL reserved words count
192 * @global integer MySQL column types count
193 * @global integer MySQL function names count
194 * @global array List of available character sets
195 * @global array List of available collations
196 * @global integer Character sets count
197 * @global integer Collations count
199 * @access public
201 function PMA_SQP_parse($sql)
203 global $cfg;
204 global $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word, $PMA_SQPdata_column_type, $PMA_SQPdata_function_name,
205 $PMA_SQPdata_column_attrib_cnt, $PMA_SQPdata_reserved_word_cnt, $PMA_SQPdata_column_type_cnt, $PMA_SQPdata_function_name_cnt;
206 global $mysql_charsets, $mysql_collations_flat, $mysql_charsets_count, $mysql_collations_count;
207 global $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt;
209 // rabus: Convert all line feeds to Unix style
210 $sql = str_replace("\r\n", "\n", $sql);
211 $sql = str_replace("\r", "\n", $sql);
213 $len = PMA_strlen($sql);
214 if ($len == 0) {
215 return array();
218 $sql_array = array();
219 $sql_array['raw'] = $sql;
220 $count1 = 0;
221 $count2 = 0;
222 $punct_queryend = ';';
223 $punct_qualifier = '.';
224 $punct_listsep = ',';
225 $punct_level_plus = '(';
226 $punct_level_minus = ')';
227 $digit_floatdecimal = '.';
228 $digit_hexset = 'x';
229 $bracket_list = '()[]{}';
230 $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
231 $allpunct_list_pair = array (
232 0 => '!=',
233 1 => '&&',
234 2 => ':=',
235 3 => '<<',
236 4 => '<=',
237 5 => '<=>',
238 6 => '<>',
239 7 => '>=',
240 8 => '>>',
241 9 => '||'
243 $allpunct_list_pair_size = 10; //count($allpunct_list_pair);
244 $quote_list = '\'"`';
245 $arraysize = 0;
247 while ($count2 < $len) {
248 $c = PMA_substr($sql, $count2, 1);
249 $count1 = $count2;
251 if (($c == "\n")) {
252 $count2++;
253 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
254 continue;
257 // Checks for white space
258 if (PMA_STR_isSpace($c)) {
259 $count2++;
260 continue;
263 // Checks for comment lines.
264 // MySQL style #
265 // C style /* */
266 // ANSI style --
267 if (($c == '#')
268 || (($count2 + 1 < $len) && ($c == '/') && (PMA_substr($sql, $count2 + 1, 1) == '*'))
269 || (($count2 + 2 == $len) && ($c == '-') && (PMA_substr($sql, $count2 + 1, 1) == '-'))
270 || (($count2 + 2 < $len) && ($c == '-') && (PMA_substr($sql, $count2 + 1, 1) == '-') && ((PMA_substr($sql, $count2 + 2, 1) <= ' ')))) {
271 $count2++;
272 $pos = 0;
273 $type = 'bad';
274 switch ($c) {
275 case '#':
276 $type = 'mysql';
277 case '-':
278 $type = 'ansi';
279 $pos = $GLOBALS['PMA_strpos']($sql, "\n", $count2);
280 break;
281 case '/':
282 $type = 'c';
283 $pos = $GLOBALS['PMA_strpos']($sql, '*/', $count2);
284 $pos += 2;
285 break;
286 default:
287 break;
288 } // end switch
289 $count2 = ($pos < $count2) ? $len : $pos;
290 $str = PMA_substr($sql, $count1, $count2 - $count1);
291 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
292 continue;
293 } // end if
295 // Checks for something inside quotation marks
296 if (PMA_STR_strInStr($c, $quote_list)) {
297 $startquotepos = $count2;
298 $quotetype = $c;
299 $count2++;
300 $escaped = FALSE;
301 $escaped_escaped = FALSE;
302 $pos = $count2;
303 $oldpos = 0;
304 do {
305 $oldpos = $pos;
306 $pos = $GLOBALS['PMA_strpos'](' ' . $sql, $quotetype, $oldpos + 1) - 1;
307 // ($pos === FALSE)
308 if ($pos < 0) {
309 $debugstr = $GLOBALS['strSQPBugUnclosedQuote'] . ' @ ' . $startquotepos. "\n"
310 . 'STR: ' . htmlspecialchars($quotetype);
311 PMA_SQP_throwError($debugstr, $sql);
312 return $sql;
315 // If the quote is the first character, it can't be
316 // escaped, so don't do the rest of the code
317 if ($pos == 0) {
318 break;
321 // Checks for MySQL escaping using a \
322 // And checks for ANSI escaping using the $quotetype character
323 if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos)) {
324 $pos ++;
325 continue;
326 } elseif (($pos + 1 < $len) && (PMA_substr($sql, $pos, 1) == $quotetype) && (PMA_substr($sql, $pos + 1, 1) == $quotetype)) {
327 $pos = $pos + 2;
328 continue;
329 } else {
330 break;
332 } while ($len > $pos); // end do
334 $count2 = $pos;
335 $count2++;
336 $type = 'quote_';
337 switch ($quotetype) {
338 case '\'':
339 $type .= 'single';
340 break;
341 case '"':
342 $type .= 'double';
343 break;
344 case '`':
345 $type .= 'backtick';
346 break;
347 default:
348 break;
349 } // end switch
350 $data = PMA_substr($sql, $count1, $count2 - $count1);
351 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
352 continue;
355 // Checks for brackets
356 if (PMA_STR_strInStr($c, $bracket_list)) {
357 // All bracket tokens are only one item long
358 $count2++;
359 $type_type = '';
360 if (PMA_STR_strInStr($c, '([{')) {
361 $type_type = 'open';
362 } else {
363 $type_type = 'close';
366 $type_style = '';
367 if (PMA_STR_strInStr($c, '()')) {
368 $type_style = 'round';
369 } elseif (PMA_STR_strInStr($c, '[]')) {
370 $type_style = 'square';
371 } else {
372 $type_style = 'curly';
375 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
376 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
377 continue;
380 // Checks for identifier (alpha or numeric)
381 if (PMA_STR_isSqlIdentifier($c, FALSE) || ($c == '@') || ($c == '.' && PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1)))) {
382 $count2 ++;
384 //TODO: a @ can also be present in expressions like
385 // FROM 'user'@'%'
386 // or TO 'user'@'%'
387 // in this case, the @ is wrongly marked as alpha_variable
389 $is_sql_variable = ($c == '@');
390 $is_digit = (!$is_sql_variable) && PMA_STR_isDigit($c);
391 $is_hex_digit = ($is_digit) && ($c == '.') && ($c == '0') && ($count2 < $len) && (PMA_substr($sql, $count2, 1) == 'x');
392 $is_float_digit = $c == '.';
393 $is_float_digit_exponent = FALSE;
395 // Nijel: Fast skip is especially needed for huge BLOB data, requires PHP at least 4.3.0:
396 if (PMA_PHP_INT_VERSION >= 40300) {
397 if ($is_hex_digit) {
398 $count2++;
399 $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
400 if ($pos > $count2) {
401 $count2 = $pos;
403 unset($pos);
404 } elseif ($is_digit) {
405 $pos = strspn($sql, '0123456789', $count2);
406 if ($pos > $count2) {
407 $count2 = $pos;
409 unset($pos);
413 while (($count2 < $len) && PMA_STR_isSqlIdentifier(PMA_substr($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
414 $c2 = PMA_substr($sql, $count2, 1);
415 if ($is_sql_variable && ($c2 == '.')) {
416 $count2++;
417 continue;
419 if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
420 $count2++;
421 if (!$is_float_digit) {
422 $is_float_digit = TRUE;
423 continue;
424 } else {
425 $debugstr = $GLOBALS['strSQPBugInvalidIdentifer'] . ' @ ' . ($count1+1) . "\n"
426 . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
427 PMA_SQP_throwError($debugstr, $sql);
428 return $sql;
431 if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
432 if (!$is_float_digit_exponent) {
433 $is_float_digit_exponent = TRUE;
434 $is_float_digit = TRUE;
435 $count2++;
436 continue;
437 } else {
438 $is_digit = FALSE;
439 $is_float_digit = FALSE;
442 if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && PMA_STR_isDigit($c2))) {
443 $count2++;
444 continue;
445 } else {
446 $is_digit = FALSE;
447 $is_hex_digit = FALSE;
450 $count2++;
451 } // end while
453 $l = $count2 - $count1;
454 $str = PMA_substr($sql, $count1, $l);
456 $type = '';
457 if ($is_digit) {
458 $type = 'digit';
459 if ($is_float_digit) {
460 $type .= '_float';
461 } elseif ($is_hex_digit) {
462 $type .= '_hex';
463 } else {
464 $type .= '_integer';
466 } else {
467 if ($is_sql_variable != FALSE) {
468 $type = 'alpha_variable';
469 } else {
470 $type = 'alpha';
472 } // end if... else....
473 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize);
475 continue;
478 // Checks for punct
479 if (PMA_STR_strInStr($c, $allpunct_list)) {
480 while (($count2 < $len) && PMA_STR_strInStr(PMA_substr($sql, $count2, 1), $allpunct_list)) {
481 $count2++;
483 $l = $count2 - $count1;
484 if ($l == 1) {
485 $punct_data = $c;
486 } else {
487 $punct_data = PMA_substr($sql, $count1, $l);
490 // Special case, sometimes, althought two characters are
491 // adjectent directly, they ACTUALLY need to be seperate
492 if ($l == 1) {
493 $t_suffix = '';
494 switch ($punct_data) {
495 case $punct_queryend:
496 $t_suffix = '_queryend';
497 break;
498 case $punct_qualifier:
499 $t_suffix = '_qualifier';
500 break;
501 case $punct_listsep:
502 $t_suffix = '_listsep';
503 break;
504 default:
505 break;
507 PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
508 } elseif (PMA_STR_binarySearchInArr($punct_data, $allpunct_list_pair, $allpunct_list_pair_size)) {
509 // Ok, we have one of the valid combined punct expressions
510 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
511 } else {
512 // Bad luck, lets split it up more
513 $first = $punct_data[0];
514 $first2 = $punct_data[0] . $punct_data[1];
515 $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
516 $last = $punct_data[$l - 1];
517 if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
518 $count2 = $count1 + 1;
519 $punct_data = $first;
520 } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || PMA_substr($sql, $count2, 1) <= ' ') )) {
521 $count2 -= 2;
522 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
523 } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
524 $count2--;
525 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
526 // TODO: for negation operator, split in 2 tokens ?
527 // "select x&~1 from t"
528 // becomes "select x & ~ 1 from t" ?
530 } elseif ($last != '~') {
531 $debugstr = $GLOBALS['strSQPBugUnknownPunctuation'] . ' @ ' . ($count1+1) . "\n"
532 . 'STR: ' . htmlspecialchars($punct_data);
533 PMA_SQP_throwError($debugstr, $sql);
534 return $sql;
536 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
537 continue;
538 } // end if... elseif... else
539 continue;
542 // DEBUG
543 $count2++;
545 $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
546 . 'STR: ' . PMA_substr($sql, $count1, $count2 - $count1) . "\n";
547 PMA_SQP_bug($debugstr, $sql);
548 return $sql;
550 } // end while ($count2 < $len)
553 if ($arraysize > 0) {
554 $t_next = $sql_array[0]['type'];
555 $t_prev = '';
556 $t_bef_prev = '';
557 $t_cur = '';
558 $d_next = $sql_array[0]['data'];
559 $d_prev = '';
560 $d_bef_prev = '';
561 $d_cur = '';
562 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
563 $d_prev_upper = '';
564 $d_bef_prev_upper = '';
565 $d_cur_upper = '';
568 for ($i = 0; $i < $arraysize; $i++) {
569 $t_bef_prev = $t_prev;
570 $t_prev = $t_cur;
571 $t_cur = $t_next;
572 $d_bef_prev = $d_prev;
573 $d_prev = $d_cur;
574 $d_cur = $d_next;
575 $d_bef_prev_upper = $d_prev_upper;
576 $d_prev_upper = $d_cur_upper;
577 $d_cur_upper = $d_next_upper;
578 if (($i + 1) < $arraysize) {
579 $t_next = $sql_array[$i + 1]['type'];
580 $d_next = $sql_array[$i + 1]['data'];
581 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
582 } else {
583 $t_next = '';
584 $d_next = '';
585 $d_next_upper = '';
588 //DEBUG echo "[prev: <b>".$d_prev."</b> ".$t_prev."][cur: <b>".$d_cur."</b> ".$t_cur."][next: <b>".$d_next."</b> ".$t_next."]<br />";
590 if ($t_cur == 'alpha') {
591 $t_suffix = '_identifier';
592 if (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
593 $t_suffix = '_identifier';
594 } elseif (($t_next == 'punct_bracket_open_round')
595 && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_function_name, $PMA_SQPdata_function_name_cnt)) {
596 // FIXME-2005-10-16: in the case of a CREATE TABLE containing a TIMESTAMP,
597 // since TIMESTAMP() is also a function, it's found here and
598 // the token is wrongly marked as alpha_functionName. But we
599 // compensate for this when analysing for timestamp_not_null
600 // later in this script.
601 $t_suffix = '_functionName';
602 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt)) {
603 $t_suffix = '_columnType';
605 // Temporary fix for BUG #621357
606 //TODO FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
607 if ($d_cur_upper == 'SET' && $t_next != 'punct_bracket_open_round') {
608 $t_suffix = '_reservedWord';
610 //END OF TEMPORARY FIX
612 // CHARACTER is a synonym for CHAR, but can also be meant as
613 // CHARACTER SET. In this case, we have a reserved word.
614 if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
615 $t_suffix = '_reservedWord';
618 // experimental
619 // current is a column type, so previous must not be
620 // a reserved word but an identifier
621 // CREATE TABLE SG_Persons (first varchar(64))
623 //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
624 // $sql_array[$i-1]['type'] = 'alpha_identifier';
627 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_reserved_word, $PMA_SQPdata_reserved_word_cnt)) {
628 $t_suffix = '_reservedWord';
629 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_attrib, $PMA_SQPdata_column_attrib_cnt)) {
630 $t_suffix = '_columnAttrib';
631 // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
632 // it should be regarded as a reserved word.
633 if ($d_cur_upper == 'INNODB' && $d_prev_upper == 'SHOW' && $d_next_upper == 'STATUS') {
634 $t_suffix = '_reservedWord';
637 if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
638 $t_suffix = '_reservedWord';
640 // Binary as character set
641 if ($d_cur_upper == 'BINARY' && (
642 ($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
643 || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
644 || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
645 || $d_prev_upper == 'CHARSET'
646 ) && PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, count($mysql_charsets))) {
647 $t_suffix = '_charset';
649 } elseif (PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, $mysql_charsets_count)
650 || PMA_STR_binarySearchInArr($d_cur, $mysql_collations_flat, $mysql_collations_count)
651 || ($d_cur{0} == '_' && PMA_STR_binarySearchInArr(substr($d_cur, 1), $mysql_charsets, $mysql_charsets_count))) {
652 $t_suffix = '_charset';
653 } else {
654 // Do nothing
656 // check if present in the list of forbidden words
657 if ($t_suffix == '_reservedWord' && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt)) {
658 $sql_array[$i]['forbidden'] = TRUE;
659 } else {
660 $sql_array[$i]['forbidden'] = FALSE;
662 $sql_array[$i]['type'] .= $t_suffix;
664 } // end for
666 // Stores the size of the array inside the array, as count() is a slow
667 // operation.
668 $sql_array['len'] = $arraysize;
670 // Sends the data back
671 return $sql_array;
672 } // end of the "PMA_SQP_parse()" function
675 * Checks for token types being what we want...
677 * @param string String of type that we have
678 * @param string String of type that we want
680 * @return boolean result of check
682 * @access private
684 function PMA_SQP_typeCheck($toCheck, $whatWeWant)
686 $typeSeperator = '_';
687 if (strcmp($whatWeWant, $toCheck) == 0) {
688 return TRUE;
689 } else {
690 if (strpos($whatWeWant, $typeSeperator) === FALSE) {
691 return strncmp($whatWeWant, $toCheck, strpos($toCheck, $typeSeperator)) == 0;
692 } else {
693 return FALSE;
700 * Analyzes SQL queries
702 * @param array The SQL queries
704 * @return array The analyzed SQL queries
706 * @access public
708 function PMA_SQP_analyze($arr)
710 if ($arr == array()) {
711 return array();
713 $result = array();
714 $size = $arr['len'];
715 $subresult = array(
716 'querytype' => '',
717 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
718 'position_of_first_select' => '', // the array index
719 'from_clause'=> '',
720 'group_by_clause'=> '',
721 'order_by_clause'=> '',
722 'having_clause' => '',
723 'where_clause' => '',
724 'where_clause_identifiers' => array(),
725 'unsorted_query' => '',
726 'queryflags' => array(),
727 'select_expr' => array(),
728 'table_ref' => array(),
729 'foreign_keys' => array(),
730 'create_table_fields' => array()
732 $subresult_empty = $subresult;
733 $seek_queryend = FALSE;
734 $seen_end_of_table_ref = FALSE;
735 $number_of_brackets_in_extract = 0;
736 $number_of_brackets_in_group_concat = 0;
738 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
739 // we must not use CURDATE as a table_ref
740 // so we track wether we are in the EXTRACT()
741 $in_extract = FALSE;
743 // for GROUP_CONCAT( ... )
744 $in_group_concat = FALSE;
746 /* Description of analyzer results by lem9
748 * db, table, column, alias
749 * ------------------------
751 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
753 * The SELECT syntax (simplified) is
755 * SELECT
756 * select_expression,...
757 * [FROM [table_references]
760 * ['select_expr'] is filled with each expression, the key represents the
761 * expression position in the list (0-based) (so we don't lose track of
762 * multiple occurences of the same column).
764 * ['table_ref'] is filled with each table ref, same thing for the key.
766 * I create all sub-values empty, even if they are
767 * not present (for example no select_expression alias).
769 * There is a debug section at the end of loop #1, if you want to
770 * see the exact contents of select_expr and table_ref
772 * queryflags
773 * ----------
775 * In $subresult, array 'queryflags' is filled, according to what we
776 * find in the query.
778 * Currently, those are generated:
780 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
781 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
782 * ['queryflags']['distinct'] = 1; for a DISTINCT
783 * ['queryflags']['union'] = 1; for a UNION
784 * ['queryflags']['join'] = 1; for a JOIN
785 * ['queryflags']['offset'] = 1; for the presence of OFFSET
787 * query clauses
788 * -------------
790 * The select is splitted in those clauses:
791 * ['select_expr_clause']
792 * ['from_clause']
793 * ['group_by_clause']
794 * ['order_by_clause']
795 * ['having_clause']
796 * ['where_clause']
798 * The identifiers of the WHERE clause are put into the array
799 * ['where_clause_identifier']
801 * For a SELECT, the whole query without the ORDER BY clause is put into
802 * ['unsorted_query']
804 * foreign keys
805 * ------------
806 * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
807 * analyzed and ['foreign_keys'] is an array filled with
808 * the constraint name, the index list,
809 * the REFERENCES table name and REFERENCES index list,
810 * and ON UPDATE | ON DELETE clauses
812 * position_of_first_select
813 * ------------------------
815 * The array index of the first SELECT we find. Will be used to
816 * insert a SQL_CALC_FOUND_ROWS.
818 * create_table_fields
819 * -------------------
821 * For now, mostly used to detect the DEFAULT CURRENT_TIMESTAMP and
822 * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
823 * An array, each element is the identifier name.
824 * Note that for now, the timestamp_not_null element is created
825 * even for non-TIMESTAMP fields.
827 * Sub-elements: ['type'] which contains the column type
828 * optional (currently they are never false but can be absent):
829 * ['default_current_timestamp'] boolean
830 * ['on_update_current_timestamp'] boolean
831 * ['timestamp_not_null'] boolean
833 * section_before_limit, section_after_limit
834 * -----------------------------------------
836 * Marks the point of the query where we can insert a LIMIT clause;
837 * so the section_before_limit will contain the left part before
838 * a possible LIMIT clause
841 * End of description of analyzer results
844 // must be sorted
845 // TODO: current logic checks for only one word, so I put only the
846 // first word of the reserved expressions that end a table ref;
847 // maybe this is not ok (the first word might mean something else)
848 // $words_ending_table_ref = array(
849 // 'FOR UPDATE',
850 // 'GROUP BY',
851 // 'HAVING',
852 // 'LIMIT',
853 // 'LOCK IN SHARE MODE',
854 // 'ORDER BY',
855 // 'PROCEDURE',
856 // 'UNION',
857 // 'WHERE'
858 // );
859 $words_ending_table_ref = array(
860 'FOR',
861 'GROUP',
862 'HAVING',
863 'LIMIT',
864 'LOCK',
865 'ORDER',
866 'PROCEDURE',
867 'UNION',
868 'WHERE'
870 $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
872 $words_ending_clauses = array(
873 'FOR',
874 'LIMIT',
875 'LOCK',
876 'PROCEDURE',
877 'UNION'
879 $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
884 // must be sorted
885 $supported_query_types = array(
886 'SELECT'
888 // Support for these additional query types will come later on.
889 'DELETE',
890 'INSERT',
891 'REPLACE',
892 'TRUNCATE',
893 'UPDATE'
894 'EXPLAIN',
895 'DESCRIBE',
896 'SHOW',
897 'CREATE',
898 'SET',
899 'ALTER'
902 $supported_query_types_cnt = count($supported_query_types);
904 // loop #1 for each token: select_expr, table_ref for SELECT
906 for ($i = 0; $i < $size; $i++) {
907 //DEBUG echo "trace loop1 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
909 // High speed seek for locating the end of the current query
910 if ($seek_queryend == TRUE) {
911 if ($arr[$i]['type'] == 'punct_queryend') {
912 $seek_queryend = FALSE;
913 } else {
914 continue;
915 } // end if (type == punct_queryend)
916 } // end if ($seek_queryend)
918 // TODO: when we find a UNION, should we split
919 // in another subresult?
920 // Note: do not split if this is a punct_queryend for the
921 // first and only query
922 if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
923 $result[] = $subresult;
924 $subresult = $subresult_empty;
925 continue;
926 } // end if (type == punct_queryend)
928 // ==============================================================
929 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
930 if ($in_extract) {
931 $number_of_brackets_in_extract++;
933 if ($in_group_concat) {
934 $number_of_brackets_in_group_concat++;
937 // ==============================================================
938 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
939 if ($in_extract) {
940 $number_of_brackets_in_extract--;
941 if ($number_of_brackets_in_extract == 0) {
942 $in_extract = FALSE;
945 if ($in_group_concat) {
946 $number_of_brackets_in_group_concat--;
947 if ($number_of_brackets_in_group_concat == 0) {
948 $in_group_concat = FALSE;
952 // ==============================================================
953 if ($arr[$i]['type'] == 'alpha_functionName') {
954 $upper_data = strtoupper($arr[$i]['data']);
955 if ($upper_data =='EXTRACT') {
956 $in_extract = TRUE;
957 $number_of_brackets_in_extract = 0;
959 if ($upper_data =='GROUP_CONCAT') {
960 $in_group_concat = TRUE;
961 $number_of_brackets_in_group_concat = 0;
965 // ==============================================================
966 if ($arr[$i]['type'] == 'alpha_reservedWord'
967 // && $arr[$i]['forbidden'] == FALSE) {
969 // We don't know what type of query yet, so run this
970 if ($subresult['querytype'] == '') {
971 $subresult['querytype'] = strtoupper($arr[$i]['data']);
972 } // end if (querytype was empty)
974 // Check if we support this type of query
975 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
976 // Skip ahead to the next one if we don't
977 $seek_queryend = TRUE;
978 continue;
979 } // end if (query not supported)
981 // upper once
982 $upper_data = strtoupper($arr[$i]['data']);
983 //TODO: reset for each query?
985 if ($upper_data == 'SELECT') {
986 $seen_from = FALSE;
987 $previous_was_identifier = FALSE;
988 $current_select_expr = -1;
989 $seen_end_of_table_ref = FALSE;
990 } // end if ( data == SELECT)
992 if ($upper_data =='FROM' && !$in_extract) {
993 $current_table_ref = -1;
994 $seen_from = TRUE;
995 $previous_was_identifier = FALSE;
996 $save_table_ref = TRUE;
997 } // end if (data == FROM)
999 // here, do not 'continue' the loop, as we have more work for
1000 // reserved words below
1001 } // end if (type == alpha_reservedWord)
1003 // ==============================
1004 if ($arr[$i]['type'] == 'quote_backtick'
1005 || $arr[$i]['type'] == 'quote_double'
1006 || $arr[$i]['type'] == 'quote_single'
1007 || $arr[$i]['type'] == 'alpha_identifier'
1008 || ($arr[$i]['type'] == 'alpha_reservedWord'
1009 && $arr[$i]['forbidden'] == FALSE)) {
1011 switch ($arr[$i]['type']) {
1012 case 'alpha_identifier':
1013 case 'alpha_reservedWord':
1014 // this is not a real reservedWord, because
1015 // it's not present in the list of forbidden words,
1016 // for example "storage" which can be used as
1017 // an identifier
1019 // TODO: avoid the pretty printing in color
1020 // in this case
1022 $identifier = $arr[$i]['data'];
1023 break;
1025 case 'quote_backtick':
1026 case 'quote_double':
1027 case 'quote_single':
1028 $identifier = PMA_unQuote($arr[$i]['data']);
1029 break;
1030 } // end switch
1032 if ($subresult['querytype'] == 'SELECT' && !$in_group_concat) {
1033 if (!$seen_from) {
1034 if ($previous_was_identifier && isset($chain)) {
1035 // found alias for this select_expr, save it
1036 // but only if we got something in $chain
1037 // (for example, SELECT COUNT(*) AS cnt
1038 // puts nothing in $chain, so we avoid
1039 // setting the alias)
1040 $alias_for_select_expr = $identifier;
1041 } else {
1042 $chain[] = $identifier;
1043 $previous_was_identifier = TRUE;
1045 } // end if !$previous_was_identifier
1046 } else {
1047 // ($seen_from)
1048 if ($save_table_ref && !$seen_end_of_table_ref) {
1049 if ($previous_was_identifier) {
1050 // found alias for table ref
1051 // save it for later
1052 $alias_for_table_ref = $identifier;
1053 } else {
1054 $chain[] = $identifier;
1055 $previous_was_identifier = TRUE;
1057 } // end if ($previous_was_identifier)
1058 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
1059 } // end if (!$seen_from)
1060 } // end if (querytype SELECT)
1061 } // end if ( quote_backtick or double quote or alpha_identifier)
1063 // ===================================
1064 if ($arr[$i]['type'] == 'punct_qualifier') {
1065 // to be able to detect an identifier following another
1066 $previous_was_identifier = FALSE;
1067 continue;
1068 } // end if (punct_qualifier)
1070 // TODO: check if 3 identifiers following one another -> error
1072 // s a v e a s e l e c t e x p r
1073 // finding a list separator or FROM
1074 // means that we must save the current chain of identifiers
1075 // into a select expression
1077 // for now, we only save a select expression if it contains
1078 // at least one identifier, as we are interested in checking
1079 // the columns and table names, so in "select * from persons",
1080 // the "*" is not saved
1082 if (isset($chain) && !$seen_end_of_table_ref
1083 && ( (!$seen_from
1084 && $arr[$i]['type'] == 'punct_listsep')
1085 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM')) ) {
1086 $size_chain = count($chain);
1087 $current_select_expr++;
1088 $subresult['select_expr'][$current_select_expr] = array(
1089 'expr' => '',
1090 'alias' => '',
1091 'db' => '',
1092 'table_name' => '',
1093 'table_true_name' => '',
1094 'column' => ''
1097 if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
1098 // we had found an alias for this select expression
1099 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
1100 unset($alias_for_select_expr);
1102 // there is at least a column
1103 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
1104 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
1106 // maybe a table
1107 if ($size_chain > 1) {
1108 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
1109 // we assume for now that this is also the true name
1110 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
1111 $subresult['select_expr'][$current_select_expr]['expr']
1112 = $subresult['select_expr'][$current_select_expr]['table_name']
1113 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1114 } // end if ($size_chain > 1)
1116 // maybe a db
1117 if ($size_chain > 2) {
1118 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
1119 $subresult['select_expr'][$current_select_expr]['expr']
1120 = $subresult['select_expr'][$current_select_expr]['db']
1121 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1122 } // end if ($size_chain > 2)
1123 unset($chain);
1125 // TODO: explain this:
1126 if (($arr[$i]['type'] == 'alpha_reservedWord')
1127 && ($upper_data != 'FROM')) {
1128 $previous_was_identifier = TRUE;
1131 } // end if (save a select expr)
1134 //======================================
1135 // s a v e a t a b l e r e f
1136 //======================================
1138 // maybe we just saw the end of table refs
1139 // but the last table ref has to be saved
1140 // or we are at the last token (TODO: there could be another
1141 // query after this one)
1142 // or we just got a reserved word
1144 if (isset($chain) && $seen_from && $save_table_ref
1145 && ($arr[$i]['type'] == 'punct_listsep'
1146 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
1147 || $seen_end_of_table_ref
1148 || $i==$size-1 )) {
1150 $size_chain = count($chain);
1151 $current_table_ref++;
1152 $subresult['table_ref'][$current_table_ref] = array(
1153 'expr' => '',
1154 'db' => '',
1155 'table_name' => '',
1156 'table_alias' => '',
1157 'table_true_name' => ''
1159 if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
1160 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1161 unset($alias_for_table_ref);
1163 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1164 // we assume for now that this is also the true name
1165 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1166 $subresult['table_ref'][$current_table_ref]['expr']
1167 = $subresult['table_ref'][$current_table_ref]['table_name'];
1168 // maybe a db
1169 if ($size_chain > 1) {
1170 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1171 $subresult['table_ref'][$current_table_ref]['expr']
1172 = $subresult['table_ref'][$current_table_ref]['db']
1173 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1174 } // end if ($size_chain > 1)
1176 // add the table alias into the whole expression
1177 $subresult['table_ref'][$current_table_ref]['expr']
1178 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1180 unset($chain);
1181 $previous_was_identifier = TRUE;
1182 //continue;
1184 } // end if (save a table ref)
1187 // when we have found all table refs,
1188 // for each table_ref alias, put the true name of the table
1189 // in the corresponding select expressions
1191 if (isset($current_table_ref) && ($seen_end_of_table_ref || $i == $size-1) && $subresult != $subresult_empty) {
1192 for ($tr=0; $tr <= $current_table_ref; $tr++) {
1193 $alias = $subresult['table_ref'][$tr]['table_alias'];
1194 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1195 for ($se=0; $se <= $current_select_expr; $se++) {
1196 if (isset($alias) && strlen($alias) && $subresult['select_expr'][$se]['table_true_name']
1197 == $alias) {
1198 $subresult['select_expr'][$se]['table_true_name']
1199 = $truename;
1200 } // end if (found the alias)
1201 } // end for (select expressions)
1203 } // end for (table refs)
1204 } // end if (set the true names)
1207 // e n d i n g l o o p #1
1208 // set the $previous_was_identifier to FALSE if the current
1209 // token is not an identifier
1210 if (($arr[$i]['type'] != 'alpha_identifier')
1211 && ($arr[$i]['type'] != 'quote_double')
1212 && ($arr[$i]['type'] != 'quote_single')
1213 && ($arr[$i]['type'] != 'quote_backtick')) {
1214 $previous_was_identifier = FALSE;
1215 } // end if
1217 // however, if we are on AS, we must keep the $previous_was_identifier
1218 if (($arr[$i]['type'] == 'alpha_reservedWord')
1219 && ($upper_data == 'AS')) {
1220 $previous_was_identifier = TRUE;
1223 if (($arr[$i]['type'] == 'alpha_reservedWord')
1224 && ($upper_data =='ON' || $upper_data =='USING')) {
1225 $save_table_ref = FALSE;
1226 } // end if (data == ON)
1228 if (($arr[$i]['type'] == 'alpha_reservedWord')
1229 && ($upper_data =='JOIN' || $upper_data =='FROM')) {
1230 $save_table_ref = TRUE;
1231 } // end if (data == JOIN)
1233 // no need to check the end of table ref if we already did
1234 // TODO: maybe add "&& $seen_from"
1235 if (!$seen_end_of_table_ref) {
1236 // if this is the last token, it implies that we have
1237 // seen the end of table references
1238 // Check for the end of table references
1240 // Note: if we are analyzing a GROUP_CONCAT clause,
1241 // we might find a word that seems to indicate that
1242 // we have found the end of table refs (like ORDER)
1243 // but it's a modifier of the GROUP_CONCAT so
1244 // it's not the real end of table refs
1245 if (($i == $size-1)
1246 || ($arr[$i]['type'] == 'alpha_reservedWord'
1247 && !$in_group_concat
1248 && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
1249 $seen_end_of_table_ref = TRUE;
1250 // to be able to save the last table ref, but do not
1251 // set it true if we found a word like "ON" that has
1252 // already set it to false
1253 if (isset($save_table_ref) && $save_table_ref != FALSE) {
1254 $save_table_ref = TRUE;
1255 } //end if
1257 } // end if (check for end of table ref)
1258 } //end if (!$seen_end_of_table_ref)
1260 if ($seen_end_of_table_ref) {
1261 $save_table_ref = FALSE;
1262 } // end if
1264 } // end for $i (loop #1)
1266 // -------------------------------------------------------
1267 // This is a big hunk of debugging code by Marc for this.
1268 // -------------------------------------------------------
1270 if (isset($current_select_expr)) {
1271 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1272 echo "<br />";
1273 reset ($subresult['select_expr'][$trace]);
1274 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1275 echo "sel expr $trace $key => $val<br />\n";
1279 if (isset($current_table_ref)) {
1280 echo "current_table_ref = " . $current_table_ref . "<br>";
1281 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1283 echo "<br />";
1284 reset ($subresult['table_ref'][$trace]);
1285 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1286 echo "table ref $trace $key => $val<br />\n";
1290 // -------------------------------------------------------
1293 // loop #2: - queryflags
1294 // - querytype (for queries != 'SELECT')
1295 // - section_before_limit, section_after_limit
1297 // we will also need this queryflag in loop 2
1298 // so set it here
1299 if (isset($current_table_ref) && $current_table_ref > -1) {
1300 $subresult['queryflags']['select_from'] = 1;
1303 $collect_section_before_limit = TRUE;
1304 $section_before_limit = '';
1305 $section_after_limit = '';
1306 $seen_reserved_word = FALSE;
1307 $seen_group = FALSE;
1308 $seen_order = FALSE;
1309 $in_group_by = FALSE; // true when we are inside the GROUP BY clause
1310 $in_order_by = FALSE; // true when we are inside the ORDER BY clause
1311 $in_having = FALSE; // true when we are inside the HAVING clause
1312 $in_select_expr = FALSE; // true when we are inside the select expr clause
1313 $in_where = FALSE; // true when we are inside the WHERE clause
1314 $in_from = FALSE;
1315 $in_group_concat = FALSE;
1316 $unsorted_query = '';
1317 $first_reserved_word = '';
1318 $current_identifier = '';
1320 for ($i = 0; $i < $size; $i++) {
1321 //DEBUG echo "trace loop2 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
1323 // need_confirm
1325 // check for reserved words that will have to generate
1326 // a confirmation request later in sql.php
1327 // the cases are:
1328 // DROP TABLE
1329 // DROP DATABASE
1330 // ALTER TABLE... DROP
1331 // DELETE FROM...
1333 // this code is not used for confirmations coming from functions.js
1335 // TODO: check for punct_queryend
1338 // TODO: verify C-style comments?
1339 if ($arr[$i]['type'] == 'comment_ansi') {
1340 $collect_section_before_limit = FALSE;
1343 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1344 $upper_data = strtoupper($arr[$i]['data']);
1345 if (!$seen_reserved_word) {
1346 $first_reserved_word = $upper_data;
1347 $subresult['querytype'] = $upper_data;
1348 $seen_reserved_word = TRUE;
1350 // if the first reserved word is DROP or DELETE,
1351 // we know this is a query that needs to be confirmed
1352 if ($first_reserved_word=='DROP'
1353 || $first_reserved_word == 'DELETE'
1354 || $first_reserved_word == 'TRUNCATE') {
1355 $subresult['queryflags']['need_confirm'] = 1;
1358 if ($first_reserved_word=='SELECT'){
1359 $position_of_first_select = $i;
1362 } else {
1363 if ($upper_data=='DROP' && $first_reserved_word=='ALTER') {
1364 $subresult['queryflags']['need_confirm'] = 1;
1368 if ($upper_data == 'PROCEDURE') {
1369 $collect_section_before_limit = FALSE;
1371 // TODO: set also to FALSE if we find
1372 // FOR UPDATE
1373 // LOCK IN SHARE MODE
1375 if ($upper_data == 'SELECT') {
1376 $in_select_expr = TRUE;
1377 $select_expr_clause = '';
1379 if ($upper_data == 'DISTINCT' && !$in_group_concat) {
1380 $subresult['queryflags']['distinct'] = 1;
1383 if ($upper_data == 'UNION') {
1384 $subresult['queryflags']['union'] = 1;
1387 if ($upper_data == 'JOIN') {
1388 $subresult['queryflags']['join'] = 1;
1391 if ($upper_data == 'OFFSET') {
1392 $subresult['queryflags']['offset'] = 1;
1395 // if this is a real SELECT...FROM
1396 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1397 $in_from = TRUE;
1398 $from_clause = '';
1399 $in_select_expr = FALSE;
1403 // (we could have less resetting of variables to FALSE
1404 // if we trust that the query respects the standard
1405 // MySQL order for clauses)
1407 // we use $seen_group and $seen_order because we are looking
1408 // for the BY
1409 if ($upper_data == 'GROUP') {
1410 $seen_group = TRUE;
1411 $seen_order = FALSE;
1412 $in_having = FALSE;
1413 $in_order_by = FALSE;
1414 $in_where = FALSE;
1415 $in_select_expr = FALSE;
1416 $in_from = FALSE;
1418 if ($upper_data == 'ORDER' && !$in_group_concat) {
1419 $seen_order = TRUE;
1420 $seen_group = FALSE;
1421 $in_having = FALSE;
1422 $in_group_by = FALSE;
1423 $in_where = FALSE;
1424 $in_select_expr = FALSE;
1425 $in_from = FALSE;
1427 if ($upper_data == 'HAVING') {
1428 $in_having = TRUE;
1429 $having_clause = '';
1430 $seen_group = FALSE;
1431 $seen_order = FALSE;
1432 $in_group_by = FALSE;
1433 $in_order_by = FALSE;
1434 $in_where = FALSE;
1435 $in_select_expr = FALSE;
1436 $in_from = FALSE;
1439 if ($upper_data == 'WHERE') {
1440 $in_where = TRUE;
1441 $where_clause = '';
1442 $where_clause_identifiers = array();
1443 $seen_group = FALSE;
1444 $seen_order = FALSE;
1445 $in_group_by = FALSE;
1446 $in_order_by = FALSE;
1447 $in_having = FALSE;
1448 $in_select_expr = FALSE;
1449 $in_from = FALSE;
1452 if ($upper_data == 'BY') {
1453 if ($seen_group) {
1454 $in_group_by = TRUE;
1455 $group_by_clause = '';
1457 if ($seen_order) {
1458 $in_order_by = TRUE;
1459 $order_by_clause = '';
1463 // if we find one of the words that could end the clause
1464 if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
1466 $in_group_by = FALSE;
1467 $in_order_by = FALSE;
1468 $in_having = FALSE;
1469 $in_where = FALSE;
1470 $in_select_expr = FALSE;
1471 $in_from = FALSE;
1474 } // endif (reservedWord)
1477 // do not add a blank after a function name
1478 // TODO: can we combine loop 2 and loop 1?
1479 // some code is repeated here...
1481 $sep=' ';
1482 if ($arr[$i]['type'] == 'alpha_functionName') {
1483 $sep='';
1484 $upper_data = strtoupper($arr[$i]['data']);
1485 if ($upper_data =='GROUP_CONCAT') {
1486 $in_group_concat = TRUE;
1487 $number_of_brackets_in_group_concat = 0;
1491 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1492 if ($in_group_concat) {
1493 $number_of_brackets_in_group_concat++;
1496 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1497 if ($in_group_concat) {
1498 $number_of_brackets_in_group_concat--;
1499 if ($number_of_brackets_in_group_concat == 0) {
1500 $in_group_concat = FALSE;
1505 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1506 $select_expr_clause .= $arr[$i]['data'] . $sep;
1508 if ($in_from && $upper_data != 'FROM') {
1509 $from_clause .= $arr[$i]['data'] . $sep;
1511 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1512 $group_by_clause .= $arr[$i]['data'] . $sep;
1514 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1515 // add a space only before ASC or DESC
1516 // not around the dot between dbname and tablename
1517 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1518 $order_by_clause .= $sep;
1520 $order_by_clause .= $arr[$i]['data'];
1522 if ($in_having && $upper_data != 'HAVING') {
1523 $having_clause .= $arr[$i]['data'] . $sep;
1525 if ($in_where && $upper_data != 'WHERE') {
1526 $where_clause .= $arr[$i]['data'] . $sep;
1528 if (($arr[$i]['type'] == 'quote_backtick')
1529 || ($arr[$i]['type'] == 'alpha_identifier')) {
1530 $where_clause_identifiers[] = $arr[$i]['data'];
1534 if (isset($subresult['queryflags']['select_from'])
1535 && $subresult['queryflags']['select_from'] == 1
1536 && !$seen_order) {
1537 $unsorted_query .= $arr[$i]['data'];
1539 if ($arr[$i]['type'] != 'punct_bracket_open_round'
1540 && $arr[$i]['type'] != 'punct_bracket_close_round'
1541 && $arr[$i]['type'] != 'punct') {
1542 $unsorted_query .= $sep;
1546 // clear $upper_data for next iteration
1547 $upper_data='';
1549 if ($collect_section_before_limit && $arr[$i]['type'] != 'punct_queryend') {
1550 $section_before_limit .= $arr[$i]['data'] . $sep;
1551 } else {
1552 $section_after_limit .= $arr[$i]['data'] . $sep;
1556 } // end for $i (loop #2)
1559 // -----------------------------------------------------
1560 // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
1561 // (for now, check only the first query)
1562 // (for now, identifiers are assumed to be backquoted)
1564 // If we find that we are dealing with a CREATE TABLE query,
1565 // we look for the next punct_bracket_open_round, which
1566 // introduces the fields list. Then, when we find a
1567 // quote_backtick, it must be a field, so we put it into
1568 // the create_table_fields array. Even if this field is
1569 // not a timestamp, it will be useful when logic has been
1570 // added for complete field attributes analysis.
1572 $seen_foreign = FALSE;
1573 $seen_references = FALSE;
1574 $seen_constraint = FALSE;
1575 $foreign_key_number = -1;
1576 $seen_create_table = FALSE;
1577 $seen_create = FALSE;
1578 $in_create_table_fields = FALSE;
1579 $brackets_level = 0;
1580 $in_timestamp_options = FALSE;
1581 $seen_default = FALSE;
1583 for ($i = 0; $i < $size; $i++) {
1584 // DEBUG echo "<b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
1586 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1587 $upper_data = strtoupper($arr[$i]['data']);
1589 if ($upper_data == 'NOT' && $in_timestamp_options) {
1590 $create_table_fields[$current_identifier]['timestamp_not_null'] = TRUE;
1594 if ($upper_data == 'CREATE') {
1595 $seen_create = TRUE;
1598 if ($upper_data == 'TABLE' && $seen_create) {
1599 $seen_create_table = TRUE;
1600 $create_table_fields = array();
1603 if ($upper_data == 'CURRENT_TIMESTAMP') {
1604 if ($in_timestamp_options) {
1605 if ($seen_default) {
1606 $create_table_fields[$current_identifier]['default_current_timestamp'] = TRUE;
1611 if ($upper_data == 'CONSTRAINT') {
1612 $foreign_key_number++;
1613 $seen_foreign = FALSE;
1614 $seen_references = FALSE;
1615 $seen_constraint = TRUE;
1617 if ($upper_data == 'FOREIGN') {
1618 $seen_foreign = TRUE;
1619 $seen_references = FALSE;
1620 $seen_constraint = FALSE;
1622 if ($upper_data == 'REFERENCES') {
1623 $seen_foreign = FALSE;
1624 $seen_references = TRUE;
1625 $seen_constraint = FALSE;
1629 // Cases covered:
1631 // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1632 // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1634 // but we set ['on_delete'] or ['on_cascade'] to
1635 // CASCADE | SET_NULL | NO_ACTION | RESTRICT
1637 // ON UPDATE CURRENT_TIMESTAMP
1639 if ($upper_data == 'ON') {
1640 if ($arr[$i+1]['type'] == 'alpha_reservedWord') {
1641 $second_upper_data = strtoupper($arr[$i+1]['data']);
1642 if ($second_upper_data == 'DELETE') {
1643 $clause = 'on_delete';
1645 if ($second_upper_data == 'UPDATE') {
1646 $clause = 'on_update';
1648 if (isset($clause)
1649 && ($arr[$i+2]['type'] == 'alpha_reservedWord'
1651 // ugly workaround because currently, NO is not
1652 // in the list of reserved words in sqlparser.data
1653 // (we got a bug report about not being able to use
1654 // 'no' as an identifier)
1655 || ($arr[$i+2]['type'] == 'alpha_identifier'
1656 && strtoupper($arr[$i+2]['data'])=='NO') )
1658 $third_upper_data = strtoupper($arr[$i+2]['data']);
1659 if ($third_upper_data == 'CASCADE'
1660 || $third_upper_data == 'RESTRICT') {
1661 $value = $third_upper_data;
1662 } elseif ($third_upper_data == 'SET'
1663 || $third_upper_data == 'NO') {
1664 if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
1665 $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
1667 } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
1668 if ($clause == 'on_update'
1669 && $in_timestamp_options) {
1670 $create_table_fields[$current_identifier]['on_update_current_timestamp'] = TRUE;
1671 $seen_default = FALSE;
1674 } else {
1675 $value = '';
1677 if (!empty($value)) {
1678 $foreign[$foreign_key_number][$clause] = $value;
1680 unset($clause);
1681 } // endif (isset($clause))
1685 } // end of reserved words analysis
1688 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1689 $brackets_level++;
1690 if ($seen_create_table && $brackets_level == 1) {
1691 $in_create_table_fields = TRUE;
1696 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1697 $brackets_level--;
1698 if ($seen_references) {
1699 $seen_references = FALSE;
1701 if ($seen_create_table && $brackets_level == 0) {
1702 $in_create_table_fields = FALSE;
1706 if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
1707 $upper_data = strtoupper($arr[$i]['data']);
1708 if ($seen_create_table && $in_create_table_fields) {
1709 if ($upper_data == 'DEFAULT') {
1710 $seen_default = TRUE;
1715 // note: the "or" part here is a workaround for a bug
1716 // (see FIXME-2005-10-16)
1717 if (($arr[$i]['type'] == 'alpha_columnType') || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)) {
1718 $upper_data = strtoupper($arr[$i]['data']);
1719 if ($seen_create_table && $in_create_table_fields && isset($current_identifier)) {
1720 $create_table_fields[$current_identifier]['type'] = $upper_data;
1721 if ($upper_data == 'TIMESTAMP') {
1722 $in_timestamp_options = TRUE;
1723 } else {
1724 $in_timestamp_options = FALSE;
1730 if ($arr[$i]['type'] == 'quote_backtick' || $arr[$i]['type'] == 'alpha_identifier') {
1732 if ($arr[$i]['type'] == 'quote_backtick') {
1733 // remove backquotes
1734 $identifier = PMA_unQuote($arr[$i]['data']);
1735 } else {
1736 $identifier = $arr[$i]['data'];
1739 if ($seen_create_table && $in_create_table_fields) {
1740 $current_identifier = $identifier;
1741 // warning: we set this one even for non TIMESTAMP type
1742 $create_table_fields[$current_identifier]['timestamp_not_null'] = FALSE;
1745 if ($seen_constraint) {
1746 $foreign[$foreign_key_number]['constraint'] = $identifier;
1749 if ($seen_foreign && $brackets_level > 0) {
1750 $foreign[$foreign_key_number]['index_list'][] = $identifier;
1753 if ($seen_references) {
1754 // here, the first bracket level corresponds to the
1755 // bracket of CREATE TABLE
1756 // so if we are on level 2, it must be the index list
1757 // of the foreign key REFERENCES
1758 if ($brackets_level > 1) {
1759 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1760 } else {
1761 // for MySQL 4.0.18, identifier is
1762 // `table` or `db`.`table`
1763 // the first pass will pick the db name
1764 // the next pass will execute the else and pick the
1765 // db name in $db_table[0]
1766 if ($arr[$i+1]['type'] == 'punct_qualifier') {
1767 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
1768 } else {
1769 // for MySQL 4.0.16, identifier is
1770 // `table` or `db.table`
1771 $db_table = explode('.', $identifier);
1772 if (isset($db_table[1])) {
1773 $foreign[$foreign_key_number]['ref_db_name'] = $db_table[0];
1774 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[1];
1775 } else {
1776 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[0];
1782 } // end for $i (loop #3)
1785 // Fill the $subresult array
1787 if (isset($create_table_fields)) {
1788 $subresult['create_table_fields'] = $create_table_fields;
1791 if (isset($foreign)) {
1792 $subresult['foreign_keys'] = $foreign;
1795 if (isset($select_expr_clause)) {
1796 $subresult['select_expr_clause'] = $select_expr_clause;
1798 if (isset($from_clause)) {
1799 $subresult['from_clause'] = $from_clause;
1801 if (isset($group_by_clause)) {
1802 $subresult['group_by_clause'] = $group_by_clause;
1804 if (isset($order_by_clause)) {
1805 $subresult['order_by_clause'] = $order_by_clause;
1807 if (isset($having_clause)) {
1808 $subresult['having_clause'] = $having_clause;
1810 if (isset($where_clause)) {
1811 $subresult['where_clause'] = $where_clause;
1813 if (isset($unsorted_query) && !empty($unsorted_query)) {
1814 $subresult['unsorted_query'] = $unsorted_query;
1816 if (isset($where_clause_identifiers)) {
1817 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
1820 if (isset($position_of_first_select)) {
1821 $subresult['position_of_first_select'] = $position_of_first_select;
1822 $subresult['section_before_limit'] = $section_before_limit;
1823 $subresult['section_after_limit'] = $section_after_limit;
1826 // They are naughty and didn't have a trailing semi-colon,
1827 // then still handle it properly
1828 if ($subresult['querytype'] != '') {
1829 $result[] = $subresult;
1831 return $result;
1832 } // end of the "PMA_SQP_analyze()" function
1836 * Colorizes SQL queries html formatted
1838 * @param array The SQL queries html formatted
1840 * @return array The colorized SQL queries
1842 * @access public
1844 function PMA_SQP_formatHtml_colorize($arr)
1846 $i = $GLOBALS['PMA_strpos']($arr['type'], '_');
1847 $class = '';
1848 if ($i > 0) {
1849 $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
1852 $class .= 'syntax_' . $arr['type'];
1854 //TODO: check why adding a "\n" after the </span> would cause extra
1855 // blanks to be displayed:
1856 // SELECT p . person_name
1858 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
1859 } // end of the "PMA_SQP_formatHtml_colorize()" function
1863 * Formats SQL queries to html
1865 * @param array The SQL queries
1866 * @param string mode
1867 * @param integer starting token
1868 * @param integer number of tokens to format, -1 = all
1870 * @return string The formatted SQL queries
1872 * @access public
1874 function PMA_SQP_formatHtml($arr, $mode='color', $start_token=0,
1875 $number_of_tokens=-1)
1877 // then check for an array
1878 if (!is_array($arr)) {
1879 return htmlspecialchars($arr);
1881 // first check for the SQL parser having hit an error
1882 if (PMA_SQP_isError()) {
1883 return htmlspecialchars($arr['raw']);
1885 // else do it properly
1886 switch ($mode) {
1887 case 'color':
1888 $str = '<span class="syntax">';
1889 $html_line_break = '<br />';
1890 break;
1891 case 'query_only':
1892 $str = '';
1893 $html_line_break = "\n";
1894 break;
1895 case 'text':
1896 $str = '';
1897 $html_line_break = '<br />';
1898 break;
1899 } // end switch
1900 $indent = 0;
1901 $bracketlevel = 0;
1902 $functionlevel = 0;
1903 $infunction = FALSE;
1904 $space_punct_listsep = ' ';
1905 $space_punct_listsep_function_name = ' ';
1906 // $space_alpha_reserved_word = '<br />'."\n";
1907 $space_alpha_reserved_word = ' ';
1909 $keywords_with_brackets_1before = array(
1910 'INDEX',
1911 'KEY',
1912 'ON',
1913 'USING'
1915 $keywords_with_brackets_1before_cnt = 4;
1917 $keywords_with_brackets_2before = array(
1918 'IGNORE',
1919 'INDEX',
1920 'INTO',
1921 'KEY',
1922 'PRIMARY',
1923 'PROCEDURE',
1924 'REFERENCES',
1925 'UNIQUE',
1926 'USE'
1928 // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
1929 $keywords_with_brackets_2before_cnt = 9;
1931 // These reserved words do NOT get a newline placed near them.
1932 $keywords_no_newline = array(
1933 'AS',
1934 'ASC',
1935 'DESC',
1936 'DISTINCT',
1937 'HOUR',
1938 'INTERVAL',
1939 'IS',
1940 'LIKE',
1941 'NOT',
1942 'NULL',
1943 'ON',
1944 'REGEXP'
1946 $keywords_no_newline_cnt = 12;
1948 // These reserved words introduce a privilege list
1949 $keywords_priv_list = array(
1950 'GRANT',
1951 'REVOKE'
1953 $keywords_priv_list_cnt = 2;
1955 if ($number_of_tokens == -1) {
1956 $arraysize = $arr['len'];
1957 } else {
1958 $arraysize = $number_of_tokens;
1960 $typearr = array();
1961 if ($arraysize >= 0) {
1962 $typearr[0] = '';
1963 $typearr[1] = '';
1964 $typearr[2] = '';
1965 //$typearr[3] = $arr[0]['type'];
1966 $typearr[3] = $arr[$start_token]['type'];
1969 $in_priv_list = FALSE;
1970 for ($i = $start_token; $i < $arraysize; $i++) {
1971 // DEBUG echo "<b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
1972 $before = '';
1973 $after = '';
1974 $indent = 0;
1975 // array_shift($typearr);
1977 0 prev2
1978 1 prev
1979 2 current
1980 3 next
1982 if (($i + 1) < $arraysize) {
1983 // array_push($typearr, $arr[$i + 1]['type']);
1984 $typearr[4] = $arr[$i + 1]['type'];
1985 } else {
1986 //array_push($typearr, null);
1987 $typearr[4] = '';
1990 for ($j=0; $j<4; $j++) {
1991 $typearr[$j] = $typearr[$j + 1];
1994 switch ($typearr[2]) {
1995 case 'white_newline':
1996 $before = '';
1997 break;
1998 case 'punct_bracket_open_round':
1999 $bracketlevel++;
2000 $infunction = FALSE;
2001 // Make sure this array is sorted!
2002 if (($typearr[1] == 'alpha_functionName') || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
2003 || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex') || ($typearr[3] == 'digit_float')
2004 || (($typearr[0] == 'alpha_reservedWord')
2005 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
2006 || (($typearr[1] == 'alpha_reservedWord')
2007 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
2009 $functionlevel++;
2010 $infunction = TRUE;
2011 $after .= ' ';
2012 } else {
2013 $indent++;
2014 $after .= ($mode != 'query_only' ? '<div class="syntax_indent' . $indent . '">' : ' ');
2016 break;
2017 case 'alpha_identifier':
2018 if (($typearr[1] == 'punct_qualifier') || ($typearr[3] == 'punct_qualifier')) {
2019 $after = '';
2020 $before = '';
2022 if (($typearr[3] == 'alpha_columnType') || ($typearr[3] == 'alpha_identifier')) {
2023 $after .= ' ';
2025 break;
2026 case 'punct_qualifier':
2027 $before = '';
2028 $after = '';
2029 break;
2030 case 'punct_listsep':
2031 if ($infunction == TRUE) {
2032 $after .= $space_punct_listsep_function_name;
2033 } else {
2034 $after .= $space_punct_listsep;
2036 break;
2037 case 'punct_queryend':
2038 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi') && $typearr[3] != 'comment_c') {
2039 $after .= $html_line_break;
2040 $after .= $html_line_break;
2042 $space_punct_listsep = ' ';
2043 $space_punct_listsep_function_name = ' ';
2044 $space_alpha_reserved_word = ' ';
2045 $in_priv_list = FALSE;
2046 break;
2047 case 'comment_mysql':
2048 case 'comment_ansi':
2049 $after .= $html_line_break;
2050 break;
2051 case 'punct':
2052 $before .= ' ';
2053 // workaround for
2054 // select * from mytable limit 0,-1
2055 // (a side effect of this workaround is that
2056 // select 20 - 9
2057 // becomes
2058 // select 20 -9
2059 // )
2060 if ($typearr[3] != 'digit_integer') {
2061 $after .= ' ';
2063 break;
2064 case 'punct_bracket_close_round':
2065 $bracketlevel--;
2066 if ($infunction == TRUE) {
2067 $functionlevel--;
2068 $after .= ' ';
2069 $before .= ' ';
2070 } else {
2071 $indent--;
2072 $before .= ($mode != 'query_only' ? '</div>' : ' ');
2074 $infunction = ($functionlevel > 0) ? TRUE : FALSE;
2075 break;
2076 case 'alpha_columnType':
2077 if ($typearr[3] == 'alpha_columnAttrib') {
2078 $after .= ' ';
2080 if ($typearr[1] == 'alpha_columnType') {
2081 $before .= ' ';
2083 break;
2084 case 'alpha_columnAttrib':
2086 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
2087 // COLLATE LATIN1_GENERAL_CI DEFAULT
2088 if ($typearr[1] == 'alpha_identifier' || $typearr[1] == 'alpha_charset') {
2089 $before .= ' ';
2091 if (($typearr[3] == 'alpha_columnAttrib') || ($typearr[3] == 'quote_single') || ($typearr[3] == 'digit_integer')) {
2092 $after .= ' ';
2094 // workaround for
2095 // AUTO_INCREMENT = 31DEFAULT_CHARSET = utf-8
2097 if ($typearr[2] == 'alpha_columnAttrib' && $typearr[3] == 'alpha_reservedWord') {
2098 $before .= ' ';
2100 // workaround for
2101 // select * from mysql.user where binary user="root"
2102 // binary is marked as alpha_columnAttrib
2103 // but should be marked as a reserved word
2104 if (strtoupper($arr[$i]['data']) == 'BINARY'
2105 && $typearr[3] == 'alpha_identifier') {
2106 $after .= ' ';
2108 break;
2109 case 'alpha_reservedWord':
2110 // do not uppercase the reserved word if we are calling
2111 // this function in query_only mode, because we need
2112 // the original query (otherwise we get problems with
2113 // semi-reserved words like "storage" which is legal
2114 // as an identifier name)
2116 if ($mode != 'query_only') {
2117 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
2120 if ((($typearr[1] != 'alpha_reservedWord')
2121 || (($typearr[1] == 'alpha_reservedWord')
2122 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
2123 && ($typearr[1] != 'punct_level_plus')
2124 && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
2125 // do not put a space before the first token, because
2126 // we use a lot of eregi() checking for the first
2127 // reserved word at beginning of query
2128 // so do not put a newline before
2130 // also we must not be inside a privilege list
2131 if ($i > 0) {
2132 // the alpha_identifier exception is there to
2133 // catch cases like
2134 // GRANT SELECT ON mydb.mytable TO myuser@localhost
2135 // (else, we get mydb.mytableTO )
2137 // the quote_single exception is there to
2138 // catch cases like
2139 // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
2141 // TODO: fix all cases and find why this happens
2143 if (!$in_priv_list || $typearr[1] == 'alpha_identifier' || $typearr[1] == 'quote_single' || $typearr[1] == 'white_newline') {
2144 $before .= $space_alpha_reserved_word;
2146 } else {
2147 // on first keyword, check if it introduces a
2148 // privilege list
2149 if (PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_priv_list, $keywords_priv_list_cnt)) {
2150 $in_priv_list = TRUE;
2153 } else {
2154 $before .= ' ';
2157 switch ($arr[$i]['data']) {
2158 case 'CREATE':
2159 if (!$in_priv_list) {
2160 $space_punct_listsep = $html_line_break;
2161 $space_alpha_reserved_word = ' ';
2163 break;
2164 case 'EXPLAIN':
2165 case 'DESCRIBE':
2166 case 'SET':
2167 case 'ALTER':
2168 case 'DELETE':
2169 case 'SHOW':
2170 case 'DROP':
2171 case 'UPDATE':
2172 case 'TRUNCATE':
2173 case 'ANALYZE':
2174 case 'ANALYSE':
2175 if (!$in_priv_list) {
2176 $space_punct_listsep = $html_line_break;
2177 $space_alpha_reserved_word = ' ';
2179 break;
2180 case 'INSERT':
2181 case 'REPLACE':
2182 if (!$in_priv_list) {
2183 $space_punct_listsep = $html_line_break;
2184 $space_alpha_reserved_word = $html_line_break;
2186 break;
2187 case 'VALUES':
2188 $space_punct_listsep = ' ';
2189 $space_alpha_reserved_word = $html_line_break;
2190 break;
2191 case 'SELECT':
2192 $space_punct_listsep = ' ';
2193 $space_alpha_reserved_word = $html_line_break;
2194 break;
2195 default:
2196 break;
2197 } // end switch ($arr[$i]['data'])
2199 $after .= ' ';
2200 break;
2201 case 'digit_integer':
2202 case 'digit_float':
2203 case 'digit_hex':
2204 //TODO: could there be other types preceding a digit?
2205 if ($typearr[1] == 'alpha_reservedWord') {
2206 $after .= ' ';
2208 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2209 $after .= ' ';
2211 if ($typearr[1] == 'alpha_columnAttrib') {
2212 $before .= ' ';
2214 break;
2215 case 'alpha_variable':
2216 // other workaround for a problem similar to the one
2217 // explained below for quote_single
2218 if (!$in_priv_list && $typearr[3] != 'quote_backtick') {
2219 $after = ' ';
2221 break;
2222 case 'quote_double':
2223 case 'quote_single':
2224 // workaround: for the query
2225 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
2226 // the @ is incorrectly marked as alpha_variable
2227 // in the parser, and here, the '%' gets a blank before,
2228 // which is a syntax error
2229 if ($typearr[1] !='alpha_variable') {
2230 $before .= ' ';
2232 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2233 $after .= ' ';
2235 break;
2236 case 'quote_backtick':
2237 if ($typearr[3] != 'punct_qualifier' && $typearr[3] != 'alpha_variable') {
2238 $after .= ' ';
2240 if ($typearr[1] != 'punct_qualifier' && $typearr[1] != 'alpha_variable') {
2241 $before .= ' ';
2243 break;
2244 default:
2245 break;
2246 } // end switch ($typearr[2])
2249 if ($typearr[3] != 'punct_qualifier') {
2250 $after .= ' ';
2252 $after .= "\n";
2254 $str .= $before . ($mode=='color' ? PMA_SQP_formatHTML_colorize($arr[$i]) : $arr[$i]['data']). $after;
2255 } // end for
2256 if ($mode=='color') {
2257 $str .= '</span>';
2260 return $str;
2261 } // end of the "PMA_SQP_formatHtml()" function
2265 * Builds a CSS rule used for html formatted SQL queries
2267 * @param string The class name
2268 * @param string The property name
2269 * @param string The property value
2271 * @return string The CSS rule
2273 * @access public
2275 * @see PMA_SQP_buildCssData()
2277 function PMA_SQP_buildCssRule($classname, $property, $value)
2279 $str = '.' . $classname . ' {';
2280 if ($value != '') {
2281 $str .= $property . ': ' . $value . ';';
2283 $str .= '}' . "\n";
2285 return $str;
2286 } // end of the "PMA_SQP_buildCssRule()" function
2290 * Builds CSS rules used for html formatted SQL queries
2292 * @return string The CSS rules set
2294 * @access public
2296 * @global array The current PMA configuration
2298 * @see PMA_SQP_buildCssRule()
2300 function PMA_SQP_buildCssData()
2302 global $cfg;
2304 $css_string = '';
2305 foreach ($cfg['SQP']['fmtColor'] AS $key => $col) {
2306 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
2309 for ($i = 0; $i < 8; $i++) {
2310 $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
2313 return $css_string;
2314 } // end of the "PMA_SQP_buildCssData()" function
2316 if ( ! defined( 'PMA_MINIMUM_COMMON' ) ) {
2318 * Gets SQL queries with no format
2320 * @param array The SQL queries list
2322 * @return string The SQL queries with no format
2324 * @access public
2326 function PMA_SQP_formatNone($arr)
2328 $formatted_sql = htmlspecialchars($arr['raw']);
2329 $formatted_sql = preg_replace("@((\015\012)|(\015)|(\012)){3,}@", "\n\n", $formatted_sql);
2331 return $formatted_sql;
2332 } // end of the "PMA_SQP_formatNone()" function
2336 * Gets SQL queries in text format
2338 * @param array The SQL queries list
2340 * @return string The SQL queries in text format
2342 * @access public
2344 function PMA_SQP_formatText($arr)
2347 * TODO WRITE THIS!
2349 return PMA_SQP_formatNone($arr);
2350 } // end of the "PMA_SQP_formatText()" function
2351 } // end if: minimal common.lib needed?