Revert initial commit
[phpmyadmin/blinky.git] / libraries / sqlparser.lib.php
blob349d010e6383d77ab0a70122922ad1a1eb5c3878
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /** SQL Parser Functions for phpMyAdmin
5 * These functions define an SQL parser system, capable of understanding and
6 * extracting data from a MySQL type SQL query.
8 * The basic procedure for using the new SQL parser:
9 * On any page that needs to extract data from a query or to pretty-print a
10 * query, you need code like this up at the top:
12 * ($sql contains the query)
13 * $parsed_sql = PMA_SQP_parse($sql);
15 * If you want to extract data from it then, you just need to run
16 * $sql_info = PMA_SQP_analyze($parsed_sql);
18 * See comments in PMA_SQP_analyze for the returned info
19 * from the analyzer.
21 * If you want a pretty-printed version of the query, do:
22 * $string = PMA_SQP_formatHtml($parsed_sql);
23 * (note that that you need to have syntax.css.php included somehow in your
24 * page for it to work, I recommend '<link rel="stylesheet" type="text/css"
25 * href="syntax.css.php" />' at the moment.)
27 * @version $Id$
28 * @package phpMyAdmin
30 if (! defined('PHPMYADMIN')) {
31 exit;
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 /**
57 * currently we don't need the $pos (token position in query)
58 * for other purposes than LIMIT clause verification,
59 * 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 /**
68 * This is debug variant of above.
69 * @ignore
71 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
73 global $timer;
75 $t = $timer;
76 $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos, 'time' => $t);
77 $timer = microtime();
78 $arrsize++;
79 } // end of the "PMA_SQP_arrayAdd()" function
80 } // end if... else...
83 /**
84 * Reset the error variable for the SQL parser
86 * @access public
88 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
89 function PMA_SQP_resetError()
91 global $SQP_errorString;
92 $SQP_errorString = '';
93 unset($SQP_errorString);
96 /**
97 * Get the contents of the error variable for the SQL parser
99 * @return string Error string from SQL parser
101 * @access public
103 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
104 function PMA_SQP_getErrorString()
106 global $SQP_errorString;
107 return isset($SQP_errorString) ? $SQP_errorString : '';
111 * Check if the SQL parser hit an error
113 * @return boolean error state
115 * @access public
117 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
118 function PMA_SQP_isError()
120 global $SQP_errorString;
121 return isset($SQP_errorString) && !empty($SQP_errorString);
125 * Set an error message for the system
127 * @param string The error message
128 * @param string The failing SQL query
130 * @access private
131 * @scope SQL Parser internal
133 // Revised, Robbat2 - 13 Janurary 2003, 2:59PM
134 function PMA_SQP_throwError($message, $sql)
136 global $SQP_errorString;
137 $SQP_errorString = '<p>'.__('There seems to be an error in your SQL query. The MySQL server error output below, if there is any, may also help you in diagnosing the problem') . '</p>' . "\n"
138 . '<pre>' . "\n"
139 . 'ERROR: ' . $message . "\n"
140 . 'SQL: ' . htmlspecialchars($sql) . "\n"
141 . '</pre>' . "\n";
143 } // end of the "PMA_SQP_throwError()" function
147 * Do display the bug report
149 * @param string The error message
150 * @param string The failing SQL query
152 * @access public
154 function PMA_SQP_bug($message, $sql)
156 global $SQP_errorString;
157 $debugstr = 'ERROR: ' . $message . "\n";
158 $debugstr .= 'SVN: $Id$' . "\n";
159 $debugstr .= 'MySQL: '.PMA_MYSQL_STR_VERSION . "\n";
160 $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS . ' ' . PMA_USR_BROWSER_AGENT . ' ' . PMA_USR_BROWSER_VER . "\n";
161 $debugstr .= 'PMA: ' . PMA_VERSION . "\n";
162 $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION . ' ' . PHP_OS . "\n";
163 $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
164 $debugstr .= 'SQL: ' . htmlspecialchars($sql);
166 $encodedstr = $debugstr;
167 if (@function_exists('gzcompress')) {
168 $encodedstr = gzcompress($debugstr, 9);
170 $encodedstr = preg_replace("/(\015\012)|(\015)|(\012)/", '<br />' . "\n", chunk_split(base64_encode($encodedstr)));
173 $SQP_errorString .= __('There is a chance that you may have found a bug in the SQL parser. Please examine your query closely, and check that the quotes are correct and not mis-matched. Other possible failure causes may be that you are uploading a file with binary outside of a quoted text area. You can also try your query on the MySQL command line interface. The MySQL server error output below, if there is any, may also help you in diagnosing the problem. If you still have problems or if the parser fails where the command line interface succeeds, please reduce your SQL query input to the single query that causes problems, and submit a bug report with the data chunk in the CUT section below:')
174 . '<br />' . "\n"
175 . '----' . __('BEGIN CUT') . '----' . '<br />' . "\n"
176 . $encodedstr . "\n"
177 . '----' . __('END CUT') . '----' . '<br />' . "\n";
179 $SQP_errorString .= '----' . __('BEGIN RAW') . '----<br />' . "\n"
180 . '<pre>' . "\n"
181 . $debugstr
182 . '</pre>' . "\n"
183 . '----' . __('END RAW') . '----<br />' . "\n";
185 } // end of the "PMA_SQP_bug()" function
189 * Parses the SQL queries
191 * @param string The SQL query list
193 * @return mixed Most of times, nothing...
195 * @global array The current PMA configuration
196 * @global array MySQL column attributes
197 * @global array MySQL reserved words
198 * @global array MySQL column types
199 * @global array MySQL function names
200 * @global integer MySQL column attributes count
201 * @global integer MySQL reserved words count
202 * @global integer MySQL column types count
203 * @global integer MySQL function names count
204 * @global array List of available character sets
205 * @global array List of available collations
206 * @global integer Character sets count
207 * @global integer Collations count
209 * @access public
211 function PMA_SQP_parse($sql)
213 global $cfg;
214 global $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word, $PMA_SQPdata_column_type, $PMA_SQPdata_function_name,
215 $PMA_SQPdata_column_attrib_cnt, $PMA_SQPdata_reserved_word_cnt, $PMA_SQPdata_column_type_cnt, $PMA_SQPdata_function_name_cnt;
216 global $mysql_charsets, $mysql_collations_flat, $mysql_charsets_count, $mysql_collations_count;
217 global $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt;
219 // Convert all line feeds to Unix style
220 $sql = str_replace("\r\n", "\n", $sql);
221 $sql = str_replace("\r", "\n", $sql);
223 $len = PMA_strlen($sql);
224 if ($len == 0) {
225 return array();
228 $sql_array = array();
229 $sql_array['raw'] = $sql;
230 $count1 = 0;
231 $count2 = 0;
232 $punct_queryend = ';';
233 $punct_qualifier = '.';
234 $punct_listsep = ',';
235 $punct_level_plus = '(';
236 $punct_level_minus = ')';
237 $punct_user = '@';
238 $digit_floatdecimal = '.';
239 $digit_hexset = 'x';
240 $bracket_list = '()[]{}';
241 $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
242 $allpunct_list_pair = array (
243 0 => '!=',
244 1 => '&&',
245 2 => ':=',
246 3 => '<<',
247 4 => '<=',
248 5 => '<=>',
249 6 => '<>',
250 7 => '>=',
251 8 => '>>',
252 9 => '||'
254 $allpunct_list_pair_size = 10; //count($allpunct_list_pair);
255 $quote_list = '\'"`';
256 $arraysize = 0;
258 $previous_was_space = false;
259 $this_was_space = false;
260 $previous_was_bracket = false;
261 $this_was_bracket = false;
262 $previous_was_punct = false;
263 $this_was_punct = false;
264 $previous_was_listsep = false;
265 $this_was_listsep = false;
266 $previous_was_quote = false;
267 $this_was_quote = false;
269 while ($count2 < $len) {
270 $c = $GLOBALS['PMA_substr']($sql, $count2, 1);
271 $count1 = $count2;
273 $previous_was_space = $this_was_space;
274 $this_was_space = false;
275 $previous_was_bracket = $this_was_bracket;
276 $this_was_bracket = false;
277 $previous_was_punct = $this_was_punct;
278 $this_was_punct = false;
279 $previous_was_listsep = $this_was_listsep;
280 $this_was_listsep = false;
281 $previous_was_quote = $this_was_quote;
282 $this_was_quote = false;
284 if (($c == "\n")) {
285 $this_was_space = true;
286 $count2++;
287 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
288 continue;
291 // Checks for white space
292 if ($GLOBALS['PMA_STR_isSpace']($c)) {
293 $this_was_space = true;
294 $count2++;
295 continue;
298 // Checks for comment lines.
299 // MySQL style #
300 // C style /* */
301 // ANSI style --
302 if (($c == '#')
303 || (($count2 + 1 < $len) && ($c == '/') && ($GLOBALS['PMA_substr']($sql, $count2 + 1, 1) == '*'))
304 || (($count2 + 2 == $len) && ($c == '-') && ($GLOBALS['PMA_substr']($sql, $count2 + 1, 1) == '-'))
305 || (($count2 + 2 < $len) && ($c == '-') && ($GLOBALS['PMA_substr']($sql, $count2 + 1, 1) == '-') && (($GLOBALS['PMA_substr']($sql, $count2 + 2, 1) <= ' ')))) {
306 $count2++;
307 $pos = 0;
308 $type = 'bad';
309 switch ($c) {
310 case '#':
311 $type = 'mysql';
312 case '-':
313 $type = 'ansi';
314 $pos = $GLOBALS['PMA_strpos']($sql, "\n", $count2);
315 break;
316 case '/':
317 $type = 'c';
318 $pos = $GLOBALS['PMA_strpos']($sql, '*/', $count2);
319 $pos += 2;
320 break;
321 default:
322 break;
323 } // end switch
324 $count2 = ($pos < $count2) ? $len : $pos;
325 $str = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
326 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
327 continue;
328 } // end if
330 // Checks for something inside quotation marks
331 if ($GLOBALS['PMA_strpos']($quote_list, $c) !== false) {
332 $startquotepos = $count2;
333 $quotetype = $c;
334 $count2++;
335 $escaped = FALSE;
336 $escaped_escaped = FALSE;
337 $pos = $count2;
338 $oldpos = 0;
339 do {
340 $oldpos = $pos;
341 $pos = $GLOBALS['PMA_strpos'](' ' . $sql, $quotetype, $oldpos + 1) - 1;
342 // ($pos === FALSE)
343 if ($pos < 0) {
344 $debugstr = __('Unclosed quote') . ' @ ' . $startquotepos. "\n"
345 . 'STR: ' . htmlspecialchars($quotetype);
346 PMA_SQP_throwError($debugstr, $sql);
347 return $sql_array;
350 // If the quote is the first character, it can't be
351 // escaped, so don't do the rest of the code
352 if ($pos == 0) {
353 break;
356 // Checks for MySQL escaping using a \
357 // And checks for ANSI escaping using the $quotetype character
358 if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos)) {
359 $pos ++;
360 continue;
361 } elseif (($pos + 1 < $len) && ($GLOBALS['PMA_substr']($sql, $pos, 1) == $quotetype) && ($GLOBALS['PMA_substr']($sql, $pos + 1, 1) == $quotetype)) {
362 $pos = $pos + 2;
363 continue;
364 } else {
365 break;
367 } while ($len > $pos); // end do
369 $count2 = $pos;
370 $count2++;
371 $type = 'quote_';
372 switch ($quotetype) {
373 case '\'':
374 $type .= 'single';
375 $this_was_quote = true;
376 break;
377 case '"':
378 $type .= 'double';
379 $this_was_quote = true;
380 break;
381 case '`':
382 $type .= 'backtick';
383 $this_was_quote = true;
384 break;
385 default:
386 break;
387 } // end switch
388 $data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
389 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
390 continue;
393 // Checks for brackets
394 if ($GLOBALS['PMA_strpos']($bracket_list, $c) !== false) {
395 // All bracket tokens are only one item long
396 $this_was_bracket = true;
397 $count2++;
398 $type_type = '';
399 if ($GLOBALS['PMA_strpos']('([{', $c) !== false) {
400 $type_type = 'open';
401 } else {
402 $type_type = 'close';
405 $type_style = '';
406 if ($GLOBALS['PMA_strpos']('()', $c) !== false) {
407 $type_style = 'round';
408 } elseif ($GLOBALS['PMA_strpos']('[]', $c) !== false) {
409 $type_style = 'square';
410 } else {
411 $type_style = 'curly';
414 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
415 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
416 continue;
419 /* DEBUG
420 echo '<pre>1';
421 var_dump(PMA_STR_isSqlIdentifier($c, false));
422 var_dump($c == '@');
423 var_dump($c == '.');
424 var_dump(PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1)));
425 var_dump($previous_was_space);
426 var_dump($previous_was_bracket);
427 var_dump($previous_was_listsep);
428 echo '</pre>';
431 // Checks for identifier (alpha or numeric)
432 if (PMA_STR_isSqlIdentifier($c, false)
433 || $c == '@'
434 || ($c == '.'
435 && $GLOBALS['PMA_STR_isDigit']($GLOBALS['PMA_substr']($sql, $count2 + 1, 1))
436 && ($previous_was_space || $previous_was_bracket || $previous_was_listsep))) {
438 /* DEBUG
439 echo PMA_substr($sql, $count2);
440 echo '<hr />';
443 $count2++;
446 * @todo a @ can also be present in expressions like
447 * FROM 'user'@'%' or TO 'user'@'%'
448 * in this case, the @ is wrongly marked as alpha_variable
450 $is_identifier = $previous_was_punct;
451 $is_sql_variable = $c == '@' && ! $previous_was_quote;
452 $is_user = $c == '@' && $previous_was_quote;
453 $is_digit = !$is_identifier && !$is_sql_variable && $GLOBALS['PMA_STR_isDigit']($c);
454 $is_hex_digit = $is_digit && $c == '0' && $count2 < $len && $GLOBALS['PMA_substr']($sql, $count2, 1) == 'x';
455 $is_float_digit = $c == '.';
456 $is_float_digit_exponent = FALSE;
458 /* DEBUG
459 echo '<pre>2';
460 var_dump($is_identifier);
461 var_dump($is_sql_variable);
462 var_dump($is_digit);
463 var_dump($is_float_digit);
464 echo '</pre>';
467 // Nijel: Fast skip is especially needed for huge BLOB data, requires PHP at least 4.3.0:
468 if ($is_hex_digit) {
469 $count2++;
470 $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
471 if ($pos > $count2) {
472 $count2 = $pos;
474 unset($pos);
475 } elseif ($is_digit) {
476 $pos = strspn($sql, '0123456789', $count2);
477 if ($pos > $count2) {
478 $count2 = $pos;
480 unset($pos);
483 while (($count2 < $len) && PMA_STR_isSqlIdentifier($GLOBALS['PMA_substr']($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
484 $c2 = $GLOBALS['PMA_substr']($sql, $count2, 1);
485 if ($is_sql_variable && ($c2 == '.')) {
486 $count2++;
487 continue;
489 if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
490 $count2++;
491 if (!$is_float_digit) {
492 $is_float_digit = TRUE;
493 continue;
494 } else {
495 $debugstr = __('Invalid Identifer') . ' @ ' . ($count1+1) . "\n"
496 . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
497 PMA_SQP_throwError($debugstr, $sql);
498 return $sql_array;
501 if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
502 if (!$is_float_digit_exponent) {
503 $is_float_digit_exponent = TRUE;
504 $is_float_digit = TRUE;
505 $count2++;
506 continue;
507 } else {
508 $is_digit = FALSE;
509 $is_float_digit = FALSE;
512 if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && $GLOBALS['PMA_STR_isDigit']($c2))) {
513 $count2++;
514 continue;
515 } else {
516 $is_digit = FALSE;
517 $is_hex_digit = FALSE;
520 $count2++;
521 } // end while
523 $l = $count2 - $count1;
524 $str = $GLOBALS['PMA_substr']($sql, $count1, $l);
526 $type = '';
527 if ($is_digit || $is_float_digit || $is_hex_digit) {
528 $type = 'digit';
529 if ($is_float_digit) {
530 $type .= '_float';
531 } elseif ($is_hex_digit) {
532 $type .= '_hex';
533 } else {
534 $type .= '_integer';
536 } elseif ($is_user) {
537 $type = 'punct_user';
538 } elseif ($is_sql_variable != FALSE) {
539 $type = 'alpha_variable';
540 } else {
541 $type = 'alpha';
542 } // end if... else....
543 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize, $count2);
545 continue;
548 // Checks for punct
549 if ($GLOBALS['PMA_strpos']($allpunct_list, $c) !== false) {
550 while (($count2 < $len) && $GLOBALS['PMA_strpos']($allpunct_list, $GLOBALS['PMA_substr']($sql, $count2, 1)) !== false) {
551 $count2++;
553 $l = $count2 - $count1;
554 if ($l == 1) {
555 $punct_data = $c;
556 } else {
557 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $l);
560 // Special case, sometimes, althought two characters are
561 // adjectent directly, they ACTUALLY need to be seperate
562 /* DEBUG
563 echo '<pre>';
564 var_dump($l);
565 var_dump($punct_data);
566 echo '</pre>';
569 if ($l == 1) {
570 $t_suffix = '';
571 switch ($punct_data) {
572 case $punct_queryend:
573 $t_suffix = '_queryend';
574 break;
575 case $punct_qualifier:
576 $t_suffix = '_qualifier';
577 $this_was_punct = true;
578 break;
579 case $punct_listsep:
580 $this_was_listsep = true;
581 $t_suffix = '_listsep';
582 break;
583 default:
584 break;
586 PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
587 } elseif ($punct_data == $GLOBALS['sql_delimiter'] || PMA_STR_binarySearchInArr($punct_data, $allpunct_list_pair, $allpunct_list_pair_size)) {
588 // Ok, we have one of the valid combined punct expressions
589 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
590 } else {
591 // Bad luck, lets split it up more
592 $first = $punct_data[0];
593 $first2 = $punct_data[0] . $punct_data[1];
594 $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
595 $last = $punct_data[$l - 1];
596 if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
597 $count2 = $count1 + 1;
598 $punct_data = $first;
599 } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || $GLOBALS['PMA_substr']($sql, $count2, 1) <= ' '))) {
600 $count2 -= 2;
601 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
602 } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
603 $count2--;
604 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
606 * @todo for negation operator, split in 2 tokens ?
607 * "select x&~1 from t"
608 * becomes "select x & ~ 1 from t" ?
611 } elseif ($last != '~') {
612 $debugstr = __('Unknown Punctuation String') . ' @ ' . ($count1+1) . "\n"
613 . 'STR: ' . htmlspecialchars($punct_data);
614 PMA_SQP_throwError($debugstr, $sql);
615 return $sql_array;
617 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
618 continue;
619 } // end if... elseif... else
620 continue;
623 // DEBUG
624 $count2++;
626 $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
627 . 'STR: ' . $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1) . "\n";
628 PMA_SQP_bug($debugstr, $sql);
629 return $sql_array;
631 } // end while ($count2 < $len)
634 echo '<pre>';
635 print_r($sql_array);
636 echo '</pre>';
639 if ($arraysize > 0) {
640 $t_next = $sql_array[0]['type'];
641 $t_prev = '';
642 $t_bef_prev = '';
643 $t_cur = '';
644 $d_next = $sql_array[0]['data'];
645 $d_prev = '';
646 $d_bef_prev = '';
647 $d_cur = '';
648 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
649 $d_prev_upper = '';
650 $d_bef_prev_upper = '';
651 $d_cur_upper = '';
654 for ($i = 0; $i < $arraysize; $i++) {
655 $t_bef_prev = $t_prev;
656 $t_prev = $t_cur;
657 $t_cur = $t_next;
658 $d_bef_prev = $d_prev;
659 $d_prev = $d_cur;
660 $d_cur = $d_next;
661 $d_bef_prev_upper = $d_prev_upper;
662 $d_prev_upper = $d_cur_upper;
663 $d_cur_upper = $d_next_upper;
664 if (($i + 1) < $arraysize) {
665 $t_next = $sql_array[$i + 1]['type'];
666 $d_next = $sql_array[$i + 1]['data'];
667 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
668 } else {
669 $t_next = '';
670 $d_next = '';
671 $d_next_upper = '';
674 //DEBUG echo "[prev: <strong>".$d_prev."</strong> ".$t_prev."][cur: <strong>".$d_cur."</strong> ".$t_cur."][next: <strong>".$d_next."</strong> ".$t_next."]<br />";
676 if ($t_cur == 'alpha') {
677 $t_suffix = '_identifier';
678 // for example: `thebit` bit(8) NOT NULL DEFAULT b'0'
679 if ($t_prev == 'alpha' && $d_prev == 'DEFAULT' && $d_cur == 'b' && $t_next == 'quote_single') {
680 $t_suffix = '_bitfield_constant_introducer';
681 } elseif (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
682 $t_suffix = '_identifier';
683 } elseif (($t_next == 'punct_bracket_open_round')
684 && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_function_name, $PMA_SQPdata_function_name_cnt)) {
686 * @todo 2005-10-16: in the case of a CREATE TABLE containing
687 * a TIMESTAMP, since TIMESTAMP() is also a function, it's
688 * found here and the token is wrongly marked as alpha_functionName.
689 * But we compensate for this when analysing for timestamp_not_null
690 * later in this script.
692 * Same applies to CHAR vs. CHAR() function.
694 $t_suffix = '_functionName';
695 /* There are functions which might be as well column types */
696 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt)) {
697 $t_suffix = '_columnType';
700 * Temporary fix for BUG #621357 and #2027720
702 * @todo FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
704 if (($d_cur_upper == 'SET' || $d_cur_upper == 'BINARY') && $t_next != 'punct_bracket_open_round') {
705 $t_suffix = '_reservedWord';
707 //END OF TEMPORARY FIX
709 // CHARACTER is a synonym for CHAR, but can also be meant as
710 // CHARACTER SET. In this case, we have a reserved word.
711 if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
712 $t_suffix = '_reservedWord';
715 // experimental
716 // current is a column type, so previous must not be
717 // a reserved word but an identifier
718 // CREATE TABLE SG_Persons (first varchar(64))
720 //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
721 // $sql_array[$i-1]['type'] = 'alpha_identifier';
724 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_reserved_word, $PMA_SQPdata_reserved_word_cnt)) {
725 $t_suffix = '_reservedWord';
726 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_attrib, $PMA_SQPdata_column_attrib_cnt)) {
727 $t_suffix = '_columnAttrib';
728 // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
729 // it should be regarded as a reserved word.
730 if ($d_cur_upper == 'INNODB' && $d_prev_upper == 'SHOW' && $d_next_upper == 'STATUS') {
731 $t_suffix = '_reservedWord';
734 if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
735 $t_suffix = '_reservedWord';
737 // Binary as character set
738 if ($d_cur_upper == 'BINARY' && (
739 ($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
740 || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
741 || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
742 || $d_prev_upper == 'CHARSET'
743 ) && PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, count($mysql_charsets))) {
744 $t_suffix = '_charset';
746 } elseif (PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, $mysql_charsets_count)
747 || PMA_STR_binarySearchInArr($d_cur, $mysql_collations_flat, $mysql_collations_count)
748 || ($d_cur{0} == '_' && PMA_STR_binarySearchInArr(substr($d_cur, 1), $mysql_charsets, $mysql_charsets_count))) {
749 $t_suffix = '_charset';
750 } else {
751 // Do nothing
753 // check if present in the list of forbidden words
754 if ($t_suffix == '_reservedWord' && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt)) {
755 $sql_array[$i]['forbidden'] = TRUE;
756 } else {
757 $sql_array[$i]['forbidden'] = FALSE;
759 $sql_array[$i]['type'] .= $t_suffix;
761 } // end for
763 // Stores the size of the array inside the array, as count() is a slow
764 // operation.
765 $sql_array['len'] = $arraysize;
767 // DEBUG echo 'After parsing<pre>'; print_r($sql_array); echo '</pre>';
768 // Sends the data back
769 return $sql_array;
770 } // end of the "PMA_SQP_parse()" function
773 * Checks for token types being what we want...
775 * @param string String of type that we have
776 * @param string String of type that we want
778 * @return boolean result of check
780 * @access private
782 function PMA_SQP_typeCheck($toCheck, $whatWeWant)
784 $typeSeperator = '_';
785 if (strcmp($whatWeWant, $toCheck) == 0) {
786 return TRUE;
787 } else {
788 if (strpos($whatWeWant, $typeSeperator) === FALSE) {
789 return strncmp($whatWeWant, $toCheck, strpos($toCheck, $typeSeperator)) == 0;
790 } else {
791 return FALSE;
798 * Analyzes SQL queries
800 * @param array The SQL queries
802 * @return array The analyzed SQL queries
804 * @access public
806 function PMA_SQP_analyze($arr)
808 if ($arr == array()) {
809 return array();
811 $result = array();
812 $size = $arr['len'];
813 $subresult = array(
814 'querytype' => '',
815 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
816 'position_of_first_select' => '', // the array index
817 'from_clause'=> '',
818 'group_by_clause'=> '',
819 'order_by_clause'=> '',
820 'having_clause' => '',
821 'limit_clause' => '',
822 'where_clause' => '',
823 'where_clause_identifiers' => array(),
824 'unsorted_query' => '',
825 'queryflags' => array(),
826 'select_expr' => array(),
827 'table_ref' => array(),
828 'foreign_keys' => array(),
829 'create_table_fields' => array()
831 $subresult_empty = $subresult;
832 $seek_queryend = FALSE;
833 $seen_end_of_table_ref = FALSE;
834 $number_of_brackets_in_extract = 0;
835 $number_of_brackets_in_group_concat = 0;
837 $number_of_brackets = 0;
838 $in_subquery = false;
839 $seen_subquery = false;
840 $seen_from = false;
842 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
843 // we must not use CURDATE as a table_ref
844 // so we track whether we are in the EXTRACT()
845 $in_extract = FALSE;
847 // for GROUP_CONCAT(...)
848 $in_group_concat = FALSE;
850 /* Description of analyzer results
852 * db, table, column, alias
853 * ------------------------
855 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
857 * The SELECT syntax (simplified) is
859 * SELECT
860 * select_expression,...
861 * [FROM [table_references]
864 * ['select_expr'] is filled with each expression, the key represents the
865 * expression position in the list (0-based) (so we don't lose track of
866 * multiple occurences of the same column).
868 * ['table_ref'] is filled with each table ref, same thing for the key.
870 * I create all sub-values empty, even if they are
871 * not present (for example no select_expression alias).
873 * There is a debug section at the end of loop #1, if you want to
874 * see the exact contents of select_expr and table_ref
876 * queryflags
877 * ----------
879 * In $subresult, array 'queryflags' is filled, according to what we
880 * find in the query.
882 * Currently, those are generated:
884 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
885 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
886 * ['queryflags']['distinct'] = 1; for a DISTINCT
887 * ['queryflags']['union'] = 1; for a UNION
888 * ['queryflags']['join'] = 1; for a JOIN
889 * ['queryflags']['offset'] = 1; for the presence of OFFSET
890 * ['queryflags']['procedure'] = 1; for the presence of PROCEDURE
892 * query clauses
893 * -------------
895 * The select is splitted in those clauses:
896 * ['select_expr_clause']
897 * ['from_clause']
898 * ['group_by_clause']
899 * ['order_by_clause']
900 * ['having_clause']
901 * ['limit_clause']
902 * ['where_clause']
904 * The identifiers of the WHERE clause are put into the array
905 * ['where_clause_identifier']
907 * For a SELECT, the whole query without the ORDER BY clause is put into
908 * ['unsorted_query']
910 * foreign keys
911 * ------------
912 * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
913 * analyzed and ['foreign_keys'] is an array filled with
914 * the constraint name, the index list,
915 * the REFERENCES table name and REFERENCES index list,
916 * and ON UPDATE | ON DELETE clauses
918 * position_of_first_select
919 * ------------------------
921 * The array index of the first SELECT we find. Will be used to
922 * insert a SQL_CALC_FOUND_ROWS.
924 * create_table_fields
925 * -------------------
927 * Used to detect the DEFAULT CURRENT_TIMESTAMP and
928 * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
929 * Also used to store the default value of the field.
930 * An array, each element is the identifier name.
931 * Note that for now, the timestamp_not_null element is created
932 * even for non-TIMESTAMP fields.
934 * Sub-elements: ['type'] which contains the column type
935 * optional (currently they are never false but can be absent):
936 * ['default_current_timestamp'] boolean
937 * ['on_update_current_timestamp'] boolean
938 * ['timestamp_not_null'] boolean
940 * section_before_limit, section_after_limit
941 * -----------------------------------------
943 * Marks the point of the query where we can insert a LIMIT clause;
944 * so the section_before_limit will contain the left part before
945 * a possible LIMIT clause
948 * End of description of analyzer results
951 // must be sorted
952 // TODO: current logic checks for only one word, so I put only the
953 // first word of the reserved expressions that end a table ref;
954 // maybe this is not ok (the first word might mean something else)
955 // $words_ending_table_ref = array(
956 // 'FOR UPDATE',
957 // 'GROUP BY',
958 // 'HAVING',
959 // 'LIMIT',
960 // 'LOCK IN SHARE MODE',
961 // 'ORDER BY',
962 // 'PROCEDURE',
963 // 'UNION',
964 // 'WHERE'
965 // );
966 $words_ending_table_ref = array(
967 'FOR',
968 'GROUP',
969 'HAVING',
970 'LIMIT',
971 'LOCK',
972 'ORDER',
973 'PROCEDURE',
974 'UNION',
975 'WHERE'
977 $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
979 $words_ending_clauses = array(
980 'FOR',
981 'LIMIT',
982 'LOCK',
983 'PROCEDURE',
984 'UNION'
986 $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
991 // must be sorted
992 $supported_query_types = array(
993 'SELECT'
995 // Support for these additional query types will come later on.
996 'DELETE',
997 'INSERT',
998 'REPLACE',
999 'TRUNCATE',
1000 'UPDATE'
1001 'EXPLAIN',
1002 'DESCRIBE',
1003 'SHOW',
1004 'CREATE',
1005 'SET',
1006 'ALTER'
1009 $supported_query_types_cnt = count($supported_query_types);
1011 // loop #1 for each token: select_expr, table_ref for SELECT
1013 for ($i = 0; $i < $size; $i++) {
1014 //DEBUG echo "Loop1 <strong>" . $arr[$i]['data'] . "</strong> (" . $arr[$i]['type'] . ")<br />";
1016 // High speed seek for locating the end of the current query
1017 if ($seek_queryend == TRUE) {
1018 if ($arr[$i]['type'] == 'punct_queryend') {
1019 $seek_queryend = FALSE;
1020 } else {
1021 continue;
1022 } // end if (type == punct_queryend)
1023 } // end if ($seek_queryend)
1026 * Note: do not split if this is a punct_queryend for the first and only query
1027 * @todo when we find a UNION, should we split in another subresult?
1029 if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
1030 $result[] = $subresult;
1031 $subresult = $subresult_empty;
1032 continue;
1033 } // end if (type == punct_queryend)
1035 // ==============================================================
1036 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1037 $number_of_brackets++;
1038 if ($in_extract) {
1039 $number_of_brackets_in_extract++;
1041 if ($in_group_concat) {
1042 $number_of_brackets_in_group_concat++;
1045 // ==============================================================
1046 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1047 $number_of_brackets--;
1048 if ($number_of_brackets == 0) {
1049 $in_subquery = false;
1051 if ($in_extract) {
1052 $number_of_brackets_in_extract--;
1053 if ($number_of_brackets_in_extract == 0) {
1054 $in_extract = FALSE;
1057 if ($in_group_concat) {
1058 $number_of_brackets_in_group_concat--;
1059 if ($number_of_brackets_in_group_concat == 0) {
1060 $in_group_concat = FALSE;
1065 if ($in_subquery) {
1067 * skip the subquery to avoid setting
1068 * select_expr or table_ref with the contents
1069 * of this subquery; this is to avoid a bug when
1070 * trying to edit the results of
1071 * select * from child where not exists (select id from
1072 * parent where child.parent_id = parent.id);
1074 continue;
1076 // ==============================================================
1077 if ($arr[$i]['type'] == 'alpha_functionName') {
1078 $upper_data = strtoupper($arr[$i]['data']);
1079 if ($upper_data =='EXTRACT') {
1080 $in_extract = TRUE;
1081 $number_of_brackets_in_extract = 0;
1083 if ($upper_data =='GROUP_CONCAT') {
1084 $in_group_concat = TRUE;
1085 $number_of_brackets_in_group_concat = 0;
1089 // ==============================================================
1090 if ($arr[$i]['type'] == 'alpha_reservedWord'
1091 // && $arr[$i]['forbidden'] == FALSE) {
1093 // We don't know what type of query yet, so run this
1094 if ($subresult['querytype'] == '') {
1095 $subresult['querytype'] = strtoupper($arr[$i]['data']);
1096 } // end if (querytype was empty)
1098 // Check if we support this type of query
1099 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
1100 // Skip ahead to the next one if we don't
1101 $seek_queryend = TRUE;
1102 continue;
1103 } // end if (query not supported)
1105 // upper once
1106 $upper_data = strtoupper($arr[$i]['data']);
1108 * @todo reset for each query?
1111 if ($upper_data == 'SELECT') {
1112 if ($number_of_brackets > 0) {
1113 $in_subquery = true;
1114 $seen_subquery = true;
1115 // this is a subquery so do not analyze inside it
1116 continue;
1118 $seen_from = FALSE;
1119 $previous_was_identifier = FALSE;
1120 $current_select_expr = -1;
1121 $seen_end_of_table_ref = FALSE;
1122 } // end if (data == SELECT)
1124 if ($upper_data =='FROM' && !$in_extract) {
1125 $current_table_ref = -1;
1126 $seen_from = TRUE;
1127 $previous_was_identifier = FALSE;
1128 $save_table_ref = TRUE;
1129 } // end if (data == FROM)
1131 // here, do not 'continue' the loop, as we have more work for
1132 // reserved words below
1133 } // end if (type == alpha_reservedWord)
1135 // ==============================
1136 if ($arr[$i]['type'] == 'quote_backtick'
1137 || $arr[$i]['type'] == 'quote_double'
1138 || $arr[$i]['type'] == 'quote_single'
1139 || $arr[$i]['type'] == 'alpha_identifier'
1140 || ($arr[$i]['type'] == 'alpha_reservedWord'
1141 && $arr[$i]['forbidden'] == FALSE)) {
1143 switch ($arr[$i]['type']) {
1144 case 'alpha_identifier':
1145 case 'alpha_reservedWord':
1147 * this is not a real reservedWord, because it's not
1148 * present in the list of forbidden words, for example
1149 * "storage" which can be used as an identifier
1151 * @todo avoid the pretty printing in color in this case
1153 $identifier = $arr[$i]['data'];
1154 break;
1156 case 'quote_backtick':
1157 case 'quote_double':
1158 case 'quote_single':
1159 $identifier = PMA_unQuote($arr[$i]['data']);
1160 break;
1161 } // end switch
1163 if ($subresult['querytype'] == 'SELECT'
1164 && ! $in_group_concat
1165 && ! ($seen_subquery && $arr[$i - 1]['type'] == 'punct_bracket_close_round')) {
1166 if (!$seen_from) {
1167 if ($previous_was_identifier && isset($chain)) {
1168 // found alias for this select_expr, save it
1169 // but only if we got something in $chain
1170 // (for example, SELECT COUNT(*) AS cnt
1171 // puts nothing in $chain, so we avoid
1172 // setting the alias)
1173 $alias_for_select_expr = $identifier;
1174 } else {
1175 $chain[] = $identifier;
1176 $previous_was_identifier = TRUE;
1178 } // end if !$previous_was_identifier
1179 } else {
1180 // ($seen_from)
1181 if ($save_table_ref && !$seen_end_of_table_ref) {
1182 if ($previous_was_identifier) {
1183 // found alias for table ref
1184 // save it for later
1185 $alias_for_table_ref = $identifier;
1186 } else {
1187 $chain[] = $identifier;
1188 $previous_was_identifier = TRUE;
1190 } // end if ($previous_was_identifier)
1191 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
1192 } // end if (!$seen_from)
1193 } // end if (querytype SELECT)
1194 } // end if (quote_backtick or double quote or alpha_identifier)
1196 // ===================================
1197 if ($arr[$i]['type'] == 'punct_qualifier') {
1198 // to be able to detect an identifier following another
1199 $previous_was_identifier = FALSE;
1200 continue;
1201 } // end if (punct_qualifier)
1204 * @todo check if 3 identifiers following one another -> error
1207 // s a v e a s e l e c t e x p r
1208 // finding a list separator or FROM
1209 // means that we must save the current chain of identifiers
1210 // into a select expression
1212 // for now, we only save a select expression if it contains
1213 // at least one identifier, as we are interested in checking
1214 // the columns and table names, so in "select * from persons",
1215 // the "*" is not saved
1217 if (isset($chain) && !$seen_end_of_table_ref
1218 && ((!$seen_from && $arr[$i]['type'] == 'punct_listsep')
1219 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM'))) {
1220 $size_chain = count($chain);
1221 $current_select_expr++;
1222 $subresult['select_expr'][$current_select_expr] = array(
1223 'expr' => '',
1224 'alias' => '',
1225 'db' => '',
1226 'table_name' => '',
1227 'table_true_name' => '',
1228 'column' => ''
1231 if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
1232 // we had found an alias for this select expression
1233 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
1234 unset($alias_for_select_expr);
1236 // there is at least a column
1237 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
1238 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
1240 // maybe a table
1241 if ($size_chain > 1) {
1242 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
1243 // we assume for now that this is also the true name
1244 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
1245 $subresult['select_expr'][$current_select_expr]['expr']
1246 = $subresult['select_expr'][$current_select_expr]['table_name']
1247 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1248 } // end if ($size_chain > 1)
1250 // maybe a db
1251 if ($size_chain > 2) {
1252 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
1253 $subresult['select_expr'][$current_select_expr]['expr']
1254 = $subresult['select_expr'][$current_select_expr]['db']
1255 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1256 } // end if ($size_chain > 2)
1257 unset($chain);
1260 * @todo explain this:
1262 if (($arr[$i]['type'] == 'alpha_reservedWord')
1263 && ($upper_data != 'FROM')) {
1264 $previous_was_identifier = TRUE;
1267 } // end if (save a select expr)
1270 //======================================
1271 // s a v e a t a b l e r e f
1272 //======================================
1274 // maybe we just saw the end of table refs
1275 // but the last table ref has to be saved
1276 // or we are at the last token
1277 // or we just got a reserved word
1279 * @todo there could be another query after this one
1282 if (isset($chain) && $seen_from && $save_table_ref
1283 && ($arr[$i]['type'] == 'punct_listsep'
1284 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
1285 || $seen_end_of_table_ref
1286 || $i==$size-1)) {
1288 $size_chain = count($chain);
1289 $current_table_ref++;
1290 $subresult['table_ref'][$current_table_ref] = array(
1291 'expr' => '',
1292 'db' => '',
1293 'table_name' => '',
1294 'table_alias' => '',
1295 'table_true_name' => ''
1297 if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
1298 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1299 unset($alias_for_table_ref);
1301 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1302 // we assume for now that this is also the true name
1303 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1304 $subresult['table_ref'][$current_table_ref]['expr']
1305 = $subresult['table_ref'][$current_table_ref]['table_name'];
1306 // maybe a db
1307 if ($size_chain > 1) {
1308 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1309 $subresult['table_ref'][$current_table_ref]['expr']
1310 = $subresult['table_ref'][$current_table_ref]['db']
1311 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1312 } // end if ($size_chain > 1)
1314 // add the table alias into the whole expression
1315 $subresult['table_ref'][$current_table_ref]['expr']
1316 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1318 unset($chain);
1319 $previous_was_identifier = TRUE;
1320 //continue;
1322 } // end if (save a table ref)
1325 // when we have found all table refs,
1326 // for each table_ref alias, put the true name of the table
1327 // in the corresponding select expressions
1329 if (isset($current_table_ref) && ($seen_end_of_table_ref || $i == $size-1) && $subresult != $subresult_empty) {
1330 for ($tr=0; $tr <= $current_table_ref; $tr++) {
1331 $alias = $subresult['table_ref'][$tr]['table_alias'];
1332 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1333 for ($se=0; $se <= $current_select_expr; $se++) {
1334 if (isset($alias) && strlen($alias) && $subresult['select_expr'][$se]['table_true_name']
1335 == $alias) {
1336 $subresult['select_expr'][$se]['table_true_name']
1337 = $truename;
1338 } // end if (found the alias)
1339 } // end for (select expressions)
1341 } // end for (table refs)
1342 } // end if (set the true names)
1345 // e n d i n g l o o p #1
1346 // set the $previous_was_identifier to FALSE if the current
1347 // token is not an identifier
1348 if (($arr[$i]['type'] != 'alpha_identifier')
1349 && ($arr[$i]['type'] != 'quote_double')
1350 && ($arr[$i]['type'] != 'quote_single')
1351 && ($arr[$i]['type'] != 'quote_backtick')) {
1352 $previous_was_identifier = FALSE;
1353 } // end if
1355 // however, if we are on AS, we must keep the $previous_was_identifier
1356 if (($arr[$i]['type'] == 'alpha_reservedWord')
1357 && ($upper_data == 'AS')) {
1358 $previous_was_identifier = TRUE;
1361 if (($arr[$i]['type'] == 'alpha_reservedWord')
1362 && ($upper_data =='ON' || $upper_data =='USING')) {
1363 $save_table_ref = FALSE;
1364 } // end if (data == ON)
1366 if (($arr[$i]['type'] == 'alpha_reservedWord')
1367 && ($upper_data =='JOIN' || $upper_data =='FROM')) {
1368 $save_table_ref = TRUE;
1369 } // end if (data == JOIN)
1372 * no need to check the end of table ref if we already did
1374 * @todo maybe add "&& $seen_from"
1376 if (!$seen_end_of_table_ref) {
1377 // if this is the last token, it implies that we have
1378 // seen the end of table references
1379 // Check for the end of table references
1381 // Note: if we are analyzing a GROUP_CONCAT clause,
1382 // we might find a word that seems to indicate that
1383 // we have found the end of table refs (like ORDER)
1384 // but it's a modifier of the GROUP_CONCAT so
1385 // it's not the real end of table refs
1386 if (($i == $size-1)
1387 || ($arr[$i]['type'] == 'alpha_reservedWord'
1388 && !$in_group_concat
1389 && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
1390 $seen_end_of_table_ref = TRUE;
1391 // to be able to save the last table ref, but do not
1392 // set it true if we found a word like "ON" that has
1393 // already set it to false
1394 if (isset($save_table_ref) && $save_table_ref != FALSE) {
1395 $save_table_ref = TRUE;
1396 } //end if
1398 } // end if (check for end of table ref)
1399 } //end if (!$seen_end_of_table_ref)
1401 if ($seen_end_of_table_ref) {
1402 $save_table_ref = FALSE;
1403 } // end if
1405 } // end for $i (loop #1)
1407 //DEBUG
1409 if (isset($current_select_expr)) {
1410 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1411 echo "<br />";
1412 reset ($subresult['select_expr'][$trace]);
1413 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1414 echo "sel expr $trace $key => $val<br />\n";
1418 if (isset($current_table_ref)) {
1419 echo "current_table_ref = " . $current_table_ref . "<br>";
1420 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1422 echo "<br />";
1423 reset ($subresult['table_ref'][$trace]);
1424 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1425 echo "table ref $trace $key => $val<br />\n";
1429 // -------------------------------------------------------
1432 // loop #2: - queryflags
1433 // - querytype (for queries != 'SELECT')
1434 // - section_before_limit, section_after_limit
1436 // we will also need this queryflag in loop 2
1437 // so set it here
1438 if (isset($current_table_ref) && $current_table_ref > -1) {
1439 $subresult['queryflags']['select_from'] = 1;
1442 $section_before_limit = '';
1443 $section_after_limit = ''; // truly the section after the limit clause
1444 $seen_reserved_word = FALSE;
1445 $seen_group = FALSE;
1446 $seen_order = FALSE;
1447 $seen_order_by = FALSE;
1448 $in_group_by = FALSE; // true when we are inside the GROUP BY clause
1449 $in_order_by = FALSE; // true when we are inside the ORDER BY clause
1450 $in_having = FALSE; // true when we are inside the HAVING clause
1451 $in_select_expr = FALSE; // true when we are inside the select expr clause
1452 $in_where = FALSE; // true when we are inside the WHERE clause
1453 $seen_limit = FALSE; // true if we have seen a LIMIT clause
1454 $in_limit = FALSE; // true when we are inside the LIMIT clause
1455 $after_limit = FALSE; // true when we are after the LIMIT clause
1456 $in_from = FALSE; // true when we are in the FROM clause
1457 $in_group_concat = FALSE;
1458 $first_reserved_word = '';
1459 $current_identifier = '';
1460 $unsorted_query = $arr['raw']; // in case there is no ORDER BY
1461 $number_of_brackets = 0;
1462 $in_subquery = false;
1464 for ($i = 0; $i < $size; $i++) {
1465 //DEBUG echo "Loop2 <strong>" . $arr[$i]['data'] . "</strong> (" . $arr[$i]['type'] . ")<br />";
1467 // need_confirm
1469 // check for reserved words that will have to generate
1470 // a confirmation request later in sql.php
1471 // the cases are:
1472 // DROP TABLE
1473 // DROP DATABASE
1474 // ALTER TABLE... DROP
1475 // DELETE FROM...
1477 // this code is not used for confirmations coming from functions.js
1479 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1480 $number_of_brackets++;
1483 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1484 $number_of_brackets--;
1485 if ($number_of_brackets == 0) {
1486 $in_subquery = false;
1490 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1491 $upper_data = strtoupper($arr[$i]['data']);
1493 if ($upper_data == 'SELECT' && $number_of_brackets > 0) {
1494 $in_subquery = true;
1497 if (!$seen_reserved_word) {
1498 $first_reserved_word = $upper_data;
1499 $subresult['querytype'] = $upper_data;
1500 $seen_reserved_word = TRUE;
1502 // if the first reserved word is DROP or DELETE,
1503 // we know this is a query that needs to be confirmed
1504 if ($first_reserved_word=='DROP'
1505 || $first_reserved_word == 'DELETE'
1506 || $first_reserved_word == 'TRUNCATE') {
1507 $subresult['queryflags']['need_confirm'] = 1;
1510 if ($first_reserved_word=='SELECT'){
1511 $position_of_first_select = $i;
1514 } else {
1515 if ($upper_data == 'DROP' && $first_reserved_word == 'ALTER') {
1516 $subresult['queryflags']['need_confirm'] = 1;
1520 if ($upper_data == 'LIMIT' && ! $in_subquery) {
1521 $section_before_limit = substr($arr['raw'], 0, $arr[$i]['pos'] - 5);
1522 $in_limit = TRUE;
1523 $seen_limit = TRUE;
1524 $limit_clause = '';
1525 $in_order_by = FALSE; // @todo maybe others to set FALSE
1528 if ($upper_data == 'PROCEDURE') {
1529 $subresult['queryflags']['procedure'] = 1;
1530 $in_limit = FALSE;
1531 $after_limit = TRUE;
1534 * @todo set also to FALSE if we find FOR UPDATE or LOCK IN SHARE MODE
1536 if ($upper_data == 'SELECT') {
1537 $in_select_expr = TRUE;
1538 $select_expr_clause = '';
1540 if ($upper_data == 'DISTINCT' && !$in_group_concat) {
1541 $subresult['queryflags']['distinct'] = 1;
1544 if ($upper_data == 'UNION') {
1545 $subresult['queryflags']['union'] = 1;
1548 if ($upper_data == 'JOIN') {
1549 $subresult['queryflags']['join'] = 1;
1552 if ($upper_data == 'OFFSET') {
1553 $subresult['queryflags']['offset'] = 1;
1556 // if this is a real SELECT...FROM
1557 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1558 $in_from = TRUE;
1559 $from_clause = '';
1560 $in_select_expr = FALSE;
1564 // (we could have less resetting of variables to FALSE
1565 // if we trust that the query respects the standard
1566 // MySQL order for clauses)
1568 // we use $seen_group and $seen_order because we are looking
1569 // for the BY
1570 if ($upper_data == 'GROUP') {
1571 $seen_group = TRUE;
1572 $seen_order = FALSE;
1573 $in_having = FALSE;
1574 $in_order_by = FALSE;
1575 $in_where = FALSE;
1576 $in_select_expr = FALSE;
1577 $in_from = FALSE;
1579 if ($upper_data == 'ORDER' && !$in_group_concat) {
1580 $seen_order = TRUE;
1581 $seen_group = FALSE;
1582 $in_having = FALSE;
1583 $in_group_by = FALSE;
1584 $in_where = FALSE;
1585 $in_select_expr = FALSE;
1586 $in_from = FALSE;
1588 if ($upper_data == 'HAVING') {
1589 $in_having = TRUE;
1590 $having_clause = '';
1591 $seen_group = FALSE;
1592 $seen_order = FALSE;
1593 $in_group_by = FALSE;
1594 $in_order_by = FALSE;
1595 $in_where = FALSE;
1596 $in_select_expr = FALSE;
1597 $in_from = FALSE;
1600 if ($upper_data == 'WHERE') {
1601 $in_where = TRUE;
1602 $where_clause = '';
1603 $where_clause_identifiers = array();
1604 $seen_group = FALSE;
1605 $seen_order = FALSE;
1606 $in_group_by = FALSE;
1607 $in_order_by = FALSE;
1608 $in_having = FALSE;
1609 $in_select_expr = FALSE;
1610 $in_from = FALSE;
1613 if ($upper_data == 'BY') {
1614 if ($seen_group) {
1615 $in_group_by = TRUE;
1616 $group_by_clause = '';
1618 if ($seen_order) {
1619 $seen_order_by = TRUE;
1620 // Here we assume that the ORDER BY keywords took
1621 // exactly 8 characters.
1622 // We use PMA_substr() to be charset-safe; otherwise
1623 // if the table name contains accents, the unsorted
1624 // query would be missing some characters.
1625 $unsorted_query = PMA_substr($arr['raw'], 0, $arr[$i]['pos'] - 8);
1626 $in_order_by = TRUE;
1627 $order_by_clause = '';
1631 // if we find one of the words that could end the clause
1632 if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
1634 $in_group_by = FALSE;
1635 $in_order_by = FALSE;
1636 $in_having = FALSE;
1637 $in_where = FALSE;
1638 $in_select_expr = FALSE;
1639 $in_from = FALSE;
1642 } // endif (reservedWord)
1645 // do not add a space after a function name
1647 * @todo can we combine loop 2 and loop 1? some code is repeated here...
1650 $sep = ' ';
1651 if ($arr[$i]['type'] == 'alpha_functionName') {
1652 $sep='';
1653 $upper_data = strtoupper($arr[$i]['data']);
1654 if ($upper_data =='GROUP_CONCAT') {
1655 $in_group_concat = TRUE;
1656 $number_of_brackets_in_group_concat = 0;
1660 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1661 if ($in_group_concat) {
1662 $number_of_brackets_in_group_concat++;
1665 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1666 if ($in_group_concat) {
1667 $number_of_brackets_in_group_concat--;
1668 if ($number_of_brackets_in_group_concat == 0) {
1669 $in_group_concat = FALSE;
1674 // do not add a space after an identifier if followed by a dot
1675 if ($arr[$i]['type'] == 'alpha_identifier' && $i < $size - 1 && $arr[$i + 1]['data'] == '.') {
1676 $sep = '';
1679 // do not add a space after a dot if followed by an identifier
1680 if ($arr[$i]['data'] == '.' && $i < $size - 1 && $arr[$i + 1]['type'] == 'alpha_identifier') {
1681 $sep = '';
1684 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1685 $select_expr_clause .= $arr[$i]['data'] . $sep;
1687 if ($in_from && $upper_data != 'FROM') {
1688 $from_clause .= $arr[$i]['data'] . $sep;
1690 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1691 $group_by_clause .= $arr[$i]['data'] . $sep;
1693 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1694 // add a space only before ASC or DESC
1695 // not around the dot between dbname and tablename
1696 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1697 $order_by_clause .= $sep;
1699 $order_by_clause .= $arr[$i]['data'];
1701 if ($in_having && $upper_data != 'HAVING') {
1702 $having_clause .= $arr[$i]['data'] . $sep;
1704 if ($in_where && $upper_data != 'WHERE') {
1705 $where_clause .= $arr[$i]['data'] . $sep;
1707 if (($arr[$i]['type'] == 'quote_backtick')
1708 || ($arr[$i]['type'] == 'alpha_identifier')) {
1709 $where_clause_identifiers[] = $arr[$i]['data'];
1713 // to grab the rest of the query after the ORDER BY clause
1714 if (isset($subresult['queryflags']['select_from'])
1715 && $subresult['queryflags']['select_from'] == 1
1716 && ! $in_order_by
1717 && $seen_order_by
1718 && $upper_data != 'BY') {
1719 $unsorted_query .= $arr[$i]['data'];
1720 if ($arr[$i]['type'] != 'punct_bracket_open_round'
1721 && $arr[$i]['type'] != 'punct_bracket_close_round'
1722 && $arr[$i]['type'] != 'punct') {
1723 $unsorted_query .= $sep;
1727 if ($in_limit) {
1728 if ($upper_data == 'OFFSET') {
1729 $limit_clause .= $sep;
1731 $limit_clause .= $arr[$i]['data'];
1732 if ($upper_data == 'LIMIT' || $upper_data == 'OFFSET') {
1733 $limit_clause .= $sep;
1736 if ($after_limit && $seen_limit) {
1737 $section_after_limit .= $arr[$i]['data'] . $sep;
1740 // clear $upper_data for next iteration
1741 $upper_data='';
1742 } // end for $i (loop #2)
1743 if (empty($section_before_limit)) {
1744 $section_before_limit = $arr['raw'];
1747 // -----------------------------------------------------
1748 // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
1749 // (for now, check only the first query)
1750 // (for now, identifiers are assumed to be backquoted)
1752 // If we find that we are dealing with a CREATE TABLE query,
1753 // we look for the next punct_bracket_open_round, which
1754 // introduces the fields list. Then, when we find a
1755 // quote_backtick, it must be a field, so we put it into
1756 // the create_table_fields array. Even if this field is
1757 // not a timestamp, it will be useful when logic has been
1758 // added for complete field attributes analysis.
1760 $seen_foreign = FALSE;
1761 $seen_references = FALSE;
1762 $seen_constraint = FALSE;
1763 $foreign_key_number = -1;
1764 $seen_create_table = FALSE;
1765 $seen_create = FALSE;
1766 $seen_alter = FALSE;
1767 $in_create_table_fields = FALSE;
1768 $brackets_level = 0;
1769 $in_timestamp_options = FALSE;
1770 $seen_default = FALSE;
1772 for ($i = 0; $i < $size; $i++) {
1773 // DEBUG echo "Loop 3 <strong>" . $arr[$i]['data'] . "</strong> " . $arr[$i]['type'] . "<br />";
1775 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1776 $upper_data = strtoupper($arr[$i]['data']);
1778 if ($upper_data == 'NOT' && $in_timestamp_options) {
1779 $create_table_fields[$current_identifier]['timestamp_not_null'] = TRUE;
1783 if ($upper_data == 'CREATE') {
1784 $seen_create = TRUE;
1787 if ($upper_data == 'ALTER') {
1788 $seen_alter = TRUE;
1791 if ($upper_data == 'TABLE' && $seen_create) {
1792 $seen_create_table = TRUE;
1793 $create_table_fields = array();
1796 if ($upper_data == 'CURRENT_TIMESTAMP') {
1797 if ($in_timestamp_options) {
1798 if ($seen_default) {
1799 $create_table_fields[$current_identifier]['default_current_timestamp'] = TRUE;
1804 if ($upper_data == 'CONSTRAINT') {
1805 $foreign_key_number++;
1806 $seen_foreign = FALSE;
1807 $seen_references = FALSE;
1808 $seen_constraint = TRUE;
1810 if ($upper_data == 'FOREIGN') {
1811 $seen_foreign = TRUE;
1812 $seen_references = FALSE;
1813 $seen_constraint = FALSE;
1815 if ($upper_data == 'REFERENCES') {
1816 $seen_foreign = FALSE;
1817 $seen_references = TRUE;
1818 $seen_constraint = FALSE;
1822 // Cases covered:
1824 // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1825 // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1827 // but we set ['on_delete'] or ['on_cascade'] to
1828 // CASCADE | SET_NULL | NO_ACTION | RESTRICT
1830 // ON UPDATE CURRENT_TIMESTAMP
1832 if ($upper_data == 'ON') {
1833 if (isset($arr[$i+1]) && $arr[$i+1]['type'] == 'alpha_reservedWord') {
1834 $second_upper_data = strtoupper($arr[$i+1]['data']);
1835 if ($second_upper_data == 'DELETE') {
1836 $clause = 'on_delete';
1838 if ($second_upper_data == 'UPDATE') {
1839 $clause = 'on_update';
1841 if (isset($clause)
1842 && ($arr[$i+2]['type'] == 'alpha_reservedWord'
1844 // ugly workaround because currently, NO is not
1845 // in the list of reserved words in sqlparser.data
1846 // (we got a bug report about not being able to use
1847 // 'no' as an identifier)
1848 || ($arr[$i+2]['type'] == 'alpha_identifier'
1849 && strtoupper($arr[$i+2]['data'])=='NO'))
1851 $third_upper_data = strtoupper($arr[$i+2]['data']);
1852 if ($third_upper_data == 'CASCADE'
1853 || $third_upper_data == 'RESTRICT') {
1854 $value = $third_upper_data;
1855 } elseif ($third_upper_data == 'SET'
1856 || $third_upper_data == 'NO') {
1857 if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
1858 $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
1860 } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
1861 if ($clause == 'on_update'
1862 && $in_timestamp_options) {
1863 $create_table_fields[$current_identifier]['on_update_current_timestamp'] = TRUE;
1864 $seen_default = FALSE;
1867 } else {
1868 $value = '';
1870 if (!empty($value)) {
1871 $foreign[$foreign_key_number][$clause] = $value;
1873 unset($clause);
1874 } // endif (isset($clause))
1878 } // end of reserved words analysis
1881 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1882 $brackets_level++;
1883 if ($seen_create_table && $brackets_level == 1) {
1884 $in_create_table_fields = TRUE;
1889 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1890 $brackets_level--;
1891 if ($seen_references) {
1892 $seen_references = FALSE;
1894 if ($seen_create_table && $brackets_level == 0) {
1895 $in_create_table_fields = FALSE;
1899 if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
1900 $upper_data = strtoupper($arr[$i]['data']);
1901 if ($seen_create_table && $in_create_table_fields) {
1902 if ($upper_data == 'DEFAULT') {
1903 $seen_default = TRUE;
1904 $create_table_fields[$current_identifier]['default_value'] = $arr[$i + 1]['data'];
1910 * @see @todo 2005-10-16 note: the "or" part here is a workaround for a bug
1912 if (($arr[$i]['type'] == 'alpha_columnType') || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)) {
1913 $upper_data = strtoupper($arr[$i]['data']);
1914 if ($seen_create_table && $in_create_table_fields && isset($current_identifier)) {
1915 $create_table_fields[$current_identifier]['type'] = $upper_data;
1916 if ($upper_data == 'TIMESTAMP') {
1917 $arr[$i]['type'] = 'alpha_columnType';
1918 $in_timestamp_options = TRUE;
1919 } else {
1920 $in_timestamp_options = FALSE;
1921 if ($upper_data == 'CHAR') {
1922 $arr[$i]['type'] = 'alpha_columnType';
1929 if ($arr[$i]['type'] == 'quote_backtick' || $arr[$i]['type'] == 'alpha_identifier') {
1931 if ($arr[$i]['type'] == 'quote_backtick') {
1932 // remove backquotes
1933 $identifier = PMA_unQuote($arr[$i]['data']);
1934 } else {
1935 $identifier = $arr[$i]['data'];
1938 if ($seen_create_table && $in_create_table_fields) {
1939 $current_identifier = $identifier;
1940 // warning: we set this one even for non TIMESTAMP type
1941 $create_table_fields[$current_identifier]['timestamp_not_null'] = FALSE;
1944 if ($seen_constraint) {
1945 $foreign[$foreign_key_number]['constraint'] = $identifier;
1948 if ($seen_foreign && $brackets_level > 0) {
1949 $foreign[$foreign_key_number]['index_list'][] = $identifier;
1952 if ($seen_references) {
1953 if ($seen_alter && $brackets_level > 0) {
1954 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1955 // here, the first bracket level corresponds to the
1956 // bracket of CREATE TABLE
1957 // so if we are on level 2, it must be the index list
1958 // of the foreign key REFERENCES
1959 } elseif ($brackets_level > 1) {
1960 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1961 } elseif ($arr[$i+1]['type'] == 'punct_qualifier') {
1962 // identifier is `db`.`table`
1963 // the first pass will pick the db name
1964 // the next pass will pick the table name
1965 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
1966 } else {
1967 // identifier is `table`
1968 $foreign[$foreign_key_number]['ref_table_name'] = $identifier;
1972 } // end for $i (loop #3)
1975 // Fill the $subresult array
1977 if (isset($create_table_fields)) {
1978 $subresult['create_table_fields'] = $create_table_fields;
1981 if (isset($foreign)) {
1982 $subresult['foreign_keys'] = $foreign;
1985 if (isset($select_expr_clause)) {
1986 $subresult['select_expr_clause'] = $select_expr_clause;
1988 if (isset($from_clause)) {
1989 $subresult['from_clause'] = $from_clause;
1991 if (isset($group_by_clause)) {
1992 $subresult['group_by_clause'] = $group_by_clause;
1994 if (isset($order_by_clause)) {
1995 $subresult['order_by_clause'] = $order_by_clause;
1997 if (isset($having_clause)) {
1998 $subresult['having_clause'] = $having_clause;
2000 if (isset($limit_clause)) {
2001 $subresult['limit_clause'] = $limit_clause;
2003 if (isset($where_clause)) {
2004 $subresult['where_clause'] = $where_clause;
2006 if (isset($unsorted_query) && !empty($unsorted_query)) {
2007 $subresult['unsorted_query'] = $unsorted_query;
2009 if (isset($where_clause_identifiers)) {
2010 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
2013 if (isset($position_of_first_select)) {
2014 $subresult['position_of_first_select'] = $position_of_first_select;
2015 $subresult['section_before_limit'] = $section_before_limit;
2016 $subresult['section_after_limit'] = $section_after_limit;
2019 // They are naughty and didn't have a trailing semi-colon,
2020 // then still handle it properly
2021 if ($subresult['querytype'] != '') {
2022 $result[] = $subresult;
2024 return $result;
2025 } // end of the "PMA_SQP_analyze()" function
2029 * Colorizes SQL queries html formatted
2031 * @todo check why adding a "\n" after the </span> would cause extra blanks
2032 * to be displayed: SELECT p . person_name
2033 * @param array The SQL queries html formatted
2035 * @return array The colorized SQL queries
2037 * @access public
2039 function PMA_SQP_formatHtml_colorize($arr)
2041 $i = $GLOBALS['PMA_strpos']($arr['type'], '_');
2042 $class = '';
2043 if ($i > 0) {
2044 $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
2047 $class .= 'syntax_' . $arr['type'];
2049 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
2050 } // end of the "PMA_SQP_formatHtml_colorize()" function
2054 * Formats SQL queries to html
2056 * @param array The SQL queries
2057 * @param string mode
2058 * @param integer starting token
2059 * @param integer number of tokens to format, -1 = all
2061 * @return string The formatted SQL queries
2063 * @access public
2065 function PMA_SQP_formatHtml($arr, $mode='color', $start_token=0,
2066 $number_of_tokens=-1)
2068 //DEBUG echo 'in Format<pre>'; print_r($arr); echo '</pre>';
2069 // then check for an array
2070 if (!is_array($arr)) {
2071 return htmlspecialchars($arr);
2073 // first check for the SQL parser having hit an error
2074 if (PMA_SQP_isError()) {
2075 return htmlspecialchars($arr['raw']);
2077 // else do it properly
2078 switch ($mode) {
2079 case 'color':
2080 $str = '<span class="syntax">';
2081 $html_line_break = '<br />';
2082 $docu = TRUE;
2083 break;
2084 case 'query_only':
2085 $str = '';
2086 $html_line_break = "\n";
2087 $docu = FALSE;
2088 break;
2089 case 'text':
2090 $str = '';
2091 $html_line_break = '<br />';
2092 $docu = TRUE;
2093 break;
2094 } // end switch
2095 $close_docu_link = false;
2096 $indent = 0;
2097 $bracketlevel = 0;
2098 $functionlevel = 0;
2099 $infunction = FALSE;
2100 $space_punct_listsep = ' ';
2101 $space_punct_listsep_function_name = ' ';
2102 // $space_alpha_reserved_word = '<br />'."\n";
2103 $space_alpha_reserved_word = ' ';
2105 $keywords_with_brackets_1before = array(
2106 'INDEX',
2107 'KEY',
2108 'ON',
2109 'USING'
2111 $keywords_with_brackets_1before_cnt = 4;
2113 $keywords_with_brackets_2before = array(
2114 'IGNORE',
2115 'INDEX',
2116 'INTO',
2117 'KEY',
2118 'PRIMARY',
2119 'PROCEDURE',
2120 'REFERENCES',
2121 'UNIQUE',
2122 'USE'
2124 // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
2125 $keywords_with_brackets_2before_cnt = 9;
2127 // These reserved words do NOT get a newline placed near them.
2128 $keywords_no_newline = array(
2129 'AS',
2130 'ASC',
2131 'DESC',
2132 'DISTINCT',
2133 'DUPLICATE',
2134 'HOUR',
2135 'INTERVAL',
2136 'IS',
2137 'LIKE',
2138 'NOT',
2139 'NULL',
2140 'ON',
2141 'REGEXP'
2143 $keywords_no_newline_cnt = 12;
2145 // These reserved words introduce a privilege list
2146 $keywords_priv_list = array(
2147 'GRANT',
2148 'REVOKE'
2150 $keywords_priv_list_cnt = 2;
2152 if ($number_of_tokens == -1) {
2153 $arraysize = $arr['len'];
2154 } else {
2155 $arraysize = $number_of_tokens;
2157 $typearr = array();
2158 if ($arraysize >= 0) {
2159 $typearr[0] = '';
2160 $typearr[1] = '';
2161 $typearr[2] = '';
2162 //$typearr[3] = $arr[0]['type'];
2163 $typearr[3] = $arr[$start_token]['type'];
2166 $in_priv_list = FALSE;
2167 for ($i = $start_token; $i < $arraysize; $i++) {
2168 // DEBUG echo "Loop format <strong>" . $arr[$i]['data'] . "</strong> " . $arr[$i]['type'] . "<br />";
2169 $before = '';
2170 $after = '';
2171 $indent = 0;
2172 // array_shift($typearr);
2174 0 prev2
2175 1 prev
2176 2 current
2177 3 next
2179 if (($i + 1) < $arraysize) {
2180 // array_push($typearr, $arr[$i + 1]['type']);
2181 $typearr[4] = $arr[$i + 1]['type'];
2182 } else {
2183 //array_push($typearr, null);
2184 $typearr[4] = '';
2187 for ($j=0; $j<4; $j++) {
2188 $typearr[$j] = $typearr[$j + 1];
2191 switch ($typearr[2]) {
2192 case 'alpha_bitfield_constant_introducer':
2193 $before = ' ';
2194 $after = '';
2195 break;
2196 case 'white_newline':
2197 $before = '';
2198 break;
2199 case 'punct_bracket_open_round':
2200 $bracketlevel++;
2201 $infunction = FALSE;
2202 // Make sure this array is sorted!
2203 if (($typearr[1] == 'alpha_functionName') || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
2204 || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex') || ($typearr[3] == 'digit_float')
2205 || (($typearr[0] == 'alpha_reservedWord')
2206 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
2207 || (($typearr[1] == 'alpha_reservedWord')
2208 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
2210 $functionlevel++;
2211 $infunction = TRUE;
2212 $after .= ' ';
2213 } else {
2214 $indent++;
2215 $after .= ($mode != 'query_only' ? '<div class="syntax_indent' . $indent . '">' : ' ');
2217 break;
2218 case 'alpha_identifier':
2219 if (($typearr[1] == 'punct_qualifier') || ($typearr[3] == 'punct_qualifier')) {
2220 $after = '';
2221 $before = '';
2223 if (($typearr[3] == 'alpha_columnType') || ($typearr[3] == 'alpha_identifier')) {
2224 $after .= ' ';
2226 break;
2227 case 'punct_user':
2228 case 'punct_qualifier':
2229 $before = '';
2230 $after = '';
2231 break;
2232 case 'punct_listsep':
2233 if ($infunction == TRUE) {
2234 $after .= $space_punct_listsep_function_name;
2235 } else {
2236 $after .= $space_punct_listsep;
2238 break;
2239 case 'punct_queryend':
2240 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi') && $typearr[3] != 'comment_c') {
2241 $after .= $html_line_break;
2242 $after .= $html_line_break;
2244 $space_punct_listsep = ' ';
2245 $space_punct_listsep_function_name = ' ';
2246 $space_alpha_reserved_word = ' ';
2247 $in_priv_list = FALSE;
2248 break;
2249 case 'comment_mysql':
2250 case 'comment_ansi':
2251 $after .= $html_line_break;
2252 break;
2253 case 'punct':
2254 $before .= ' ';
2255 // workaround for
2256 // select * from mytable limit 0,-1
2257 // (a side effect of this workaround is that
2258 // select 20 - 9
2259 // becomes
2260 // select 20 -9
2261 // )
2262 if ($typearr[3] != 'digit_integer') {
2263 $after .= ' ';
2265 break;
2266 case 'punct_bracket_close_round':
2267 $bracketlevel--;
2268 if ($infunction == TRUE) {
2269 $functionlevel--;
2270 $after .= ' ';
2271 $before .= ' ';
2272 } else {
2273 $indent--;
2274 $before .= ($mode != 'query_only' ? '</div>' : ' ');
2276 $infunction = ($functionlevel > 0) ? TRUE : FALSE;
2277 break;
2278 case 'alpha_columnType':
2279 if ($docu) {
2280 switch ($arr[$i]['data']) {
2281 case 'tinyint':
2282 case 'smallint':
2283 case 'mediumint':
2284 case 'int':
2285 case 'bigint':
2286 case 'decimal':
2287 case 'float':
2288 case 'double':
2289 case 'real':
2290 case 'bit':
2291 case 'boolean':
2292 case 'serial':
2293 $before .= PMA_showMySQLDocu('data-types', 'numeric-types', false, '', true);
2294 $after = '</a>' . $after;
2295 break;
2296 case 'date':
2297 case 'datetime':
2298 case 'timestamp':
2299 case 'time':
2300 case 'year':
2301 $before .= PMA_showMySQLDocu('data-types', 'date-and-time-types', false, '', true);
2302 $after = '</a>' . $after;
2303 break;
2304 case 'char':
2305 case 'varchar':
2306 case 'tinytext':
2307 case 'text':
2308 case 'mediumtext':
2309 case 'longtext':
2310 case 'binary':
2311 case 'varbinary':
2312 case 'tinyblob':
2313 case 'mediumblob':
2314 case 'blob':
2315 case 'longblob':
2316 case 'enum':
2317 case 'set':
2318 $before .= PMA_showMySQLDocu('data-types', 'string-types', false, '', true);
2319 $after = '</a>' . $after;
2320 break;
2323 if ($typearr[3] == 'alpha_columnAttrib') {
2324 $after .= ' ';
2326 if ($typearr[1] == 'alpha_columnType') {
2327 $before .= ' ';
2329 break;
2330 case 'alpha_columnAttrib':
2332 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
2333 // COLLATE LATIN1_GENERAL_CI DEFAULT
2334 if ($typearr[1] == 'alpha_identifier' || $typearr[1] == 'alpha_charset') {
2335 $before .= ' ';
2337 if (($typearr[3] == 'alpha_columnAttrib') || ($typearr[3] == 'quote_single') || ($typearr[3] == 'digit_integer')) {
2338 $after .= ' ';
2340 // workaround for
2341 // AUTO_INCREMENT = 31DEFAULT_CHARSET = utf-8
2343 if ($typearr[2] == 'alpha_columnAttrib' && $typearr[3] == 'alpha_reservedWord') {
2344 $before .= ' ';
2346 // workaround for
2347 // select * from mysql.user where binary user="root"
2348 // binary is marked as alpha_columnAttrib
2349 // but should be marked as a reserved word
2350 if (strtoupper($arr[$i]['data']) == 'BINARY'
2351 && $typearr[3] == 'alpha_identifier') {
2352 $after .= ' ';
2354 break;
2355 case 'alpha_reservedWord':
2356 // do not uppercase the reserved word if we are calling
2357 // this function in query_only mode, because we need
2358 // the original query (otherwise we get problems with
2359 // semi-reserved words like "storage" which is legal
2360 // as an identifier name)
2362 if ($mode != 'query_only') {
2363 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
2366 if ((($typearr[1] != 'alpha_reservedWord')
2367 || (($typearr[1] == 'alpha_reservedWord')
2368 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
2369 && ($typearr[1] != 'punct_level_plus')
2370 && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
2371 // do not put a space before the first token, because
2372 // we use a lot of pattern matching checking for the
2373 // first reserved word at beginning of query
2374 // so do not put a newline before
2376 // also we must not be inside a privilege list
2377 if ($i > 0) {
2378 // the alpha_identifier exception is there to
2379 // catch cases like
2380 // GRANT SELECT ON mydb.mytable TO myuser@localhost
2381 // (else, we get mydb.mytableTO)
2383 // the quote_single exception is there to
2384 // catch cases like
2385 // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
2387 * @todo fix all cases and find why this happens
2390 if (!$in_priv_list || $typearr[1] == 'alpha_identifier' || $typearr[1] == 'quote_single' || $typearr[1] == 'white_newline') {
2391 $before .= $space_alpha_reserved_word;
2393 } else {
2394 // on first keyword, check if it introduces a
2395 // privilege list
2396 if (PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_priv_list, $keywords_priv_list_cnt)) {
2397 $in_priv_list = TRUE;
2400 } else {
2401 $before .= ' ';
2404 switch ($arr[$i]['data']) {
2405 case 'CREATE':
2406 case 'ALTER':
2407 case 'DROP':
2408 case 'RENAME';
2409 case 'TRUNCATE':
2410 case 'ANALYZE':
2411 case 'ANALYSE':
2412 case 'OPTIMIZE':
2413 if ($docu) {
2414 switch ($arr[$i + 1]['data']) {
2415 case 'EVENT':
2416 case 'TABLE':
2417 case 'TABLESPACE':
2418 case 'FUNCTION':
2419 case 'INDEX':
2420 case 'PROCEDURE':
2421 case 'TRIGGER':
2422 case 'SERVER':
2423 case 'DATABASE':
2424 case 'VIEW':
2425 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'] . '_' . $arr[$i + 1]['data'], false, '', true);
2426 $close_docu_link = true;
2427 break;
2429 if ($arr[$i + 1]['data'] == 'LOGFILE' && $arr[$i + 2]['data'] == 'GROUP') {
2430 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'] . '_LOGFILE_GROUP', false, '', true);
2431 $close_docu_link = true;
2434 if (!$in_priv_list) {
2435 $space_punct_listsep = $html_line_break;
2436 $space_alpha_reserved_word = ' ';
2438 break;
2439 case 'EVENT':
2440 case 'TABLESPACE':
2441 case 'TABLE':
2442 case 'FUNCTION':
2443 case 'INDEX':
2444 case 'PROCEDURE':
2445 case 'SERVER':
2446 case 'TRIGGER':
2447 case 'DATABASE':
2448 case 'VIEW':
2449 case 'GROUP':
2450 if ($close_docu_link) {
2451 $after = '</a>' . $after;
2452 $close_docu_link = false;
2454 break;
2455 case 'SET':
2456 if ($docu && ($i == 0 || $arr[$i - 1]['data'] != 'CHARACTER')) {
2457 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2458 $after = '</a>' . $after;
2460 if (!$in_priv_list) {
2461 $space_punct_listsep = $html_line_break;
2462 $space_alpha_reserved_word = ' ';
2464 break;
2465 case 'EXPLAIN':
2466 case 'DESCRIBE':
2467 case 'DELETE':
2468 case 'SHOW':
2469 case 'UPDATE':
2470 if ($docu) {
2471 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2472 $after = '</a>' . $after;
2474 if (!$in_priv_list) {
2475 $space_punct_listsep = $html_line_break;
2476 $space_alpha_reserved_word = ' ';
2478 break;
2479 case 'INSERT':
2480 case 'REPLACE':
2481 if ($docu) {
2482 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2483 $after = '</a>' . $after;
2485 if (!$in_priv_list) {
2486 $space_punct_listsep = $html_line_break;
2487 $space_alpha_reserved_word = $html_line_break;
2489 break;
2490 case 'VALUES':
2491 $space_punct_listsep = ' ';
2492 $space_alpha_reserved_word = $html_line_break;
2493 break;
2494 case 'SELECT':
2495 if ($docu) {
2496 $before .= PMA_showMySQLDocu('SQL-Syntax', 'SELECT', false, '', true);
2497 $after = '</a>' . $after;
2499 $space_punct_listsep = ' ';
2500 $space_alpha_reserved_word = $html_line_break;
2501 break;
2502 case 'CALL':
2503 case 'DO':
2504 case 'HANDLER':
2505 if ($docu) {
2506 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2507 $after = '</a>' . $after;
2509 break;
2510 default:
2511 break;
2512 } // end switch ($arr[$i]['data'])
2514 $after .= ' ';
2515 break;
2516 case 'digit_integer':
2517 case 'digit_float':
2518 case 'digit_hex':
2520 * @todo could there be other types preceding a digit?
2522 if ($typearr[1] == 'alpha_reservedWord') {
2523 $after .= ' ';
2525 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2526 $after .= ' ';
2528 if ($typearr[1] == 'alpha_columnAttrib') {
2529 $before .= ' ';
2531 break;
2532 case 'alpha_variable':
2533 $after = ' ';
2534 break;
2535 case 'quote_double':
2536 case 'quote_single':
2537 // workaround: for the query
2538 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
2539 // the @ is incorrectly marked as alpha_variable
2540 // in the parser, and here, the '%' gets a blank before,
2541 // which is a syntax error
2542 if ($typearr[1] != 'punct_user' && $typearr[1] != 'alpha_bitfield_constant_introducer') {
2543 $before .= ' ';
2545 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2546 $after .= ' ';
2548 break;
2549 case 'quote_backtick':
2550 // here we check for punct_user to handle correctly
2551 // DEFINER = `username`@`%`
2552 // where @ is the punct_user and `%` is the quote_backtick
2553 if ($typearr[3] != 'punct_qualifier' && $typearr[3] != 'alpha_variable' && $typearr[3] != 'punct_user') {
2554 $after .= ' ';
2556 if ($typearr[1] != 'punct_qualifier' && $typearr[1] != 'alpha_variable' && $typearr[1] != 'punct_user') {
2557 $before .= ' ';
2559 break;
2560 default:
2561 break;
2562 } // end switch ($typearr[2])
2565 if ($typearr[3] != 'punct_qualifier') {
2566 $after .= ' ';
2568 $after .= "\n";
2570 $str .= $before . ($mode=='color' ? PMA_SQP_formatHTML_colorize($arr[$i]) : $arr[$i]['data']). $after;
2571 } // end for
2572 /* End possibly unclosed documentation link */
2573 if ($close_docu_link) {
2574 $str .= '</a>';
2575 $close_docu_link = false;
2577 if ($mode=='color') {
2578 $str .= '</span>';
2581 return $str;
2582 } // end of the "PMA_SQP_formatHtml()" function
2586 * Builds a CSS rule used for html formatted SQL queries
2588 * @param string The class name
2589 * @param string The property name
2590 * @param string The property value
2592 * @return string The CSS rule
2594 * @access public
2596 * @see PMA_SQP_buildCssData()
2598 function PMA_SQP_buildCssRule($classname, $property, $value)
2600 $str = '.' . $classname . ' {';
2601 if ($value != '') {
2602 $str .= $property . ': ' . $value . ';';
2604 $str .= '}' . "\n";
2606 return $str;
2607 } // end of the "PMA_SQP_buildCssRule()" function
2611 * Builds CSS rules used for html formatted SQL queries
2613 * @return string The CSS rules set
2615 * @access public
2617 * @global array The current PMA configuration
2619 * @see PMA_SQP_buildCssRule()
2621 function PMA_SQP_buildCssData()
2623 global $cfg;
2625 $css_string = '';
2626 foreach ($cfg['SQP']['fmtColor'] AS $key => $col) {
2627 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
2630 for ($i = 0; $i < 8; $i++) {
2631 $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
2634 return $css_string;
2635 } // end of the "PMA_SQP_buildCssData()" function
2637 if (! defined('PMA_MINIMUM_COMMON')) {
2639 * Gets SQL queries with no format
2641 * @param array The SQL queries list
2643 * @return string The SQL queries with no format
2645 * @access public
2647 function PMA_SQP_formatNone($arr)
2649 $formatted_sql = htmlspecialchars($arr['raw']);
2650 $formatted_sql = preg_replace("@((\015\012)|(\015)|(\012)){3,}@", "\n\n", $formatted_sql);
2652 return $formatted_sql;
2653 } // end of the "PMA_SQP_formatNone()" function
2657 * Gets SQL queries in text format
2659 * @todo WRITE THIS!
2660 * @param array The SQL queries list
2662 * @return string The SQL queries in text format
2664 * @access public
2666 function PMA_SQP_formatText($arr)
2668 return PMA_SQP_formatNone($arr);
2669 } // end of the "PMA_SQP_formatText()" function
2670 } // end if: minimal common.lib needed?