Merge pull request #11650 from udan11/QA_4_5

Fixed multiple parser issues (#11646, #11635, #11631, #11626, #11608, #11602).
This commit is contained in:
Dan Ungureanu 2015-11-10 20:15:39 +02:00
commit 195945a7c5
15 changed files with 112 additions and 22 deletions

View File

@ -46,6 +46,7 @@ class Condition extends Component
'LIKE' => 1,
'NOT IN' => 1,
'NOT NULL' => 1,
'NOT' => 1,
'NULL' => 1,
'OR' => 1,
'XOR' => 1,

View File

@ -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) {

View File

@ -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
);

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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) {

View File

@ -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.
*

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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.