Merge pull request #11650 from udan11/QA_4_5
Fixed multiple parser issues (#11646, #11635, #11631, #11626, #11608, #11602).
This commit is contained in:
commit
195945a7c5
@ -46,6 +46,7 @@ class Condition extends Component
|
||||
'LIKE' => 1,
|
||||
'NOT IN' => 1,
|
||||
'NOT NULL' => 1,
|
||||
'NOT' => 1,
|
||||
'NULL' => 1,
|
||||
'OR' => 1,
|
||||
'XOR' => 1,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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.
|
||||
*
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user