3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHARNTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
20 namespace Doctrine\ORM\Query;
22 use Doctrine\ORM\Query;
23 use Doctrine\ORM\Mapping\ClassMetadata;
26 * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
27 * Parses a DQL query, reports any errors in it, and generates an AST.
30 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
31 * @author Jonathan Wage <jonwage@gmail.com>
32 * @author Roman Borschel <roman@code-factory.org>
33 * @author Janne Vanhala <jpvanhal@cc.hut.fi>
37 /** READ-ONLY: Maps BUILT-IN string function names to AST class names. */
38 private static $_STRING_FUNCTIONS = array(
39 'concat' => 'Doctrine\ORM\Query\AST\Functions\ConcatFunction',
40 'substring' => 'Doctrine\ORM\Query\AST\Functions\SubstringFunction',
41 'trim' => 'Doctrine\ORM\Query\AST\Functions\TrimFunction',
42 'lower' => 'Doctrine\ORM\Query\AST\Functions\LowerFunction',
43 'upper' => 'Doctrine\ORM\Query\AST\Functions\UpperFunction',
44 'identity' => 'Doctrine\ORM\Query\AST\Functions\IdentityFunction',
47 /** READ-ONLY: Maps BUILT-IN numeric function names to AST class names. */
48 private static $_NUMERIC_FUNCTIONS = array(
49 'length' => 'Doctrine\ORM\Query\AST\Functions\LengthFunction',
50 'locate' => 'Doctrine\ORM\Query\AST\Functions\LocateFunction',
51 'abs' => 'Doctrine\ORM\Query\AST\Functions\AbsFunction',
52 'sqrt' => 'Doctrine\ORM\Query\AST\Functions\SqrtFunction',
53 'mod' => 'Doctrine\ORM\Query\AST\Functions\ModFunction',
54 'size' => 'Doctrine\ORM\Query\AST\Functions\SizeFunction',
55 'date_diff' => 'Doctrine\ORM\Query\AST\Functions\DateDiffFunction',
56 'bit_and' => 'Doctrine\ORM\Query\AST\Functions\BitAndFunction',
57 'bit_or' => 'Doctrine\ORM\Query\AST\Functions\BitOrFunction',
60 /** READ-ONLY: Maps BUILT-IN datetime function names to AST class names. */
61 private static $_DATETIME_FUNCTIONS = array(
62 'current_date' => 'Doctrine\ORM\Query\AST\Functions\CurrentDateFunction',
63 'current_time' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimeFunction',
64 'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction',
65 'date_add' => 'Doctrine\ORM\Query\AST\Functions\DateAddFunction',
66 'date_sub' => 'Doctrine\ORM\Query\AST\Functions\DateSubFunction',
70 * Expressions that were encountered during parsing of identifiers and expressions
71 * and still need to be validated.
73 private $_deferredIdentificationVariables = array();
74 private $_deferredPartialObjectExpressions = array();
75 private $_deferredPathExpressions = array();
76 private $_deferredResultVariables = array();
81 * @var \Doctrine\ORM\Query\Lexer
88 * @var \Doctrine\ORM\Query\ParserResult
90 private $_parserResult;
100 * The Query to parse.
107 * Map of declared query components in the parsed query.
111 private $_queryComponents = array();
114 * Keeps the nesting level of defined ResultVariables
118 private $_nestingLevel = 0;
121 * Any additional custom tree walkers that modify the AST.
125 private $_customTreeWalkers = array();
128 * The custom last tree walker, if any, that is responsible for producing the output.
132 private $_customOutputWalker;
137 private $_identVariableExpressions = array();
140 * Check if a function is internally defined. Used to prevent overwriting
141 * of built-in functions through user-defined functions.
143 * @param string $functionName
146 static public function isInternalFunction($functionName)
148 $functionName = strtolower($functionName);
150 return isset(self::$_STRING_FUNCTIONS[$functionName])
151 || isset(self::$_DATETIME_FUNCTIONS[$functionName])
152 || isset(self::$_NUMERIC_FUNCTIONS[$functionName]);
156 * Creates a new query parser object.
158 * @param Query $query The Query to parse.
160 public function __construct(Query $query)
162 $this->_query = $query;
163 $this->_em = $query->getEntityManager();
164 $this->_lexer = new Lexer($query->getDql());
165 $this->_parserResult = new ParserResult();
169 * Sets a custom tree walker that produces output.
170 * This tree walker will be run last over the AST, after any other walkers.
172 * @param string $className
174 public function setCustomOutputTreeWalker($className)
176 $this->_customOutputWalker = $className;
180 * Adds a custom tree walker for modifying the AST.
182 * @param string $className
184 public function addCustomTreeWalker($className)
186 $this->_customTreeWalkers[] = $className;
190 * Gets the lexer used by the parser.
192 * @return \Doctrine\ORM\Query\Lexer
194 public function getLexer()
196 return $this->_lexer;
200 * Gets the ParserResult that is being filled with information during parsing.
202 * @return \Doctrine\ORM\Query\ParserResult
204 public function getParserResult()
206 return $this->_parserResult;
210 * Gets the EntityManager used by the parser.
212 * @return EntityManager
214 public function getEntityManager()
220 * Parse and build AST for the given Query.
222 * @return \Doctrine\ORM\Query\AST\SelectStatement |
223 * \Doctrine\ORM\Query\AST\UpdateStatement |
224 * \Doctrine\ORM\Query\AST\DeleteStatement
226 public function getAST()
229 $AST = $this->QueryLanguage();
231 // Process any deferred validations of some nodes in the AST.
232 // This also allows post-processing of the AST for modification purposes.
233 $this->_processDeferredIdentificationVariables();
235 if ($this->_deferredPartialObjectExpressions) {
236 $this->_processDeferredPartialObjectExpressions();
239 if ($this->_deferredPathExpressions) {
240 $this->_processDeferredPathExpressions($AST);
243 if ($this->_deferredResultVariables) {
244 $this->_processDeferredResultVariables();
247 $this->_processRootEntityAliasSelected();
249 // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
250 $this->fixIdentificationVariableOrder($AST);
256 * Attempts to match the given token with the current lookahead token.
258 * If they match, updates the lookahead token; otherwise raises a syntax
261 * @param int token type
263 * @throws QueryException If the tokens dont match.
265 public function match($token)
267 $lookaheadType = $this->_lexer->lookahead['type'];
269 // short-circuit on first condition, usually types match
270 if ($lookaheadType !== $token && $token !== Lexer::T_IDENTIFIER && $lookaheadType <= Lexer::T_IDENTIFIER) {
271 $this->syntaxError($this->_lexer->getLiteral($token));
274 $this->_lexer->moveNext();
278 * Free this parser enabling it to be reused
280 * @param boolean $deep Whether to clean peek and reset errors
281 * @param integer $position Position to reset
283 public function free($deep = false, $position = 0)
285 // WARNING! Use this method with care. It resets the scanner!
286 $this->_lexer->resetPosition($position);
288 // Deep = true cleans peek and also any previously defined errors
290 $this->_lexer->resetPeek();
293 $this->_lexer->token = null;
294 $this->_lexer->lookahead = null;
298 * Parses a query string.
300 * @return ParserResult
302 public function parse()
304 $AST = $this->getAST();
306 if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
307 $this->_customTreeWalkers = $customWalkers;
310 if (($customOutputWalker = $this->_query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) {
311 $this->_customOutputWalker = $customOutputWalker;
314 // Run any custom tree walkers over the AST
315 if ($this->_customTreeWalkers) {
316 $treeWalkerChain = new TreeWalkerChain($this->_query, $this->_parserResult, $this->_queryComponents);
318 foreach ($this->_customTreeWalkers as $walker) {
319 $treeWalkerChain->addTreeWalker($walker);
323 case ($AST instanceof AST\UpdateStatement):
324 $treeWalkerChain->walkUpdateStatement($AST);
327 case ($AST instanceof AST\DeleteStatement):
328 $treeWalkerChain->walkDeleteStatement($AST);
331 case ($AST instanceof AST\SelectStatement):
333 $treeWalkerChain->walkSelectStatement($AST);
337 $outputWalkerClass = $this->_customOutputWalker ?: __NAMESPACE__ . '\SqlWalker';
338 $outputWalker = new $outputWalkerClass($this->_query, $this->_parserResult, $this->_queryComponents);
340 // Assign an SQL executor to the parser result
341 $this->_parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
343 return $this->_parserResult;
347 * Fix order of identification variables.
349 * They have to appear in the select clause in the same order as the
350 * declarations (from ... x join ... y join ... z ...) appear in the query
351 * as the hydration process relies on that order for proper operation.
353 * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
356 private function fixIdentificationVariableOrder($AST)
358 if (count($this->_identVariableExpressions) <= 1) {
362 foreach ($this->_queryComponents as $dqlAlias => $qComp) {
363 if ( ! isset($this->_identVariableExpressions[$dqlAlias])) {
367 $expr = $this->_identVariableExpressions[$dqlAlias];
368 $key = array_search($expr, $AST->selectClause->selectExpressions);
370 unset($AST->selectClause->selectExpressions[$key]);
372 $AST->selectClause->selectExpressions[] = $expr;
377 * Generates a new syntax error.
379 * @param string $expected Expected string.
380 * @param array $token Got token.
382 * @throws \Doctrine\ORM\Query\QueryException
384 public function syntaxError($expected = '', $token = null)
386 if ($token === null) {
387 $token = $this->_lexer->lookahead;
390 $tokenPos = (isset($token['position'])) ? $token['position'] : '-1';
392 $message = "line 0, col {$tokenPos}: Error: ";
393 $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected ';
394 $message .= ($this->_lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'";
396 throw QueryException::syntaxError($message, QueryException::dqlError($this->_query->getDQL()));
400 * Generates a new semantical error.
402 * @param string $message Optional message.
403 * @param array $token Optional token.
405 * @throws \Doctrine\ORM\Query\QueryException
407 public function semanticalError($message = '', $token = null)
409 if ($token === null) {
410 $token = $this->_lexer->lookahead;
413 // Minimum exposed chars ahead of token
416 // Find a position of a final word to display in error string
417 $dql = $this->_query->getDql();
418 $length = strlen($dql);
419 $pos = $token['position'] + $distance;
420 $pos = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
421 $length = ($pos !== false) ? $pos - $token['position'] : $distance;
423 $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
424 $tokenStr = substr($dql, $token['position'], $length);
426 // Building informative message
427 $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
429 throw QueryException::semanticalError($message, QueryException::dqlError($this->_query->getDQL()));
433 * Peek beyond the matched closing parenthesis and return the first token after that one.
435 * @param boolean $resetPeek Reset peek after finding the closing parenthesis
438 private function _peekBeyondClosingParenthesis($resetPeek = true)
440 $token = $this->_lexer->peek();
443 while ($numUnmatched > 0 && $token !== null) {
444 switch ($token['type']) {
445 case Lexer::T_OPEN_PARENTHESIS:
449 case Lexer::T_CLOSE_PARENTHESIS:
457 $token = $this->_lexer->peek();
461 $this->_lexer->resetPeek();
468 * Checks if the given token indicates a mathematical operator.
470 * @return boolean TRUE if the token is a mathematical operator, FALSE otherwise.
472 private function _isMathOperator($token)
474 return in_array($token['type'], array(Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY));
478 * Checks if the next-next (after lookahead) token starts a function.
480 * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise.
482 private function _isFunction()
484 $peek = $this->_lexer->peek();
485 $nextpeek = $this->_lexer->peek();
487 $this->_lexer->resetPeek();
489 // We deny the COUNT(SELECT * FROM User u) here. COUNT won't be considered a function
490 return ($peek['type'] === Lexer::T_OPEN_PARENTHESIS && $nextpeek['type'] !== Lexer::T_SELECT);
494 * Checks whether the given token type indicates an aggregate function.
496 * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise.
498 private function _isAggregateFunction($tokenType)
500 return in_array($tokenType, array(Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT));
504 * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME.
508 private function _isNextAllAnySome()
510 return in_array($this->_lexer->lookahead['type'], array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME));
514 * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
515 * It must exist in query components list.
519 private function _processDeferredIdentificationVariables()
521 foreach ($this->_deferredIdentificationVariables as $deferredItem) {
522 $identVariable = $deferredItem['expression'];
524 // Check if IdentificationVariable exists in queryComponents
525 if ( ! isset($this->_queryComponents[$identVariable])) {
526 $this->semanticalError(
527 "'$identVariable' is not defined.", $deferredItem['token']
531 $qComp = $this->_queryComponents[$identVariable];
533 // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
534 if ( ! isset($qComp['metadata'])) {
535 $this->semanticalError(
536 "'$identVariable' does not point to a Class.", $deferredItem['token']
540 // Validate if identification variable nesting level is lower or equal than the current one
541 if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
542 $this->semanticalError(
543 "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
550 * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
551 * It must exist in query components list.
555 private function _processDeferredPartialObjectExpressions()
557 foreach ($this->_deferredPartialObjectExpressions as $deferredItem) {
558 $expr = $deferredItem['expression'];
559 $class = $this->_queryComponents[$expr->identificationVariable]['metadata'];
561 foreach ($expr->partialFieldSet as $field) {
562 if (isset($class->fieldMappings[$field])) {
566 $this->semanticalError(
567 "There is no mapped field named '$field' on class " . $class->name . ".", $deferredItem['token']
571 if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) {
572 $this->semanticalError(
573 "The partial field selection of class " . $class->name . " must contain the identifier.",
574 $deferredItem['token']
581 * Validates that the given <tt>ResultVariable</tt> is semantically correct.
582 * It must exist in query components list.
586 private function _processDeferredResultVariables()
588 foreach ($this->_deferredResultVariables as $deferredItem) {
589 $resultVariable = $deferredItem['expression'];
591 // Check if ResultVariable exists in queryComponents
592 if ( ! isset($this->_queryComponents[$resultVariable])) {
593 $this->semanticalError(
594 "'$resultVariable' is not defined.", $deferredItem['token']
598 $qComp = $this->_queryComponents[$resultVariable];
600 // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
601 if ( ! isset($qComp['resultVariable'])) {
602 $this->semanticalError(
603 "'$resultVariable' does not point to a ResultVariable.", $deferredItem['token']
607 // Validate if identification variable nesting level is lower or equal than the current one
608 if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
609 $this->semanticalError(
610 "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
617 * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
619 * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
620 * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
621 * StateFieldPathExpression ::= IdentificationVariable "." StateField
622 * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
623 * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
625 * @param array $deferredItem
628 private function _processDeferredPathExpressions($AST)
630 foreach ($this->_deferredPathExpressions as $deferredItem) {
631 $pathExpression = $deferredItem['expression'];
633 $qComp = $this->_queryComponents[$pathExpression->identificationVariable];
634 $class = $qComp['metadata'];
636 if (($field = $pathExpression->field) === null) {
637 $field = $pathExpression->field = $class->identifier[0];
640 // Check if field or association exists
641 if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
642 $this->semanticalError(
643 'Class ' . $class->name . ' has no field or association named ' . $field,
644 $deferredItem['token']
648 $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
650 if (isset($class->associationMappings[$field])) {
651 $assoc = $class->associationMappings[$field];
653 $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE)
654 ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
655 : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
658 // Validate if PathExpression is one of the expected types
659 $expectedType = $pathExpression->expectedType;
661 if ( ! ($expectedType & $fieldType)) {
662 // We need to recognize which was expected type(s)
663 $expectedStringTypes = array();
665 // Validate state field type
666 if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
667 $expectedStringTypes[] = 'StateFieldPathExpression';
670 // Validate single valued association (*-to-one)
671 if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
672 $expectedStringTypes[] = 'SingleValuedAssociationField';
675 // Validate single valued association (*-to-many)
676 if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
677 $expectedStringTypes[] = 'CollectionValuedAssociationField';
680 // Build the error message
681 $semanticalError = 'Invalid PathExpression. ';
682 $semanticalError .= (count($expectedStringTypes) == 1)
683 ? 'Must be a ' . $expectedStringTypes[0] . '.'
684 : implode(' or ', $expectedStringTypes) . ' expected.';
686 $this->semanticalError($semanticalError, $deferredItem['token']);
689 // We need to force the type in PathExpression
690 $pathExpression->type = $fieldType;
694 private function _processRootEntityAliasSelected()
696 if ( ! count($this->_identVariableExpressions)) {
700 $foundRootEntity = false;
702 foreach ($this->_identVariableExpressions as $dqlAlias => $expr) {
703 if (isset($this->_queryComponents[$dqlAlias]) && $this->_queryComponents[$dqlAlias]['parent'] === null) {
704 $foundRootEntity = true;
708 if ( ! $foundRootEntity) {
709 $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
714 * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
716 * @return \Doctrine\ORM\Query\AST\SelectStatement |
717 * \Doctrine\ORM\Query\AST\UpdateStatement |
718 * \Doctrine\ORM\Query\AST\DeleteStatement
720 public function QueryLanguage()
722 $this->_lexer->moveNext();
724 switch ($this->_lexer->lookahead['type']) {
725 case Lexer::T_SELECT:
726 $statement = $this->SelectStatement();
729 case Lexer::T_UPDATE:
730 $statement = $this->UpdateStatement();
733 case Lexer::T_DELETE:
734 $statement = $this->DeleteStatement();
738 $this->syntaxError('SELECT, UPDATE or DELETE');
742 // Check for end of string
743 if ($this->_lexer->lookahead !== null) {
744 $this->syntaxError('end of string');
751 * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
753 * @return \Doctrine\ORM\Query\AST\SelectStatement
755 public function SelectStatement()
757 $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
759 $selectStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
760 $selectStatement->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
761 $selectStatement->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
762 $selectStatement->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
764 return $selectStatement;
768 * UpdateStatement ::= UpdateClause [WhereClause]
770 * @return \Doctrine\ORM\Query\AST\UpdateStatement
772 public function UpdateStatement()
774 $updateStatement = new AST\UpdateStatement($this->UpdateClause());
776 $updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
778 return $updateStatement;
782 * DeleteStatement ::= DeleteClause [WhereClause]
784 * @return \Doctrine\ORM\Query\AST\DeleteStatement
786 public function DeleteStatement()
788 $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
790 $deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
792 return $deleteStatement;
796 * IdentificationVariable ::= identifier
800 public function IdentificationVariable()
802 $this->match(Lexer::T_IDENTIFIER);
804 $identVariable = $this->_lexer->token['value'];
806 $this->_deferredIdentificationVariables[] = array(
807 'expression' => $identVariable,
808 'nestingLevel' => $this->_nestingLevel,
809 'token' => $this->_lexer->token,
812 return $identVariable;
816 * AliasIdentificationVariable = identifier
820 public function AliasIdentificationVariable()
822 $this->match(Lexer::T_IDENTIFIER);
824 $aliasIdentVariable = $this->_lexer->token['value'];
825 $exists = isset($this->_queryComponents[$aliasIdentVariable]);
828 $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->_lexer->token);
831 return $aliasIdentVariable;
835 * AbstractSchemaName ::= identifier
839 public function AbstractSchemaName()
841 $this->match(Lexer::T_IDENTIFIER);
843 $schemaName = ltrim($this->_lexer->token['value'], '\\');
845 if (strrpos($schemaName, ':') !== false) {
846 list($namespaceAlias, $simpleClassName) = explode(':', $schemaName);
848 $schemaName = $this->_em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
851 $exists = class_exists($schemaName, true);
854 $this->semanticalError("Class '$schemaName' is not defined.", $this->_lexer->token);
861 * AliasResultVariable ::= identifier
865 public function AliasResultVariable()
867 $this->match(Lexer::T_IDENTIFIER);
869 $resultVariable = $this->_lexer->token['value'];
870 $exists = isset($this->_queryComponents[$resultVariable]);
873 $this->semanticalError("'$resultVariable' is already defined.", $this->_lexer->token);
876 return $resultVariable;
880 * ResultVariable ::= identifier
884 public function ResultVariable()
886 $this->match(Lexer::T_IDENTIFIER);
888 $resultVariable = $this->_lexer->token['value'];
890 // Defer ResultVariable validation
891 $this->_deferredResultVariables[] = array(
892 'expression' => $resultVariable,
893 'nestingLevel' => $this->_nestingLevel,
894 'token' => $this->_lexer->token,
897 return $resultVariable;
901 * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
903 * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
905 public function JoinAssociationPathExpression()
907 $identVariable = $this->IdentificationVariable();
909 if ( ! isset($this->_queryComponents[$identVariable])) {
910 $this->semanticalError(
911 'Identification Variable ' . $identVariable .' used in join path expression but was not defined before.'
915 $this->match(Lexer::T_DOT);
916 $this->match(Lexer::T_IDENTIFIER);
918 $field = $this->_lexer->token['value'];
920 // Validate association field
921 $qComp = $this->_queryComponents[$identVariable];
922 $class = $qComp['metadata'];
924 if ( ! $class->hasAssociation($field)) {
925 $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
928 return new AST\JoinAssociationPathExpression($identVariable, $field);
932 * Parses an arbitrary path expression and defers semantical validation
933 * based on expected types.
935 * PathExpression ::= IdentificationVariable "." identifier
937 * @param integer $expectedTypes
938 * @return \Doctrine\ORM\Query\AST\PathExpression
940 public function PathExpression($expectedTypes)
942 $identVariable = $this->IdentificationVariable();
945 if ($this->_lexer->isNextToken(Lexer::T_DOT)) {
946 $this->match(Lexer::T_DOT);
947 $this->match(Lexer::T_IDENTIFIER);
949 $field = $this->_lexer->token['value'];
953 $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
955 // Defer PathExpression validation if requested to be defered
956 $this->_deferredPathExpressions[] = array(
957 'expression' => $pathExpr,
958 'nestingLevel' => $this->_nestingLevel,
959 'token' => $this->_lexer->token,
966 * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
968 * @return \Doctrine\ORM\Query\AST\PathExpression
970 public function AssociationPathExpression()
972 return $this->PathExpression(
973 AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
974 AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
979 * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
981 * @return \Doctrine\ORM\Query\AST\PathExpression
983 public function SingleValuedPathExpression()
985 return $this->PathExpression(
986 AST\PathExpression::TYPE_STATE_FIELD |
987 AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
992 * StateFieldPathExpression ::= IdentificationVariable "." StateField
994 * @return \Doctrine\ORM\Query\AST\PathExpression
996 public function StateFieldPathExpression()
998 return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
1002 * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
1004 * @return \Doctrine\ORM\Query\AST\PathExpression
1006 public function SingleValuedAssociationPathExpression()
1008 return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
1012 * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
1014 * @return \Doctrine\ORM\Query\AST\PathExpression
1016 public function CollectionValuedPathExpression()
1018 return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
1022 * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
1024 * @return \Doctrine\ORM\Query\AST\SelectClause
1026 public function SelectClause()
1028 $isDistinct = false;
1029 $this->match(Lexer::T_SELECT);
1031 // Check for DISTINCT
1032 if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
1033 $this->match(Lexer::T_DISTINCT);
1038 // Process SelectExpressions (1..N)
1039 $selectExpressions = array();
1040 $selectExpressions[] = $this->SelectExpression();
1042 while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
1043 $this->match(Lexer::T_COMMA);
1045 $selectExpressions[] = $this->SelectExpression();
1048 return new AST\SelectClause($selectExpressions, $isDistinct);
1052 * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1054 * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1056 public function SimpleSelectClause()
1058 $isDistinct = false;
1059 $this->match(Lexer::T_SELECT);
1061 if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
1062 $this->match(Lexer::T_DISTINCT);
1067 return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1071 * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1073 * @return \Doctrine\ORM\Query\AST\UpdateClause
1075 public function UpdateClause()
1077 $this->match(Lexer::T_UPDATE);
1078 $token = $this->_lexer->lookahead;
1079 $abstractSchemaName = $this->AbstractSchemaName();
1081 if ($this->_lexer->isNextToken(Lexer::T_AS)) {
1082 $this->match(Lexer::T_AS);
1085 $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1087 $class = $this->_em->getClassMetadata($abstractSchemaName);
1089 // Building queryComponent
1090 $queryComponent = array(
1091 'metadata' => $class,
1095 'nestingLevel' => $this->_nestingLevel,
1099 $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
1101 $this->match(Lexer::T_SET);
1103 $updateItems = array();
1104 $updateItems[] = $this->UpdateItem();
1106 while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
1107 $this->match(Lexer::T_COMMA);
1109 $updateItems[] = $this->UpdateItem();
1112 $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
1113 $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1115 return $updateClause;
1119 * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1121 * @return \Doctrine\ORM\Query\AST\DeleteClause
1123 public function DeleteClause()
1125 $this->match(Lexer::T_DELETE);
1127 if ($this->_lexer->isNextToken(Lexer::T_FROM)) {
1128 $this->match(Lexer::T_FROM);
1131 $token = $this->_lexer->lookahead;
1132 $deleteClause = new AST\DeleteClause($this->AbstractSchemaName());
1134 if ($this->_lexer->isNextToken(Lexer::T_AS)) {
1135 $this->match(Lexer::T_AS);
1138 $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1140 $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1141 $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName);
1143 // Building queryComponent
1144 $queryComponent = array(
1145 'metadata' => $class,
1149 'nestingLevel' => $this->_nestingLevel,
1153 $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
1155 return $deleteClause;
1159 * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1161 * @return \Doctrine\ORM\Query\AST\FromClause
1163 public function FromClause()
1165 $this->match(Lexer::T_FROM);
1167 $identificationVariableDeclarations = array();
1168 $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1170 while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
1171 $this->match(Lexer::T_COMMA);
1173 $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1176 return new AST\FromClause($identificationVariableDeclarations);
1180 * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1182 * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1184 public function SubselectFromClause()
1186 $this->match(Lexer::T_FROM);
1188 $identificationVariables = array();
1189 $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1191 while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
1192 $this->match(Lexer::T_COMMA);
1194 $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1197 return new AST\SubselectFromClause($identificationVariables);
1201 * WhereClause ::= "WHERE" ConditionalExpression
1203 * @return \Doctrine\ORM\Query\AST\WhereClause
1205 public function WhereClause()
1207 $this->match(Lexer::T_WHERE);
1209 return new AST\WhereClause($this->ConditionalExpression());
1213 * HavingClause ::= "HAVING" ConditionalExpression
1215 * @return \Doctrine\ORM\Query\AST\HavingClause
1217 public function HavingClause()
1219 $this->match(Lexer::T_HAVING);
1221 return new AST\HavingClause($this->ConditionalExpression());
1225 * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1227 * @return \Doctrine\ORM\Query\AST\GroupByClause
1229 public function GroupByClause()
1231 $this->match(Lexer::T_GROUP);
1232 $this->match(Lexer::T_BY);
1234 $groupByItems = array($this->GroupByItem());
1236 while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
1237 $this->match(Lexer::T_COMMA);
1239 $groupByItems[] = $this->GroupByItem();
1242 return new AST\GroupByClause($groupByItems);
1246 * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1248 * @return \Doctrine\ORM\Query\AST\OrderByClause
1250 public function OrderByClause()
1252 $this->match(Lexer::T_ORDER);
1253 $this->match(Lexer::T_BY);
1255 $orderByItems = array();
1256 $orderByItems[] = $this->OrderByItem();
1258 while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
1259 $this->match(Lexer::T_COMMA);
1261 $orderByItems[] = $this->OrderByItem();
1264 return new AST\OrderByClause($orderByItems);
1268 * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1270 * @return \Doctrine\ORM\Query\AST\Subselect
1272 public function Subselect()
1274 // Increase query nesting level
1275 $this->_nestingLevel++;
1277 $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1279 $subselect->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1280 $subselect->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1281 $subselect->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1282 $subselect->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1284 // Decrease query nesting level
1285 $this->_nestingLevel--;
1291 * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1293 * @return \Doctrine\ORM\Query\AST\UpdateItem
1295 public function UpdateItem()
1297 $pathExpr = $this->SingleValuedPathExpression();
1299 $this->match(Lexer::T_EQUALS);
1301 $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1307 * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1309 * @return string | \Doctrine\ORM\Query\AST\PathExpression
1311 public function GroupByItem()
1313 // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1314 $glimpse = $this->_lexer->glimpse();
1316 if ($glimpse['type'] === Lexer::T_DOT) {
1317 return $this->SingleValuedPathExpression();
1320 // Still need to decide between IdentificationVariable or ResultVariable
1321 $lookaheadValue = $this->_lexer->lookahead['value'];
1323 if ( ! isset($this->_queryComponents[$lookaheadValue])) {
1324 $this->semanticalError('Cannot group by undefined identification or result variable.');
1327 return (isset($this->_queryComponents[$lookaheadValue]['metadata']))
1328 ? $this->IdentificationVariable()
1329 : $this->ResultVariable();
1334 * SimpleArithmeticExpression | SingleValuedPathExpression |
1335 * ScalarExpression | ResultVariable
1336 * ) ["ASC" | "DESC"]
1338 * @return \Doctrine\ORM\Query\AST\OrderByItem
1340 public function OrderByItem()
1343 $this->_lexer->peek(); // lookahead => '.'
1344 $this->_lexer->peek(); // lookahead => token after '.'
1345 $peek = $this->_lexer->peek(); // lookahead => token after the token after the '.'
1346 $this->_lexer->resetPeek();
1347 $glimpse = $this->_lexer->glimpse();
1351 case ($this->_isMathOperator($peek)):
1352 $expr = $this->SimpleArithmeticExpression();
1355 case ($glimpse['type'] === Lexer::T_DOT):
1356 $expr = $this->SingleValuedPathExpression();
1359 case ($this->_lexer->peek() && $this->_isMathOperator($this->_peekBeyondClosingParenthesis())):
1360 $expr = $this->ScalarExpression();
1364 $expr = $this->ResultVariable();
1370 $item = new AST\OrderByItem($expr);
1373 case ($this->_lexer->isNextToken(Lexer::T_DESC)):
1374 $this->match(Lexer::T_DESC);
1378 case ($this->_lexer->isNextToken(Lexer::T_ASC)):
1379 $this->match(Lexer::T_ASC);
1386 $item->type = $type;
1392 * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1393 * EnumPrimary | SimpleEntityExpression | "NULL"
1395 * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1396 * grammar that needs to be supported:
1398 * NewValue ::= SimpleArithmeticExpression | "NULL"
1400 * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1402 public function NewValue()
1404 if ($this->_lexer->isNextToken(Lexer::T_NULL)) {
1405 $this->match(Lexer::T_NULL);
1410 if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1411 $this->match(Lexer::T_INPUT_PARAMETER);
1413 return new AST\InputParameter($this->_lexer->token['value']);
1416 return $this->SimpleArithmeticExpression();
1420 * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1422 * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1424 public function IdentificationVariableDeclaration()
1426 $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1427 $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1431 $this->_lexer->isNextToken(Lexer::T_LEFT) ||
1432 $this->_lexer->isNextToken(Lexer::T_INNER) ||
1433 $this->_lexer->isNextToken(Lexer::T_JOIN)
1435 $joins[] = $this->Join();
1438 return new AST\IdentificationVariableDeclaration(
1439 $rangeVariableDeclaration, $indexBy, $joins
1444 * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1446 * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
1447 * \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1449 public function SubselectIdentificationVariableDeclaration()
1451 $this->_lexer->glimpse();
1453 /* NOT YET IMPLEMENTED!
1455 if ($glimpse['type'] == Lexer::T_DOT) {
1456 $subselectIdVarDecl = new AST\SubselectIdentificationVariableDeclaration();
1457 $subselectIdVarDecl->associationPathExpression = $this->AssociationPathExpression();
1458 $this->match(Lexer::T_AS);
1459 $subselectIdVarDecl->aliasIdentificationVariable = $this->AliasIdentificationVariable();
1461 return $subselectIdVarDecl;
1465 return $this->IdentificationVariableDeclaration();
1469 * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1470 * (JoinAssociationDeclaration | RangeVariableDeclaration)
1471 * ["WITH" ConditionalExpression]
1473 * @return \Doctrine\ORM\Query\AST\Join
1475 public function Join()
1478 $joinType = AST\Join::JOIN_TYPE_INNER;
1481 case ($this->_lexer->isNextToken(Lexer::T_LEFT)):
1482 $this->match(Lexer::T_LEFT);
1484 $joinType = AST\Join::JOIN_TYPE_LEFT;
1486 // Possible LEFT OUTER join
1487 if ($this->_lexer->isNextToken(Lexer::T_OUTER)) {
1488 $this->match(Lexer::T_OUTER);
1490 $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1494 case ($this->_lexer->isNextToken(Lexer::T_INNER)):
1495 $this->match(Lexer::T_INNER);
1502 $this->match(Lexer::T_JOIN);
1504 $next = $this->_lexer->glimpse();
1505 $joinDeclaration = ($next['type'] === Lexer::T_DOT)
1506 ? $this->JoinAssociationDeclaration()
1507 : $this->RangeVariableDeclaration();
1510 $join = new AST\Join($joinType, $joinDeclaration);
1512 // Check for ad-hoc Join conditions
1513 if ($this->_lexer->isNextToken(Lexer::T_WITH) || $joinDeclaration instanceof AST\RangeVariableDeclaration) {
1514 $this->match(Lexer::T_WITH);
1516 $join->conditionalExpression = $this->ConditionalExpression();
1523 * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1525 * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1527 public function RangeVariableDeclaration()
1529 $abstractSchemaName = $this->AbstractSchemaName();
1531 if ($this->_lexer->isNextToken(Lexer::T_AS)) {
1532 $this->match(Lexer::T_AS);
1535 $token = $this->_lexer->lookahead;
1536 $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1537 $classMetadata = $this->_em->getClassMetadata($abstractSchemaName);
1539 // Building queryComponent
1540 $queryComponent = array(
1541 'metadata' => $classMetadata,
1545 'nestingLevel' => $this->_nestingLevel,
1549 $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
1551 return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1555 * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
1557 * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1559 public function JoinAssociationDeclaration()
1561 $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1563 if ($this->_lexer->isNextToken(Lexer::T_AS)) {
1564 $this->match(Lexer::T_AS);
1567 $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1568 $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1570 $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1571 $field = $joinAssociationPathExpression->associationField;
1573 $class = $this->_queryComponents[$identificationVariable]['metadata'];
1574 $targetClass = $this->_em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1576 // Building queryComponent
1577 $joinQueryComponent = array(
1578 'metadata' => $targetClass,
1579 'parent' => $joinAssociationPathExpression->identificationVariable,
1580 'relation' => $class->getAssociationMapping($field),
1582 'nestingLevel' => $this->_nestingLevel,
1583 'token' => $this->_lexer->lookahead
1586 $this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1588 return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy);
1592 * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
1593 * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
1597 public function PartialObjectExpression()
1599 $this->match(Lexer::T_PARTIAL);
1601 $partialFieldSet = array();
1603 $identificationVariable = $this->IdentificationVariable();
1605 $this->match(Lexer::T_DOT);
1606 $this->match(Lexer::T_OPEN_CURLY_BRACE);
1607 $this->match(Lexer::T_IDENTIFIER);
1609 $partialFieldSet[] = $this->_lexer->token['value'];
1611 while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
1612 $this->match(Lexer::T_COMMA);
1613 $this->match(Lexer::T_IDENTIFIER);
1615 $partialFieldSet[] = $this->_lexer->token['value'];
1618 $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1620 $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1622 // Defer PartialObjectExpression validation
1623 $this->_deferredPartialObjectExpressions[] = array(
1624 'expression' => $partialObjectExpression,
1625 'nestingLevel' => $this->_nestingLevel,
1626 'token' => $this->_lexer->token,
1629 return $partialObjectExpression;
1633 * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1635 * @return \Doctrine\ORM\Query\AST\IndexBy
1637 public function IndexBy()
1639 $this->match(Lexer::T_INDEX);
1640 $this->match(Lexer::T_BY);
1641 $pathExpr = $this->StateFieldPathExpression();
1643 // Add the INDEX BY info to the query component
1644 $this->_queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1646 return new AST\IndexBy($pathExpr);
1650 * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1651 * StateFieldPathExpression | BooleanPrimary | CaseExpression |
1652 * InstanceOfExpression
1654 * @return mixed One of the possible expressions or subexpressions.
1656 public function ScalarExpression()
1658 $lookahead = $this->_lexer->lookahead['type'];
1660 switch ($lookahead) {
1661 case Lexer::T_IDENTIFIER:
1662 $this->_lexer->peek(); // lookahead => '.'
1663 $this->_lexer->peek(); // lookahead => token after '.'
1664 $peek = $this->_lexer->peek(); // lookahead => token after the token after the '.'
1665 $this->_lexer->resetPeek();
1667 if ($this->_isMathOperator($peek)) {
1668 return $this->SimpleArithmeticExpression();
1671 return $this->StateFieldPathExpression();
1673 case Lexer::T_INTEGER:
1674 case Lexer::T_FLOAT:
1675 return $this->SimpleArithmeticExpression();
1677 case Lexer::T_STRING:
1678 return $this->StringPrimary();
1681 case Lexer::T_FALSE:
1682 $this->match($lookahead);
1684 return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']);
1686 case Lexer::T_INPUT_PARAMETER:
1687 return $this->InputParameter();
1690 case Lexer::T_COALESCE:
1691 case Lexer::T_NULLIF:
1692 // Since NULLIF and COALESCE can be identified as a function,
1693 // we need to check if before check for FunctionDeclaration
1694 return $this->CaseExpression();
1697 if ( ! ($this->_isFunction() || $this->_isAggregateFunction($lookahead))) {
1698 $this->syntaxError();
1701 // We may be in an ArithmeticExpression (find the matching ")" and inspect for Math operator)
1702 $this->_lexer->peek(); // "("
1703 $peek = $this->_peekBeyondClosingParenthesis();
1705 if ($this->_isMathOperator($peek)) {
1706 return $this->SimpleArithmeticExpression();
1709 if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
1710 return $this->AggregateExpression();
1713 return $this->FunctionDeclaration();
1718 * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
1719 * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
1720 * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
1721 * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
1722 * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
1723 * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
1724 * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
1725 * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
1727 * @return mixed One of the possible expressions or subexpressions.
1729 public function CaseExpression()
1731 $lookahead = $this->_lexer->lookahead['type'];
1733 switch ($lookahead) {
1734 case Lexer::T_NULLIF:
1735 return $this->NullIfExpression();
1737 case Lexer::T_COALESCE:
1738 return $this->CoalesceExpression();
1741 $this->_lexer->resetPeek();
1742 $peek = $this->_lexer->peek();
1744 if ($peek['type'] === Lexer::T_WHEN) {
1745 return $this->GeneralCaseExpression();
1748 return $this->SimpleCaseExpression();
1755 $this->syntaxError();
1759 * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
1761 * @return \Doctrine\ORM\Query\AST\CoalesceExpression
1763 public function CoalesceExpression()
1765 $this->match(Lexer::T_COALESCE);
1766 $this->match(Lexer::T_OPEN_PARENTHESIS);
1768 // Process ScalarExpressions (1..N)
1769 $scalarExpressions = array();
1770 $scalarExpressions[] = $this->ScalarExpression();
1772 while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
1773 $this->match(Lexer::T_COMMA);
1775 $scalarExpressions[] = $this->ScalarExpression();
1778 $this->match(Lexer::T_CLOSE_PARENTHESIS);
1780 return new AST\CoalesceExpression($scalarExpressions);
1784 * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
1786 * @return \Doctrine\ORM\Query\AST\NullIfExpression
1788 public function NullIfExpression()
1790 $this->match(Lexer::T_NULLIF);
1791 $this->match(Lexer::T_OPEN_PARENTHESIS);
1793 $firstExpression = $this->ScalarExpression();
1794 $this->match(Lexer::T_COMMA);
1795 $secondExpression = $this->ScalarExpression();
1797 $this->match(Lexer::T_CLOSE_PARENTHESIS);
1799 return new AST\NullIfExpression($firstExpression, $secondExpression);
1803 * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
1805 * @return \Doctrine\ORM\Query\AST\GeneralExpression
1807 public function GeneralCaseExpression()
1809 $this->match(Lexer::T_CASE);
1811 // Process WhenClause (1..N)
1812 $whenClauses = array();
1815 $whenClauses[] = $this->WhenClause();
1816 } while ($this->_lexer->isNextToken(Lexer::T_WHEN));
1818 $this->match(Lexer::T_ELSE);
1819 $scalarExpression = $this->ScalarExpression();
1820 $this->match(Lexer::T_END);
1822 return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
1826 * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
1827 * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
1829 public function SimpleCaseExpression()
1831 $this->match(Lexer::T_CASE);
1832 $caseOperand = $this->StateFieldPathExpression();
1834 // Process SimpleWhenClause (1..N)
1835 $simpleWhenClauses = array();
1838 $simpleWhenClauses[] = $this->SimpleWhenClause();
1839 } while ($this->_lexer->isNextToken(Lexer::T_WHEN));
1841 $this->match(Lexer::T_ELSE);
1842 $scalarExpression = $this->ScalarExpression();
1843 $this->match(Lexer::T_END);
1845 return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
1849 * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
1851 * @return \Doctrine\ORM\Query\AST\WhenExpression
1853 public function WhenClause()
1855 $this->match(Lexer::T_WHEN);
1856 $conditionalExpression = $this->ConditionalExpression();
1857 $this->match(Lexer::T_THEN);
1859 return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
1863 * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
1865 * @return \Doctrine\ORM\Query\AST\SimpleWhenExpression
1867 public function SimpleWhenClause()
1869 $this->match(Lexer::T_WHEN);
1870 $conditionalExpression = $this->ScalarExpression();
1871 $this->match(Lexer::T_THEN);
1873 return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
1877 * SelectExpression ::= (
1878 * IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
1879 * PartialObjectExpression | "(" Subselect ")" | CaseExpression
1880 * ) [["AS"] ["HIDDEN"] AliasResultVariable]
1882 * @return \Doctrine\ORM\Query\AST\SelectExpression
1884 public function SelectExpression()
1887 $identVariable = null;
1888 $peek = $this->_lexer->glimpse();
1889 $lookaheadType = $this->_lexer->lookahead['type'];
1892 // ScalarExpression (u.name)
1893 case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
1894 $expression = $this->ScalarExpression();
1897 // IdentificationVariable (u)
1898 case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
1899 $expression = $identVariable = $this->IdentificationVariable();
1902 // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
1903 case ($lookaheadType === Lexer::T_CASE):
1904 case ($lookaheadType === Lexer::T_COALESCE):
1905 case ($lookaheadType === Lexer::T_NULLIF):
1906 $expression = $this->CaseExpression();
1909 // DQL Function (SUM(u.value) or SUM(u.value) + 1)
1910 case ($this->_isFunction()):
1911 $this->_lexer->peek(); // "("
1914 case ($this->_isMathOperator($this->_peekBeyondClosingParenthesis())):
1915 // SUM(u.id) + COUNT(u.id)
1916 $expression = $this->ScalarExpression();
1919 case ($this->_isAggregateFunction($lookaheadType)):
1921 $expression = $this->AggregateExpression();
1926 $expression = $this->FunctionDeclaration();
1932 // PartialObjectExpression (PARTIAL u.{id, name})
1933 case ($lookaheadType === Lexer::T_PARTIAL):
1934 $expression = $this->PartialObjectExpression();
1935 $identVariable = $expression->identificationVariable;
1939 case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
1940 $this->match(Lexer::T_OPEN_PARENTHESIS);
1941 $expression = $this->Subselect();
1942 $this->match(Lexer::T_CLOSE_PARENTHESIS);
1945 // Shortcut: ScalarExpression => SimpleArithmeticExpression
1946 case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
1947 case ($lookaheadType === Lexer::T_INTEGER):
1948 case ($lookaheadType === Lexer::T_STRING):
1949 case ($lookaheadType === Lexer::T_FLOAT):
1950 // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
1951 case ($lookaheadType === Lexer::T_MINUS):
1952 case ($lookaheadType === Lexer::T_PLUS):
1953 $expression = $this->SimpleArithmeticExpression();
1958 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
1959 $this->_lexer->lookahead
1963 // [["AS"] ["HIDDEN"] AliasResultVariable]
1965 if ($this->_lexer->isNextToken(Lexer::T_AS)) {
1966 $this->match(Lexer::T_AS);
1969 $hiddenAliasResultVariable = false;
1971 if ($this->_lexer->isNextToken(Lexer::T_HIDDEN)) {
1972 $this->match(Lexer::T_HIDDEN);
1974 $hiddenAliasResultVariable = true;
1977 $aliasResultVariable = null;
1979 if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
1980 $token = $this->_lexer->lookahead;
1981 $aliasResultVariable = $this->AliasResultVariable();
1983 // Include AliasResultVariable in query components.
1984 $this->_queryComponents[$aliasResultVariable] = array(
1985 'resultVariable' => $expression,
1986 'nestingLevel' => $this->_nestingLevel,
1993 $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
1995 if ($identVariable) {
1996 $this->_identVariableExpressions[$identVariable] = $expr;
2003 * SimpleSelectExpression ::= (
2004 * StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2005 * AggregateExpression | "(" Subselect ")" | ScalarExpression
2006 * ) [["AS"] AliasResultVariable]
2008 * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2010 public function SimpleSelectExpression()
2012 $peek = $this->_lexer->glimpse();
2014 switch ($this->_lexer->lookahead['type']) {
2015 case Lexer::T_IDENTIFIER:
2017 case ($peek['type'] === Lexer::T_DOT):
2018 $expression = $this->StateFieldPathExpression();
2020 return new AST\SimpleSelectExpression($expression);
2022 case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2023 $expression = $this->IdentificationVariable();
2025 return new AST\SimpleSelectExpression($expression);
2027 case ($this->_isFunction()):
2028 // SUM(u.id) + COUNT(u.id)
2029 if ($this->_isMathOperator($this->_peekBeyondClosingParenthesis())) {
2030 return new AST\SimpleSelectExpression($this->ScalarExpression());
2033 if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
2034 return new AST\SimpleSelectExpression($this->AggregateExpression());
2037 return new AST\SimpleSelectExpression($this->FunctionDeclaration());
2044 case Lexer::T_OPEN_PARENTHESIS:
2045 if ($peek['type'] !== Lexer::T_SELECT) {
2046 // Shortcut: ScalarExpression => SimpleArithmeticExpression
2047 $expression = $this->SimpleArithmeticExpression();
2049 return new AST\SimpleSelectExpression($expression);
2053 $this->match(Lexer::T_OPEN_PARENTHESIS);
2054 $expression = $this->Subselect();
2055 $this->match(Lexer::T_CLOSE_PARENTHESIS);
2057 return new AST\SimpleSelectExpression($expression);
2063 $this->_lexer->peek();
2065 $expression = $this->ScalarExpression();
2066 $expr = new AST\SimpleSelectExpression($expression);
2068 if ($this->_lexer->isNextToken(Lexer::T_AS)) {
2069 $this->match(Lexer::T_AS);
2072 if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2073 $token = $this->_lexer->lookahead;
2074 $resultVariable = $this->AliasResultVariable();
2075 $expr->fieldIdentificationVariable = $resultVariable;
2077 // Include AliasResultVariable in query components.
2078 $this->_queryComponents[$resultVariable] = array(
2079 'resultvariable' => $expr,
2080 'nestingLevel' => $this->_nestingLevel,
2089 * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2091 * @return \Doctrine\ORM\Query\AST\ConditionalExpression
2093 public function ConditionalExpression()
2095 $conditionalTerms = array();
2096 $conditionalTerms[] = $this->ConditionalTerm();
2098 while ($this->_lexer->isNextToken(Lexer::T_OR)) {
2099 $this->match(Lexer::T_OR);
2101 $conditionalTerms[] = $this->ConditionalTerm();
2104 // Phase 1 AST optimization: Prevent AST\ConditionalExpression
2105 // if only one AST\ConditionalTerm is defined
2106 if (count($conditionalTerms) == 1) {
2107 return $conditionalTerms[0];
2110 return new AST\ConditionalExpression($conditionalTerms);
2114 * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
2116 * @return \Doctrine\ORM\Query\AST\ConditionalTerm
2118 public function ConditionalTerm()
2120 $conditionalFactors = array();
2121 $conditionalFactors[] = $this->ConditionalFactor();
2123 while ($this->_lexer->isNextToken(Lexer::T_AND)) {
2124 $this->match(Lexer::T_AND);
2126 $conditionalFactors[] = $this->ConditionalFactor();
2129 // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2130 // if only one AST\ConditionalFactor is defined
2131 if (count($conditionalFactors) == 1) {
2132 return $conditionalFactors[0];
2135 return new AST\ConditionalTerm($conditionalFactors);
2139 * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2141 * @return \Doctrine\ORM\Query\AST\ConditionalFactor
2143 public function ConditionalFactor()
2147 if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
2148 $this->match(Lexer::T_NOT);
2153 $conditionalPrimary = $this->ConditionalPrimary();
2155 // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2156 // if only one AST\ConditionalPrimary is defined
2158 return $conditionalPrimary;
2161 $conditionalFactor = new AST\ConditionalFactor($conditionalPrimary);
2162 $conditionalFactor->not = $not;
2164 return $conditionalFactor;
2168 * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
2170 * @return \Doctrine\ORM\Query\AST\ConditionalPrimary
2172 public function ConditionalPrimary()
2174 $condPrimary = new AST\ConditionalPrimary;
2176 if ( ! $this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2177 $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2179 return $condPrimary;
2182 // Peek beyond the matching closing paranthesis ')'
2183 $peek = $this->_peekBeyondClosingParenthesis();
2185 if (in_array($peek['value'], array("=", "<", "<=", "<>", ">", ">=", "!=")) ||
2186 in_array($peek['type'], array(Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS)) ||
2187 $this->_isMathOperator($peek)) {
2188 $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2190 return $condPrimary;
2193 $this->match(Lexer::T_OPEN_PARENTHESIS);
2194 $condPrimary->conditionalExpression = $this->ConditionalExpression();
2195 $this->match(Lexer::T_CLOSE_PARENTHESIS);
2197 return $condPrimary;
2201 * SimpleConditionalExpression ::=
2202 * ComparisonExpression | BetweenExpression | LikeExpression |
2203 * InExpression | NullComparisonExpression | ExistsExpression |
2204 * EmptyCollectionComparisonExpression | CollectionMemberExpression |
2205 * InstanceOfExpression
2207 public function SimpleConditionalExpression()
2209 $token = $this->_lexer->lookahead;
2211 if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
2212 $token = $this->_lexer->glimpse();
2215 if ($token['type'] === Lexer::T_EXISTS) {
2216 return $this->ExistsExpression();
2219 $peek = $this->_lexer->glimpse();
2221 if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER) {
2222 if ($peek['value'] == '(') {
2223 // Peek beyond the matching closing paranthesis ')'
2224 $this->_lexer->peek();
2225 $token = $this->_peekBeyondClosingParenthesis(false);
2227 if ($token['type'] === Lexer::T_NOT) {
2228 $token = $this->_lexer->peek();
2231 $this->_lexer->resetPeek();
2233 // Peek beyond the PathExpression (or InputParameter)
2234 $peek = $this->_lexer->peek();
2236 while ($peek['value'] === '.') {
2237 $this->_lexer->peek();
2238 $peek = $this->_lexer->peek();
2241 // Also peek beyond a NOT if there is one
2242 if ($peek['type'] === Lexer::T_NOT) {
2243 $peek = $this->_lexer->peek();
2248 // We need to go even further in case of IS (differenciate between NULL and EMPTY)
2249 $lookahead = $this->_lexer->peek();
2251 // Also peek beyond a NOT if there is one
2252 if ($lookahead['type'] === Lexer::T_NOT) {
2253 $lookahead = $this->_lexer->peek();
2256 $this->_lexer->resetPeek();
2260 switch ($token['type']) {
2261 case Lexer::T_BETWEEN:
2262 return $this->BetweenExpression();
2264 return $this->LikeExpression();
2266 return $this->InExpression();
2267 case Lexer::T_INSTANCE:
2268 return $this->InstanceOfExpression();
2270 if ($lookahead['type'] == Lexer::T_NULL) {
2271 return $this->NullComparisonExpression();
2273 return $this->EmptyCollectionComparisonExpression();
2274 case Lexer::T_MEMBER:
2275 return $this->CollectionMemberExpression();
2277 return $this->ComparisonExpression();
2282 * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2284 * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2286 public function EmptyCollectionComparisonExpression()
2288 $emptyColletionCompExpr = new AST\EmptyCollectionComparisonExpression(
2289 $this->CollectionValuedPathExpression()
2291 $this->match(Lexer::T_IS);
2293 if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
2294 $this->match(Lexer::T_NOT);
2295 $emptyColletionCompExpr->not = true;
2298 $this->match(Lexer::T_EMPTY);
2300 return $emptyColletionCompExpr;
2304 * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2306 * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2307 * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2309 * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
2311 public function CollectionMemberExpression()
2314 $entityExpr = $this->EntityExpression();
2316 if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
2317 $this->match(Lexer::T_NOT);
2322 $this->match(Lexer::T_MEMBER);
2324 if ($this->_lexer->isNextToken(Lexer::T_OF)) {
2325 $this->match(Lexer::T_OF);
2328 $collMemberExpr = new AST\CollectionMemberExpression(
2329 $entityExpr, $this->CollectionValuedPathExpression()
2331 $collMemberExpr->not = $not;
2333 return $collMemberExpr;
2337 * Literal ::= string | char | integer | float | boolean
2341 public function Literal()
2343 switch ($this->_lexer->lookahead['type']) {
2344 case Lexer::T_STRING:
2345 $this->match(Lexer::T_STRING);
2346 return new AST\Literal(AST\Literal::STRING, $this->_lexer->token['value']);
2348 case Lexer::T_INTEGER:
2349 case Lexer::T_FLOAT:
2351 $this->_lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2353 return new AST\Literal(AST\Literal::NUMERIC, $this->_lexer->token['value']);
2356 case Lexer::T_FALSE:
2358 $this->_lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2360 return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']);
2363 $this->syntaxError('Literal');
2368 * InParameter ::= Literal | InputParameter
2370 * @return string | \Doctrine\ORM\Query\AST\InputParameter
2372 public function InParameter()
2374 if ($this->_lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
2375 return $this->InputParameter();
2378 return $this->Literal();
2382 * InputParameter ::= PositionalParameter | NamedParameter
2384 * @return \Doctrine\ORM\Query\AST\InputParameter
2386 public function InputParameter()
2388 $this->match(Lexer::T_INPUT_PARAMETER);
2390 return new AST\InputParameter($this->_lexer->token['value']);
2394 * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2396 * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2398 public function ArithmeticExpression()
2400 $expr = new AST\ArithmeticExpression;
2402 if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2403 $peek = $this->_lexer->glimpse();
2405 if ($peek['type'] === Lexer::T_SELECT) {
2406 $this->match(Lexer::T_OPEN_PARENTHESIS);
2407 $expr->subselect = $this->Subselect();
2408 $this->match(Lexer::T_CLOSE_PARENTHESIS);
2414 $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2420 * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2422 * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2424 public function SimpleArithmeticExpression()
2427 $terms[] = $this->ArithmeticTerm();
2429 while (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) {
2430 $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2432 $terms[] = $this->_lexer->token['value'];
2433 $terms[] = $this->ArithmeticTerm();
2436 // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2437 // if only one AST\ArithmeticTerm is defined
2438 if (count($terms) == 1) {
2442 return new AST\SimpleArithmeticExpression($terms);
2446 * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2448 * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2450 public function ArithmeticTerm()
2453 $factors[] = $this->ArithmeticFactor();
2455 while (($isMult = $this->_lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->_lexer->isNextToken(Lexer::T_DIVIDE)) {
2456 $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2458 $factors[] = $this->_lexer->token['value'];
2459 $factors[] = $this->ArithmeticFactor();
2462 // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2463 // if only one AST\ArithmeticFactor is defined
2464 if (count($factors) == 1) {
2468 return new AST\ArithmeticTerm($factors);
2472 * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2474 * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2476 public function ArithmeticFactor()
2480 if (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) {
2481 $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2485 $primary = $this->ArithmeticPrimary();
2487 // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2488 // if only one AST\ArithmeticPrimary is defined
2489 if ($sign === null) {
2493 return new AST\ArithmeticFactor($primary, $sign);
2497 * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")"
2498 * | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2499 * | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2500 * | InputParameter | CaseExpression
2502 public function ArithmeticPrimary()
2504 if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2505 $this->match(Lexer::T_OPEN_PARENTHESIS);
2506 $expr = $this->SimpleArithmeticExpression();
2508 $this->match(Lexer::T_CLOSE_PARENTHESIS);
2513 switch ($this->_lexer->lookahead['type']) {
2514 case Lexer::T_COALESCE:
2515 case Lexer::T_NULLIF:
2517 return $this->CaseExpression();
2519 case Lexer::T_IDENTIFIER:
2520 $peek = $this->_lexer->glimpse();
2522 if ($peek['value'] == '(') {
2523 return $this->FunctionDeclaration();
2526 if ($peek['value'] == '.') {
2527 return $this->SingleValuedPathExpression();
2530 if (isset($this->_queryComponents[$this->_lexer->lookahead['value']]['resultVariable'])) {
2531 return $this->ResultVariable();
2534 return $this->StateFieldPathExpression();
2536 case Lexer::T_INPUT_PARAMETER:
2537 return $this->InputParameter();
2540 $peek = $this->_lexer->glimpse();
2542 if ($peek['value'] == '(') {
2543 if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
2544 return $this->AggregateExpression();
2547 return $this->FunctionDeclaration();
2550 return $this->Literal();
2555 * StringExpression ::= StringPrimary | "(" Subselect ")"
2557 * @return \Doctrine\ORM\Query\AST\StringPrimary |
2558 * \Doctrine]ORM\Query\AST\Subselect
2560 public function StringExpression()
2562 if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2563 $peek = $this->_lexer->glimpse();
2565 if ($peek['type'] === Lexer::T_SELECT) {
2566 $this->match(Lexer::T_OPEN_PARENTHESIS);
2567 $expr = $this->Subselect();
2568 $this->match(Lexer::T_CLOSE_PARENTHESIS);
2574 return $this->StringPrimary();
2578 * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2580 public function StringPrimary()
2582 $lookaheadType = $this->_lexer->lookahead['type'];
2584 switch ($lookaheadType) {
2585 case Lexer::T_IDENTIFIER:
2586 $peek = $this->_lexer->glimpse();
2588 if ($peek['value'] == '.') {
2589 return $this->StateFieldPathExpression();
2592 if ($peek['value'] == '(') {
2593 // do NOT directly go to FunctionsReturningString() because it doesnt check for custom functions.
2594 return $this->FunctionDeclaration();
2597 $this->syntaxError("'.' or '('");
2600 case Lexer::T_STRING:
2601 $this->match(Lexer::T_STRING);
2603 return new AST\Literal(AST\Literal::STRING, $this->_lexer->token['value']);
2605 case Lexer::T_INPUT_PARAMETER:
2606 return $this->InputParameter();
2609 case Lexer::T_COALESCE:
2610 case Lexer::T_NULLIF:
2611 return $this->CaseExpression();
2614 if ($this->_isAggregateFunction($lookaheadType)) {
2615 return $this->AggregateExpression();
2620 'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
2625 * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2627 * @return \Doctrine\ORM\Query\AST\SingleValuedAssociationPathExpression |
2628 * \Doctrine\ORM\Query\AST\SimpleEntityExpression
2630 public function EntityExpression()
2632 $glimpse = $this->_lexer->glimpse();
2634 if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2635 return $this->SingleValuedAssociationPathExpression();
2638 return $this->SimpleEntityExpression();
2642 * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2644 * @return string | \Doctrine\ORM\Query\AST\InputParameter
2646 public function SimpleEntityExpression()
2648 if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2649 return $this->InputParameter();
2652 return $this->StateFieldPathExpression();
2656 * AggregateExpression ::=
2657 * ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
2658 * "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")"
2660 * @return \Doctrine\ORM\Query\AST\AggregateExpression
2662 public function AggregateExpression()
2664 $lookaheadType = $this->_lexer->lookahead['type'];
2665 $isDistinct = false;
2667 if ( ! in_array($lookaheadType, array(Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM))) {
2668 $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2671 $this->match($lookaheadType);
2672 $functionName = $this->_lexer->token['value'];
2673 $this->match(Lexer::T_OPEN_PARENTHESIS);
2675 if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
2676 $this->match(Lexer::T_DISTINCT);
2680 $pathExp = ($lookaheadType === Lexer::T_COUNT)
2681 ? $this->SingleValuedPathExpression()
2682 : $this->SimpleArithmeticExpression();
2684 $this->match(Lexer::T_CLOSE_PARENTHESIS);
2686 return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
2690 * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2692 * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
2694 public function QuantifiedExpression()
2696 $lookaheadType = $this->_lexer->lookahead['type'];
2697 $value = $this->_lexer->lookahead['value'];
2699 if ( ! in_array($lookaheadType, array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME))) {
2700 $this->syntaxError('ALL, ANY or SOME');
2703 $this->match($lookaheadType);
2704 $this->match(Lexer::T_OPEN_PARENTHESIS);
2706 $qExpr = new AST\QuantifiedExpression($this->Subselect());
2707 $qExpr->type = $value;
2709 $this->match(Lexer::T_CLOSE_PARENTHESIS);
2715 * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
2717 * @return \Doctrine\ORM\Query\AST\BetweenExpression
2719 public function BetweenExpression()
2722 $arithExpr1 = $this->ArithmeticExpression();
2724 if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
2725 $this->match(Lexer::T_NOT);
2729 $this->match(Lexer::T_BETWEEN);
2730 $arithExpr2 = $this->ArithmeticExpression();
2731 $this->match(Lexer::T_AND);
2732 $arithExpr3 = $this->ArithmeticExpression();
2734 $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
2735 $betweenExpr->not = $not;
2737 return $betweenExpr;
2741 * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
2743 * @return \Doctrine\ORM\Query\AST\ComparisonExpression
2745 public function ComparisonExpression()
2747 $this->_lexer->glimpse();
2749 $leftExpr = $this->ArithmeticExpression();
2750 $operator = $this->ComparisonOperator();
2751 $rightExpr = ($this->_isNextAllAnySome())
2752 ? $this->QuantifiedExpression()
2753 : $this->ArithmeticExpression();
2755 return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
2759 * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
2761 * @return \Doctrine\ORM\Query\AST\InExpression
2763 public function InExpression()
2765 $inExpression = new AST\InExpression($this->ArithmeticExpression());
2767 if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
2768 $this->match(Lexer::T_NOT);
2769 $inExpression->not = true;
2772 $this->match(Lexer::T_IN);
2773 $this->match(Lexer::T_OPEN_PARENTHESIS);
2775 if ($this->_lexer->isNextToken(Lexer::T_SELECT)) {
2776 $inExpression->subselect = $this->Subselect();
2778 $literals = array();
2779 $literals[] = $this->InParameter();
2781 while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
2782 $this->match(Lexer::T_COMMA);
2783 $literals[] = $this->InParameter();
2786 $inExpression->literals = $literals;
2789 $this->match(Lexer::T_CLOSE_PARENTHESIS);
2791 return $inExpression;
2795 * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
2797 * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
2799 public function InstanceOfExpression()
2801 $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
2803 if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
2804 $this->match(Lexer::T_NOT);
2805 $instanceOfExpression->not = true;
2808 $this->match(Lexer::T_INSTANCE);
2809 $this->match(Lexer::T_OF);
2811 $exprValues = array();
2813 if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2814 $this->match(Lexer::T_OPEN_PARENTHESIS);
2816 $exprValues[] = $this->InstanceOfParameter();
2818 while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
2819 $this->match(Lexer::T_COMMA);
2821 $exprValues[] = $this->InstanceOfParameter();
2824 $this->match(Lexer::T_CLOSE_PARENTHESIS);
2826 $instanceOfExpression->value = $exprValues;
2828 return $instanceOfExpression;
2831 $exprValues[] = $this->InstanceOfParameter();
2833 $instanceOfExpression->value = $exprValues;
2835 return $instanceOfExpression;
2839 * InstanceOfParameter ::= AbstractSchemaName | InputParameter
2843 public function InstanceOfParameter()
2845 if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2846 $this->match(Lexer::T_INPUT_PARAMETER);
2848 return new AST\InputParameter($this->_lexer->token['value']);
2851 return $this->AliasIdentificationVariable();
2855 * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
2857 * @return \Doctrine\ORM\Query\AST\LikeExpression
2859 public function LikeExpression()
2861 $stringExpr = $this->StringExpression();
2864 if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
2865 $this->match(Lexer::T_NOT);
2869 $this->match(Lexer::T_LIKE);
2871 if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2872 $this->match(Lexer::T_INPUT_PARAMETER);
2873 $stringPattern = new AST\InputParameter($this->_lexer->token['value']);
2875 $stringPattern = $this->StringPrimary();
2880 if ($this->_lexer->lookahead['type'] === Lexer::T_ESCAPE) {
2881 $this->match(Lexer::T_ESCAPE);
2882 $this->match(Lexer::T_STRING);
2884 $escapeChar = new AST\Literal(AST\Literal::STRING, $this->_lexer->token['value']);
2887 $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
2888 $likeExpr->not = $not;
2894 * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
2896 * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
2898 public function NullComparisonExpression()
2900 if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2901 $this->match(Lexer::T_INPUT_PARAMETER);
2902 $expr = new AST\InputParameter($this->_lexer->token['value']);
2904 $expr = $this->SingleValuedPathExpression();
2907 $nullCompExpr = new AST\NullComparisonExpression($expr);
2908 $this->match(Lexer::T_IS);
2910 if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
2911 $this->match(Lexer::T_NOT);
2912 $nullCompExpr->not = true;
2915 $this->match(Lexer::T_NULL);
2917 return $nullCompExpr;
2921 * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
2923 * @return \Doctrine\ORM\Query\AST\ExistsExpression
2925 public function ExistsExpression()
2929 if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
2930 $this->match(Lexer::T_NOT);
2934 $this->match(Lexer::T_EXISTS);
2935 $this->match(Lexer::T_OPEN_PARENTHESIS);
2937 $existsExpression = new AST\ExistsExpression($this->Subselect());
2938 $existsExpression->not = $not;
2940 $this->match(Lexer::T_CLOSE_PARENTHESIS);
2942 return $existsExpression;
2946 * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
2950 public function ComparisonOperator()
2952 switch ($this->_lexer->lookahead['value']) {
2954 $this->match(Lexer::T_EQUALS);
2959 $this->match(Lexer::T_LOWER_THAN);
2962 if ($this->_lexer->isNextToken(Lexer::T_EQUALS)) {
2963 $this->match(Lexer::T_EQUALS);
2965 } else if ($this->_lexer->isNextToken(Lexer::T_GREATER_THAN)) {
2966 $this->match(Lexer::T_GREATER_THAN);
2973 $this->match(Lexer::T_GREATER_THAN);
2976 if ($this->_lexer->isNextToken(Lexer::T_EQUALS)) {
2977 $this->match(Lexer::T_EQUALS);
2984 $this->match(Lexer::T_NEGATE);
2985 $this->match(Lexer::T_EQUALS);
2990 $this->syntaxError('=, <, <=, <>, >, >=, !=');
2995 * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
2997 public function FunctionDeclaration()
2999 $token = $this->_lexer->lookahead;
3000 $funcName = strtolower($token['value']);
3002 // Check for built-in functions first!
3004 case (isset(self::$_STRING_FUNCTIONS[$funcName])):
3005 return $this->FunctionsReturningStrings();
3007 case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
3008 return $this->FunctionsReturningNumerics();
3010 case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
3011 return $this->FunctionsReturningDatetime();
3014 return $this->CustomFunctionDeclaration();
3019 * Helper function for FunctionDeclaration grammar rule
3021 private function CustomFunctionDeclaration()
3023 $token = $this->_lexer->lookahead;
3024 $funcName = strtolower($token['value']);
3026 // Check for custom functions afterwards
3027 $config = $this->_em->getConfiguration();
3030 case ($config->getCustomStringFunction($funcName) !== null):
3031 return $this->CustomFunctionsReturningStrings();
3033 case ($config->getCustomNumericFunction($funcName) !== null):
3034 return $this->CustomFunctionsReturningNumerics();
3036 case ($config->getCustomDatetimeFunction($funcName) !== null):
3037 return $this->CustomFunctionsReturningDatetime();
3040 $this->syntaxError('known function', $token);
3045 * FunctionsReturningNumerics ::=
3046 * "LENGTH" "(" StringPrimary ")" |
3047 * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
3048 * "ABS" "(" SimpleArithmeticExpression ")" |
3049 * "SQRT" "(" SimpleArithmeticExpression ")" |
3050 * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3051 * "SIZE" "(" CollectionValuedPathExpression ")"
3053 public function FunctionsReturningNumerics()
3055 $funcNameLower = strtolower($this->_lexer->lookahead['value']);
3056 $funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
3058 $function = new $funcClass($funcNameLower);
3059 $function->parse($this);
3064 public function CustomFunctionsReturningNumerics()
3066 // getCustomNumericFunction is case-insensitive
3067 $funcName = strtolower($this->_lexer->lookahead['value']);
3068 $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcName);
3070 $function = new $funcClass($funcName);
3071 $function->parse($this);
3077 * FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP"
3079 public function FunctionsReturningDatetime()
3081 $funcNameLower = strtolower($this->_lexer->lookahead['value']);
3082 $funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower];
3084 $function = new $funcClass($funcNameLower);
3085 $function->parse($this);
3090 public function CustomFunctionsReturningDatetime()
3092 // getCustomDatetimeFunction is case-insensitive
3093 $funcName = $this->_lexer->lookahead['value'];
3094 $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcName);
3096 $function = new $funcClass($funcName);
3097 $function->parse($this);
3103 * FunctionsReturningStrings ::=
3104 * "CONCAT" "(" StringPrimary "," StringPrimary ")" |
3105 * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3106 * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
3107 * "LOWER" "(" StringPrimary ")" |
3108 * "UPPER" "(" StringPrimary ")"
3110 public function FunctionsReturningStrings()
3112 $funcNameLower = strtolower($this->_lexer->lookahead['value']);
3113 $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower];
3115 $function = new $funcClass($funcNameLower);
3116 $function->parse($this);
3121 public function CustomFunctionsReturningStrings()
3123 // getCustomStringFunction is case-insensitive
3124 $funcName = $this->_lexer->lookahead['value'];
3125 $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcName);
3127 $function = new $funcClass($funcName);
3128 $function->parse($this);