From 7e12feba5440aca64570d14bae8af76c12b2e542 Mon Sep 17 00:00:00 2001 From: Dan Ungureanu Date: Wed, 10 Feb 2016 18:35:32 +0200 Subject: [PATCH 1/2] Updated sql-parser library to phpmyadmin/sql-parser@38b10f585ac012554f8a3d0dd6e7e3fd255ff94f (v3.1.0). The update includes multiple fixes: - Fixes #11885. Call to undefined function SqlParser\ctype_alnum() - Fixes #11879. 4.5.3.1 - NOW() function not recognized by parser - Fixes #11867. Gracefully handle the DESC statement - Fixes #11843. Fractional timestamp causes corrupted SQL export - Fixes #11836. Static analysis error for valid WHERE condition with IF keyword - Fixes #11800. Syntax Verifier error using REGEXP in SQL statement - Fixes #11799. Backslashes in comments are being interpreted as escape characters Signed-off-by: Dan Ungureanu --- .../sql-parser/src/Components/ArrayObj.php | 135 ++++++---- .../sql-parser/src/Components/Condition.php | 39 +-- .../src/Components/CreateDefinition.php | 2 +- .../sql-parser/src/Components/Expression.php | 234 +++++++++--------- .../src/Components/SetOperation.php | 2 +- libraries/sql-parser/src/Context.php | 9 +- .../src/Contexts/ContextMySql50000.php | 6 +- .../src/Contexts/ContextMySql50100.php | 6 +- .../src/Contexts/ContextMySql50500.php | 6 +- .../src/Contexts/ContextMySql50600.php | 6 +- .../src/Contexts/ContextMySql50700.php | 6 +- libraries/sql-parser/src/Parser.php | 1 + .../src/Statements/AlterStatement.php | 4 +- .../sql-parser/src/Utils/BufferedQuery.php | 20 +- libraries/sql-parser/src/Utils/Formatter.php | 4 +- 15 files changed, 272 insertions(+), 208 deletions(-) diff --git a/libraries/sql-parser/src/Components/ArrayObj.php b/libraries/sql-parser/src/Components/ArrayObj.php index fb199eacac..56c7247e9b 100644 --- a/libraries/sql-parser/src/Components/ArrayObj.php +++ b/libraries/sql-parser/src/Components/ArrayObj.php @@ -63,20 +63,32 @@ class ArrayObj extends Component $ret = empty($options['type']) ? new ArrayObj() : array(); /** - * The state of the parser. + * The last raw expression. * - * Below are the states of the parser. - * - * 0 -----------------------[ ( ]------------------------> 1 - * - * 1 ------------------[ array element ]-----------------> 2 - * - * 2 ------------------------[ , ]-----------------------> 1 - * 2 ------------------------[ ) ]-----------------------> (END) - * - * @var int $state + * @var string $lastRaw */ - $state = 0; + $lastRaw = ''; + + /** + * The last value. + * + * @var string $lastValue + */ + $lastValue = ''; + + /** + * Counts brackets. + * + * @var int $brackets + */ + $brackets = 0; + + /** + * Last separator (bracket or comma). + * + * @var boolean $isCommaLast + */ + $isCommaLast = false; for (; $list->idx < $list->count; ++$list->idx) { /** @@ -92,49 +104,72 @@ class ArrayObj extends Component } // Skipping whitespaces and comments. - if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) { + if (($token->type === Token::TYPE_WHITESPACE) + || ($token->type === Token::TYPE_COMMENT) + ) { + $lastRaw .= $token->token; + $lastValue = trim($lastValue) .' '; continue; } - if ($state === 0) { - if (($token->type !== Token::TYPE_OPERATOR) || ($token->value !== '(')) { - $parser->error( - __('An opening bracket was expected.'), - $token - ); - break; - } - $state = 1; - } elseif ($state === 1) { - if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ')')) { - // Empty array. - break; - } - if (empty($options['type'])) { - $ret->values[] = $token->value; - $ret->raw[] = $token->token; - } else { - $ret[] = $options['type']::parse( - $parser, - $list, - empty($options['typeOptions']) ? array() : $options['typeOptions'] - ); - } - $state = 2; - } elseif ($state === 2) { - if (($token->type !== Token::TYPE_OPERATOR) || (($token->value !== ',') && ($token->value !== ')'))) { - $parser->error( - __('A comma or a closing bracket was expected'), - $token - ); - break; - } - if ($token->value === ',') { - $state = 1; - } else { // ) - break; + if (($brackets === 0) + && (($token->type !== Token::TYPE_OPERATOR) + || ($token->value !== '(')) + ) { + $parser->error(__('An opening bracket was expected.'), $token); + break; + } + + if ($token->type === Token::TYPE_OPERATOR) { + if ($token->value === '(') { + if (++$brackets === 1) { // 1 is the base level. + continue; + } + } elseif ($token->value === ')') { + if (--$brackets === 0) { // Array ended. + break; + } + } elseif ($token->value === ',') { + if ($brackets === 1) { + $isCommaLast = true; + if (empty($options['type'])) { + $ret->raw[] = trim($lastRaw); + $ret->values[] = trim($lastValue); + $lastRaw = $lastValue = ''; + } + } + continue; } } + + if (empty($options['type'])) { + $lastRaw .= $token->token; + $lastValue .= $token->value; + } else { + $ret[] = $options['type']::parse( + $parser, + $list, + empty($options['typeOptions']) ? array() : $options['typeOptions'] + ); + } + } + + // Handling last element. + // + // This is treated differently to treat the following cases: + // + // => array() + // (,) => array('', '') + // () => array() + // (a,) => array('a', '') + // (a) => array('a') + // + $lastRaw = trim($lastRaw); + if ((empty($options['type'])) + && ((strlen($lastRaw) > 0) || ($isCommaLast)) + ) { + $ret->raw[] = $lastRaw; + $ret->values[] = trim($lastValue); } return $ret; diff --git a/libraries/sql-parser/src/Components/Condition.php b/libraries/sql-parser/src/Components/Condition.php index 9d7d586927..f820c93439 100644 --- a/libraries/sql-parser/src/Components/Condition.php +++ b/libraries/sql-parser/src/Components/Condition.php @@ -30,17 +30,18 @@ class Condition extends Component * * @var array */ - public static $DELIMITERS = array('&&', 'AND', 'OR', 'XOR', '||'); + public static $DELIMITERS = array('&&', '||', 'AND', 'OR', 'XOR'); /** - * Hash map containing reserved keywords that are also operators. + * List of allowed reserved keywords in conditions. * * @var array */ - public static $OPERATORS = array( + public static $ALLOWED_KEYWORDS = array( 'AND' => 1, 'BETWEEN' => 1, 'EXISTS' => 1, + 'IF' => 1, 'IN' => 1, 'IS' => 1, 'LIKE' => 1, @@ -106,6 +107,7 @@ class Condition extends Component /** * Whether there was a `BETWEEN` keyword before or not. + * * It is required to keep track of them because their structure contains * the keyword `AND`, which is also an operator that delimits * expressions. @@ -115,6 +117,7 @@ class Condition extends Component $betweenBefore = false; for (; $list->idx < $list->count; ++$list->idx) { + /** * Token parsed at this moment. * @@ -142,11 +145,12 @@ class Condition extends Component // Conditions are delimited by logical operators. if (in_array($token->value, static::$DELIMITERS, true)) { if (($betweenBefore) && ($token->value === 'AND')) { + // The syntax of keyword `BETWEEN` is hard-coded. $betweenBefore = false; } else { + // The expression ended. $expr->expr = trim($expr->expr); if (!empty($expr->expr)) { - // Adding the condition that is delimited by this operator. $ret[] = $expr; } @@ -155,11 +159,21 @@ class Condition extends Component $expr->isOperator = true; $ret[] = $expr; + // Preparing to parse another condition. $expr = new Condition(); continue; } } + if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) { + if ($token->value === 'BETWEEN') { + $betweenBefore = true; + } + if (($brackets === 0) && (empty(static::$ALLOWED_KEYWORDS[$token->value]))) { + break; + } + } + if ($token->type === Token::TYPE_OPERATOR) { if ($token->value === '(') { ++$brackets; @@ -168,23 +182,16 @@ class Condition extends Component } } - // No keyword is expected. - if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) { - if ($token->value === 'BETWEEN') { - $betweenBefore = true; - } - if (($brackets === 0) && (empty(static::$OPERATORS[$token->value]))) { - break; - } - } - $expr->expr .= $token->token; if (($token->type === Token::TYPE_NONE) - || (($token->type === Token::TYPE_KEYWORD) && (!($token->flags & Token::FLAG_KEYWORD_RESERVED))) + || (($token->type === Token::TYPE_KEYWORD) + && (!($token->flags & Token::FLAG_KEYWORD_RESERVED))) || ($token->type === Token::TYPE_STRING) || ($token->type === Token::TYPE_SYMBOL) ) { - $expr->identifiers[] = $token->value; + if (!in_array($token->value, $expr->identifiers)) { + $expr->identifiers[] = $token->value; + } } } diff --git a/libraries/sql-parser/src/Components/CreateDefinition.php b/libraries/sql-parser/src/Components/CreateDefinition.php index 21f44feaed..66ea4b2116 100644 --- a/libraries/sql-parser/src/Components/CreateDefinition.php +++ b/libraries/sql-parser/src/Components/CreateDefinition.php @@ -43,7 +43,7 @@ class CreateDefinition extends Component 'NOT NULL' => 1, 'NULL' => 1, - 'DEFAULT' => array(2, 'var'), + 'DEFAULT' => array(2, 'expr'), 'AUTO_INCREMENT' => 3, 'PRIMARY' => 4, 'PRIMARY KEY' => 4, diff --git a/libraries/sql-parser/src/Components/Expression.php b/libraries/sql-parser/src/Components/Expression.php index d91d9020f4..a365d321a1 100644 --- a/libraries/sql-parser/src/Components/Expression.php +++ b/libraries/sql-parser/src/Components/Expression.php @@ -28,6 +28,15 @@ use SqlParser\TokensList; class Expression extends Component { + /** + * List of allowed reserved keywords in expressions. + * + * @var array + */ + private static $ALLOWED_KEYWORDS = array( + 'AS' => 1, 'DUAL' => 1, 'NULL' => 1, 'REGEXP' => 1 + ); + /** * The name of this database. * @@ -137,9 +146,9 @@ class Expression extends Component /** * Whether an alias is expected. Is 2 if `AS` keyword was found. * - * @var int $alias + * @var bool $alias */ - $alias = 0; + $alias = false; /** * Counts brackets. @@ -149,17 +158,14 @@ class Expression extends Component $brackets = 0; /** - * Keeps track of the previous token. - * Possible values: - * string, if function was previously found; - * true, if opening bracket was previously found; - * null, in any other case. + * Keeps track of the last two previous tokens. * - * @var string|bool $prev + * @var Token[] $prev */ - $prev = null; + $prev = array(null, null); for (; $list->idx < $list->count; ++$list->idx) { + /** * Token parsed at this moment. * @@ -173,36 +179,48 @@ class Expression extends Component } // Skipping whitespaces and comments. - if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) { - if (($isExpr) && (!$alias)) { + if (($token->type === Token::TYPE_WHITESPACE) + || ($token->type === Token::TYPE_COMMENT) + ) { + if ($isExpr) { $ret->expr .= $token->token; } - if (($alias === 0) && (empty($options['noAlias'])) && (!$isExpr) && (!$dot) && (!empty($ret->expr))) { - $alias = 1; - } continue; } - if (($token->type === Token::TYPE_KEYWORD) - && ($token->flags & Token::FLAG_KEYWORD_RESERVED) - && ($token->value !== 'DUAL') - && ($token->value !== 'NULL') - ) { - // Keywords may be found only between brackets. - if ($brackets === 0) { - if ((empty($options['noAlias'])) && ($token->value === 'AS')) { - $alias = 2; - continue; - } - if (!($token->flags & Token::FLAG_KEYWORD_FUNCTION)) { + if ($token->type === Token::TYPE_KEYWORD) { + if (($brackets > 0) && (empty($ret->subquery)) + && (!empty(Parser::$STATEMENT_PARSERS[$token->value])) + ) { + // A `(` was previously found and this keyword is the + // beginning of a statement, so this is a subquery. + $ret->subquery = $token->value; + } elseif ($token->flags & Token::FLAG_KEYWORD_FUNCTION) { + $isExpr = true; + } elseif (($token->flags & Token::FLAG_KEYWORD_RESERVED) + && ($brackets === 0) + ) { + if (empty(self::$ALLOWED_KEYWORDS[$token->value])) { + // A reserved keyword that is not allowed in the + // expression was found so the expression must have + // ended and a new clause is starting. break; } - } elseif ($prev === true) { - if ((empty($ret->subquery) && (!empty(Parser::$STATEMENT_PARSERS[$token->value])))) { - // A `(` was previously found and this keyword is the - // beginning of a statement, so this is a subquery. - $ret->subquery = $token->value; + if ($token->value === 'AS') { + if (!empty($options['noAlias'])) { + break; + } + if (!empty($ret->alias)) { + $parser->error( + __('An alias was previously found.'), + $token + ); + break; + } + $alias = true; + continue; } + $isExpr = true; } } @@ -210,22 +228,25 @@ class Expression extends Component if ((!empty($options['noBrackets'])) && (($token->value === '(') || ($token->value === ')')) ) { + // No brackets were expected. break; } if ($token->value === '(') { ++$brackets; - if ((empty($ret->function)) && ($prev !== null) && ($prev !== true)) { - // A function name was previously found and now an open - // bracket, so this is a function call. - $ret->function = $prev; + if ((empty($ret->function)) && ($prev[1] !== null) + && (($prev[1]->type === Token::TYPE_NONE) + || ($prev[1]->type === Token::TYPE_SYMBOL) + || (($prev[1]->type === Token::TYPE_KEYWORD) + && ($prev[1]->flags & Token::FLAG_KEYWORD_FUNCTION))) + ) { + $ret->function = $prev[1]->value; } - $isExpr = true; } elseif ($token->value === ')') { --$brackets; if ($brackets === 0) { if (!empty($options['bracketsDelimited'])) { - // The current token is the last brackets, the next - // one will be outside. + // The current token is the last bracket, the next + // one will be outside the expression. $ret->expr .= $token->token; ++$list->idx; break; @@ -236,109 +257,100 @@ class Expression extends Component break; } } elseif ($token->value === ',') { + // Expressions are comma-delimited. if ($brackets === 0) { break; } } } - if (($token->type === Token::TYPE_NUMBER) || ($token->type === Token::TYPE_BOOL) - || (($token->type === Token::TYPE_SYMBOL) && ($token->flags & Token::FLAG_SYMBOL_VARIABLE)) - || (($token->type === Token::TYPE_OPERATOR)) && ($token->value !== '.') + if (($token->type === Token::TYPE_NUMBER) + || ($token->type === Token::TYPE_BOOL) + || (($token->type === Token::TYPE_SYMBOL) + && ($token->flags & Token::FLAG_SYMBOL_VARIABLE)) + || (($token->type === Token::TYPE_OPERATOR) + && ($token->value !== '.')) ) { - // Numbers, booleans and operators are usually part of expressions. + // Numbers, booleans and operators (except dot) are usually part + // of expressions. $isExpr = true; } + // Saving the previous token. + $prev[0] = $prev[1]; + $prev[1] = $token; + if ($alias) { // An alias is expected (the keyword `AS` was previously found). if (!empty($ret->alias)) { $parser->error(__('An alias was previously found.'), $token); + break; } $ret->alias = $token->value; - $alias = 0; - } else { - if (!$isExpr) { - if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '.')) { - // Found a `.` which means we expect a column name and - // the column name we parsed is actually the table name - // and the table name is actually a database name. - if ((!empty($ret->database)) || ($dot)) { - $parser->error(__('Unexpected dot.'), $token); - } - $ret->database = $ret->table; - $ret->table = $ret->column; - $ret->column = null; - $dot = true; - } else { - // We found the name of a column (or table if column - // field should be skipped; used to parse table names). - $field = (!empty($options['skipColumn'])) ? 'table' : 'column'; - if (!empty($ret->$field)) { - // No alias is expected. - if (!empty($options['noAlias'])) { - break; - } - - // Parsing aliases without `AS` keyword and any - // whitespace. - // Example: SELECT 1`foo` - if (($token->type === Token::TYPE_STRING) - || (($token->type === Token::TYPE_SYMBOL) - && ($token->flags & Token::FLAG_SYMBOL_BACKTICK)) - ) { - if (!empty($ret->alias)) { - $parser->error( - __('An alias was previously found.'), - $token - ); - } - $ret->alias = $token->value; - } - } else { - $ret->$field = $token->value; - } - $dot = false; + $alias = false; + } elseif ($isExpr) { + // Handling aliases. + if (/* (empty($ret->alias)) && */ ($brackets === 0) + && (($prev[0] === null) + || ((($prev[0]->type !== Token::TYPE_OPERATOR) + || ($prev[0]->token === ')')) + && (($prev[0]->type !== Token::TYPE_KEYWORD) + || (!($prev[0]->flags & Token::FLAG_KEYWORD_RESERVED))))) + && (($prev[1]->type === Token::TYPE_STRING) + || (($prev[1]->type === Token::TYPE_SYMBOL) + && (!($prev[1]->flags & Token::FLAG_SYMBOL_VARIABLE))) + || ($prev[1]->type === Token::TYPE_NONE)) + ) { + if (!empty($ret->alias)) { + $parser->error(__('An alias was previously found.'), $token); + break; } + $ret->alias = $prev[1]->value; } else { - // Parsing aliases without `AS` keyword. - // Example: SELECT 'foo' `bar` - if (($brackets === 0) && (empty($options['noAlias']))) { - if (($token->type === Token::TYPE_NONE) || ($token->type === Token::TYPE_STRING) - || (($token->type === Token::TYPE_SYMBOL) && ($token->flags & Token::FLAG_SYMBOL_BACKTICK)) - ) { - if (!empty($ret->alias)) { - $parser->error( - __('An alias was previously found.'), - $token - ); - } - $ret->alias = $token->value; - continue; + $ret->expr .= $token->token; + } + } elseif (!$isExpr) { + if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '.')) { + // Found a `.` which means we expect a column name and + // the column name we parsed is actually the table name + // and the table name is actually a database name. + if ((!empty($ret->database)) || ($dot)) { + $parser->error(__('Unexpected dot.'), $token); + } + $ret->database = $ret->table; + $ret->table = $ret->column; + $ret->column = null; + $dot = true; + $ret->expr .= $token->token; + } else { + $field = (!empty($options['skipColumn'])) ? 'table' : 'column'; + if (empty($ret->$field)) { + $ret->$field = $token->value; + $ret->expr .= $token->token; + $dot = false; + } else { + // No alias is expected. + if (!empty($options['noAlias'])) { + break; } + if (!empty($ret->alias)) { + $parser->error(__('An alias was previously found.'), $token); + break; + } + $ret->alias = $token->value; } } - - $ret->expr .= $token->token; - } - - if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_FUNCTION)) { - $prev = strtoupper($token->value); - } elseif (($token->type === Token::TYPE_OPERATOR) || ($token->value === '(')) { - $prev = true; - } else { - $prev = null; } } - if ($alias === 2) { + if ($alias) { $parser->error( __('An alias was expected.'), $list->tokens[$list->idx - 1] ); } - // Whitespaces might be added at the end. + // White-spaces might be added at the end. $ret->expr = trim($ret->expr); if (empty($ret->expr)) { diff --git a/libraries/sql-parser/src/Components/SetOperation.php b/libraries/sql-parser/src/Components/SetOperation.php index 427f48d7dd..38aa7f6262 100644 --- a/libraries/sql-parser/src/Components/SetOperation.php +++ b/libraries/sql-parser/src/Components/SetOperation.php @@ -92,7 +92,7 @@ class SetOperation extends Component if ($state === 0) { if ($token->token === '=') { $state = 1; - } else if ($token->value !== ',') { + } elseif ($token->value !== ',') { $expr->column .= $token->token; } } elseif ($state === 1) { diff --git a/libraries/sql-parser/src/Context.php b/libraries/sql-parser/src/Context.php index 40cd93470b..7637953ab5 100644 --- a/libraries/sql-parser/src/Context.php +++ b/libraries/sql-parser/src/Context.php @@ -107,7 +107,7 @@ abstract class Context ':=' => 8, // @see Token::FLAG_OPERATOR_SQL - '(' => 16, ')' => 16, '.' => 16, ',' => 16, + '(' => 16, ')' => 16, '.' => 16, ',' => 16, ';' => 16, ); /** @@ -397,9 +397,12 @@ abstract class Context */ public static function isSeparator($str) { - // NOTES: Only ASCII characters may be separators. + // NOTES: Only non alphanumeric ASCII characters may be separators. // `~` is the last printable ASCII character. - return ($str <= '~') && (!ctype_alnum($str)) && ($str !== '_'); + return ($str <= '~') && ($str !== '_') + && (($str < '0') || ($str > '9')) + && (($str < 'a') || ($str > 'z')) + && (($str < 'A') || ($str > 'Z')); } /** diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50000.php b/libraries/sql-parser/src/Contexts/ContextMySql50000.php index ceedd1704a..91f1179e0d 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50000.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50000.php @@ -37,8 +37,8 @@ class ContextMySql50000 extends Context public static $KEYWORDS = array( 'DO' => 1, 'IO' => 1, 'NO' => 1, 'XA' => 1, - 'ANY' => 1, 'BDB' => 1, 'BIT' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, - 'NDB' => 1, 'NEW' => 1, 'ONE' => 1, 'ROW' => 1, + 'ANY' => 1, 'BDB' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, + 'NEW' => 1, 'ONE' => 1, 'ROW' => 1, 'BOOL' => 1, 'BYTE' => 1, 'CODE' => 1, 'CUBE' => 1, 'DATA' => 1, 'FAST' => 1, 'FILE' => 1, 'FULL' => 1, 'HASH' => 1, 'HELP' => 1, 'LAST' => 1, 'LOGS' => 1, 'MODE' => 1, 'NAME' => 1, 'NEXT' => 1, 'NONE' => 1, 'OPEN' => 1, 'PAGE' => 1, @@ -162,7 +162,7 @@ class ContextMySql50000 extends Context 'DEFAULT CHARACTER SET' => 7, 'WITH CONSISTENT SNAPSHOT' => 7, - 'XML' => 9, + 'BIT' => 9, 'XML' => 9, 'ENUM' => 9, 'JSON' => 9, 'TEXT' => 9, 'ARRAY' => 9, 'SERIAL' => 9, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50100.php b/libraries/sql-parser/src/Contexts/ContextMySql50100.php index 07ea04a2b5..a62d2ecaf1 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50100.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50100.php @@ -37,8 +37,8 @@ class ContextMySql50100 extends Context public static $KEYWORDS = array( 'AT' => 1, 'DO' => 1, 'IO' => 1, 'NO' => 1, 'XA' => 1, - 'ANY' => 1, 'BDB' => 1, 'BIT' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, - 'NDB' => 1, 'NEW' => 1, 'ONE' => 1, 'ROW' => 1, + 'ANY' => 1, 'BDB' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, + 'NEW' => 1, 'ONE' => 1, 'ROW' => 1, 'BOOL' => 1, 'BYTE' => 1, 'CODE' => 1, 'CUBE' => 1, 'DATA' => 1, 'DISK' => 1, 'ENDS' => 1, 'FAST' => 1, 'FILE' => 1, 'FULL' => 1, 'GOTO' => 1, 'HASH' => 1, 'HELP' => 1, 'HOST' => 1, 'LAST' => 1, 'LESS' => 1, 'LIST' => 1, 'LOGS' => 1, @@ -175,7 +175,7 @@ class ContextMySql50100 extends Context 'DEFAULT CHARACTER SET' => 7, 'WITH CONSISTENT SNAPSHOT' => 7, - 'XML' => 9, + 'BIT' => 9, 'XML' => 9, 'ENUM' => 9, 'JSON' => 9, 'TEXT' => 9, 'ARRAY' => 9, 'SERIAL' => 9, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50500.php b/libraries/sql-parser/src/Contexts/ContextMySql50500.php index a56ba1bb65..03dd24f033 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50500.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50500.php @@ -37,8 +37,8 @@ class ContextMySql50500 extends Context public static $KEYWORDS = array( 'AT' => 1, 'DO' => 1, 'IO' => 1, 'NO' => 1, 'XA' => 1, - 'ANY' => 1, 'BIT' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, - 'NEW' => 1, 'ONE' => 1, 'ROW' => 1, + 'ANY' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, 'NEW' => 1, + 'ONE' => 1, 'ROW' => 1, 'BOOL' => 1, 'BYTE' => 1, 'CODE' => 1, 'CUBE' => 1, 'DATA' => 1, 'DISK' => 1, 'ENDS' => 1, 'FAST' => 1, 'FILE' => 1, 'FULL' => 1, 'HASH' => 1, 'HELP' => 1, 'HOST' => 1, 'LAST' => 1, 'LESS' => 1, 'LIST' => 1, 'LOGS' => 1, 'MODE' => 1, @@ -180,7 +180,7 @@ class ContextMySql50500 extends Context 'DEFAULT CHARACTER SET' => 7, 'WITH CONSISTENT SNAPSHOT' => 7, - 'XML' => 9, + 'BIT' => 9, 'XML' => 9, 'ENUM' => 9, 'JSON' => 9, 'TEXT' => 9, 'ARRAY' => 9, 'SERIAL' => 9, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50600.php b/libraries/sql-parser/src/Contexts/ContextMySql50600.php index 481cff5698..3773d233e2 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50600.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50600.php @@ -37,8 +37,8 @@ class ContextMySql50600 extends Context public static $KEYWORDS = array( 'AT' => 1, 'DO' => 1, 'IO' => 1, 'NO' => 1, 'XA' => 1, - 'ANY' => 1, 'BIT' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, - 'NEW' => 1, 'ONE' => 1, 'ROW' => 1, + 'ANY' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, 'NEW' => 1, + 'ONE' => 1, 'ROW' => 1, 'BOOL' => 1, 'BYTE' => 1, 'CODE' => 1, 'CUBE' => 1, 'DATA' => 1, 'DISK' => 1, 'ENDS' => 1, 'FAST' => 1, 'FILE' => 1, 'FULL' => 1, 'HASH' => 1, 'HELP' => 1, 'HOST' => 1, 'LAST' => 1, 'LESS' => 1, 'LIST' => 1, 'LOGS' => 1, 'MODE' => 1, @@ -186,7 +186,7 @@ class ContextMySql50600 extends Context 'DEFAULT CHARACTER SET' => 7, 'WITH CONSISTENT SNAPSHOT' => 7, - 'XML' => 9, + 'BIT' => 9, 'XML' => 9, 'ENUM' => 9, 'JSON' => 9, 'TEXT' => 9, 'ARRAY' => 9, 'SERIAL' => 9, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50700.php b/libraries/sql-parser/src/Contexts/ContextMySql50700.php index 14daee2682..c4867c2a7b 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50700.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50700.php @@ -37,8 +37,8 @@ class ContextMySql50700 extends Context public static $KEYWORDS = array( 'AT' => 1, 'DO' => 1, 'IO' => 1, 'NO' => 1, 'XA' => 1, - 'ANY' => 1, 'BIT' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, - 'NEW' => 1, 'ONE' => 1, 'ROW' => 1, 'XID' => 1, + 'ANY' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, 'NEW' => 1, + 'ONE' => 1, 'ROW' => 1, 'XID' => 1, 'BOOL' => 1, 'BYTE' => 1, 'CODE' => 1, 'CUBE' => 1, 'DATA' => 1, 'DISK' => 1, 'ENDS' => 1, 'FAST' => 1, 'FILE' => 1, 'FULL' => 1, 'HASH' => 1, 'HELP' => 1, 'HOST' => 1, 'LAST' => 1, 'LESS' => 1, 'LIST' => 1, 'LOGS' => 1, 'MODE' => 1, @@ -193,7 +193,7 @@ class ContextMySql50700 extends Context 'DEFAULT CHARACTER SET' => 7, 'WITH CONSISTENT SNAPSHOT' => 7, - 'XML' => 9, + 'BIT' => 9, 'XML' => 9, 'ENUM' => 9, 'JSON' => 9, 'TEXT' => 9, 'ARRAY' => 9, 'SERIAL' => 9, diff --git a/libraries/sql-parser/src/Parser.php b/libraries/sql-parser/src/Parser.php index c050e42d7e..2aca938a21 100644 --- a/libraries/sql-parser/src/Parser.php +++ b/libraries/sql-parser/src/Parser.php @@ -36,6 +36,7 @@ class Parser // MySQL Utility Statements 'DESCRIBE' => 'SqlParser\\Statements\\ExplainStatement', + 'DESC' => 'SqlParser\\Statements\\ExplainStatement', 'EXPLAIN' => 'SqlParser\\Statements\\ExplainStatement', 'FLUSH' => '', 'GRANT' => '', diff --git a/libraries/sql-parser/src/Statements/AlterStatement.php b/libraries/sql-parser/src/Statements/AlterStatement.php index 4da360aafc..275eae7011 100644 --- a/libraries/sql-parser/src/Statements/AlterStatement.php +++ b/libraries/sql-parser/src/Statements/AlterStatement.php @@ -76,8 +76,8 @@ class AlterStatement extends Statement $parser, $list, array( - 'noAlias' => true, - 'noBrackets' => true, + 'noAlias' => true, + 'noBrackets' => true, ) ); ++$list->idx; // Skipping field. diff --git a/libraries/sql-parser/src/Utils/BufferedQuery.php b/libraries/sql-parser/src/Utils/BufferedQuery.php index f399220b22..1aaa9d7910 100644 --- a/libraries/sql-parser/src/Utils/BufferedQuery.php +++ b/libraries/sql-parser/src/Utils/BufferedQuery.php @@ -29,12 +29,18 @@ class BufferedQuery { // Constants that describe the current status of the parser. - const STATUS_STRING_SINGLE_QUOTES = 1; - const STATUS_STRING_DOUBLE_QUOTES = 2; - const STATUS_STRING_BACKTICK = 3; - const STATUS_COMMENT_BASH = 4; - const STATUS_COMMENT_C = 5; - const STATUS_COMMENT_SQL = 6; + + // A string is being parsed. + const STATUS_STRING = 16; // 0001 0000 + const STATUS_STRING_SINGLE_QUOTES = 17; // 0001 0001 + const STATUS_STRING_DOUBLE_QUOTES = 18; // 0001 0010 + const STATUS_STRING_BACKTICK = 20; // 0001 0100 + + // A comment is being parsed. + const STATUS_COMMENT = 32; // 0010 0000 + const STATUS_COMMENT_BASH = 33; // 0010 0001 + const STATUS_COMMENT_C = 34; // 0010 0010 + const STATUS_COMMENT_SQL = 36; // 0010 0100 /** * The query that is being processed. @@ -193,7 +199,7 @@ class BufferedQuery * treated differently, because of the preceding backslash, it will * be ignored. */ - if ($this->query[$i] === '\\') { + if (($this->status & static::STATUS_COMMENT == 0) && ($this->query[$i] === '\\')) { $this->current .= $this->query[$i] . $this->query[++$i]; continue; } diff --git a/libraries/sql-parser/src/Utils/Formatter.php b/libraries/sql-parser/src/Utils/Formatter.php index 7ff13463f6..266440bcd6 100644 --- a/libraries/sql-parser/src/Utils/Formatter.php +++ b/libraries/sql-parser/src/Utils/Formatter.php @@ -305,8 +305,8 @@ class Formatter && (!$formattedOptions) && (empty(self::$INLINE_CLAUSES[$lastClause])) && (($curr->type !== Token::TYPE_KEYWORD) - || (($curr->type === Token::TYPE_KEYWORD) - && ($curr->flags & Token::FLAG_KEYWORD_FUNCTION))) + || (($curr->type === Token::TYPE_KEYWORD) + && ($curr->flags & Token::FLAG_KEYWORD_FUNCTION))) ) { $formattedOptions = true; $lineEnded = true; From 9389db2916c334c3c475dab9fa9f2cd0a76a9a70 Mon Sep 17 00:00:00 2001 From: Dan Ungureanu Date: Wed, 10 Feb 2016 18:36:44 +0200 Subject: [PATCH 2/2] Added changelog entries for 7e12feba5440aca64570d14bae8af76c12b2e542. Signed-off-by: Dan Ungureanu --- ChangeLog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ChangeLog b/ChangeLog index 986cea3f7a..6c9b34f94e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,13 @@ phpMyAdmin - ChangeLog - issue #11834 Adjust privileges fails if database name contains underscores - issue #11906 'Loading...' banner shows on login screen - issue #11930 Fixed changing of table parameters, eg. AUTO_INCREMENT +- issue #11885 Call to undefined function SqlParser\ctype_alnum() +- issue #11879 4.5.3.1 - NOW() function not recognized by parser +- issue #11867 Gracefully handle the DESC statement +- issue #11843 Fractional timestamp causes corrupted SQL export +- issue #11836 Static analysis error for valid WHERE condition with IF keyword +- issue #11800 Syntax Verifier error using REGEXP in SQL statement +- issue #11799 Backslashes in comments are being interpreted as escape characters 4.5.4.1 (2016-01-29) - issue #11892 Error with PMA 4.4.15.3