1403 lines
51 KiB
PHP
1403 lines
51 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Class PHPSQLParser
|
|
* Pure PHP SQL parser
|
|
*
|
|
* @package gulliver.system
|
|
* @access public
|
|
*/
|
|
class PHPSQLParser
|
|
{
|
|
public $reserved = array ();
|
|
public $functions = array ();
|
|
|
|
function __construct ($sql = false)
|
|
{
|
|
#LOAD THE LIST OF RESERVED WORDS
|
|
$this->load_reserved_words();
|
|
if ($sql) {
|
|
$this->parse( $sql );
|
|
}
|
|
}
|
|
|
|
public function parse ($sql)
|
|
{
|
|
$sql = trim( $sql );
|
|
|
|
#lex the SQL statement
|
|
$in = $this->split_sql( $sql );
|
|
|
|
#sometimes the parser needs to skip ahead until a particular
|
|
#token is found
|
|
$skip_until = false;
|
|
|
|
#this is the output tree which is being parsed
|
|
$out = array ();
|
|
|
|
#This is the last type of union used (UNION or UNION ALL)
|
|
#indicates a) presence of at least one union in this query
|
|
# b) the type of union if this is the first or last query
|
|
$union = false;
|
|
|
|
#Sometimes a "query" consists of more than one query (like a UNION query)
|
|
#this array holds all the queries
|
|
$queries = array ();
|
|
|
|
#This is the highest level lexical analysis. This is the part of the
|
|
#code which finds UNION and UNION ALL query parts
|
|
foreach ($in as $key => $token) {
|
|
$token = trim( $token );
|
|
|
|
if ($skip_until) {
|
|
if ($token) {
|
|
if (strtoupper( $token ) == $skip_until) {
|
|
$skip_until = false;
|
|
continue;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (strtoupper( $token ) == "UNION") {
|
|
$union = 'UNION';
|
|
for ($i = $key + 1; $i < count( $in ); ++ $i) {
|
|
if (trim( $in[$i] ) == '') {
|
|
continue;
|
|
}
|
|
if (strtoupper( $in[$i] ) == 'ALL') {
|
|
$skip_until = 'ALL';
|
|
$union = 'UNION ALL';
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
$queries[$union][] = $out;
|
|
$out = array ();
|
|
|
|
} else {
|
|
$out[] = $token;
|
|
}
|
|
|
|
}
|
|
|
|
if (! empty( $out )) {
|
|
if ($union) {
|
|
$queries[$union][] = $out;
|
|
} else {
|
|
$queries[] = $out;
|
|
}
|
|
}
|
|
|
|
/* MySQL supports a special form of UNION:
|
|
(select ...)
|
|
union
|
|
(select ...)
|
|
|
|
This block handles this query syntax. Only one such subquery
|
|
is supported in each UNION block. (select)(select)union(select) is not legal.
|
|
The extra queries will be silently ignored.
|
|
*/
|
|
$union_types = array ('UNION','UNION ALL'
|
|
);
|
|
foreach ($union_types as $union_type) {
|
|
if (! empty( $queries[$union_type] )) {
|
|
foreach ($queries[$union_type] as $i => $tok_list) {
|
|
foreach ($tok_list as $z => $tok) {
|
|
$tok = trim( $tok );
|
|
if (! $tok) {
|
|
continue;
|
|
}
|
|
if (preg_match( '/^\\(\\s*select\\s*/i', $tok )) {
|
|
$queries[$union_type][$i] = $this->parse( substr( $tok, 1, - 1 ) );
|
|
break;
|
|
} else {
|
|
$queries[$union_type][$i] = $this->process_sql( $queries[$union_type][$i] );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If there was no UNION or UNION ALL in the query, then the query is
|
|
stored at $queries[0].
|
|
*/
|
|
if (! empty( $queries[0] )) {
|
|
$queries[0] = $this->process_sql( $queries[0] );
|
|
|
|
}
|
|
|
|
if (count( $queries ) == 1 && ! $union) {
|
|
$queries = $queries[0];
|
|
}
|
|
|
|
$this->parsed = $queries;
|
|
return $this->parsed;
|
|
}
|
|
|
|
#This function counts open and close parenthesis and
|
|
#returns their location. This might be faster as a regex
|
|
private function count_paren ($token, $chars = array('(',')'))
|
|
{
|
|
$len = strlen( $token );
|
|
$open = array ();
|
|
$close = array ();
|
|
for ($i = 0; $i < $len; ++ $i) {
|
|
if ($token[$i] == $chars[0]) {
|
|
$open[] = $i;
|
|
} elseif ($token[$i] == $chars[1]) {
|
|
$close[] = $i;
|
|
}
|
|
|
|
}
|
|
return array ('open' => $open,'close' => $close,'balanced' => (count( $close ) - count( $open ))
|
|
);
|
|
}
|
|
|
|
#This function counts open and close parenthesis and
|
|
#returns their location. This might be faster as a regex
|
|
private function count_backtick ($token)
|
|
{
|
|
$len = strlen( $token );
|
|
$cnt = 0;
|
|
for ($i = 0; $i < $len; ++ $i) {
|
|
if ($token[$i] == '`') {
|
|
++ $cnt;
|
|
}
|
|
}
|
|
return $cnt;
|
|
}
|
|
|
|
#This is the lexer
|
|
#this function splits up a SQL statement into easy to "parse"
|
|
#tokens for the SQL processor
|
|
private function split_sql ($sql)
|
|
{
|
|
if (! is_string( $sql )) {
|
|
echo "SQL:\n";
|
|
print_r( $sql );
|
|
exit();
|
|
}
|
|
|
|
$sql = str_replace( array ('\\\'','\\"',"\r\n","\n","()"
|
|
), array ("''",'""'," "," "," "
|
|
), $sql );
|
|
$regex = <<<EOREGEX
|
|
/(`(?:[^`]|``)`|[@A-Za-z0-9_.`-]+(?:\(\s*\)){0,1})
|
|
|(\+|-|\*|\/|!=|>=|<=|<>|>|<|&&|\|\||=|\^)
|
|
|(\(.*?\)) # Match FUNCTION(...) OR BAREWORDS
|
|
|('(?:[^']|'')*'+)
|
|
|("(?:[^"]|"")*"+)
|
|
|([^ ,]+)
|
|
/ix
|
|
EOREGEX;
|
|
|
|
$tokens = preg_split( $regex, $sql, - 1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
|
|
$token_count = count( $tokens );
|
|
|
|
/*
|
|
The above regex has one problem, because the parenthetical match is not greedy.
|
|
Thus, when matching grouped expresions such as ( (a and b) or c) the
|
|
tokenizer will produce "( (a and b)", " ", "or", " " , "c,")"
|
|
|
|
This block detects the number of open/close parens in the given token. If the parens are balanced
|
|
(balanced == 0) then we don't need to do anything.
|
|
|
|
otherwise, we need to balance the expression.
|
|
*/
|
|
$reset = false;
|
|
for ($i = 0; $i < $token_count; ++ $i) {
|
|
|
|
if (empty( $tokens[$i] )) {
|
|
continue;
|
|
}
|
|
|
|
$token = $tokens[$i];
|
|
$trim = trim( $token );
|
|
if ($trim) {
|
|
if ($trim[0] != '(' && substr( $trim, - 1 ) == ')') {
|
|
$trim = trim( substr( $trim, 0, strpos( $trim, '(' ) ) );
|
|
}
|
|
$tokens[$i] = $trim;
|
|
$token = $trim;
|
|
}
|
|
|
|
if ($token && $token[0] == '(') {
|
|
$info = $this->count_paren( $token );
|
|
if ($info['balanced'] == 0) {
|
|
continue;
|
|
}
|
|
|
|
#we need to find this many closing parens
|
|
$needed = abs( $info['balanced'] );
|
|
$n = $i;
|
|
while ($needed > 0 && $n < $token_count - 1) {
|
|
++ $n;
|
|
#echo "LOOKING FORWARD TO $n [ " . $tokens[$n] . "]\n";
|
|
$token2 = $tokens[$n];
|
|
$info2 = $this->count_paren( $token2 );
|
|
$closes = count( $info2['close'] );
|
|
if ($closes != $needed) {
|
|
$tokens[$i] .= $tokens[$n];
|
|
unset( $tokens[$n] );
|
|
$reset = true;
|
|
$info2 = $this->count_paren( $tokens[$i] );
|
|
$needed = abs( $info2['balanced'] );
|
|
# echo "CLOSES LESS THAN NEEDED (still need $needed)\n";
|
|
} else {
|
|
/*get the string pos of the last close paren we need*/
|
|
$pos = $info2['close'][count( $info2['close'] ) - 1];
|
|
$str1 = $str2 = "";
|
|
if ($pos == 0) {
|
|
$str1 = ')';
|
|
} else {
|
|
$str1 = substr( $tokens[$n], 0, $pos ) . ')';
|
|
$str2 = substr( $tokens[$n], $pos + 1 );
|
|
}
|
|
#echo "CLOSES FOUND AT $n, offset:$pos [$str1] [$str2]\n";
|
|
if (strlen( $str2 ) > 0) {
|
|
$tokens[$n] = $str2;
|
|
} else {
|
|
unset( $tokens[$n] );
|
|
$reset = true;
|
|
}
|
|
$tokens[$i] .= $str1;
|
|
$info2 = $this->count_paren( $tokens[$i] );
|
|
$needed = abs( $info2['balanced'] );
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#the same problem appears with backticks :(
|
|
|
|
/* reset the array if we deleted any tokens above */
|
|
if ($reset) {
|
|
$tokens = array_values( $tokens );
|
|
}
|
|
|
|
$token_count = count( $tokens );
|
|
for ($i = 0; $i < $token_count; ++ $i) {
|
|
if (empty( $tokens[$i] )) {
|
|
continue;
|
|
}
|
|
$token = $tokens[$i];
|
|
$needed = true;
|
|
$reset = false;
|
|
if ($needed && $token && strpos( $token, '`' ) !== false) {
|
|
$info = $this->count_backtick( $token );
|
|
if ($info % 2 == 0) {
|
|
#even number of backticks means we are balanced
|
|
continue;
|
|
}
|
|
$needed = 1;
|
|
|
|
$n = $i;
|
|
while ($needed && $n < $token_count - 1) {
|
|
$reset = true;
|
|
#echo "BACKTICK COUNT[$i]: $info old: {$tokens[$i]}, new: ($token)\n";
|
|
++ $n;
|
|
$token .= $tokens[$n];
|
|
unset( $tokens[$n] );
|
|
$needed = $this->count_backtick( $token ) % 2;
|
|
}
|
|
}
|
|
if ($reset) {
|
|
$tokens[$i] = $token;
|
|
}
|
|
|
|
}
|
|
/* reset the array if we deleted any tokens above */
|
|
$tokens = array_values( $tokens );
|
|
|
|
return $tokens;
|
|
|
|
}
|
|
|
|
/**
|
|
* This function breaks up the SQL statement into logical sections.
|
|
* Some sections are then further handled by specialized functions.
|
|
*/
|
|
private function process_sql (&$tokens, $start_at = 0, $stop_at = false)
|
|
{
|
|
$prev_category = "";
|
|
$start = microtime( true );
|
|
$token_category = "";
|
|
|
|
$skip_next = false;
|
|
$token_count = count( $tokens );
|
|
|
|
if (! $stop_at) {
|
|
$stop_at = $token_count;
|
|
}
|
|
|
|
$out = false;
|
|
|
|
for ($token_number = $start_at; $token_number < $stop_at; ++ $token_number) {
|
|
$token = trim( $tokens[$token_number] );
|
|
if ($token && $token[0] == '(' && $token_category == "") {
|
|
$token_category = 'SELECT';
|
|
}
|
|
|
|
/* If it isn't obvious, when $skip_next is set, then we ignore the next real
|
|
token, that is we ignore whitespace.
|
|
*/
|
|
if ($skip_next) {
|
|
#whitespace does not count as a next token
|
|
if ($token == "") {
|
|
continue;
|
|
}
|
|
|
|
#to skip the token we replace it with whitespace
|
|
$new_token = "";
|
|
$skip_next = false;
|
|
}
|
|
|
|
$upper = strtoupper( $token );
|
|
switch ($upper) {
|
|
|
|
/* Tokens that get their own sections. These keywords have subclauses. */
|
|
case 'SELECT':
|
|
case 'ORDER':
|
|
case 'LIMIT':
|
|
case 'SET':
|
|
case 'DUPLICATE':
|
|
case 'VALUES':
|
|
case 'GROUP':
|
|
case 'ORDER':
|
|
case 'HAVING':
|
|
case 'INTO':
|
|
case 'WHERE':
|
|
case 'RENAME':
|
|
case 'CALL':
|
|
case 'PROCEDURE':
|
|
case 'FUNCTION':
|
|
case 'DATABASE':
|
|
case 'SERVER':
|
|
case 'LOGFILE':
|
|
case 'DEFINER':
|
|
case 'RETURNS':
|
|
case 'EVENT':
|
|
case 'TABLESPACE':
|
|
case 'VIEW':
|
|
case 'TRIGGER':
|
|
case 'DATA':
|
|
case 'DO':
|
|
case 'PASSWORD':
|
|
case 'USER':
|
|
case 'PLUGIN':
|
|
case 'FROM':
|
|
case 'FLUSH':
|
|
case 'KILL':
|
|
case 'RESET':
|
|
case 'START':
|
|
case 'STOP':
|
|
case 'PURGE':
|
|
case 'EXECUTE':
|
|
case 'PREPARE':
|
|
case 'DEALLOCATE':
|
|
if ($token == 'DEALLOCATE') {
|
|
$skip_next = true;
|
|
}
|
|
/* this FROM is different from FROM in other DML (not join related) */
|
|
if ($token_category == 'PREPARE' && $upper == 'FROM') {
|
|
continue 2;
|
|
}
|
|
|
|
$token_category = $upper;
|
|
#$join_type = 'JOIN';
|
|
if ($upper == 'FROM' && $token_category == 'FROM') {
|
|
/* DO NOTHING*/
|
|
} else {
|
|
continue 2;
|
|
|
|
}
|
|
break;
|
|
/* These tokens get their own section, but have no subclauses.
|
|
These tokens identify the statement but have no specific subclauses of their own. */
|
|
case 'DELETE':
|
|
case 'ALTER':
|
|
case 'INSERT':
|
|
case 'REPLACE':
|
|
case 'TRUNCATE':
|
|
case 'CREATE':
|
|
case 'TRUNCATE':
|
|
case 'OPTIMIZE':
|
|
case 'GRANT':
|
|
case 'REVOKE':
|
|
case 'SHOW':
|
|
case 'HANDLER':
|
|
case 'LOAD':
|
|
case 'ROLLBACK':
|
|
case 'SAVEPOINT':
|
|
case 'UNLOCK':
|
|
case 'INSTALL':
|
|
case 'UNINSTALL':
|
|
case 'ANALZYE':
|
|
case 'BACKUP':
|
|
case 'CHECK':
|
|
case 'CHECKSUM':
|
|
case 'REPAIR':
|
|
case 'RESTORE':
|
|
case 'CACHE':
|
|
case 'DESCRIBE':
|
|
case 'EXPLAIN':
|
|
case 'USE':
|
|
case 'HELP':
|
|
$token_category = $upper; /* set the category in case these get subclauses in a future version of MySQL */
|
|
$out[$upper][0] = $upper;
|
|
continue 2;
|
|
break;
|
|
/* This is either LOCK TABLES or SELECT ... LOCK IN SHARE MODE*/
|
|
case 'LOCK':
|
|
if ($token_category == "") {
|
|
$token_category = $upper;
|
|
$out[$upper][0] = $upper;
|
|
} else {
|
|
$token = 'LOCK IN SHARE MODE';
|
|
$skip_next = true;
|
|
$out['OPTIONS'][] = $token;
|
|
}
|
|
continue 2;
|
|
break;
|
|
case 'USING':
|
|
/* USING in FROM clause is different from USING w/ prepared statement*/
|
|
if ($token_category == 'EXECUTE') {
|
|
$token_category = $upper;
|
|
continue 2;
|
|
}
|
|
if ($token_category == 'FROM' && ! empty( $out['DELETE'] )) {
|
|
$token_category = $upper;
|
|
continue 2;
|
|
}
|
|
break;
|
|
/* DROP TABLE is different from ALTER TABLE DROP ... */
|
|
case 'DROP':
|
|
if ($token_category != 'ALTER') {
|
|
$token_category = $upper;
|
|
$out[$upper][0] = $upper;
|
|
continue 2;
|
|
}
|
|
break;
|
|
case 'FOR':
|
|
$skip_next = true;
|
|
$out['OPTIONS'][] = 'FOR UPDATE';
|
|
continue 2;
|
|
break;
|
|
case 'UPDATE':
|
|
if ($token_category == "") {
|
|
$token_category = $upper;
|
|
continue 2;
|
|
|
|
}
|
|
if ($token_category == 'DUPLICATE') {
|
|
continue 2;
|
|
}
|
|
break;
|
|
break;
|
|
case 'START':
|
|
$token = "BEGIN";
|
|
$out[$upper][0] = $upper;
|
|
$skip_next = true;
|
|
break;
|
|
/* These tokens are ignored. */
|
|
case 'BY':
|
|
case 'ALL':
|
|
case 'SHARE':
|
|
case 'MODE':
|
|
case 'TO':
|
|
case ';':
|
|
continue 2;
|
|
break;
|
|
case 'KEY':
|
|
if ($token_category == 'DUPLICATE') {
|
|
continue 2;
|
|
}
|
|
break;
|
|
/* These tokens set particular options for the statement. They never stand alone.*/
|
|
case 'DISTINCTROW':
|
|
$token = 'DISTINCT';
|
|
//DISTINCTROW seemed deprecated
|
|
case 'DISTINCT':
|
|
case 'HIGH_PRIORITY':
|
|
case 'LOW_PRIORITY':
|
|
case 'DELAYED':
|
|
case 'IGNORE':
|
|
case 'FORCE':
|
|
case 'STRAIGHT_JOIN':
|
|
case 'SQL_SMALL_RESULT':
|
|
case 'SQL_BIG_RESULT':
|
|
case 'QUICK':
|
|
case 'SQL_BUFFER_RESULT':
|
|
case 'SQL_CACHE':
|
|
case 'SQL_NO_CACHE':
|
|
case 'SQL_CALC_FOUND_ROWS':
|
|
$out['OPTIONS'][] = $upper;
|
|
continue 2;
|
|
break;
|
|
case 'WITH':
|
|
if ($token_category == 'GROUP') {
|
|
$skip_next = true;
|
|
$out['OPTIONS'][] = 'WITH ROLLUP';
|
|
continue 2;
|
|
}
|
|
break;
|
|
case 'AS':
|
|
break;
|
|
case '':
|
|
case ',':
|
|
case ';':
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ($prev_category == $token_category) {
|
|
$out[$token_category][] = $token;
|
|
}
|
|
|
|
$prev_category = $token_category;
|
|
}
|
|
|
|
if (! $out) {
|
|
return false;
|
|
}
|
|
|
|
#process the SELECT clause
|
|
if (! empty( $out['SELECT'] )) {
|
|
$out['SELECT'] = $this->process_select( $out['SELECT'] );
|
|
}
|
|
if (! empty( $out['FROM'] )) {
|
|
$out['FROM'] = $this->process_from( $out['FROM'] );
|
|
}
|
|
if (! empty( $out['USING'] )) {
|
|
$out['USING'] = $this->process_from( $out['USING'] );
|
|
}
|
|
if (! empty( $out['UPDATE'] )) {
|
|
$out['UPDATE'] = $this->process_from( $out['UPDATE'] );
|
|
}
|
|
if (! empty( $out['GROUP'] )) {
|
|
$out['GROUP'] = $this->process_group( $out['GROUP'], $out['SELECT'] );
|
|
}
|
|
if (! empty( $out['ORDER'] )) {
|
|
$out['ORDER'] = $this->process_group( $out['ORDER'], $out['SELECT'] );
|
|
}
|
|
if (! empty( $out['LIMIT'] )) {
|
|
$out['LIMIT'] = $this->process_limit( $out['LIMIT'] );
|
|
}
|
|
if (! empty( $out['WHERE'] )) {
|
|
$out['WHERE'] = $this->process_expr_list( $out['WHERE'] );
|
|
}
|
|
if (! empty( $out['HAVING'] )) {
|
|
$out['HAVING'] = $this->process_expr_list( $out['HAVING'] );
|
|
}
|
|
if (! empty( $out['SET'] )) {
|
|
$out['SET'] = $this->process_set_list( $out['SET'] );
|
|
}
|
|
if (! empty( $out['DUPLICATE'] )) {
|
|
$out['ON DUPLICATE KEY UPDATE'] = $this->process_set_list( $out['DUPLICATE'] );
|
|
unset( $out['DUPLICATE'] );
|
|
}
|
|
if (! empty( $out['INSERT'] )) {
|
|
$out = $this->process_insert( $out );
|
|
}
|
|
if (! empty( $out['REPLACE'] )) {
|
|
$out = $this->process_insert( $out, 'REPLACE' );
|
|
}
|
|
if (! empty( $out['DELETE'] )) {
|
|
$out = $this->process_delete( $out );
|
|
}
|
|
|
|
return $out;
|
|
|
|
}
|
|
|
|
/**
|
|
* A SET list is simply a list of key = value expressions separated by comma (,).
|
|
* This function produces a list of the key/value expressions.
|
|
*/
|
|
private function process_set_list ($tokens)
|
|
{
|
|
$column = "";
|
|
$expression = "";
|
|
foreach ($tokens as $token) {
|
|
$token = trim( $token );
|
|
if (! $column) {
|
|
if ($token === false || empty( $token )) {
|
|
continue;
|
|
}
|
|
$column .= $token;
|
|
continue;
|
|
}
|
|
|
|
if ($token == '=') {
|
|
continue;
|
|
}
|
|
|
|
if ($token == ',') {
|
|
$expr[] = array ('column' => trim( $column ),'expr' => trim( $expression )
|
|
);
|
|
$expression = $column = "";
|
|
continue;
|
|
}
|
|
|
|
$expression .= $token;
|
|
}
|
|
if ($expression) {
|
|
$expr[] = array ('column' => trim( $column ),'expr' => trim( $expression )
|
|
);
|
|
}
|
|
|
|
return $expr;
|
|
}
|
|
|
|
/**
|
|
* This function processes the LIMIT section.
|
|
* start,end are set. If only end is provided in the query
|
|
* then start is set to 0.
|
|
*/
|
|
private function process_limit ($tokens)
|
|
{
|
|
$start = 0;
|
|
$end = 0;
|
|
|
|
if ($pos = array_search( ',', $tokens )) {
|
|
for ($i = 0; $i < $pos; ++ $i) {
|
|
if ($tokens[$i] != '') {
|
|
$start = $tokens[$i];
|
|
break;
|
|
}
|
|
}
|
|
$pos = $pos + 1;
|
|
|
|
} else {
|
|
$pos = 0;
|
|
}
|
|
|
|
for ($i = $pos; $i < count( $tokens ); ++ $i) {
|
|
if ($tokens[$i] != '') {
|
|
$end = $tokens[$i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return array ('start' => $start,'end' => $end
|
|
);
|
|
}
|
|
|
|
/* This function processes the SELECT section. It splits the clauses at the commas.
|
|
* Each clause is then processed by process_select_expr() and the results are added to
|
|
* the expression list.
|
|
*
|
|
* Finally, at the end, the epxression list is returned.
|
|
*/
|
|
private function process_select (&$tokens)
|
|
{
|
|
$expression = "";
|
|
$expr = array ();
|
|
foreach ($tokens as $token) {
|
|
if ($token == ',') {
|
|
$expr[] = $this->process_select_expr( trim( $expression ) );
|
|
$expression = "";
|
|
} else {
|
|
if ($token === "" || $token === false) {
|
|
$token = " ";
|
|
}
|
|
$expression .= $token;
|
|
}
|
|
}
|
|
if ($expression) {
|
|
$expr[] = $this->process_select_expr( trim( $expression ) );
|
|
}
|
|
return $expr;
|
|
}
|
|
|
|
/**
|
|
* This fuction processes each SELECT clause.
|
|
* We determine what (if any) alias
|
|
* is provided, and we set the type of expression.
|
|
*/
|
|
private function process_select_expr ($expression)
|
|
{
|
|
$capture = false;
|
|
$alias = "";
|
|
$base_expression = $expression;
|
|
$upper = trim( strtoupper( $expression ) );
|
|
#if necessary, unpack the expression
|
|
if ($upper[0] == '(') {
|
|
#$expression = substr($expression,1,-1);
|
|
$base_expression = $expression;
|
|
}
|
|
|
|
$tokens = $this->split_sql( $expression );
|
|
$token_count = count( $tokens );
|
|
|
|
/* Determine if there is an explicit alias after the AS clause.
|
|
* If AS is found, then the next non-whitespace token is captured as the alias.
|
|
* The tokens after (and including) the AS are removed.
|
|
*/
|
|
$base_expr = "";
|
|
$stripped = array ();
|
|
$capture = false;
|
|
$alias = "";
|
|
$processed = false;
|
|
for ($i = 0; $i < $token_count; ++ $i) {
|
|
$token = strtoupper( $tokens[$i] );
|
|
if (trim( $token )) {
|
|
$stripped[] = $tokens[$i];
|
|
}
|
|
|
|
if ($token == 'AS') {
|
|
unset( $tokens[$i] );
|
|
$capture = true;
|
|
continue;
|
|
}
|
|
|
|
if ($capture) {
|
|
if (trim( $token )) {
|
|
$alias .= $tokens[$i];
|
|
}
|
|
unset( $tokens[$i] );
|
|
continue;
|
|
}
|
|
$base_expr .= $tokens[$i];
|
|
}
|
|
|
|
$stripped = $this->process_expr_list( $stripped );
|
|
$last = array_pop( $stripped );
|
|
if (! $alias && $last['expr_type'] == 'colref') {
|
|
$prev = array_pop( $stripped );
|
|
if ($prev['expr_type'] == 'operator' || $prev['expr_type'] == 'const' || $prev['expr_type'] == 'function' || $prev['expr_type'] == 'expression' ||
|
|
#$prev['expr_type'] == 'aggregate_function' ||
|
|
$prev['expr_type'] == 'subquery' || $prev['expr_type'] == 'colref') {
|
|
$alias = $last['base_expr'];
|
|
|
|
#remove the last token
|
|
array_pop( $tokens );
|
|
|
|
$base_expr = join( "", $tokens );
|
|
|
|
}
|
|
}
|
|
|
|
if (! $alias) {
|
|
$base_expr = join( "", $tokens );
|
|
$alias = $base_expr;
|
|
}
|
|
|
|
/* Properly escape the alias if it is not escaped */
|
|
if ($alias[0] != '`') {
|
|
$alias = '`' . str_replace( '`', '``', $alias ) . '`';
|
|
}
|
|
$processed = false;
|
|
$type = 'expression';
|
|
|
|
if (substr( trim( $base_expr ), 0, 1 ) == '(') {
|
|
//-- $base_expr = substr($expression,1,-1);
|
|
if (preg_match( '/^sel/i', $base_expr )) {
|
|
$type = 'subquery';
|
|
$processed = $this->parse( $base_expr );
|
|
}
|
|
}
|
|
if (! $processed) {
|
|
$processed = $this->process_expr_list( $tokens );
|
|
}
|
|
|
|
if (count( $processed ) == 1) {
|
|
$type = $processed[0]['expr_type'];
|
|
$processed = false;
|
|
}
|
|
|
|
return array ('expr_type' => $type,'alias' => $alias,'base_expr' => $base_expr,'sub_tree' => $processed
|
|
);
|
|
|
|
}
|
|
|
|
private function process_from (&$tokens)
|
|
{
|
|
$expression = "";
|
|
$expr = array ();
|
|
$token_count = 0;
|
|
$table = "";
|
|
$alias = "";
|
|
|
|
$skip_next = false;
|
|
$i = 0;
|
|
$join_type = '';
|
|
$ref_type = "";
|
|
$ref_expr = "";
|
|
$base_expr = "";
|
|
$sub_tree = false;
|
|
$subquery = "";
|
|
|
|
$first_join = true;
|
|
$modifier = "";
|
|
$saved_join_type = "";
|
|
|
|
foreach ($tokens as $token) {
|
|
$base_expr = false;
|
|
$upper = strtoupper( trim( $token ) );
|
|
|
|
if ($skip_next && $token) {
|
|
$token_count ++;
|
|
$skip_next = false;
|
|
continue;
|
|
} else {
|
|
if ($skip_next) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (preg_match( "/^\\s*\\(\\s*select/i", $token )) {
|
|
$type = 'subquery';
|
|
$table = "DEPENDENT-SUBQUERY";
|
|
$sub_tree = $this->parse( trim( $token, '() ' ) );
|
|
$subquery = $token;
|
|
}
|
|
|
|
switch ($upper) {
|
|
case 'OUTER':
|
|
case 'LEFT':
|
|
case 'RIGHT':
|
|
case 'NATURAL':
|
|
case 'CROSS':
|
|
case ',':
|
|
case 'JOIN':
|
|
break;
|
|
default:
|
|
$expression .= ($token == '') ? " " : $token;
|
|
if ($ref_type) {
|
|
$ref_expr .= ($token == '') ? " " : $token;
|
|
}
|
|
break;
|
|
}
|
|
switch ($upper) {
|
|
case 'AS':
|
|
$token_count ++;
|
|
$n = 1;
|
|
$alias = "";
|
|
while ($alias == "") {
|
|
$alias = trim( $tokens[$i + $n] );
|
|
++ $n;
|
|
}
|
|
|
|
break;
|
|
case 'INDEX':
|
|
if ($token_category == 'CREATE') {
|
|
$token_category = $upper;
|
|
continue 2;
|
|
}
|
|
|
|
break;
|
|
case 'USING':
|
|
case 'ON':
|
|
$ref_type = $upper;
|
|
$ref_expr = "";
|
|
//ON is ...
|
|
case 'CROSS':
|
|
case 'USE':
|
|
case 'FORCE':
|
|
case 'IGNORE':
|
|
case 'INNER':
|
|
case 'OUTER':
|
|
# $expression .= $token;
|
|
$token_count ++;
|
|
break;
|
|
case 'FOR':
|
|
$token_count ++;
|
|
$skip_next = true;
|
|
break;
|
|
case 'LEFT':
|
|
case 'RIGHT':
|
|
case 'STRAIGHT_JOIN':
|
|
$join_type = $saved_join_type;
|
|
$modifier = $upper . " ";
|
|
break;
|
|
case ',':
|
|
$modifier = 'CROSS';
|
|
// ',' got the same process as JOIN
|
|
case 'JOIN':
|
|
|
|
if ($first_join) {
|
|
$join_type = 'JOIN';
|
|
$saved_join_type = ($modifier ? $modifier : 'JOIN');
|
|
} else {
|
|
$new_join_type = ($modifier ? $modifier : 'JOIN');
|
|
$join_type = $saved_join_type;
|
|
$saved_join_type = $new_join_type;
|
|
unset( $new_join_type );
|
|
}
|
|
|
|
$first_join = false;
|
|
|
|
if (! trim( $alias )) {
|
|
$alias = $table;
|
|
}
|
|
|
|
if ($subquery) {
|
|
$sub_tree = $this->parse( trim( $subquery, '()' ) );
|
|
$base_expr = $subquery;
|
|
}
|
|
|
|
if (substr( trim( $table ), 0, 1 ) == '(') {
|
|
$base_expr = trim( $table, '() ' );
|
|
$join_type = 'JOIN';
|
|
$sub_tree = $this->process_from( $this->split_sql( $base_expr ) );
|
|
$alias = "";
|
|
}
|
|
|
|
if ($join_type == "") {
|
|
$join_type = 'JOIN';
|
|
}
|
|
$expr[] = array ('table' => $table,'alias' => $alias,'join_type' => $join_type,'ref_type' => $ref_type,'ref_clause' => trim( $ref_expr, '() ' ),'base_expr' => $base_expr,'sub_tree' => $sub_tree
|
|
);
|
|
$modifier = "";
|
|
#$join_type=$saved_join_type;
|
|
|
|
$token_count = 0;
|
|
$table = $alias = $expression = $base_expr = $ref_type = $ref_expr = "";
|
|
$sub_tree = false;
|
|
$subquery = "";
|
|
break;
|
|
default:
|
|
if ($token === false || empty( $token ) || $token === "") {
|
|
break;
|
|
}
|
|
|
|
if ($token_count == 0) {
|
|
if (! $table) {
|
|
$table = $token;
|
|
}
|
|
} elseif ($token_count == 1) {
|
|
$alias = $token;
|
|
}
|
|
$token_count ++;
|
|
break;
|
|
}
|
|
++ $i;
|
|
}
|
|
if (substr( trim( $table ), 0, 1 ) == '(') {
|
|
$base_expr = trim( $table, '() ' );
|
|
$join_type = 'JOIN';
|
|
$sub_tree = $this->process_from( $this->split_sql( $base_expr ) );
|
|
$alias = "";
|
|
} else {
|
|
if (! trim( $alias )) {
|
|
$alias = $table;
|
|
}
|
|
}
|
|
if ($join_type == "") {
|
|
$saved_join_type = 'JOIN';
|
|
}
|
|
|
|
$expr[] = array ('table' => $table,'alias' => $alias,'join_type' => $saved_join_type,'ref_type' => $ref_type,'ref_clause' => trim( $ref_expr, '() ' ),'base_expr' => $base_expr,'sub_tree' => $sub_tree
|
|
);
|
|
|
|
return $expr;
|
|
}
|
|
|
|
private function process_group (&$tokens, &$select)
|
|
{
|
|
|
|
$out = array ();
|
|
$expression = "";
|
|
$direction = "ASC";
|
|
$type = "expression";
|
|
if (! $tokens) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($tokens as $token) {
|
|
switch (strtoupper( $token )) {
|
|
case ',':
|
|
$expression = trim( $expression );
|
|
if ($expression[0] != '`' || substr( $expression, - 1 ) != '`') {
|
|
$escaped = str_replace( '`', '``', $expression );
|
|
} else {
|
|
$escaped = $expression;
|
|
}
|
|
$escaped = '`' . $escaped . '`';
|
|
|
|
if (is_numeric( trim( $expression ) )) {
|
|
$type = 'pos';
|
|
} else {
|
|
|
|
#search to see if the expression matches an alias
|
|
foreach ($select as $clause) {
|
|
if ($clause['alias'] == $escaped) {
|
|
$type = 'alias';
|
|
}
|
|
}
|
|
|
|
if (! $type) {
|
|
$type = "expression";
|
|
}
|
|
}
|
|
|
|
$out[] = array ('type' => $type,'base_expr' => $expression,'direction' => $direction
|
|
);
|
|
$escaped = "";
|
|
$expression = "";
|
|
$direction = "ASC";
|
|
$type = "";
|
|
break;
|
|
case 'DESC':
|
|
$direction = "DESC";
|
|
break;
|
|
default:
|
|
$expression .= $token == '' ? ' ' : $token;
|
|
|
|
}
|
|
}
|
|
if ($expression) {
|
|
$expression = trim( $expression );
|
|
if ($expression[0] != '`' || substr( $expression, - 1 ) != '`') {
|
|
$escaped = str_replace( '`', '``', $expression );
|
|
} else {
|
|
$escaped = $expression;
|
|
}
|
|
$escaped = '`' . $escaped . '`';
|
|
|
|
if (is_numeric( trim( $expression ) )) {
|
|
$type = 'pos';
|
|
} else {
|
|
|
|
#search to see if the expression matches an alias
|
|
if (! $type && $select) {
|
|
foreach ($select as $clause) {
|
|
if (! is_array( $clause )) {
|
|
continue;
|
|
}
|
|
if ($clause['alias'] == $escaped) {
|
|
$type = 'alias';
|
|
}
|
|
}
|
|
} else {
|
|
$type = "expression";
|
|
}
|
|
|
|
if (! $type) {
|
|
$type = "expression";
|
|
}
|
|
}
|
|
|
|
$out[] = array ('type' => $type,'base_expr' => $expression,'direction' => $direction
|
|
);
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Some sections are just lists of expressions, like the WHERE and HAVING clauses.
|
|
* This function
|
|
* processes these sections. Recursive.
|
|
*/
|
|
private function process_expr_list ($tokens)
|
|
{
|
|
$expr = [];
|
|
$type = "";
|
|
$prev_token = "";
|
|
$skip_next = false;
|
|
$sub_expr = "";
|
|
|
|
$in_lists = array ();
|
|
foreach ($tokens as $key => $token) {
|
|
|
|
if (strlen( trim( $token ) ) == 0) {
|
|
continue;
|
|
}
|
|
if ($skip_next) {
|
|
$skip_next = false;
|
|
continue;
|
|
}
|
|
|
|
$processed = false;
|
|
$upper = strtoupper( trim( $token ) );
|
|
if (trim( $token )) {
|
|
$token = trim( $token );
|
|
}
|
|
|
|
/* is it a subquery?*/
|
|
if (preg_match( "/^\\s*\\(\\s*SELECT/i", $token )) {
|
|
$type = 'subquery';
|
|
#tokenize and parse the subquery.
|
|
#we remove the enclosing parenthesis for the tokenizer
|
|
$processed = $this->parse( trim( $token, ' ()' ) );
|
|
|
|
/* is it an inlist */
|
|
} elseif ($upper[0] == '(' && substr( $upper, - 1 ) == ')') {
|
|
if ($prev_token == 'IN') {
|
|
$type = "in-list";
|
|
$processed = $this->split_sql( substr( $token, 1, - 1 ) );
|
|
$list = array ();
|
|
foreach ($processed as $v) {
|
|
if ($v == ',') {
|
|
continue;
|
|
}
|
|
$list[] = $v;
|
|
}
|
|
$processed = $list;
|
|
unset( $list );
|
|
$prev_token = "";
|
|
|
|
} elseif ($prev_token == 'AGAINST') {
|
|
$type = "match-arguments";
|
|
$list = $this->split_sql( substr( $token, 1, - 1 ) );
|
|
if (count( $list ) > 1) {
|
|
$match_mode = implode( '', array_slice( $list, 1 ) );
|
|
$processed = array ($list[0],$match_mode
|
|
);
|
|
} else {
|
|
$processed = $list[0];
|
|
}
|
|
$prev_token = "";
|
|
}
|
|
|
|
/* it is either an operator, a colref or a constant */
|
|
} else {
|
|
switch ($upper) {
|
|
case 'AND':
|
|
case '&&':
|
|
case 'BETWEEN':
|
|
case 'AND':
|
|
case 'BINARY':
|
|
case '&':
|
|
case '~':
|
|
case '|':
|
|
case '^':
|
|
case 'CASE':
|
|
case 'WHEN':
|
|
case 'END':
|
|
case 'DIV':
|
|
case '/':
|
|
case '<=>':
|
|
case '=':
|
|
case '>=':
|
|
case '>':
|
|
case 'IS':
|
|
case 'NOT':
|
|
case 'NULL':
|
|
case '<<':
|
|
case '<=':
|
|
case '<':
|
|
case 'LIKE':
|
|
case '-':
|
|
case '%':
|
|
case '!=':
|
|
case '<>':
|
|
case 'REGEXP':
|
|
case '!':
|
|
case '||':
|
|
case 'OR':
|
|
case '+':
|
|
case '>>':
|
|
case 'RLIKE':
|
|
case 'SOUNDS':
|
|
case '*':
|
|
case '-':
|
|
case 'XOR':
|
|
case 'IN':
|
|
$processed = false;
|
|
$type = "operator";
|
|
break;
|
|
default:
|
|
switch ($token[0]) {
|
|
case "'":
|
|
case '"':
|
|
$type = 'const';
|
|
break;
|
|
case '`':
|
|
$type = 'colref';
|
|
break;
|
|
default:
|
|
if (is_numeric( $token )) {
|
|
$type = 'const';
|
|
} else {
|
|
$type = 'colref';
|
|
}
|
|
break;
|
|
|
|
}
|
|
#$processed = $token;
|
|
$processed = false;
|
|
}
|
|
}
|
|
/* is a reserved word? */
|
|
if (($type != 'operator' && $type != 'in-list' && $type != 'sub_expr') && in_array( $upper, $this->reserved )) {
|
|
$token = $upper;
|
|
if (! in_array( $upper, $this->functions )) {
|
|
$type = 'reserved';
|
|
} else {
|
|
switch ($token) {
|
|
case 'AVG':
|
|
case 'SUM':
|
|
case 'COUNT':
|
|
case 'MIN':
|
|
case 'MAX':
|
|
case 'STDDEV':
|
|
case 'STDDEV_SAMP':
|
|
case 'STDDEV_POP':
|
|
case 'VARIANCE':
|
|
case 'VAR_SAMP':
|
|
case 'VAR_POP':
|
|
case 'GROUP_CONCAT':
|
|
case 'BIT_AND':
|
|
case 'BIT_OR':
|
|
case 'BIT_XOR':
|
|
$type = 'aggregate_function';
|
|
if (! empty( $tokens[$key + 1] )) {
|
|
$sub_expr = $tokens[$key + 1];
|
|
#$skip_next=true;
|
|
}
|
|
break;
|
|
default:
|
|
$type = 'function';
|
|
if (! empty( $tokens[$key + 1] )) {
|
|
$sub_expr = $tokens[$key + 1];
|
|
} else {
|
|
$sub_expr = "()";
|
|
#$skip_next=true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! $type) {
|
|
if ($upper[0] == '(') {
|
|
$local_expr = substr( trim( $token ), 1, - 1 );
|
|
} else {
|
|
$local_expr = $token;
|
|
}
|
|
$processed = $this->process_expr_list( $this->split_sql( $local_expr ) );
|
|
$type = 'expression';
|
|
|
|
if (count( $processed ) == 1) {
|
|
$type = $processed[0]['expr_type'];
|
|
$base_expr = $processed[0]['base_expr'];
|
|
$processed = $processed[0]['sub_tree'];
|
|
}
|
|
|
|
}
|
|
|
|
$sub_expr = trim( $sub_expr );
|
|
$sub_expr = "";
|
|
|
|
$expr[] = array ('expr_type' => $type,'base_expr' => $token,'sub_tree' => $processed
|
|
);
|
|
$prev_token = $upper;
|
|
$expr_type = "";
|
|
$type = "";
|
|
}
|
|
if ($sub_expr) {
|
|
$processed['sub_tree'] = $this->process_expr_list( $this->split_sql( substr( $sub_expr, 1, - 1 ) ) );
|
|
}
|
|
|
|
if (! is_array( $processed )) {
|
|
print_r( $processed );
|
|
$processed = false;
|
|
}
|
|
|
|
if ($expr_type) {
|
|
$expr[] = array ('expr_type' => $type,'base_expr' => $token,'sub_tree' => $processed
|
|
);
|
|
}
|
|
$mod = false;
|
|
|
|
if ($mod) {
|
|
$expr = array_values( $expr );
|
|
}
|
|
|
|
return $expr;
|
|
}
|
|
|
|
private function process_update ($tokens)
|
|
{
|
|
|
|
}
|
|
|
|
private function process_delete ($tokens)
|
|
{
|
|
$tables = array ();
|
|
$del = $tokens['DELETE'];
|
|
|
|
foreach ($tokens['DELETE'] as $expression) {
|
|
if ($expression != 'DELETE' && trim( $expression, ' .*' ) != "" && $expression != ',') {
|
|
$tables[] = trim( $expression, '.* ' );
|
|
}
|
|
}
|
|
|
|
if (empty( $tables )) {
|
|
foreach ($tokens['FROM'] as $table) {
|
|
$tables[] = $table['table'];
|
|
}
|
|
}
|
|
|
|
$tokens['DELETE'] = array ('TABLES' => $tables
|
|
);
|
|
|
|
return $tokens;
|
|
}
|
|
|
|
public function process_insert ($tokens, $token_category = 'INSERT')
|
|
{
|
|
$table = "";
|
|
$cols = "";
|
|
|
|
$into = $tokens['INTO'];
|
|
foreach ($into as $token) {
|
|
if (! trim( $token )) {
|
|
continue;
|
|
}
|
|
if (! $table) {
|
|
$table = $token;
|
|
} elseif (! $cols) {
|
|
$cols = $token;
|
|
}
|
|
}
|
|
|
|
if (! $cols) {
|
|
$cols = 'ALL';
|
|
} else {
|
|
$cols = explode( ",", trim( $cols, '() ' ) );
|
|
}
|
|
unset( $tokens['INTO'] );
|
|
$tokens[$token_category] = array ('table' => $table,'cols' => $cols
|
|
);
|
|
return $tokens;
|
|
|
|
}
|
|
|
|
public function load_reserved_words ()
|
|
{
|
|
|
|
$this->functions = array ('abs','acos','adddate','addtime','aes_encrypt','aes_decrypt','against','ascii','asin','atan','avg','benchmark','bin','bit_and','bit_or','bitcount','bitlength','cast','ceiling','char','char_length','character_length','charset','coalesce','coercibility','collation','compress','concat','concat_ws','conection_id','conv','convert','convert_tz','cos','cot','count','crc32','curdate','current_user','currval','curtime','database','date_add','date_diff','date_format','date_sub','day','dayname','dayofmonth','dayofweek','dayofyear','decode','default','degrees','des_decrypt','des_encrypt','elt','encode','encrypt','exp','export_set','extract','field','find_in_set','floor','format','found_rows','from_days','from_unixtime','get_format','get_lock','group_concat',
|
|
'greatest','hex','hour','if','ifnull','in','inet_aton','inet_ntoa','insert','instr','interval','is_free_lock','is_used_lock','last_day','last_insert_id','lcase','least','left','length','ln','load_file','localtime','localtimestamp','locate','log','log2','log10','lower','lpad','ltrim','make_set','makedate','maketime','master_pos_wait','match','max','md5','microsecond','mid','min','minute','mod','month','monthname','nextval','now','nullif','oct','octet_length','old_password','ord','password','period_add','period_diff','pi','position','pow','power','quarter','quote','radians','rand','release_lock','repeat','replace','reverse','right','round','row_count','rpad','rtrim','sec_to_time','second','session_user','sha','sha1','sign','soundex','space','sqrt','std','stddev',
|
|
'stddev_pop','stddev_samp','strcmp','str_to_date','subdate','substring','substring_index','subtime','sum','sysdate','system_user','tan','time','timediff','timestamp','timestampadd','timestampdiff','time_format','time_to_sec','to_days','trim','truncate','ucase','uncompress','uncompressed_length','unhex','unix_timestamp','upper','user','utc_date','utc_time','utc_timestamp','uuid','var_pop','var_samp','variance','version','week','weekday','weekofyear','year','yearweek'
|
|
);
|
|
|
|
/* includes functions */
|
|
$this->reserved = array ('abs','acos','adddate','addtime','aes_encrypt','aes_decrypt','against','ascii','asin','atan','avg','benchmark','bin','bit_and','bit_or','bitcount','bitlength','cast','ceiling','char','char_length','character_length','charset','coalesce','coercibility','collation','compress','concat','concat_ws','conection_id','conv','convert','convert_tz','cos','cot','count','crc32','curdate','current_user','currval','curtime','database','date_add','date_diff','date_format','date_sub','day','dayname','dayofmonth','dayofweek','dayofyear','decode','default','degrees','des_decrypt','des_encrypt','elt','encode','encrypt','exp','export_set','extract','field','find_in_set','floor','format','found_rows','from_days','from_unixtime','get_format','get_lock','group_concat','greatest',
|
|
'hex','hour','if','ifnull','in','inet_aton','inet_ntoa','insert','instr','interval','is_free_lock','is_used_lock','last_day','last_insert_id','lcase','least','left','length','ln','load_file','localtime','localtimestamp','locate','log','log2','log10','lower','lpad','ltrim','make_set','makedate','maketime','master_pos_wait','match','max','md5','microsecond','mid','min','minute','mod','month','monthname','nextval','now','nullif','oct','octet_length','old_password','ord','password','period_add','period_diff','pi','position','pow','power','quarter','quote','radians','rand','release_lock','repeat','replace','reverse','right','round','row_count','rpad','rtrim','sec_to_time','second','session_user','sha','sha1','sign','soundex','space','sqrt','std','stddev','stddev_pop',
|
|
'stddev_samp','strcmp','str_to_date','subdate','substring','substring_index','subtime','sum','sysdate','system_user','tan','time','timediff','timestamp','timestampadd','timestampdiff','time_format','time_to_sec','to_days','trim','truncate','ucase','uncompress','uncompressed_length','unhex','unix_timestamp','upper','user','utc_date','utc_time','utc_timestamp','uuid','var_pop','var_samp','variance','version','week','weekday','weekofyear','year','yearweek','add','all','alter','analyze','and','as','asc','asensitive','auto_increment','bdb','before','berkeleydb','between','bigint','binary','blob','both','by','call','cascade','case','change','char','character','check','collate','column','columns','condition','connection','constraint','continue','create','cross',
|
|
'current_date','current_time','current_timestamp','cursor','database','databases','day_hour','day_microsecond','day_minute','day_second','dec','decimal','declare','default','delayed','delete','desc','describe','deterministic','distinct','distinctrow','div','double','drop','else','elseif','enclosed','escaped','exists','exit','explain','false','fetch','fields','float','for','force','foreign','found','frac_second','from','fulltext','grant','group','having','high_priority','hour_microsecond','hour_minute','hour_second','if','ignore','in','index','infile','inner','innodb','inout','insensitive','insert','int','integer','interval','into','io_thread','is','iterate','join','key','keys','kill','leading','leave','left','like','limit','lines','load','localtime','localtimestamp',
|
|
'lock','long','longblob','longtext','loop','low_priority','master_server_id','match','mediumblob','mediumint','mediumtext','middleint','minute_microsecond','minute_second','mod','natural','not','no_write_to_binlog','null','numeric','on','optimize','option','optionally','or','order','out','outer','outfile','precision','primary','privileges','procedure','purge','read','real','references','regexp','rename','repeat','replace','require','restrict','return','revoke','right','rlike','second_microsecond','select','sensitive','separator','set','show','smallint','some','soname','spatial','specific','sql','sqlexception','sqlstate','sqlwarning','sql_big_result','sql_calc_found_rows','sql_small_result','sql_tsi_day','sql_tsi_frac_second','sql_tsi_hour','sql_tsi_minute',
|
|
'sql_tsi_month','sql_tsi_quarter','sql_tsi_second','sql_tsi_week','sql_tsi_year','ssl','starting','straight_join','striped','table','tables','terminated','then','timestampadd','timestampdiff','tinyblob','tinyint','tinytext','to','trailing','true','undo','union','unique','unlock','unsigned','update','usage','use','user_resources','using','utc_date','utc_time','utc_timestamp','values','varbinary','varchar','varcharacter','varying','when','where','while','with','write','xor','year_month','zerofill'
|
|
);
|
|
|
|
for ($i = 0; $i < count( $this->reserved ); ++ $i) {
|
|
$this->reserved[$i] = strtoupper( $this->reserved[$i] );
|
|
if (! empty( $this->functions[$i] )) {
|
|
$this->functions[$i] = strtoupper( $this->functions[$i] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|