diff options
-rw-r--r-- | src/Parser.php | 9 | ||||
-rw-r--r-- | src/Statement.php | 43 | ||||
-rw-r--r-- | src/Utils/Query.php | 62 |
3 files changed, 114 insertions, 0 deletions
diff --git a/src/Parser.php b/src/Parser.php index 0d48a41..d98e8dc 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -531,6 +531,9 @@ class Parser $lastStatement->last = $statement->last; $unionType = false; + + // Validate clause order + $statement->validateClauseOrder($this, $list); continue; } @@ -556,9 +559,15 @@ class Parser } $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; diff --git a/src/Statement.php b/src/Statement.php index c23db67..b4769ef 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -422,4 +422,47 @@ abstract class Statement { 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; + } } diff --git a/src/Utils/Query.php b/src/Utils/Query.php index 2e0982a..c3167ff 100644 --- a/src/Utils/Query.php +++ b/src/Utils/Query.php @@ -783,4 +783,66 @@ class Query 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; + } } |