Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,9 @@ public function parse()
$lastStatement->last = $statement->last;

$unionType = false;

// Validate clause order
$statement->validateClauseOrder($this, $list);
continue;
}

Expand All @@ -556,9 +559,15 @@ public function parse()
}
$lastTransaction = null;
}

// Validate clause order
$statement->validateClauseOrder($this, $list);
continue;
}

// Validate clause order
$statement->validateClauseOrder($this, $list);

// Finally, storing the statement.
if ($lastTransaction !== null) {
$lastTransaction->statements[] = $statement;
Expand Down
43 changes: 43 additions & 0 deletions src/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -422,4 +422,47 @@ public function __toString()
{
return $this->build();
}

/**
* Validates the order of the clauses in parsed statement
* Ideally this should be called after successfully
* completing the parsing of each statement
*
* @param Parser $parser The instance that requests parsing.
* @param TokensList $list The list of tokens to be parsed.
*
* @return boolean
*/
public function validateClauseOrder($parser, $list)
{
$clauses = array_flip(array_keys($this->getClauses()));

if (empty($clauses)
|| count($clauses) == 0
) {
return true;
}

$minIdx = -1;
foreach ($clauses as $clauseType => $index) {
$clauseStartIdx = Utils\Query::getClauseStartOffset(
$this,
$list,
$clauseType
);

if ($clauseStartIdx != -1 && $clauseStartIdx < $minIdx) {
$token = $list->tokens[$clauseStartIdx];
$parser->error(
__('Unexpected ordering of clauses.'),
$token
);
return false;
} elseif ($clauseStartIdx != -1) {
$minIdx = $clauseStartIdx;
}
}

return true;
}
}
62 changes: 62 additions & 0 deletions src/Utils/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -783,4 +783,66 @@ public static function getFirstStatement($query, $delimiter = null)

return array(trim($statement), $query, $delimiter);
}

/**
* Gets a starting offset of a specific clause.
*
* @param Statement $statement The parsed query that has to be modified.
* @param TokensList $list The list of tokens.
* @param string $clause The clause to be returned.
*
* @return int
*/
public static function getClauseStartOffset($statement, $list, $clause)
{

/**
* The index of the current clause.
*
* @var int $currIdx
*/
$currIdx = 0;

/**
* The count of brackets.
* We keep track of them so we won't insert the clause in a subquery.
*
* @var int $brackets
*/
$brackets = 0;

/**
* The clauses of this type of statement and their index.
*
* @var array $clauses
*/
$clauses = array_flip(array_keys($statement->getClauses()));

for ($i = $statement->first; $i <= $statement->last; ++$i) {
$token = $list->tokens[$i];

if ($token->type === Token::TYPE_COMMENT) {
continue;
}

if ($token->type === Token::TYPE_OPERATOR) {
if ($token->value === '(') {
++$brackets;
} elseif ($token->value === ')') {
--$brackets;
}
}

if ($brackets == 0) {
if (($token->type === Token::TYPE_KEYWORD)
&& (isset($clauses[$token->value]))
&& ($clause === $token->value)
) {
return $i;
}
}
}

return -1;
}
}
1 change: 1 addition & 0 deletions tests/Parser/SelectStatementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public function testSelectProvider()
array('parser/parseSelectJoinNaturalLeftOuter'),
array('parser/parseSelectJoinNaturalRightOuter'),
array('parser/parseSelectJoinMultiple'),
array('parser/parseSelectWrongOrder'),
);
}
}
1 change: 1 addition & 0 deletions tests/data/parser/parseSelectWrongOrder.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELECT pid, name2 FROM tablename LIMIT 10 WHERE pid = 20
1 change: 1 addition & 0 deletions tests/data/parser/parseSelectWrongOrder.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a:4:{s:5:"query";s:56:"SELECT pid, name2 FROM tablename LIMIT 10 WHERE pid = 20";s:5:"lexer";O:15:"SqlParser\Lexer":8:{s:6:"strict";b:0;s:3:"str";s:56:"SELECT pid, name2 FROM tablename LIMIT 10 WHERE pid = 20";s:3:"len";i:56;s:4:"last";i:56;s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:23:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"pid";s:5:"value";s:3:"pid";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:",";s:5:"value";s:1:",";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:10;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:11;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"name2";s:5:"value";s:5:"name2";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:12;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:17;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:18;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:22;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:9:"tablename";s:5:"value";s:9:"tablename";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:23;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:32;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"LIMIT";s:5:"value";s:5:"LIMIT";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:33;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:38;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"10";s:5:"value";i:10;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:39;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:41;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"WHERE";s:5:"value";s:5:"WHERE";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:42;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:47;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"pid";s:5:"value";s:3:"pid";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:48;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:51;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:52;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:53;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"20";s:5:"value";i:20;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:54;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:23;s:3:"idx";i:23;}s:9:"delimiter";s:1:";";s:12:"delimiterLen";i:1;s:6:"errors";a:0:{}}s:6:"parser";O:16:"SqlParser\Parser":5:{s:4:"list";r:8;s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\SelectStatement":15:{s:4:"expr";a:2:{i:0;O:31:"SqlParser\Components\Expression":7:{s:8:"database";N;s:5:"table";N;s:6:"column";s:3:"pid";s:4:"expr";s:3:"pid";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}i:1;O:31:"SqlParser\Components\Expression":7:{s:8:"database";N;s:5:"table";N;s:6:"column";s:5:"name2";s:4:"expr";s:5:"name2";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}}s:4:"from";a:1:{i:0;O:31:"SqlParser\Components\Expression":7:{s:8:"database";N;s:5:"table";s:9:"tablename";s:6:"column";N;s:4:"expr";s:9:"tablename";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}}s:9:"partition";N;s:5:"where";a:1:{i:0;O:30:"SqlParser\Components\Condition":3:{s:11:"identifiers";a:1:{i:0;s:3:"pid";}s:10:"isOperator";b:0;s:4:"expr";s:8:"pid = 20";}}s:5:"group";N;s:6:"having";N;s:5:"order";N;s:5:"limit";O:26:"SqlParser\Components\Limit":2:{s:6:"offset";i:0;s:8:"rowCount";i:10;}s:9:"procedure";N;s:4:"into";N;s:4:"join";N;s:5:"union";a:0:{}s:7:"options";O:33:"SqlParser\Components\OptionsArray":1:{s:7:"options";a:0:{}}s:5:"first";i:0;s:4:"last";i:21;}}s:8:"brackets";i:0;}s:6:"errors";a:2:{s:5:"lexer";a:0:{}s:6:"parser";a:1:{i:0;a:3:{i:0;s:31:"Unexpected ordering of clauses.";i:1;r:76;i:2;i:0;}}}}