From 67265ceff0c2c59911f76a34b8a564a77fbc4df9 Mon Sep 17 00:00:00 2001 From: Dan Ungureanu Date: Sun, 8 Nov 2015 15:33:08 +0200 Subject: [PATCH] Fixed multiple parser issues (#11646, #11635, #11631, #11626, #11608, #11602). Updated sql-parser library to udan11/sql-parser@2a3d3b80deba3364b7a1c4ddd52b22c7bb24995b (v3.0.5). Signed-off-by: Dan Ungureanu --- .../sql-parser/src/Components/Condition.php | 1 + .../sql-parser/src/Components/Expression.php | 5 ++-- .../src/Components/OptionsArray.php | 4 +-- .../src/Contexts/ContextMySql50000.php | 2 +- .../src/Contexts/ContextMySql50100.php | 2 +- .../src/Contexts/ContextMySql50500.php | 2 +- .../src/Contexts/ContextMySql50600.php | 2 +- .../src/Contexts/ContextMySql50700.php | 2 +- libraries/sql-parser/src/Parser.php | 17 +++++++++++- libraries/sql-parser/src/Statement.php | 26 ++++++++++++++++++- .../src/Statements/CreateStatement.php | 13 ++++++++++ .../src/Statements/InsertStatement.php | 13 +++++++++- .../src/Statements/SelectStatement.php | 21 +++++++++++++++ .../sql-parser/src/Utils/BufferedQuery.php | 5 ++++ libraries/sql-parser/src/Utils/Query.php | 19 +++++++------- 15 files changed, 112 insertions(+), 22 deletions(-) diff --git a/libraries/sql-parser/src/Components/Condition.php b/libraries/sql-parser/src/Components/Condition.php index d7262c5978..ab327a2166 100644 --- a/libraries/sql-parser/src/Components/Condition.php +++ b/libraries/sql-parser/src/Components/Condition.php @@ -46,6 +46,7 @@ class Condition extends Component 'LIKE' => 1, 'NOT IN' => 1, 'NOT NULL' => 1, + 'NOT' => 1, 'NULL' => 1, 'OR' => 1, 'XOR' => 1, diff --git a/libraries/sql-parser/src/Components/Expression.php b/libraries/sql-parser/src/Components/Expression.php index 22cd23fd10..4c2c04c917 100644 --- a/libraries/sql-parser/src/Components/Expression.php +++ b/libraries/sql-parser/src/Components/Expression.php @@ -230,8 +230,9 @@ class Expression extends Component break; } } elseif ($brackets < 0) { - $parser->error(__('Unexpected closing bracket.'), $token); - $brackets = 0; + // $parser->error(__('Unexpected closing bracket.'), $token); + // $brackets = 0; + break; } } elseif ($token->value === ',') { if ($brackets === 0) { diff --git a/libraries/sql-parser/src/Components/OptionsArray.php b/libraries/sql-parser/src/Components/OptionsArray.php index 42f0762ab7..229b350110 100644 --- a/libraries/sql-parser/src/Components/OptionsArray.php +++ b/libraries/sql-parser/src/Components/OptionsArray.php @@ -146,8 +146,8 @@ class OptionsArray extends Component sprintf( __('This option conflicts with "%1$s".'), is_array($ret->options[$lastOptionId]) - ? $ret->options[$lastOptionId]['name'] - : $ret->options[$lastOptionId] + ? $ret->options[$lastOptionId]['name'] + : $ret->options[$lastOptionId] ), $token ); diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50000.php b/libraries/sql-parser/src/Contexts/ContextMySql50000.php index 01090b3323..2df5326b49 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50000.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50000.php @@ -151,7 +151,7 @@ class ContextMySql50000 extends Context 'RIGHT JOIN' => 7, 'LINEAR HASH' => 7, 'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'PARTITION BY' => 7, - 'SQL SECURITY' => 7, + 'SET PASSWORD' => 7, 'SQL SECURITY' => 7, 'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7, 'DATA DIRECTORY' => 7, 'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50100.php b/libraries/sql-parser/src/Contexts/ContextMySql50100.php index b10ff1cd32..3779c0eeeb 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50100.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50100.php @@ -164,7 +164,7 @@ class ContextMySql50100 extends Context 'RIGHT JOIN' => 7, 'LINEAR HASH' => 7, 'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'PARTITION BY' => 7, - 'SQL SECURITY' => 7, + 'SET PASSWORD' => 7, 'SQL SECURITY' => 7, 'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7, 'DATA DIRECTORY' => 7, 'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50500.php b/libraries/sql-parser/src/Contexts/ContextMySql50500.php index c7faa84771..a8cd9e7639 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50500.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50500.php @@ -169,7 +169,7 @@ class ContextMySql50500 extends Context 'RIGHT JOIN' => 7, 'LINEAR HASH' => 7, 'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'PARTITION BY' => 7, - 'SQL SECURITY' => 7, + 'SET PASSWORD' => 7, 'SQL SECURITY' => 7, 'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7, 'DATA DIRECTORY' => 7, 'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50600.php b/libraries/sql-parser/src/Contexts/ContextMySql50600.php index 9854d37fb6..d7b0080a28 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50600.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50600.php @@ -174,7 +174,7 @@ class ContextMySql50600 extends Context 'RIGHT JOIN' => 7, 'LINEAR HASH' => 7, 'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'PARTITION BY' => 7, - 'SQL SECURITY' => 7, + 'SET PASSWORD' => 7, 'SQL SECURITY' => 7, 'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7, 'DATA DIRECTORY' => 7, 'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50700.php b/libraries/sql-parser/src/Contexts/ContextMySql50700.php index a308f5a5cd..1235524263 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50700.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50700.php @@ -182,7 +182,7 @@ class ContextMySql50700 extends Context 'RIGHT JOIN' => 7, 'LINEAR HASH' => 7, 'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'PARTITION BY' => 7, - 'SQL SECURITY' => 7, + 'SET PASSWORD' => 7, 'SQL SECURITY' => 7, 'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7, 'DATA DIRECTORY' => 7, 'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7, diff --git a/libraries/sql-parser/src/Parser.php b/libraries/sql-parser/src/Parser.php index 1280d7287b..8f4b2eda92 100644 --- a/libraries/sql-parser/src/Parser.php +++ b/libraries/sql-parser/src/Parser.php @@ -37,9 +37,11 @@ class Parser // MySQL Utility Statements 'EXPLAIN' => 'SqlParser\\Statements\\ExplainStatement', 'DESCRIBE' => 'SqlParser\\Statements\\ExplainStatement', + 'GRANT' => '', 'HELP' => '', - 'USE' => '', + 'SET PASSWORD' => '', 'STATUS' => '', + 'USE' => '', // Table Maintenance Statements // https://dev.mysql.com/doc/refman/5.7/en/table-maintenance-sql.html @@ -296,6 +298,13 @@ class Parser */ public $statements = array(); + /** + * The number of opened brackets. + * + * @var int + */ + public $brackets = 0; + /** * Constructor. * @@ -380,6 +389,12 @@ class Parser continue; } + // Counting the brackets around statements. + if ($token->value === '(') { + ++$this->brackets; + continue; + } + // Statements can start with keywords only. // Comments, whitespaces, etc. are ignored. if ($token->type !== Token::TYPE_KEYWORD) { diff --git a/libraries/sql-parser/src/Statement.php b/libraries/sql-parser/src/Statement.php index b15dc3dac9..a9d4dc8c94 100644 --- a/libraries/sql-parser/src/Statement.php +++ b/libraries/sql-parser/src/Statement.php @@ -122,7 +122,14 @@ abstract class Statement */ $built = array(); - foreach (static::$CLAUSES as $clause) { + /** + * Statement's clauses. + * + * @var array + */ + $clauses = $this->getClauses(); + + foreach ($clauses as $clause) { /** * The name of the clause. * @@ -224,6 +231,13 @@ abstract class Statement break; } + // Checking if this closing bracket is the pair for a bracket + // outside the statement. + if (($token->value === ')') && ($parser->brackets > 0)) { + --$parser->brackets; + continue; + } + // Only keywords are relevant here. Other parts of the query are // processed in the functions below. if ($token->type !== Token::TYPE_KEYWORD) { @@ -363,6 +377,16 @@ abstract class Statement } + /** + * Gets the clauses of this statement. + * + * @return array + */ + public function getClauses() + { + return static::$CLAUSES; + } + /** * Builds the string representation of this statement. * diff --git a/libraries/sql-parser/src/Statements/CreateStatement.php b/libraries/sql-parser/src/Statements/CreateStatement.php index 1a46b56f40..fe6693bf9d 100644 --- a/libraries/sql-parser/src/Statements/CreateStatement.php +++ b/libraries/sql-parser/src/Statements/CreateStatement.php @@ -314,6 +314,11 @@ class CreateStatement extends Statement . Expression::build($this->name) . ' ' . ParameterDefinition::build($this->parameters) . ' ' . $tmp . ' ' . TokensList::build($this->body); + } else { + return 'CREATE ' + . OptionsArray::build($this->options) . ' ' + . Expression::build($this->name) . ' ' + . TokensList::build($this->body); } return ''; } @@ -547,6 +552,14 @@ class CreateStatement extends Statement $token = $list->tokens[$list->idx]; $this->body[] = $token; } + } else { + for (; $list->idx < $list->count; ++$list->idx) { + $token = $list->tokens[$list->idx]; + if ($token->type === Token::TYPE_DELIMITER) { + break; + } + $this->body[] = $token; + } } } } diff --git a/libraries/sql-parser/src/Statements/InsertStatement.php b/libraries/sql-parser/src/Statements/InsertStatement.php index 303de259b6..c2a02b0ec0 100644 --- a/libraries/sql-parser/src/Statements/InsertStatement.php +++ b/libraries/sql-parser/src/Statements/InsertStatement.php @@ -11,6 +11,7 @@ namespace SqlParser\Statements; use SqlParser\Statement; use SqlParser\Components\IntoKeyword; use SqlParser\Components\Array2d; +use SqlParser\Components\ArrayObj; /** * `INSERT` statement. @@ -76,7 +77,17 @@ class InsertStatement extends Statement /** * Values to be inserted. * - * @var Array2d + * @var ArrayObj[] */ public $values; + + /** + * @return string + */ + public function build() + { + return 'INSERT ' . $this->options + . ' INTO ' . $this->into + . ' VALUES ' . ArrayObj::build($this->values); + } } diff --git a/libraries/sql-parser/src/Statements/SelectStatement.php b/libraries/sql-parser/src/Statements/SelectStatement.php index add3b8b65d..4bcbf2dd87 100644 --- a/libraries/sql-parser/src/Statements/SelectStatement.php +++ b/libraries/sql-parser/src/Statements/SelectStatement.php @@ -195,4 +195,25 @@ class SelectStatement extends Statement * @var SelectStatement[] */ public $union = array(); + + /** + * Gets the clauses of this statement. + * + * @return array + */ + public function getClauses() + { + // This is a cheap fix for `SELECT` statements that contain `UNION`. + // The `ORDER BY` and `LIMIT` clauses should be at the end of the + // statement. + if (!empty($this->union)) { + $clauses = static::$CLAUSES; + unset($clauses['ORDER BY']); + unset($clauses['LIMIT']); + $clauses['ORDER BY'] = array('ORDER BY', 3); + $clauses['LIMIT'] = array('LIMIT', 3); + return $clauses; + } + return static::$CLAUSES; + } } diff --git a/libraries/sql-parser/src/Utils/BufferedQuery.php b/libraries/sql-parser/src/Utils/BufferedQuery.php index 7a2e5f4374..f399220b22 100644 --- a/libraries/sql-parser/src/Utils/BufferedQuery.php +++ b/libraries/sql-parser/src/Utils/BufferedQuery.php @@ -283,6 +283,10 @@ class BufferedQuery * `strtoupper(substr($this->query, $i, 9)) === 'DELIMITER'` * * This optimization makes the code about 3 times faster. + * + * `DELIMITER` is not being considered a keyword. The only context + * it has a special meaning is when it is the beginning of a + * statement. This is the reason for the last condition. */ if (($i + 9 < $len) && (($this->query[$i ] === 'D') || ($this->query[$i ] === 'd')) @@ -295,6 +299,7 @@ class BufferedQuery && (($this->query[$i + 7] === 'E') || ($this->query[$i + 7] === 'e')) && (($this->query[$i + 8] === 'R') || ($this->query[$i + 8] === 'r')) && (Context::isWhitespace($this->query[$i + 9])) + && (trim($this->current) === '') ) { // Saving the current index to be able to revert any parsing // done in this block. diff --git a/libraries/sql-parser/src/Utils/Query.php b/libraries/sql-parser/src/Utils/Query.php index 4325b6563b..d76e6bb723 100644 --- a/libraries/sql-parser/src/Utils/Query.php +++ b/libraries/sql-parser/src/Utils/Query.php @@ -295,7 +295,14 @@ class Query $flags['is_export'] = true; } - foreach ($statement->expr as $expr) { + $expressions = $statement->expr; + if (!empty($statement->join)) { + foreach ($statement->join as $join) { + $expressions[] = $join->expr; + } + } + + foreach ($expressions as $expr) { if (!empty($expr->function)) { if ($expr->function === 'COUNT') { $flags['is_count'] = true; @@ -539,15 +546,7 @@ class Query * * @var array $clauses */ - $clauses = array_flip(array_keys($statement::$CLAUSES)); - - // This is a cheap fix for `SELECT` statements that contain `UNION`. - // Replacing the `ORDER BY` or `LIMIT` clauses should replace the last - // clause. - if (($statement instanceof SelectStatement) && (!empty($statement->union))) { - $clauses['ORDER BY'] = count($clauses) + 1; - $clauses['LIMIT'] = count($clauses) + 2; - } + $clauses = array_flip(array_keys($statement->getClauses())); /** * Lexer used for lexing the clause.