vendor/doctrine/orm/src/Query/Parser.php line 404

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Query;
  4. use Doctrine\Common\Lexer\Token;
  5. use Doctrine\Deprecations\Deprecation;
  6. use Doctrine\ORM\EntityManagerInterface;
  7. use Doctrine\ORM\Mapping\ClassMetadata;
  8. use Doctrine\ORM\Query;
  9. use Doctrine\ORM\Query\AST\Functions;
  10. use Doctrine\ORM\Query\Exec\SqlFinalizer;
  11. use LogicException;
  12. use ReflectionClass;
  13. use function array_intersect;
  14. use function array_search;
  15. use function assert;
  16. use function class_exists;
  17. use function count;
  18. use function explode;
  19. use function implode;
  20. use function in_array;
  21. use function interface_exists;
  22. use function is_string;
  23. use function sprintf;
  24. use function str_contains;
  25. use function strlen;
  26. use function strpos;
  27. use function strrpos;
  28. use function strtolower;
  29. use function substr;
  30. /**
  31.  * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
  32.  * Parses a DQL query, reports any errors in it, and generates an AST.
  33.  *
  34.  * @psalm-import-type AssociationMapping from ClassMetadata
  35.  * @psalm-type DqlToken = Token<TokenType::T_*, string>
  36.  * @psalm-type QueryComponent = array{
  37.  *                 metadata?: ClassMetadata<object>,
  38.  *                 parent?: string|null,
  39.  *                 relation?: AssociationMapping|null,
  40.  *                 map?: string|null,
  41.  *                 resultVariable?: AST\Node|string,
  42.  *                 nestingLevel: int,
  43.  *                 token: DqlToken,
  44.  *             }
  45.  */
  46. class Parser
  47. {
  48.     /**
  49.      * @readonly Maps BUILT-IN string function names to AST class names.
  50.      * @var array<string, class-string<Functions\FunctionNode>>
  51.      */
  52.     private static $stringFunctions = [
  53.         'concat'    => Functions\ConcatFunction::class,
  54.         'substring' => Functions\SubstringFunction::class,
  55.         'trim'      => Functions\TrimFunction::class,
  56.         'lower'     => Functions\LowerFunction::class,
  57.         'upper'     => Functions\UpperFunction::class,
  58.         'identity'  => Functions\IdentityFunction::class,
  59.     ];
  60.     /**
  61.      * @readonly Maps BUILT-IN numeric function names to AST class names.
  62.      * @var array<string, class-string<Functions\FunctionNode>>
  63.      */
  64.     private static $numericFunctions = [
  65.         'length'    => Functions\LengthFunction::class,
  66.         'locate'    => Functions\LocateFunction::class,
  67.         'abs'       => Functions\AbsFunction::class,
  68.         'sqrt'      => Functions\SqrtFunction::class,
  69.         'mod'       => Functions\ModFunction::class,
  70.         'size'      => Functions\SizeFunction::class,
  71.         'date_diff' => Functions\DateDiffFunction::class,
  72.         'bit_and'   => Functions\BitAndFunction::class,
  73.         'bit_or'    => Functions\BitOrFunction::class,
  74.         // Aggregate functions
  75.         'min'       => Functions\MinFunction::class,
  76.         'max'       => Functions\MaxFunction::class,
  77.         'avg'       => Functions\AvgFunction::class,
  78.         'sum'       => Functions\SumFunction::class,
  79.         'count'     => Functions\CountFunction::class,
  80.     ];
  81.     /**
  82.      * @readonly Maps BUILT-IN datetime function names to AST class names.
  83.      * @var array<string, class-string<Functions\FunctionNode>>
  84.      */
  85.     private static $datetimeFunctions = [
  86.         'current_date'      => Functions\CurrentDateFunction::class,
  87.         'current_time'      => Functions\CurrentTimeFunction::class,
  88.         'current_timestamp' => Functions\CurrentTimestampFunction::class,
  89.         'date_add'          => Functions\DateAddFunction::class,
  90.         'date_sub'          => Functions\DateSubFunction::class,
  91.     ];
  92.     /*
  93.      * Expressions that were encountered during parsing of identifiers and expressions
  94.      * and still need to be validated.
  95.      */
  96.     /** @psalm-var list<array{token: DqlToken|null, expression: mixed, nestingLevel: int}> */
  97.     private $deferredIdentificationVariables = [];
  98.     /** @psalm-var list<array{token: DqlToken|null, expression: AST\PartialObjectExpression, nestingLevel: int}> */
  99.     private $deferredPartialObjectExpressions = [];
  100.     /** @psalm-var list<array{token: DqlToken|null, expression: AST\PathExpression, nestingLevel: int}> */
  101.     private $deferredPathExpressions = [];
  102.     /** @psalm-var list<array{token: DqlToken|null, expression: mixed, nestingLevel: int}> */
  103.     private $deferredResultVariables = [];
  104.     /** @psalm-var list<array{token: DqlToken|null, expression: AST\NewObjectExpression, nestingLevel: int}> */
  105.     private $deferredNewObjectExpressions = [];
  106.     /**
  107.      * The lexer.
  108.      *
  109.      * @var Lexer
  110.      */
  111.     private $lexer;
  112.     /**
  113.      * The parser result.
  114.      *
  115.      * @var ParserResult
  116.      */
  117.     private $parserResult;
  118.     /**
  119.      * The EntityManager.
  120.      *
  121.      * @var EntityManagerInterface
  122.      */
  123.     private $em;
  124.     /**
  125.      * The Query to parse.
  126.      *
  127.      * @var Query
  128.      */
  129.     private $query;
  130.     /**
  131.      * Map of declared query components in the parsed query.
  132.      *
  133.      * @psalm-var array<string, QueryComponent>
  134.      */
  135.     private $queryComponents = [];
  136.     /**
  137.      * Keeps the nesting level of defined ResultVariables.
  138.      *
  139.      * @var int
  140.      */
  141.     private $nestingLevel 0;
  142.     /**
  143.      * Any additional custom tree walkers that modify the AST.
  144.      *
  145.      * @var list<class-string<TreeWalker>>
  146.      */
  147.     private $customTreeWalkers = [];
  148.     /**
  149.      * The custom last tree walker, if any, that is responsible for producing the output.
  150.      *
  151.      * @var class-string<SqlWalker>|null
  152.      */
  153.     private $customOutputWalker;
  154.     /** @psalm-var array<string, AST\SelectExpression> */
  155.     private $identVariableExpressions = [];
  156.     /**
  157.      * Creates a new query parser object.
  158.      *
  159.      * @param Query $query The Query to parse.
  160.      */
  161.     public function __construct(Query $query)
  162.     {
  163.         $this->query        $query;
  164.         $this->em           $query->getEntityManager();
  165.         $this->lexer        = new Lexer((string) $query->getDQL());
  166.         $this->parserResult = new ParserResult();
  167.     }
  168.     /**
  169.      * Sets a custom tree walker that produces output.
  170.      * This tree walker will be run last over the AST, after any other walkers.
  171.      *
  172.      * @param class-string<SqlWalker> $className
  173.      *
  174.      * @return void
  175.      */
  176.     public function setCustomOutputTreeWalker($className)
  177.     {
  178.         Deprecation::trigger(
  179.             'doctrine/orm',
  180.             'https://github.com/doctrine/orm/pull/11641',
  181.             '%s is deprecated, set the output walker class with the \Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER query hint instead',
  182.             __METHOD__
  183.         );
  184.         $this->customOutputWalker $className;
  185.     }
  186.     /**
  187.      * Adds a custom tree walker for modifying the AST.
  188.      *
  189.      * @param class-string<TreeWalker> $className
  190.      *
  191.      * @return void
  192.      */
  193.     public function addCustomTreeWalker($className)
  194.     {
  195.         $this->customTreeWalkers[] = $className;
  196.     }
  197.     /**
  198.      * Gets the lexer used by the parser.
  199.      *
  200.      * @return Lexer
  201.      */
  202.     public function getLexer()
  203.     {
  204.         return $this->lexer;
  205.     }
  206.     /**
  207.      * Gets the ParserResult that is being filled with information during parsing.
  208.      *
  209.      * @return ParserResult
  210.      */
  211.     public function getParserResult()
  212.     {
  213.         return $this->parserResult;
  214.     }
  215.     /**
  216.      * Gets the EntityManager used by the parser.
  217.      *
  218.      * @return EntityManagerInterface
  219.      */
  220.     public function getEntityManager()
  221.     {
  222.         return $this->em;
  223.     }
  224.     /**
  225.      * Parses and builds AST for the given Query.
  226.      *
  227.      * @return AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement
  228.      */
  229.     public function getAST()
  230.     {
  231.         // Parse & build AST
  232.         $AST $this->QueryLanguage();
  233.         // Process any deferred validations of some nodes in the AST.
  234.         // This also allows post-processing of the AST for modification purposes.
  235.         $this->processDeferredIdentificationVariables();
  236.         if ($this->deferredPartialObjectExpressions) {
  237.             $this->processDeferredPartialObjectExpressions();
  238.         }
  239.         if ($this->deferredPathExpressions) {
  240.             $this->processDeferredPathExpressions();
  241.         }
  242.         if ($this->deferredResultVariables) {
  243.             $this->processDeferredResultVariables();
  244.         }
  245.         if ($this->deferredNewObjectExpressions) {
  246.             $this->processDeferredNewObjectExpressions($AST);
  247.         }
  248.         $this->processRootEntityAliasSelected();
  249.         // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
  250.         $this->fixIdentificationVariableOrder($AST);
  251.         return $AST;
  252.     }
  253.     /**
  254.      * Attempts to match the given token with the current lookahead token.
  255.      *
  256.      * If they match, updates the lookahead token; otherwise raises a syntax
  257.      * error.
  258.      *
  259.      * @param TokenType::T_* $token The token type.
  260.      *
  261.      * @return void
  262.      *
  263.      * @throws QueryException If the tokens don't match.
  264.      */
  265.     public function match($token)
  266.     {
  267.         $lookaheadType $this->lexer->lookahead->type ?? null;
  268.         // Short-circuit on first condition, usually types match
  269.         if ($lookaheadType === $token) {
  270.             $this->lexer->moveNext();
  271.             return;
  272.         }
  273.         // If parameter is not identifier (1-99) must be exact match
  274.         if ($token TokenType::T_IDENTIFIER) {
  275.             $this->syntaxError($this->lexer->getLiteral($token));
  276.         }
  277.         // If parameter is keyword (200+) must be exact match
  278.         if ($token TokenType::T_IDENTIFIER) {
  279.             $this->syntaxError($this->lexer->getLiteral($token));
  280.         }
  281.         // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
  282.         if ($token === TokenType::T_IDENTIFIER && $lookaheadType TokenType::T_IDENTIFIER) {
  283.             $this->syntaxError($this->lexer->getLiteral($token));
  284.         }
  285.         $this->lexer->moveNext();
  286.     }
  287.     /**
  288.      * Frees this parser, enabling it to be reused.
  289.      *
  290.      * @param bool $deep     Whether to clean peek and reset errors.
  291.      * @param int  $position Position to reset.
  292.      *
  293.      * @return void
  294.      */
  295.     public function free($deep false$position 0)
  296.     {
  297.         // WARNING! Use this method with care. It resets the scanner!
  298.         $this->lexer->resetPosition($position);
  299.         // Deep = true cleans peek and also any previously defined errors
  300.         if ($deep) {
  301.             $this->lexer->resetPeek();
  302.         }
  303.         $this->lexer->token     null;
  304.         $this->lexer->lookahead null;
  305.     }
  306.     /**
  307.      * Parses a query string.
  308.      *
  309.      * @return ParserResult
  310.      */
  311.     public function parse()
  312.     {
  313.         $AST $this->getAST();
  314.         $customWalkers $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
  315.         if ($customWalkers !== false) {
  316.             $this->customTreeWalkers $customWalkers;
  317.         }
  318.         $customOutputWalker $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER);
  319.         if ($customOutputWalker !== false) {
  320.             $this->customOutputWalker $customOutputWalker;
  321.         }
  322.         // Run any custom tree walkers over the AST
  323.         if ($this->customTreeWalkers) {
  324.             $treeWalkerChain = new TreeWalkerChain($this->query$this->parserResult$this->queryComponents);
  325.             foreach ($this->customTreeWalkers as $walker) {
  326.                 $treeWalkerChain->addTreeWalker($walker);
  327.             }
  328.             switch (true) {
  329.                 case $AST instanceof AST\UpdateStatement:
  330.                     $treeWalkerChain->walkUpdateStatement($AST);
  331.                     break;
  332.                 case $AST instanceof AST\DeleteStatement:
  333.                     $treeWalkerChain->walkDeleteStatement($AST);
  334.                     break;
  335.                 case $AST instanceof AST\SelectStatement:
  336.                 default:
  337.                     $treeWalkerChain->walkSelectStatement($AST);
  338.             }
  339.             $this->queryComponents $treeWalkerChain->getQueryComponents();
  340.         }
  341.         $outputWalkerClass $this->customOutputWalker ?: SqlOutputWalker::class;
  342.         $outputWalker      = new $outputWalkerClass($this->query$this->parserResult$this->queryComponents);
  343.         if ($outputWalker instanceof OutputWalker) {
  344.             $finalizer $outputWalker->getFinalizer($AST);
  345.             $this->parserResult->setSqlFinalizer($finalizer);
  346.         } else {
  347.             Deprecation::trigger(
  348.                 'doctrine/orm',
  349.                 'https://github.com/doctrine/orm/pull/11188/',
  350.                 'Your output walker class %s should implement %s in order to provide a %s. This also means the output walker should not use the query firstResult/maxResult values, which should be read from the query by the SqlFinalizer only.',
  351.                 $outputWalkerClass,
  352.                 OutputWalker::class,
  353.                 SqlFinalizer::class
  354.             );
  355.             // @phpstan-ignore method.deprecated
  356.             $executor $outputWalker->getExecutor($AST);
  357.             // @phpstan-ignore method.deprecated
  358.             $this->parserResult->setSqlExecutor($executor);
  359.         }
  360.         return $this->parserResult;
  361.     }
  362.     /**
  363.      * Fixes order of identification variables.
  364.      *
  365.      * They have to appear in the select clause in the same order as the
  366.      * declarations (from ... x join ... y join ... z ...) appear in the query
  367.      * as the hydration process relies on that order for proper operation.
  368.      *
  369.      * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
  370.      */
  371.     private function fixIdentificationVariableOrder(AST\Node $AST): void
  372.     {
  373.         if (count($this->identVariableExpressions) <= 1) {
  374.             return;
  375.         }
  376.         assert($AST instanceof AST\SelectStatement);
  377.         foreach ($this->queryComponents as $dqlAlias => $qComp) {
  378.             if (! isset($this->identVariableExpressions[$dqlAlias])) {
  379.                 continue;
  380.             }
  381.             $expr $this->identVariableExpressions[$dqlAlias];
  382.             $key  array_search($expr$AST->selectClause->selectExpressionstrue);
  383.             unset($AST->selectClause->selectExpressions[$key]);
  384.             $AST->selectClause->selectExpressions[] = $expr;
  385.         }
  386.     }
  387.     /**
  388.      * Generates a new syntax error.
  389.      *
  390.      * @param string       $expected Expected string.
  391.      * @param mixed[]|null $token    Got token.
  392.      * @psalm-param DqlToken|null $token
  393.      *
  394.      * @return void
  395.      * @psalm-return no-return
  396.      *
  397.      * @throws QueryException
  398.      */
  399.     public function syntaxError($expected ''$token null)
  400.     {
  401.         if ($token === null) {
  402.             $token $this->lexer->lookahead;
  403.         }
  404.         $tokenPos $token->position ?? '-1';
  405.         $message  sprintf('line 0, col %d: Error: '$tokenPos);
  406.         $message .= $expected !== '' sprintf('Expected %s, got '$expected) : 'Unexpected ';
  407.         $message .= $this->lexer->lookahead === null 'end of string.' sprintf("'%s'"$token->value);
  408.         throw QueryException::syntaxError($messageQueryException::dqlError($this->query->getDQL() ?? ''));
  409.     }
  410.     /**
  411.      * Generates a new semantical error.
  412.      *
  413.      * @param string       $message Optional message.
  414.      * @param mixed[]|null $token   Optional token.
  415.      * @psalm-param DqlToken|null $token
  416.      *
  417.      * @return void
  418.      * @psalm-return no-return
  419.      *
  420.      * @throws QueryException
  421.      */
  422.     public function semanticalError($message ''$token null)
  423.     {
  424.         if ($token === null) {
  425.             $token $this->lexer->lookahead ?? new Token('fake token'420);
  426.         }
  427.         // Minimum exposed chars ahead of token
  428.         $distance 12;
  429.         // Find a position of a final word to display in error string
  430.         $dql    $this->query->getDQL();
  431.         $length strlen($dql);
  432.         $pos    $token->position $distance;
  433.         $pos    strpos($dql' '$length $pos $pos $length);
  434.         $length $pos !== false $pos $token->position $distance;
  435.         $tokenPos $token->position $token->position '-1';
  436.         $tokenStr substr($dql$token->position$length);
  437.         // Building informative message
  438.         $message 'line 0, col ' $tokenPos " near '" $tokenStr "': Error: " $message;
  439.         throw QueryException::semanticalError($messageQueryException::dqlError($this->query->getDQL()));
  440.     }
  441.     /**
  442.      * Peeks beyond the matched closing parenthesis and returns the first token after that one.
  443.      *
  444.      * @param bool $resetPeek Reset peek after finding the closing parenthesis.
  445.      *
  446.      * @return mixed[]
  447.      * @psalm-return DqlToken|null
  448.      */
  449.     private function peekBeyondClosingParenthesis(bool $resetPeek true)
  450.     {
  451.         $token        $this->lexer->peek();
  452.         $numUnmatched 1;
  453.         while ($numUnmatched && $token !== null) {
  454.             switch ($token->type) {
  455.                 case TokenType::T_OPEN_PARENTHESIS:
  456.                     ++$numUnmatched;
  457.                     break;
  458.                 case TokenType::T_CLOSE_PARENTHESIS:
  459.                     --$numUnmatched;
  460.                     break;
  461.                 default:
  462.                     // Do nothing
  463.             }
  464.             $token $this->lexer->peek();
  465.         }
  466.         if ($resetPeek) {
  467.             $this->lexer->resetPeek();
  468.         }
  469.         return $token;
  470.     }
  471.     /**
  472.      * Checks if the given token indicates a mathematical operator.
  473.      *
  474.      * @psalm-param DqlToken|null $token
  475.      */
  476.     private function isMathOperator($token): bool
  477.     {
  478.         return $token !== null && in_array($token->type, [TokenType::T_PLUSTokenType::T_MINUSTokenType::T_DIVIDETokenType::T_MULTIPLY], true);
  479.     }
  480.     /**
  481.      * Checks if the next-next (after lookahead) token starts a function.
  482.      *
  483.      * @return bool TRUE if the next-next tokens start a function, FALSE otherwise.
  484.      */
  485.     private function isFunction(): bool
  486.     {
  487.         assert($this->lexer->lookahead !== null);
  488.         $lookaheadType $this->lexer->lookahead->type;
  489.         $peek          $this->lexer->peek();
  490.         $this->lexer->resetPeek();
  491.         return $lookaheadType >= TokenType::T_IDENTIFIER && $peek !== null && $peek->type === TokenType::T_OPEN_PARENTHESIS;
  492.     }
  493.     /**
  494.      * Checks whether the given token type indicates an aggregate function.
  495.      *
  496.      * @psalm-param TokenType::T_* $tokenType
  497.      *
  498.      * @return bool TRUE if the token type is an aggregate function, FALSE otherwise.
  499.      */
  500.     private function isAggregateFunction(int $tokenType): bool
  501.     {
  502.         return in_array(
  503.             $tokenType,
  504.             [TokenType::T_AVGTokenType::T_MINTokenType::T_MAXTokenType::T_SUMTokenType::T_COUNT],
  505.             true
  506.         );
  507.     }
  508.     /**
  509.      * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME.
  510.      */
  511.     private function isNextAllAnySome(): bool
  512.     {
  513.         assert($this->lexer->lookahead !== null);
  514.         return in_array(
  515.             $this->lexer->lookahead->type,
  516.             [TokenType::T_ALLTokenType::T_ANYTokenType::T_SOME],
  517.             true
  518.         );
  519.     }
  520.     /**
  521.      * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
  522.      * It must exist in query components list.
  523.      */
  524.     private function processDeferredIdentificationVariables(): void
  525.     {
  526.         foreach ($this->deferredIdentificationVariables as $deferredItem) {
  527.             $identVariable $deferredItem['expression'];
  528.             // Check if IdentificationVariable exists in queryComponents
  529.             if (! isset($this->queryComponents[$identVariable])) {
  530.                 $this->semanticalError(
  531.                     sprintf("'%s' is not defined."$identVariable),
  532.                     $deferredItem['token']
  533.                 );
  534.             }
  535.             $qComp $this->queryComponents[$identVariable];
  536.             // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
  537.             if (! isset($qComp['metadata'])) {
  538.                 $this->semanticalError(
  539.                     sprintf("'%s' does not point to a Class."$identVariable),
  540.                     $deferredItem['token']
  541.                 );
  542.             }
  543.             // Validate if identification variable nesting level is lower or equal than the current one
  544.             if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
  545.                 $this->semanticalError(
  546.                     sprintf("'%s' is used outside the scope of its declaration."$identVariable),
  547.                     $deferredItem['token']
  548.                 );
  549.             }
  550.         }
  551.     }
  552.     /**
  553.      * Validates that the given <tt>NewObjectExpression</tt>.
  554.      */
  555.     private function processDeferredNewObjectExpressions(AST\SelectStatement $AST): void
  556.     {
  557.         foreach ($this->deferredNewObjectExpressions as $deferredItem) {
  558.             $expression    $deferredItem['expression'];
  559.             $token         $deferredItem['token'];
  560.             $className     $expression->className;
  561.             $args          $expression->args;
  562.             $fromClassName $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null;
  563.             // If the namespace is not given then assumes the first FROM entity namespace
  564.             if (! str_contains($className'\\') && ! class_exists($className) && is_string($fromClassName) && str_contains($fromClassName'\\')) {
  565.                 $namespace substr($fromClassName0strrpos($fromClassName'\\'));
  566.                 $fqcn      $namespace '\\' $className;
  567.                 if (class_exists($fqcn)) {
  568.                     $expression->className $fqcn;
  569.                     $className             $fqcn;
  570.                 }
  571.             }
  572.             if (! class_exists($className)) {
  573.                 $this->semanticalError(sprintf('Class "%s" is not defined.'$className), $token);
  574.             }
  575.             $class = new ReflectionClass($className);
  576.             if (! $class->isInstantiable()) {
  577.                 $this->semanticalError(sprintf('Class "%s" can not be instantiated.'$className), $token);
  578.             }
  579.             if ($class->getConstructor() === null) {
  580.                 $this->semanticalError(sprintf('Class "%s" has not a valid constructor.'$className), $token);
  581.             }
  582.             if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
  583.                 $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.'$className), $token);
  584.             }
  585.         }
  586.     }
  587.     /**
  588.      * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
  589.      * It must exist in query components list.
  590.      */
  591.     private function processDeferredPartialObjectExpressions(): void
  592.     {
  593.         foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
  594.             $expr  $deferredItem['expression'];
  595.             $class $this->getMetadataForDqlAlias($expr->identificationVariable);
  596.             foreach ($expr->partialFieldSet as $field) {
  597.                 if (isset($class->fieldMappings[$field])) {
  598.                     continue;
  599.                 }
  600.                 if (
  601.                     isset($class->associationMappings[$field]) &&
  602.                     $class->associationMappings[$field]['isOwningSide'] &&
  603.                     $class->associationMappings[$field]['type'] & ClassMetadata::TO_ONE
  604.                 ) {
  605.                     continue;
  606.                 }
  607.                 $this->semanticalError(sprintf(
  608.                     "There is no mapped field named '%s' on class %s.",
  609.                     $field,
  610.                     $class->name
  611.                 ), $deferredItem['token']);
  612.             }
  613.             if (array_intersect($class->identifier$expr->partialFieldSet) !== $class->identifier) {
  614.                 $this->semanticalError(
  615.                     'The partial field selection of class ' $class->name ' must contain the identifier.',
  616.                     $deferredItem['token']
  617.                 );
  618.             }
  619.         }
  620.     }
  621.     /**
  622.      * Validates that the given <tt>ResultVariable</tt> is semantically correct.
  623.      * It must exist in query components list.
  624.      */
  625.     private function processDeferredResultVariables(): void
  626.     {
  627.         foreach ($this->deferredResultVariables as $deferredItem) {
  628.             $resultVariable $deferredItem['expression'];
  629.             // Check if ResultVariable exists in queryComponents
  630.             if (! isset($this->queryComponents[$resultVariable])) {
  631.                 $this->semanticalError(
  632.                     sprintf("'%s' is not defined."$resultVariable),
  633.                     $deferredItem['token']
  634.                 );
  635.             }
  636.             $qComp $this->queryComponents[$resultVariable];
  637.             // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
  638.             if (! isset($qComp['resultVariable'])) {
  639.                 $this->semanticalError(
  640.                     sprintf("'%s' does not point to a ResultVariable."$resultVariable),
  641.                     $deferredItem['token']
  642.                 );
  643.             }
  644.             // Validate if identification variable nesting level is lower or equal than the current one
  645.             if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
  646.                 $this->semanticalError(
  647.                     sprintf("'%s' is used outside the scope of its declaration."$resultVariable),
  648.                     $deferredItem['token']
  649.                 );
  650.             }
  651.         }
  652.     }
  653.     /**
  654.      * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
  655.      *
  656.      * AssociationPathExpression             ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
  657.      * SingleValuedPathExpression            ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
  658.      * StateFieldPathExpression              ::= IdentificationVariable "." StateField
  659.      * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
  660.      * CollectionValuedPathExpression        ::= IdentificationVariable "." CollectionValuedAssociationField
  661.      */
  662.     private function processDeferredPathExpressions(): void
  663.     {
  664.         foreach ($this->deferredPathExpressions as $deferredItem) {
  665.             $pathExpression $deferredItem['expression'];
  666.             $class $this->getMetadataForDqlAlias($pathExpression->identificationVariable);
  667.             $field $pathExpression->field;
  668.             if ($field === null) {
  669.                 $field $pathExpression->field $class->identifier[0];
  670.             }
  671.             // Check if field or association exists
  672.             if (! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
  673.                 $this->semanticalError(
  674.                     'Class ' $class->name ' has no field or association named ' $field,
  675.                     $deferredItem['token']
  676.                 );
  677.             }
  678.             $fieldType AST\PathExpression::TYPE_STATE_FIELD;
  679.             if (isset($class->associationMappings[$field])) {
  680.                 $assoc $class->associationMappings[$field];
  681.                 $fieldType $assoc['type'] & ClassMetadata::TO_ONE
  682.                     AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
  683.                     AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
  684.             }
  685.             // Validate if PathExpression is one of the expected types
  686.             $expectedType $pathExpression->expectedType;
  687.             if (! ($expectedType $fieldType)) {
  688.                 // We need to recognize which was expected type(s)
  689.                 $expectedStringTypes = [];
  690.                 // Validate state field type
  691.                 if ($expectedType AST\PathExpression::TYPE_STATE_FIELD) {
  692.                     $expectedStringTypes[] = 'StateFieldPathExpression';
  693.                 }
  694.                 // Validate single valued association (*-to-one)
  695.                 if ($expectedType AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
  696.                     $expectedStringTypes[] = 'SingleValuedAssociationField';
  697.                 }
  698.                 // Validate single valued association (*-to-many)
  699.                 if ($expectedType AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
  700.                     $expectedStringTypes[] = 'CollectionValuedAssociationField';
  701.                 }
  702.                 // Build the error message
  703.                 $semanticalError  'Invalid PathExpression. ';
  704.                 $semanticalError .= count($expectedStringTypes) === 1
  705.                     'Must be a ' $expectedStringTypes[0] . '.'
  706.                     implode(' or '$expectedStringTypes) . ' expected.';
  707.                 $this->semanticalError($semanticalError$deferredItem['token']);
  708.             }
  709.             // We need to force the type in PathExpression
  710.             $pathExpression->type $fieldType;
  711.         }
  712.     }
  713.     private function processRootEntityAliasSelected(): void
  714.     {
  715.         if (! count($this->identVariableExpressions)) {
  716.             return;
  717.         }
  718.         foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
  719.             if (isset($this->queryComponents[$dqlAlias]) && ! isset($this->queryComponents[$dqlAlias]['parent'])) {
  720.                 return;
  721.             }
  722.         }
  723.         $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
  724.     }
  725.     /**
  726.      * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
  727.      *
  728.      * @return AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement
  729.      */
  730.     public function QueryLanguage()
  731.     {
  732.         $statement null;
  733.         $this->lexer->moveNext();
  734.         switch ($this->lexer->lookahead->type ?? null) {
  735.             case TokenType::T_SELECT:
  736.                 $statement $this->SelectStatement();
  737.                 break;
  738.             case TokenType::T_UPDATE:
  739.                 $statement $this->UpdateStatement();
  740.                 break;
  741.             case TokenType::T_DELETE:
  742.                 $statement $this->DeleteStatement();
  743.                 break;
  744.             default:
  745.                 $this->syntaxError('SELECT, UPDATE or DELETE');
  746.                 break;
  747.         }
  748.         // Check for end of string
  749.         if ($this->lexer->lookahead !== null) {
  750.             $this->syntaxError('end of string');
  751.         }
  752.         return $statement;
  753.     }
  754.     /**
  755.      * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
  756.      *
  757.      * @return AST\SelectStatement
  758.      */
  759.     public function SelectStatement()
  760.     {
  761.         $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
  762.         $selectStatement->whereClause   $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
  763.         $selectStatement->groupByClause $this->lexer->isNextToken(TokenType::T_GROUP) ? $this->GroupByClause() : null;
  764.         $selectStatement->havingClause  $this->lexer->isNextToken(TokenType::T_HAVING) ? $this->HavingClause() : null;
  765.         $selectStatement->orderByClause $this->lexer->isNextToken(TokenType::T_ORDER) ? $this->OrderByClause() : null;
  766.         return $selectStatement;
  767.     }
  768.     /**
  769.      * UpdateStatement ::= UpdateClause [WhereClause]
  770.      *
  771.      * @return AST\UpdateStatement
  772.      */
  773.     public function UpdateStatement()
  774.     {
  775.         $updateStatement = new AST\UpdateStatement($this->UpdateClause());
  776.         $updateStatement->whereClause $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
  777.         return $updateStatement;
  778.     }
  779.     /**
  780.      * DeleteStatement ::= DeleteClause [WhereClause]
  781.      *
  782.      * @return AST\DeleteStatement
  783.      */
  784.     public function DeleteStatement()
  785.     {
  786.         $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
  787.         $deleteStatement->whereClause $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
  788.         return $deleteStatement;
  789.     }
  790.     /**
  791.      * IdentificationVariable ::= identifier
  792.      *
  793.      * @return string
  794.      */
  795.     public function IdentificationVariable()
  796.     {
  797.         $this->match(TokenType::T_IDENTIFIER);
  798.         assert($this->lexer->token !== null);
  799.         $identVariable $this->lexer->token->value;
  800.         $this->deferredIdentificationVariables[] = [
  801.             'expression'   => $identVariable,
  802.             'nestingLevel' => $this->nestingLevel,
  803.             'token'        => $this->lexer->token,
  804.         ];
  805.         return $identVariable;
  806.     }
  807.     /**
  808.      * AliasIdentificationVariable = identifier
  809.      *
  810.      * @return string
  811.      */
  812.     public function AliasIdentificationVariable()
  813.     {
  814.         $this->match(TokenType::T_IDENTIFIER);
  815.         assert($this->lexer->token !== null);
  816.         $aliasIdentVariable $this->lexer->token->value;
  817.         $exists             = isset($this->queryComponents[$aliasIdentVariable]);
  818.         if ($exists) {
  819.             $this->semanticalError(
  820.                 sprintf("'%s' is already defined."$aliasIdentVariable),
  821.                 $this->lexer->token
  822.             );
  823.         }
  824.         return $aliasIdentVariable;
  825.     }
  826.     /**
  827.      * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
  828.      *
  829.      * @return string
  830.      */
  831.     public function AbstractSchemaName()
  832.     {
  833.         if ($this->lexer->isNextToken(TokenType::T_FULLY_QUALIFIED_NAME)) {
  834.             $this->match(TokenType::T_FULLY_QUALIFIED_NAME);
  835.             assert($this->lexer->token !== null);
  836.             return $this->lexer->token->value;
  837.         }
  838.         if ($this->lexer->isNextToken(TokenType::T_IDENTIFIER)) {
  839.             $this->match(TokenType::T_IDENTIFIER);
  840.             assert($this->lexer->token !== null);
  841.             return $this->lexer->token->value;
  842.         }
  843.         // @phpstan-ignore classConstant.deprecated
  844.         $this->match(TokenType::T_ALIASED_NAME);
  845.         assert($this->lexer->token !== null);
  846.         Deprecation::trigger(
  847.             'doctrine/orm',
  848.             'https://github.com/doctrine/orm/issues/8818',
  849.             'Short namespace aliases such as "%s" are deprecated and will be removed in Doctrine ORM 3.0.',
  850.             $this->lexer->token->value
  851.         );
  852.         [$namespaceAlias$simpleClassName] = explode(':'$this->lexer->token->value);
  853.         return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' $simpleClassName;
  854.     }
  855.     /**
  856.      * Validates an AbstractSchemaName, making sure the class exists.
  857.      *
  858.      * @param string $schemaName The name to validate.
  859.      *
  860.      * @throws QueryException if the name does not exist.
  861.      */
  862.     private function validateAbstractSchemaName(string $schemaName): void
  863.     {
  864.         assert($this->lexer->token !== null);
  865.         if (! (class_exists($schemaNametrue) || interface_exists($schemaNametrue))) {
  866.             $this->semanticalError(
  867.                 sprintf("Class '%s' is not defined."$schemaName),
  868.                 $this->lexer->token
  869.             );
  870.         }
  871.     }
  872.     /**
  873.      * AliasResultVariable ::= identifier
  874.      *
  875.      * @return string
  876.      */
  877.     public function AliasResultVariable()
  878.     {
  879.         $this->match(TokenType::T_IDENTIFIER);
  880.         assert($this->lexer->token !== null);
  881.         $resultVariable $this->lexer->token->value;
  882.         $exists         = isset($this->queryComponents[$resultVariable]);
  883.         if ($exists) {
  884.             $this->semanticalError(
  885.                 sprintf("'%s' is already defined."$resultVariable),
  886.                 $this->lexer->token
  887.             );
  888.         }
  889.         return $resultVariable;
  890.     }
  891.     /**
  892.      * ResultVariable ::= identifier
  893.      *
  894.      * @return string
  895.      */
  896.     public function ResultVariable()
  897.     {
  898.         $this->match(TokenType::T_IDENTIFIER);
  899.         assert($this->lexer->token !== null);
  900.         $resultVariable $this->lexer->token->value;
  901.         // Defer ResultVariable validation
  902.         $this->deferredResultVariables[] = [
  903.             'expression'   => $resultVariable,
  904.             'nestingLevel' => $this->nestingLevel,
  905.             'token'        => $this->lexer->token,
  906.         ];
  907.         return $resultVariable;
  908.     }
  909.     /**
  910.      * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
  911.      *
  912.      * @return AST\JoinAssociationPathExpression
  913.      */
  914.     public function JoinAssociationPathExpression()
  915.     {
  916.         $identVariable $this->IdentificationVariable();
  917.         if (! isset($this->queryComponents[$identVariable])) {
  918.             $this->semanticalError(
  919.                 'Identification Variable ' $identVariable ' used in join path expression but was not defined before.'
  920.             );
  921.         }
  922.         $this->match(TokenType::T_DOT);
  923.         $this->match(TokenType::T_IDENTIFIER);
  924.         assert($this->lexer->token !== null);
  925.         $field $this->lexer->token->value;
  926.         // Validate association field
  927.         $class $this->getMetadataForDqlAlias($identVariable);
  928.         if (! $class->hasAssociation($field)) {
  929.             $this->semanticalError('Class ' $class->name ' has no association named ' $field);
  930.         }
  931.         return new AST\JoinAssociationPathExpression($identVariable$field);
  932.     }
  933.     /**
  934.      * Parses an arbitrary path expression and defers semantical validation
  935.      * based on expected types.
  936.      *
  937.      * PathExpression ::= IdentificationVariable {"." identifier}*
  938.      *
  939.      * @param int $expectedTypes
  940.      * @psalm-param int-mask-of<AST\PathExpression::TYPE_*> $expectedTypes
  941.      *
  942.      * @return AST\PathExpression
  943.      */
  944.     public function PathExpression($expectedTypes)
  945.     {
  946.         $identVariable $this->IdentificationVariable();
  947.         $field         null;
  948.         assert($this->lexer->token !== null);
  949.         if ($this->lexer->isNextToken(TokenType::T_DOT)) {
  950.             $this->match(TokenType::T_DOT);
  951.             $this->match(TokenType::T_IDENTIFIER);
  952.             $field $this->lexer->token->value;
  953.             while ($this->lexer->isNextToken(TokenType::T_DOT)) {
  954.                 $this->match(TokenType::T_DOT);
  955.                 $this->match(TokenType::T_IDENTIFIER);
  956.                 $field .= '.' $this->lexer->token->value;
  957.             }
  958.         }
  959.         // Creating AST node
  960.         $pathExpr = new AST\PathExpression($expectedTypes$identVariable$field);
  961.         // Defer PathExpression validation if requested to be deferred
  962.         $this->deferredPathExpressions[] = [
  963.             'expression'   => $pathExpr,
  964.             'nestingLevel' => $this->nestingLevel,
  965.             'token'        => $this->lexer->token,
  966.         ];
  967.         return $pathExpr;
  968.     }
  969.     /**
  970.      * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
  971.      *
  972.      * @return AST\PathExpression
  973.      */
  974.     public function AssociationPathExpression()
  975.     {
  976.         return $this->PathExpression(
  977.             AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
  978.             AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
  979.         );
  980.     }
  981.     /**
  982.      * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
  983.      *
  984.      * @return AST\PathExpression
  985.      */
  986.     public function SingleValuedPathExpression()
  987.     {
  988.         return $this->PathExpression(
  989.             AST\PathExpression::TYPE_STATE_FIELD |
  990.             AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
  991.         );
  992.     }
  993.     /**
  994.      * StateFieldPathExpression ::= IdentificationVariable "." StateField
  995.      *
  996.      * @return AST\PathExpression
  997.      */
  998.     public function StateFieldPathExpression()
  999.     {
  1000.         return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
  1001.     }
  1002.     /**
  1003.      * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
  1004.      *
  1005.      * @return AST\PathExpression
  1006.      */
  1007.     public function SingleValuedAssociationPathExpression()
  1008.     {
  1009.         return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
  1010.     }
  1011.     /**
  1012.      * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
  1013.      *
  1014.      * @return AST\PathExpression
  1015.      */
  1016.     public function CollectionValuedPathExpression()
  1017.     {
  1018.         return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
  1019.     }
  1020.     /**
  1021.      * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
  1022.      *
  1023.      * @return AST\SelectClause
  1024.      */
  1025.     public function SelectClause()
  1026.     {
  1027.         $isDistinct false;
  1028.         $this->match(TokenType::T_SELECT);
  1029.         // Check for DISTINCT
  1030.         if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) {
  1031.             $this->match(TokenType::T_DISTINCT);
  1032.             $isDistinct true;
  1033.         }
  1034.         // Process SelectExpressions (1..N)
  1035.         $selectExpressions   = [];
  1036.         $selectExpressions[] = $this->SelectExpression();
  1037.         while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
  1038.             $this->match(TokenType::T_COMMA);
  1039.             $selectExpressions[] = $this->SelectExpression();
  1040.         }
  1041.         return new AST\SelectClause($selectExpressions$isDistinct);
  1042.     }
  1043.     /**
  1044.      * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
  1045.      *
  1046.      * @return AST\SimpleSelectClause
  1047.      */
  1048.     public function SimpleSelectClause()
  1049.     {
  1050.         $isDistinct false;
  1051.         $this->match(TokenType::T_SELECT);
  1052.         if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) {
  1053.             $this->match(TokenType::T_DISTINCT);
  1054.             $isDistinct true;
  1055.         }
  1056.         return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
  1057.     }
  1058.     /**
  1059.      * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
  1060.      *
  1061.      * @return AST\UpdateClause
  1062.      */
  1063.     public function UpdateClause()
  1064.     {
  1065.         $this->match(TokenType::T_UPDATE);
  1066.         assert($this->lexer->lookahead !== null);
  1067.         $token              $this->lexer->lookahead;
  1068.         $abstractSchemaName $this->AbstractSchemaName();
  1069.         $this->validateAbstractSchemaName($abstractSchemaName);
  1070.         if ($this->lexer->isNextToken(TokenType::T_AS)) {
  1071.             $this->match(TokenType::T_AS);
  1072.         }
  1073.         $aliasIdentificationVariable $this->AliasIdentificationVariable();
  1074.         $class $this->em->getClassMetadata($abstractSchemaName);
  1075.         // Building queryComponent
  1076.         $queryComponent = [
  1077.             'metadata'     => $class,
  1078.             'parent'       => null,
  1079.             'relation'     => null,
  1080.             'map'          => null,
  1081.             'nestingLevel' => $this->nestingLevel,
  1082.             'token'        => $token,
  1083.         ];
  1084.         $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
  1085.         $this->match(TokenType::T_SET);
  1086.         $updateItems   = [];
  1087.         $updateItems[] = $this->UpdateItem();
  1088.         while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
  1089.             $this->match(TokenType::T_COMMA);
  1090.             $updateItems[] = $this->UpdateItem();
  1091.         }
  1092.         $updateClause                              = new AST\UpdateClause($abstractSchemaName$updateItems);
  1093.         $updateClause->aliasIdentificationVariable $aliasIdentificationVariable;
  1094.         return $updateClause;
  1095.     }
  1096.     /**
  1097.      * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
  1098.      *
  1099.      * @return AST\DeleteClause
  1100.      */
  1101.     public function DeleteClause()
  1102.     {
  1103.         $this->match(TokenType::T_DELETE);
  1104.         if ($this->lexer->isNextToken(TokenType::T_FROM)) {
  1105.             $this->match(TokenType::T_FROM);
  1106.         }
  1107.         assert($this->lexer->lookahead !== null);
  1108.         $token              $this->lexer->lookahead;
  1109.         $abstractSchemaName $this->AbstractSchemaName();
  1110.         $this->validateAbstractSchemaName($abstractSchemaName);
  1111.         $deleteClause = new AST\DeleteClause($abstractSchemaName);
  1112.         if ($this->lexer->isNextToken(TokenType::T_AS)) {
  1113.             $this->match(TokenType::T_AS);
  1114.         }
  1115.         $aliasIdentificationVariable $this->lexer->isNextToken(TokenType::T_IDENTIFIER)
  1116.             ? $this->AliasIdentificationVariable()
  1117.             : 'alias_should_have_been_set';
  1118.         $deleteClause->aliasIdentificationVariable $aliasIdentificationVariable;
  1119.         $class                                     $this->em->getClassMetadata($deleteClause->abstractSchemaName);
  1120.         // Building queryComponent
  1121.         $queryComponent = [
  1122.             'metadata'     => $class,
  1123.             'parent'       => null,
  1124.             'relation'     => null,
  1125.             'map'          => null,
  1126.             'nestingLevel' => $this->nestingLevel,
  1127.             'token'        => $token,
  1128.         ];
  1129.         $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
  1130.         return $deleteClause;
  1131.     }
  1132.     /**
  1133.      * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
  1134.      *
  1135.      * @return AST\FromClause
  1136.      */
  1137.     public function FromClause()
  1138.     {
  1139.         $this->match(TokenType::T_FROM);
  1140.         $identificationVariableDeclarations   = [];
  1141.         $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
  1142.         while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
  1143.             $this->match(TokenType::T_COMMA);
  1144.             $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
  1145.         }
  1146.         return new AST\FromClause($identificationVariableDeclarations);
  1147.     }
  1148.     /**
  1149.      * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
  1150.      *
  1151.      * @return AST\SubselectFromClause
  1152.      */
  1153.     public function SubselectFromClause()
  1154.     {
  1155.         $this->match(TokenType::T_FROM);
  1156.         $identificationVariables   = [];
  1157.         $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
  1158.         while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
  1159.             $this->match(TokenType::T_COMMA);
  1160.             $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
  1161.         }
  1162.         return new AST\SubselectFromClause($identificationVariables);
  1163.     }
  1164.     /**
  1165.      * WhereClause ::= "WHERE" ConditionalExpression
  1166.      *
  1167.      * @return AST\WhereClause
  1168.      */
  1169.     public function WhereClause()
  1170.     {
  1171.         $this->match(TokenType::T_WHERE);
  1172.         return new AST\WhereClause($this->ConditionalExpression());
  1173.     }
  1174.     /**
  1175.      * HavingClause ::= "HAVING" ConditionalExpression
  1176.      *
  1177.      * @return AST\HavingClause
  1178.      */
  1179.     public function HavingClause()
  1180.     {
  1181.         $this->match(TokenType::T_HAVING);
  1182.         return new AST\HavingClause($this->ConditionalExpression());
  1183.     }
  1184.     /**
  1185.      * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
  1186.      *
  1187.      * @return AST\GroupByClause
  1188.      */
  1189.     public function GroupByClause()
  1190.     {
  1191.         $this->match(TokenType::T_GROUP);
  1192.         $this->match(TokenType::T_BY);
  1193.         $groupByItems = [$this->GroupByItem()];
  1194.         while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
  1195.             $this->match(TokenType::T_COMMA);
  1196.             $groupByItems[] = $this->GroupByItem();
  1197.         }
  1198.         return new AST\GroupByClause($groupByItems);
  1199.     }
  1200.     /**
  1201.      * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
  1202.      *
  1203.      * @return AST\OrderByClause
  1204.      */
  1205.     public function OrderByClause()
  1206.     {
  1207.         $this->match(TokenType::T_ORDER);
  1208.         $this->match(TokenType::T_BY);
  1209.         $orderByItems   = [];
  1210.         $orderByItems[] = $this->OrderByItem();
  1211.         while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
  1212.             $this->match(TokenType::T_COMMA);
  1213.             $orderByItems[] = $this->OrderByItem();
  1214.         }
  1215.         return new AST\OrderByClause($orderByItems);
  1216.     }
  1217.     /**
  1218.      * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
  1219.      *
  1220.      * @return AST\Subselect
  1221.      */
  1222.     public function Subselect()
  1223.     {
  1224.         // Increase query nesting level
  1225.         $this->nestingLevel++;
  1226.         $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
  1227.         $subselect->whereClause   $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
  1228.         $subselect->groupByClause $this->lexer->isNextToken(TokenType::T_GROUP) ? $this->GroupByClause() : null;
  1229.         $subselect->havingClause  $this->lexer->isNextToken(TokenType::T_HAVING) ? $this->HavingClause() : null;
  1230.         $subselect->orderByClause $this->lexer->isNextToken(TokenType::T_ORDER) ? $this->OrderByClause() : null;
  1231.         // Decrease query nesting level
  1232.         $this->nestingLevel--;
  1233.         return $subselect;
  1234.     }
  1235.     /**
  1236.      * UpdateItem ::= SingleValuedPathExpression "=" NewValue
  1237.      *
  1238.      * @return AST\UpdateItem
  1239.      */
  1240.     public function UpdateItem()
  1241.     {
  1242.         $pathExpr $this->SingleValuedPathExpression();
  1243.         $this->match(TokenType::T_EQUALS);
  1244.         return new AST\UpdateItem($pathExpr$this->NewValue());
  1245.     }
  1246.     /**
  1247.      * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
  1248.      *
  1249.      * @return string|AST\PathExpression
  1250.      */
  1251.     public function GroupByItem()
  1252.     {
  1253.         // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
  1254.         $glimpse $this->lexer->glimpse();
  1255.         if ($glimpse !== null && $glimpse->type === TokenType::T_DOT) {
  1256.             return $this->SingleValuedPathExpression();
  1257.         }
  1258.         assert($this->lexer->lookahead !== null);
  1259.         // Still need to decide between IdentificationVariable or ResultVariable
  1260.         $lookaheadValue $this->lexer->lookahead->value;
  1261.         if (! isset($this->queryComponents[$lookaheadValue])) {
  1262.             $this->semanticalError('Cannot group by undefined identification or result variable.');
  1263.         }
  1264.         return isset($this->queryComponents[$lookaheadValue]['metadata'])
  1265.             ? $this->IdentificationVariable()
  1266.             : $this->ResultVariable();
  1267.     }
  1268.     /**
  1269.      * OrderByItem ::= (
  1270.      *      SimpleArithmeticExpression | SingleValuedPathExpression | CaseExpression |
  1271.      *      ScalarExpression | ResultVariable | FunctionDeclaration
  1272.      * ) ["ASC" | "DESC"]
  1273.      *
  1274.      * @return AST\OrderByItem
  1275.      */
  1276.     public function OrderByItem()
  1277.     {
  1278.         $this->lexer->peek(); // lookahead => '.'
  1279.         $this->lexer->peek(); // lookahead => token after '.'
  1280.         $peek $this->lexer->peek(); // lookahead => token after the token after the '.'
  1281.         $this->lexer->resetPeek();
  1282.         $glimpse $this->lexer->glimpse();
  1283.         assert($this->lexer->lookahead !== null);
  1284.         switch (true) {
  1285.             case $this->isMathOperator($peek):
  1286.                 $expr $this->SimpleArithmeticExpression();
  1287.                 break;
  1288.             case $glimpse !== null && $glimpse->type === TokenType::T_DOT:
  1289.                 $expr $this->SingleValuedPathExpression();
  1290.                 break;
  1291.             case $this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis()):
  1292.                 $expr $this->ScalarExpression();
  1293.                 break;
  1294.             case $this->lexer->lookahead->type === TokenType::T_CASE:
  1295.                 $expr $this->CaseExpression();
  1296.                 break;
  1297.             case $this->isFunction():
  1298.                 $expr $this->FunctionDeclaration();
  1299.                 break;
  1300.             default:
  1301.                 $expr $this->ResultVariable();
  1302.                 break;
  1303.         }
  1304.         $type 'ASC';
  1305.         $item = new AST\OrderByItem($expr);
  1306.         switch (true) {
  1307.             case $this->lexer->isNextToken(TokenType::T_DESC):
  1308.                 $this->match(TokenType::T_DESC);
  1309.                 $type 'DESC';
  1310.                 break;
  1311.             case $this->lexer->isNextToken(TokenType::T_ASC):
  1312.                 $this->match(TokenType::T_ASC);
  1313.                 break;
  1314.             default:
  1315.                 // Do nothing
  1316.         }
  1317.         $item->type $type;
  1318.         return $item;
  1319.     }
  1320.     /**
  1321.      * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
  1322.      *      EnumPrimary | SimpleEntityExpression | "NULL"
  1323.      *
  1324.      * NOTE: Since it is not possible to correctly recognize individual types, here is the full
  1325.      * grammar that needs to be supported:
  1326.      *
  1327.      * NewValue ::= SimpleArithmeticExpression | "NULL"
  1328.      *
  1329.      * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
  1330.      *
  1331.      * @return AST\ArithmeticExpression|AST\InputParameter|null
  1332.      */
  1333.     public function NewValue()
  1334.     {
  1335.         if ($this->lexer->isNextToken(TokenType::T_NULL)) {
  1336.             $this->match(TokenType::T_NULL);
  1337.             return null;
  1338.         }
  1339.         if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
  1340.             $this->match(TokenType::T_INPUT_PARAMETER);
  1341.             assert($this->lexer->token !== null);
  1342.             return new AST\InputParameter($this->lexer->token->value);
  1343.         }
  1344.         return $this->ArithmeticExpression();
  1345.     }
  1346.     /**
  1347.      * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
  1348.      *
  1349.      * @return AST\IdentificationVariableDeclaration
  1350.      */
  1351.     public function IdentificationVariableDeclaration()
  1352.     {
  1353.         $joins                    = [];
  1354.         $rangeVariableDeclaration $this->RangeVariableDeclaration();
  1355.         $indexBy                  $this->lexer->isNextToken(TokenType::T_INDEX)
  1356.             ? $this->IndexBy()
  1357.             : null;
  1358.         $rangeVariableDeclaration->isRoot true;
  1359.         while (
  1360.             $this->lexer->isNextToken(TokenType::T_LEFT) ||
  1361.             $this->lexer->isNextToken(TokenType::T_INNER) ||
  1362.             $this->lexer->isNextToken(TokenType::T_JOIN)
  1363.         ) {
  1364.             $joins[] = $this->Join();
  1365.         }
  1366.         return new AST\IdentificationVariableDeclaration(
  1367.             $rangeVariableDeclaration,
  1368.             $indexBy,
  1369.             $joins
  1370.         );
  1371.     }
  1372.     /**
  1373.      * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
  1374.      *
  1375.      * {Internal note: WARNING: Solution is harder than a bare implementation.
  1376.      * Desired EBNF support:
  1377.      *
  1378.      * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
  1379.      *
  1380.      * It demands that entire SQL generation to become programmatical. This is
  1381.      * needed because association based subselect requires "WHERE" conditional
  1382.      * expressions to be injected, but there is no scope to do that. Only scope
  1383.      * accessible is "FROM", prohibiting an easy implementation without larger
  1384.      * changes.}
  1385.      *
  1386.      * @return AST\IdentificationVariableDeclaration
  1387.      */
  1388.     public function SubselectIdentificationVariableDeclaration()
  1389.     {
  1390.         /*
  1391.         NOT YET IMPLEMENTED!
  1392.         $glimpse = $this->lexer->glimpse();
  1393.         if ($glimpse->type == TokenType::T_DOT) {
  1394.             $associationPathExpression = $this->AssociationPathExpression();
  1395.             if ($this->lexer->isNextToken(TokenType::T_AS)) {
  1396.                 $this->match(TokenType::T_AS);
  1397.             }
  1398.             $aliasIdentificationVariable = $this->AliasIdentificationVariable();
  1399.             $identificationVariable      = $associationPathExpression->identificationVariable;
  1400.             $field                       = $associationPathExpression->associationField;
  1401.             $class       = $this->queryComponents[$identificationVariable]['metadata'];
  1402.             $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
  1403.             // Building queryComponent
  1404.             $joinQueryComponent = array(
  1405.                 'metadata'     => $targetClass,
  1406.                 'parent'       => $identificationVariable,
  1407.                 'relation'     => $class->getAssociationMapping($field),
  1408.                 'map'          => null,
  1409.                 'nestingLevel' => $this->nestingLevel,
  1410.                 'token'        => $this->lexer->lookahead
  1411.             );
  1412.             $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
  1413.             return new AST\SubselectIdentificationVariableDeclaration(
  1414.                 $associationPathExpression, $aliasIdentificationVariable
  1415.             );
  1416.         }
  1417.         */
  1418.         return $this->IdentificationVariableDeclaration();
  1419.     }
  1420.     /**
  1421.      * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
  1422.      *          (JoinAssociationDeclaration | RangeVariableDeclaration)
  1423.      *          ["WITH" ConditionalExpression]
  1424.      *
  1425.      * @return AST\Join
  1426.      */
  1427.     public function Join()
  1428.     {
  1429.         // Check Join type
  1430.         $joinType AST\Join::JOIN_TYPE_INNER;
  1431.         switch (true) {
  1432.             case $this->lexer->isNextToken(TokenType::T_LEFT):
  1433.                 $this->match(TokenType::T_LEFT);
  1434.                 $joinType AST\Join::JOIN_TYPE_LEFT;
  1435.                 // Possible LEFT OUTER join
  1436.                 if ($this->lexer->isNextToken(TokenType::T_OUTER)) {
  1437.                     $this->match(TokenType::T_OUTER);
  1438.                     $joinType AST\Join::JOIN_TYPE_LEFTOUTER;
  1439.                 }
  1440.                 break;
  1441.             case $this->lexer->isNextToken(TokenType::T_INNER):
  1442.                 $this->match(TokenType::T_INNER);
  1443.                 break;
  1444.             default:
  1445.                 // Do nothing
  1446.         }
  1447.         $this->match(TokenType::T_JOIN);
  1448.         $next $this->lexer->glimpse();
  1449.         assert($next !== null);
  1450.         $joinDeclaration $next->type === TokenType::T_DOT $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
  1451.         $adhocConditions $this->lexer->isNextToken(TokenType::T_WITH);
  1452.         $join            = new AST\Join($joinType$joinDeclaration);
  1453.         // Describe non-root join declaration
  1454.         if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
  1455.             $joinDeclaration->isRoot false;
  1456.         }
  1457.         // Check for ad-hoc Join conditions
  1458.         if ($adhocConditions) {
  1459.             $this->match(TokenType::T_WITH);
  1460.             $join->conditionalExpression $this->ConditionalExpression();
  1461.         }
  1462.         return $join;
  1463.     }
  1464.     /**
  1465.      * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
  1466.      *
  1467.      * @return AST\RangeVariableDeclaration
  1468.      *
  1469.      * @throws QueryException
  1470.      */
  1471.     public function RangeVariableDeclaration()
  1472.     {
  1473.         if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()->type === TokenType::T_SELECT) {
  1474.             $this->semanticalError('Subquery is not supported here'$this->lexer->token);
  1475.         }
  1476.         $abstractSchemaName $this->AbstractSchemaName();
  1477.         $this->validateAbstractSchemaName($abstractSchemaName);
  1478.         if ($this->lexer->isNextToken(TokenType::T_AS)) {
  1479.             $this->match(TokenType::T_AS);
  1480.         }
  1481.         assert($this->lexer->lookahead !== null);
  1482.         $token                       $this->lexer->lookahead;
  1483.         $aliasIdentificationVariable $this->AliasIdentificationVariable();
  1484.         $classMetadata               $this->em->getClassMetadata($abstractSchemaName);
  1485.         // Building queryComponent
  1486.         $queryComponent = [
  1487.             'metadata'     => $classMetadata,
  1488.             'parent'       => null,
  1489.             'relation'     => null,
  1490.             'map'          => null,
  1491.             'nestingLevel' => $this->nestingLevel,
  1492.             'token'        => $token,
  1493.         ];
  1494.         $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
  1495.         return new AST\RangeVariableDeclaration($abstractSchemaName$aliasIdentificationVariable);
  1496.     }
  1497.     /**
  1498.      * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
  1499.      *
  1500.      * @return AST\JoinAssociationDeclaration
  1501.      */
  1502.     public function JoinAssociationDeclaration()
  1503.     {
  1504.         $joinAssociationPathExpression $this->JoinAssociationPathExpression();
  1505.         if ($this->lexer->isNextToken(TokenType::T_AS)) {
  1506.             $this->match(TokenType::T_AS);
  1507.         }
  1508.         assert($this->lexer->lookahead !== null);
  1509.         $aliasIdentificationVariable $this->AliasIdentificationVariable();
  1510.         $indexBy                     $this->lexer->isNextToken(TokenType::T_INDEX) ? $this->IndexBy() : null;
  1511.         $identificationVariable $joinAssociationPathExpression->identificationVariable;
  1512.         $field                  $joinAssociationPathExpression->associationField;
  1513.         $class       $this->getMetadataForDqlAlias($identificationVariable);
  1514.         $targetClass $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
  1515.         // Building queryComponent
  1516.         $joinQueryComponent = [
  1517.             'metadata'     => $targetClass,
  1518.             'parent'       => $joinAssociationPathExpression->identificationVariable,
  1519.             'relation'     => $class->getAssociationMapping($field),
  1520.             'map'          => null,
  1521.             'nestingLevel' => $this->nestingLevel,
  1522.             'token'        => $this->lexer->lookahead,
  1523.         ];
  1524.         $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
  1525.         return new AST\JoinAssociationDeclaration($joinAssociationPathExpression$aliasIdentificationVariable$indexBy);
  1526.     }
  1527.     /**
  1528.      * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
  1529.      * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
  1530.      *
  1531.      * @return AST\PartialObjectExpression
  1532.      */
  1533.     public function PartialObjectExpression()
  1534.     {
  1535.         $this->match(TokenType::T_PARTIAL);
  1536.         $partialFieldSet = [];
  1537.         $identificationVariable $this->IdentificationVariable();
  1538.         $this->match(TokenType::T_DOT);
  1539.         $this->match(TokenType::T_OPEN_CURLY_BRACE);
  1540.         $this->match(TokenType::T_IDENTIFIER);
  1541.         assert($this->lexer->token !== null);
  1542.         $field $this->lexer->token->value;
  1543.         // First field in partial expression might be embeddable property
  1544.         while ($this->lexer->isNextToken(TokenType::T_DOT)) {
  1545.             $this->match(TokenType::T_DOT);
  1546.             $this->match(TokenType::T_IDENTIFIER);
  1547.             $field .= '.' $this->lexer->token->value;
  1548.         }
  1549.         $partialFieldSet[] = $field;
  1550.         while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
  1551.             $this->match(TokenType::T_COMMA);
  1552.             $this->match(TokenType::T_IDENTIFIER);
  1553.             $field $this->lexer->token->value;
  1554.             while ($this->lexer->isNextToken(TokenType::T_DOT)) {
  1555.                 $this->match(TokenType::T_DOT);
  1556.                 $this->match(TokenType::T_IDENTIFIER);
  1557.                 $field .= '.' $this->lexer->token->value;
  1558.             }
  1559.             $partialFieldSet[] = $field;
  1560.         }
  1561.         $this->match(TokenType::T_CLOSE_CURLY_BRACE);
  1562.         $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable$partialFieldSet);
  1563.         // Defer PartialObjectExpression validation
  1564.         $this->deferredPartialObjectExpressions[] = [
  1565.             'expression'   => $partialObjectExpression,
  1566.             'nestingLevel' => $this->nestingLevel,
  1567.             'token'        => $this->lexer->token,
  1568.         ];
  1569.         return $partialObjectExpression;
  1570.     }
  1571.     /**
  1572.      * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
  1573.      *
  1574.      * @return AST\NewObjectExpression
  1575.      */
  1576.     public function NewObjectExpression()
  1577.     {
  1578.         $this->match(TokenType::T_NEW);
  1579.         $className $this->AbstractSchemaName(); // note that this is not yet validated
  1580.         $token     $this->lexer->token;
  1581.         $this->match(TokenType::T_OPEN_PARENTHESIS);
  1582.         $args[] = $this->NewObjectArg();
  1583.         while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
  1584.             $this->match(TokenType::T_COMMA);
  1585.             $args[] = $this->NewObjectArg();
  1586.         }
  1587.         $this->match(TokenType::T_CLOSE_PARENTHESIS);
  1588.         $expression = new AST\NewObjectExpression($className$args);
  1589.         // Defer NewObjectExpression validation
  1590.         $this->deferredNewObjectExpressions[] = [
  1591.             'token'        => $token,
  1592.             'expression'   => $expression,
  1593.             'nestingLevel' => $this->nestingLevel,
  1594.         ];
  1595.         return $expression;
  1596.     }
  1597.     /**
  1598.      * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
  1599.      *
  1600.      * @return mixed
  1601.      */
  1602.     public function NewObjectArg()
  1603.     {
  1604.         assert($this->lexer->lookahead !== null);
  1605.         $token $this->lexer->lookahead;
  1606.         $peek  $this->lexer->glimpse();
  1607.         assert($peek !== null);
  1608.         if ($token->type === TokenType::T_OPEN_PARENTHESIS && $peek->type === TokenType::T_SELECT) {
  1609.             $this->match(TokenType::T_OPEN_PARENTHESIS);
  1610.             $expression $this->Subselect();
  1611.             $this->match(TokenType::T_CLOSE_PARENTHESIS);
  1612.             return $expression;
  1613.         }
  1614.         return $this->ScalarExpression();
  1615.     }
  1616.     /**
  1617.      * IndexBy ::= "INDEX" "BY" SingleValuedPathExpression
  1618.      *
  1619.      * @return AST\IndexBy
  1620.      */
  1621.     public function IndexBy()
  1622.     {
  1623.         $this->match(TokenType::T_INDEX);
  1624.         $this->match(TokenType::T_BY);
  1625.         $pathExpr $this->SingleValuedPathExpression();
  1626.         // Add the INDEX BY info to the query component
  1627.         $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
  1628.         return new AST\IndexBy($pathExpr);
  1629.     }
  1630.     /**
  1631.      * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
  1632.      *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
  1633.      *                      InstanceOfExpression
  1634.      *
  1635.      * @return mixed One of the possible expressions or subexpressions.
  1636.      */
  1637.     public function ScalarExpression()
  1638.     {
  1639.         assert($this->lexer->token !== null);
  1640.         assert($this->lexer->lookahead !== null);
  1641.         $lookahead $this->lexer->lookahead->type;
  1642.         $peek      $this->lexer->glimpse();
  1643.         switch (true) {
  1644.             case $lookahead === TokenType::T_INTEGER:
  1645.             case $lookahead === TokenType::T_FLOAT:
  1646.             // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
  1647.             case $lookahead === TokenType::T_MINUS:
  1648.             case $lookahead === TokenType::T_PLUS:
  1649.                 return $this->SimpleArithmeticExpression();
  1650.             case $lookahead === TokenType::T_STRING:
  1651.                 return $this->StringPrimary();
  1652.             case $lookahead === TokenType::T_TRUE:
  1653.             case $lookahead === TokenType::T_FALSE:
  1654.                 $this->match($lookahead);
  1655.                 return new AST\Literal(AST\Literal::BOOLEAN$this->lexer->token->value);
  1656.             case $lookahead === TokenType::T_INPUT_PARAMETER:
  1657.                 switch (true) {
  1658.                     case $this->isMathOperator($peek):
  1659.                         // :param + u.value
  1660.                         return $this->SimpleArithmeticExpression();
  1661.                     default:
  1662.                         return $this->InputParameter();
  1663.                 }
  1664.             case $lookahead === TokenType::T_CASE:
  1665.             case $lookahead === TokenType::T_COALESCE:
  1666.             case $lookahead === TokenType::T_NULLIF:
  1667.                 // Since NULLIF and COALESCE can be identified as a function,
  1668.                 // we need to check these before checking for FunctionDeclaration
  1669.                 return $this->CaseExpression();
  1670.             case $lookahead === TokenType::T_OPEN_PARENTHESIS:
  1671.                 return $this->SimpleArithmeticExpression();
  1672.             // this check must be done before checking for a filed path expression
  1673.             case $this->isFunction():
  1674.                 $this->lexer->peek(); // "("
  1675.                 switch (true) {
  1676.                     case $this->isMathOperator($this->peekBeyondClosingParenthesis()):
  1677.                         // SUM(u.id) + COUNT(u.id)
  1678.                         return $this->SimpleArithmeticExpression();
  1679.                     default:
  1680.                         // IDENTITY(u)
  1681.                         return $this->FunctionDeclaration();
  1682.                 }
  1683.                 break;
  1684.             // it is no function, so it must be a field path
  1685.             case $lookahead === TokenType::T_IDENTIFIER:
  1686.                 $this->lexer->peek(); // lookahead => '.'
  1687.                 $this->lexer->peek(); // lookahead => token after '.'
  1688.                 $peek $this->lexer->peek(); // lookahead => token after the token after the '.'
  1689.                 $this->lexer->resetPeek();
  1690.                 if ($this->isMathOperator($peek)) {
  1691.                     return $this->SimpleArithmeticExpression();
  1692.                 }
  1693.                 return $this->StateFieldPathExpression();
  1694.             default:
  1695.                 $this->syntaxError();
  1696.         }
  1697.     }
  1698.     /**
  1699.      * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
  1700.      * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
  1701.      * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
  1702.      * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
  1703.      * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
  1704.      * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
  1705.      * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
  1706.      * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
  1707.      *
  1708.      * @return mixed One of the possible expressions or subexpressions.
  1709.      */
  1710.     public function CaseExpression()
  1711.     {
  1712.         assert($this->lexer->lookahead !== null);
  1713.         $lookahead $this->lexer->lookahead->type;
  1714.         switch ($lookahead) {
  1715.             case TokenType::T_NULLIF:
  1716.                 return $this->NullIfExpression();
  1717.             case TokenType::T_COALESCE:
  1718.                 return $this->CoalesceExpression();
  1719.             case TokenType::T_CASE:
  1720.                 $this->lexer->resetPeek();
  1721.                 $peek $this->lexer->peek();
  1722.                 assert($peek !== null);
  1723.                 if ($peek->type === TokenType::T_WHEN) {
  1724.                     return $this->GeneralCaseExpression();
  1725.                 }
  1726.                 return $this->SimpleCaseExpression();
  1727.             default:
  1728.                 // Do nothing
  1729.                 break;
  1730.         }
  1731.         $this->syntaxError();
  1732.     }
  1733.     /**
  1734.      * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
  1735.      *
  1736.      * @return AST\CoalesceExpression
  1737.      */
  1738.     public function CoalesceExpression()
  1739.     {
  1740.         $this->match(TokenType::T_COALESCE);
  1741.         $this->match(TokenType::T_OPEN_PARENTHESIS);
  1742.         // Process ScalarExpressions (1..N)
  1743.         $scalarExpressions   = [];
  1744.         $scalarExpressions[] = $this->ScalarExpression();
  1745.         while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
  1746.             $this->match(TokenType::T_COMMA);
  1747.             $scalarExpressions[] = $this->ScalarExpression();
  1748.         }
  1749.         $this->match(TokenType::T_CLOSE_PARENTHESIS);
  1750.         return new AST\CoalesceExpression($scalarExpressions);
  1751.     }
  1752.     /**
  1753.      * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
  1754.      *
  1755.      * @return AST\NullIfExpression
  1756.      */
  1757.     public function NullIfExpression()
  1758.     {
  1759.         $this->match(TokenType::T_NULLIF);
  1760.         $this->match(TokenType::T_OPEN_PARENTHESIS);
  1761.         $firstExpression $this->ScalarExpression();
  1762.         $this->match(TokenType::T_COMMA);
  1763.         $secondExpression $this->ScalarExpression();
  1764.         $this->match(TokenType::T_CLOSE_PARENTHESIS);
  1765.         return new AST\NullIfExpression($firstExpression$secondExpression);
  1766.     }
  1767.     /**
  1768.      * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
  1769.      *
  1770.      * @return AST\GeneralCaseExpression
  1771.      */
  1772.     public function GeneralCaseExpression()
  1773.     {
  1774.         $this->match(TokenType::T_CASE);
  1775.         // Process WhenClause (1..N)
  1776.         $whenClauses = [];
  1777.         do {
  1778.             $whenClauses[] = $this->WhenClause();
  1779.         } while ($this->lexer->isNextToken(TokenType::T_WHEN));
  1780.         $this->match(TokenType::T_ELSE);
  1781.         $scalarExpression $this->ScalarExpression();
  1782.         $this->match(TokenType::T_END);
  1783.         return new AST\GeneralCaseExpression($whenClauses$scalarExpression);
  1784.     }
  1785.     /**
  1786.      * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
  1787.      * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
  1788.      *
  1789.      * @return AST\SimpleCaseExpression
  1790.      */
  1791.     public function SimpleCaseExpression()
  1792.     {
  1793.         $this->match(TokenType::T_CASE);
  1794.         $caseOperand $this->StateFieldPathExpression();
  1795.         // Process SimpleWhenClause (1..N)
  1796.         $simpleWhenClauses = [];
  1797.         do {
  1798.             $simpleWhenClauses[] = $this->SimpleWhenClause();
  1799.         } while ($this->lexer->isNextToken(TokenType::T_WHEN));
  1800.         $this->match(TokenType::T_ELSE);
  1801.         $scalarExpression $this->ScalarExpression();
  1802.         $this->match(TokenType::T_END);
  1803.         return new AST\SimpleCaseExpression($caseOperand$simpleWhenClauses$scalarExpression);
  1804.     }
  1805.     /**
  1806.      * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
  1807.      *
  1808.      * @return AST\WhenClause
  1809.      */
  1810.     public function WhenClause()
  1811.     {
  1812.         $this->match(TokenType::T_WHEN);
  1813.         $conditionalExpression $this->ConditionalExpression();
  1814.         $this->match(TokenType::T_THEN);
  1815.         return new AST\WhenClause($conditionalExpression$this->ScalarExpression());
  1816.     }
  1817.     /**
  1818.      * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
  1819.      *
  1820.      * @return AST\SimpleWhenClause
  1821.      */
  1822.     public function SimpleWhenClause()
  1823.     {
  1824.         $this->match(TokenType::T_WHEN);
  1825.         $conditionalExpression $this->ScalarExpression();
  1826.         $this->match(TokenType::T_THEN);
  1827.         return new AST\SimpleWhenClause($conditionalExpression$this->ScalarExpression());
  1828.     }
  1829.     /**
  1830.      * SelectExpression ::= (
  1831.      *     IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
  1832.      *     PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
  1833.      * ) [["AS"] ["HIDDEN"] AliasResultVariable]
  1834.      *
  1835.      * @return AST\SelectExpression
  1836.      */
  1837.     public function SelectExpression()
  1838.     {
  1839.         assert($this->lexer->lookahead !== null);
  1840.         $expression    null;
  1841.         $identVariable null;
  1842.         $peek          $this->lexer->glimpse();
  1843.         $lookaheadType $this->lexer->lookahead->type;
  1844.         assert($peek !== null);
  1845.         switch (true) {
  1846.             // ScalarExpression (u.name)
  1847.             case $lookaheadType === TokenType::T_IDENTIFIER && $peek->type === TokenType::T_DOT:
  1848.                 $expression $this->ScalarExpression();
  1849.                 break;
  1850.             // IdentificationVariable (u)
  1851.             case $lookaheadType === TokenType::T_IDENTIFIER && $peek->type !== TokenType::T_OPEN_PARENTHESIS:
  1852.                 $expression $identVariable $this->IdentificationVariable();
  1853.                 break;
  1854.             // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
  1855.             case $lookaheadType === TokenType::T_CASE:
  1856.             case $lookaheadType === TokenType::T_COALESCE:
  1857.             case $lookaheadType === TokenType::T_NULLIF:
  1858.                 $expression $this->CaseExpression();
  1859.                 break;
  1860.             // DQL Function (SUM(u.value) or SUM(u.value) + 1)
  1861.             case $this->isFunction():
  1862.                 $this->lexer->peek(); // "("
  1863.                 switch (true) {
  1864.                     case $this->isMathOperator($this->peekBeyondClosingParenthesis()):
  1865.                         // SUM(u.id) + COUNT(u.id)
  1866.                         $expression $this->ScalarExpression();
  1867.                         break;
  1868.                     default:
  1869.                         // IDENTITY(u)
  1870.                         $expression $this->FunctionDeclaration();
  1871.                         break;
  1872.                 }
  1873.                 break;
  1874.             // PartialObjectExpression (PARTIAL u.{id, name})
  1875.             case $lookaheadType === TokenType::T_PARTIAL:
  1876.                 $expression    $this->PartialObjectExpression();
  1877.                 $identVariable $expression->identificationVariable;
  1878.                 break;
  1879.             // Subselect
  1880.             case $lookaheadType === TokenType::T_OPEN_PARENTHESIS && $peek->type === TokenType::T_SELECT:
  1881.                 $this->match(TokenType::T_OPEN_PARENTHESIS);
  1882.                 $expression $this->Subselect();
  1883.                 $this->match(TokenType::T_CLOSE_PARENTHESIS);
  1884.                 break;
  1885.             // Shortcut: ScalarExpression => SimpleArithmeticExpression
  1886.             case $lookaheadType === TokenType::T_OPEN_PARENTHESIS:
  1887.             case $lookaheadType === TokenType::T_INTEGER:
  1888.             case $lookaheadType === TokenType::T_STRING:
  1889.             case $lookaheadType === TokenType::T_FLOAT:
  1890.             // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
  1891.             case $lookaheadType === TokenType::T_MINUS:
  1892.             case $lookaheadType === TokenType::T_PLUS:
  1893.                 $expression $this->SimpleArithmeticExpression();
  1894.                 break;
  1895.             // NewObjectExpression (New ClassName(id, name))
  1896.             case $lookaheadType === TokenType::T_NEW:
  1897.                 $expression $this->NewObjectExpression();
  1898.                 break;
  1899.             default:
  1900.                 $this->syntaxError(
  1901.                     'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
  1902.                     $this->lexer->lookahead
  1903.                 );
  1904.         }
  1905.         // [["AS"] ["HIDDEN"] AliasResultVariable]
  1906.         $mustHaveAliasResultVariable false;
  1907.         if ($this->lexer->isNextToken(TokenType::T_AS)) {
  1908.             $this->match(TokenType::T_AS);
  1909.             $mustHaveAliasResultVariable true;
  1910.         }
  1911.         $hiddenAliasResultVariable false;
  1912.         if ($this->lexer->isNextToken(TokenType::T_HIDDEN)) {
  1913.             $this->match(TokenType::T_HIDDEN);
  1914.             $hiddenAliasResultVariable true;
  1915.         }
  1916.         $aliasResultVariable null;
  1917.         if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(TokenType::T_IDENTIFIER)) {
  1918.             assert($expression instanceof AST\Node || is_string($expression));
  1919.             $token               $this->lexer->lookahead;
  1920.             $aliasResultVariable $this->AliasResultVariable();
  1921.             // Include AliasResultVariable in query components.
  1922.             $this->queryComponents[$aliasResultVariable] = [
  1923.                 'resultVariable' => $expression,
  1924.                 'nestingLevel'   => $this->nestingLevel,
  1925.                 'token'          => $token,
  1926.             ];
  1927.         }
  1928.         // AST
  1929.         $expr = new AST\SelectExpression($expression$aliasResultVariable$hiddenAliasResultVariable);
  1930.         if ($identVariable) {
  1931.             $this->identVariableExpressions[$identVariable] = $expr;
  1932.         }
  1933.         return $expr;
  1934.     }
  1935.     /**
  1936.      * SimpleSelectExpression ::= (
  1937.      *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
  1938.      *      AggregateExpression | "(" Subselect ")" | ScalarExpression
  1939.      * ) [["AS"] AliasResultVariable]
  1940.      *
  1941.      * @return AST\SimpleSelectExpression
  1942.      */
  1943.     public function SimpleSelectExpression()
  1944.     {
  1945.         assert($this->lexer->lookahead !== null);
  1946.         $peek $this->lexer->glimpse();
  1947.         assert($peek !== null);
  1948.         switch ($this->lexer->lookahead->type) {
  1949.             case TokenType::T_IDENTIFIER:
  1950.                 switch (true) {
  1951.                     case $peek->type === TokenType::T_DOT:
  1952.                         $expression $this->StateFieldPathExpression();
  1953.                         return new AST\SimpleSelectExpression($expression);
  1954.                     case $peek->type !== TokenType::T_OPEN_PARENTHESIS:
  1955.                         $expression $this->IdentificationVariable();
  1956.                         return new AST\SimpleSelectExpression($expression);
  1957.                     case $this->isFunction():
  1958.                         // SUM(u.id) + COUNT(u.id)
  1959.                         if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) {
  1960.                             return new AST\SimpleSelectExpression($this->ScalarExpression());
  1961.                         }
  1962.                         // COUNT(u.id)
  1963.                         if ($this->isAggregateFunction($this->lexer->lookahead->type)) {
  1964.                             return new AST\SimpleSelectExpression($this->AggregateExpression());
  1965.                         }
  1966.                         // IDENTITY(u)
  1967.                         return new AST\SimpleSelectExpression($this->FunctionDeclaration());
  1968.                     default:
  1969.                         // Do nothing
  1970.                 }
  1971.                 break;
  1972.             case TokenType::T_OPEN_PARENTHESIS:
  1973.                 if ($peek->type !== TokenType::T_SELECT) {
  1974.                     // Shortcut: ScalarExpression => SimpleArithmeticExpression
  1975.                     $expression $this->SimpleArithmeticExpression();
  1976.                     return new AST\SimpleSelectExpression($expression);
  1977.                 }
  1978.                 // Subselect
  1979.                 $this->match(TokenType::T_OPEN_PARENTHESIS);
  1980.                 $expression $this->Subselect();
  1981.                 $this->match(TokenType::T_CLOSE_PARENTHESIS);
  1982.                 return new AST\SimpleSelectExpression($expression);
  1983.             default:
  1984.                 // Do nothing
  1985.         }
  1986.         $this->lexer->peek();
  1987.         $expression $this->ScalarExpression();
  1988.         $expr       = new AST\SimpleSelectExpression($expression);
  1989.         if ($this->lexer->isNextToken(TokenType::T_AS)) {
  1990.             $this->match(TokenType::T_AS);
  1991.         }
  1992.         if ($this->lexer->isNextToken(TokenType::T_IDENTIFIER)) {
  1993.             $token                             $this->lexer->lookahead;
  1994.             $resultVariable                    $this->AliasResultVariable();
  1995.             $expr->fieldIdentificationVariable $resultVariable;
  1996.             // Include AliasResultVariable in query components.
  1997.             $this->queryComponents[$resultVariable] = [
  1998.                 'resultvariable' => $expr,
  1999.                 'nestingLevel'   => $this->nestingLevel,
  2000.                 'token'          => $token,
  2001.             ];
  2002.         }
  2003.         return $expr;
  2004.     }
  2005.     /**
  2006.      * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
  2007.      *
  2008.      * @return AST\ConditionalExpression|AST\ConditionalFactor|AST\ConditionalPrimary|AST\ConditionalTerm
  2009.      */
  2010.     public function ConditionalExpression()
  2011.     {
  2012.         $conditionalTerms   = [];
  2013.         $conditionalTerms[] = $this->ConditionalTerm();
  2014.         while ($this->lexer->isNextToken(TokenType::T_OR)) {
  2015.             $this->match(TokenType::T_OR);
  2016.             $conditionalTerms[] = $this->ConditionalTerm();
  2017.         }
  2018.         // Phase 1 AST optimization: Prevent AST\ConditionalExpression
  2019.         // if only one AST\ConditionalTerm is defined
  2020.         if (count($conditionalTerms) === 1) {
  2021.             return $conditionalTerms[0];
  2022.         }
  2023.         return new AST\ConditionalExpression($conditionalTerms);
  2024.     }
  2025.     /**
  2026.      * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
  2027.      *
  2028.      * @return AST\ConditionalFactor|AST\ConditionalPrimary|AST\ConditionalTerm
  2029.      */
  2030.     public function ConditionalTerm()
  2031.     {
  2032.         $conditionalFactors   = [];
  2033.         $conditionalFactors[] = $this->ConditionalFactor();
  2034.         while ($this->lexer->isNextToken(TokenType::T_AND)) {
  2035.             $this->match(TokenType::T_AND);
  2036.             $conditionalFactors[] = $this->ConditionalFactor();
  2037.         }
  2038.         // Phase 1 AST optimization: Prevent AST\ConditionalTerm
  2039.         // if only one AST\ConditionalFactor is defined
  2040.         if (count($conditionalFactors) === 1) {
  2041.             return $conditionalFactors[0];
  2042.         }
  2043.         return new AST\ConditionalTerm($conditionalFactors);
  2044.     }
  2045.     /**
  2046.      * ConditionalFactor ::= ["NOT"] ConditionalPrimary
  2047.      *
  2048.      * @return AST\ConditionalFactor|AST\ConditionalPrimary
  2049.      */
  2050.     public function ConditionalFactor()
  2051.     {
  2052.         $not false;
  2053.         if ($this->lexer->isNextToken(TokenType::T_NOT)) {
  2054.             $this->match(TokenType::T_NOT);
  2055.             $not true;
  2056.         }
  2057.         $conditionalPrimary $this->ConditionalPrimary();
  2058.         // Phase 1 AST optimization: Prevent AST\ConditionalFactor
  2059.         // if only one AST\ConditionalPrimary is defined
  2060.         if (! $not) {
  2061.             return $conditionalPrimary;
  2062.         }
  2063.         return new AST\ConditionalFactor($conditionalPrimary$not);
  2064.     }
  2065.     /**
  2066.      * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
  2067.      *
  2068.      * @return AST\ConditionalPrimary
  2069.      */
  2070.     public function ConditionalPrimary()
  2071.     {
  2072.         $condPrimary = new AST\ConditionalPrimary();
  2073.         if (! $this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) {
  2074.             $condPrimary->simpleConditionalExpression $this->SimpleConditionalExpression();
  2075.             return $condPrimary;
  2076.         }
  2077.         // Peek beyond the matching closing parenthesis ')'
  2078.         $peek $this->peekBeyondClosingParenthesis();
  2079.         if (
  2080.             $peek !== null && (
  2081.             in_array($peek->value, ['=''<''<=''<>''>''>=''!='], true) ||
  2082.             in_array($peek->type, [TokenType::T_NOTTokenType::T_BETWEENTokenType::T_LIKETokenType::T_INTokenType::T_ISTokenType::T_EXISTS], true) ||
  2083.             $this->isMathOperator($peek)
  2084.             )
  2085.         ) {
  2086.             $condPrimary->simpleConditionalExpression $this->SimpleConditionalExpression();
  2087.             return $condPrimary;
  2088.         }
  2089.         $this->match(TokenType::T_OPEN_PARENTHESIS);
  2090.         $condPrimary->conditionalExpression $this->ConditionalExpression();
  2091.         $this->match(TokenType::T_CLOSE_PARENTHESIS);
  2092.         return $condPrimary;
  2093.     }
  2094.     /**
  2095.      * SimpleConditionalExpression ::=
  2096.      *      ComparisonExpression | BetweenExpression | LikeExpression |
  2097.      *      InExpression | NullComparisonExpression | ExistsExpression |
  2098.      *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
  2099.      *      InstanceOfExpression
  2100.      *
  2101.      * @return (AST\BetweenExpression|
  2102.      *         AST\CollectionMemberExpression|
  2103.      *         AST\ComparisonExpression|
  2104.      *         AST\EmptyCollectionComparisonExpression|
  2105.      *         AST\ExistsExpression|
  2106.      *         AST\InExpression|
  2107.      *         AST\InstanceOfExpression|
  2108.      *         AST\LikeExpression|
  2109.      *         AST\NullComparisonExpression)
  2110.      *
  2111.      * @phpstan-ignore return.deprecatedClass
  2112.      */
  2113.     public function SimpleConditionalExpression()
  2114.     {
  2115.         assert($this->lexer->lookahead !== null);
  2116.         if ($this->lexer->isNextToken(TokenType::T_EXISTS)) {
  2117.             return $this->ExistsExpression();
  2118.         }
  2119.         $token     $this->lexer->lookahead;
  2120.         $peek      $this->lexer->glimpse();
  2121.         $lookahead $token;
  2122.         if ($this->lexer->isNextToken(TokenType::T_NOT)) {
  2123.             $token $this->lexer->glimpse();
  2124.         }
  2125.         assert($token !== null);
  2126.         assert($peek !== null);
  2127.         if ($token->type === TokenType::T_IDENTIFIER || $token->type === TokenType::T_INPUT_PARAMETER || $this->isFunction()) {
  2128.             // Peek beyond the matching closing parenthesis.
  2129.             $beyond $this->lexer->peek();
  2130.             switch ($peek->value) {
  2131.                 case '(':
  2132.                     // Peeks beyond the matched closing parenthesis.
  2133.                     $token $this->peekBeyondClosingParenthesis(false);
  2134.                     assert($token !== null);
  2135.                     if ($token->type === TokenType::T_NOT) {
  2136.                         $token $this->lexer->peek();
  2137.                         assert($token !== null);
  2138.                     }
  2139.                     if ($token->type === TokenType::T_IS) {
  2140.                         $lookahead $this->lexer->peek();
  2141.                     }
  2142.                     break;
  2143.                 default:
  2144.                     // Peek beyond the PathExpression or InputParameter.
  2145.                     $token $beyond;
  2146.                     while ($token->value === '.') {
  2147.                         $this->lexer->peek();
  2148.                         $token $this->lexer->peek();
  2149.                         assert($token !== null);
  2150.                     }
  2151.                     // Also peek beyond a NOT if there is one.
  2152.                     assert($token !== null);
  2153.                     if ($token->type === TokenType::T_NOT) {
  2154.                         $token $this->lexer->peek();
  2155.                         assert($token !== null);
  2156.                     }
  2157.                     // We need to go even further in case of IS (differentiate between NULL and EMPTY)
  2158.                     $lookahead $this->lexer->peek();
  2159.             }
  2160.             assert($lookahead !== null);
  2161.             // Also peek beyond a NOT if there is one.
  2162.             if ($lookahead->type === TokenType::T_NOT) {
  2163.                 $lookahead $this->lexer->peek();
  2164.             }
  2165.             $this->lexer->resetPeek();
  2166.         }
  2167.         if ($token->type === TokenType::T_BETWEEN) {
  2168.             return $this->BetweenExpression();
  2169.         }
  2170.         if ($token->type === TokenType::T_LIKE) {
  2171.             return $this->LikeExpression();
  2172.         }
  2173.         if ($token->type === TokenType::T_IN) {
  2174.             return $this->InExpression();
  2175.         }
  2176.         if ($token->type === TokenType::T_INSTANCE) {
  2177.             return $this->InstanceOfExpression();
  2178.         }
  2179.         if ($token->type === TokenType::T_MEMBER) {
  2180.             return $this->CollectionMemberExpression();
  2181.         }
  2182.         assert($lookahead !== null);
  2183.         if ($token->type === TokenType::T_IS && $lookahead->type === TokenType::T_NULL) {
  2184.             return $this->NullComparisonExpression();
  2185.         }
  2186.         if ($token->type === TokenType::T_IS && $lookahead->type === TokenType::T_EMPTY) {
  2187.             return $this->EmptyCollectionComparisonExpression();
  2188.         }
  2189.         return $this->ComparisonExpression();
  2190.     }
  2191.     /**
  2192.      * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
  2193.      *
  2194.      * @return AST\EmptyCollectionComparisonExpression
  2195.      */
  2196.     public function EmptyCollectionComparisonExpression()
  2197.     {
  2198.         $pathExpression $this->CollectionValuedPathExpression();
  2199.         $this->match(TokenType::T_IS);
  2200.         $not false;
  2201.         if ($this->lexer->isNextToken(TokenType::T_NOT)) {
  2202.             $this->match(TokenType::T_NOT);
  2203.             $not true;
  2204.         }
  2205.         $this->match(TokenType::T_EMPTY);
  2206.         return new AST\EmptyCollectionComparisonExpression(
  2207.             $pathExpression,
  2208.             $not
  2209.         );
  2210.     }
  2211.     /**
  2212.      * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
  2213.      *
  2214.      * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
  2215.      * SimpleEntityExpression ::= IdentificationVariable | InputParameter
  2216.      *
  2217.      * @return AST\CollectionMemberExpression
  2218.      */
  2219.     public function CollectionMemberExpression()
  2220.     {
  2221.         $not        false;
  2222.         $entityExpr $this->EntityExpression();
  2223.         if ($this->lexer->isNextToken(TokenType::T_NOT)) {
  2224.             $this->match(TokenType::T_NOT);
  2225.             $not true;
  2226.         }
  2227.         $this->match(TokenType::T_MEMBER);
  2228.         if ($this->lexer->isNextToken(TokenType::T_OF)) {
  2229.             $this->match(TokenType::T_OF);
  2230.         }
  2231.         return new AST\CollectionMemberExpression(
  2232.             $entityExpr,
  2233.             $this->CollectionValuedPathExpression(),
  2234.             $not
  2235.         );
  2236.     }
  2237.     /**
  2238.      * Literal ::= string | char | integer | float | boolean
  2239.      *
  2240.      * @return AST\Literal
  2241.      */
  2242.     public function Literal()
  2243.     {
  2244.         assert($this->lexer->lookahead !== null);
  2245.         assert($this->lexer->token !== null);
  2246.         switch ($this->lexer->lookahead->type) {
  2247.             case TokenType::T_STRING:
  2248.                 $this->match(TokenType::T_STRING);
  2249.                 return new AST\Literal(AST\Literal::STRING$this->lexer->token->value);
  2250.             case TokenType::T_INTEGER:
  2251.             case TokenType::T_FLOAT:
  2252.                 $this->match(
  2253.                     $this->lexer->isNextToken(TokenType::T_INTEGER) ? TokenType::T_INTEGER TokenType::T_FLOAT
  2254.                 );
  2255.                 return new AST\Literal(AST\Literal::NUMERIC$this->lexer->token->value);
  2256.             case TokenType::T_TRUE:
  2257.             case TokenType::T_FALSE:
  2258.                 $this->match(
  2259.                     $this->lexer->isNextToken(TokenType::T_TRUE) ? TokenType::T_TRUE TokenType::T_FALSE
  2260.                 );
  2261.                 return new AST\Literal(AST\Literal::BOOLEAN$this->lexer->token->value);
  2262.             default:
  2263.                 $this->syntaxError('Literal');
  2264.         }
  2265.     }
  2266.     /**
  2267.      * InParameter ::= ArithmeticExpression | InputParameter
  2268.      *
  2269.      * @return AST\InputParameter|AST\ArithmeticExpression
  2270.      */
  2271.     public function InParameter()
  2272.     {
  2273.         assert($this->lexer->lookahead !== null);
  2274.         if ($this->lexer->lookahead->type === TokenType::T_INPUT_PARAMETER) {
  2275.             return $this->InputParameter();
  2276.         }
  2277.         return $this->ArithmeticExpression();
  2278.     }
  2279.     /**
  2280.      * InputParameter ::= PositionalParameter | NamedParameter
  2281.      *
  2282.      * @return AST\InputParameter
  2283.      */
  2284.     public function InputParameter()
  2285.     {
  2286.         $this->match(TokenType::T_INPUT_PARAMETER);
  2287.         assert($this->lexer->token !== null);
  2288.         return new AST\InputParameter($this->lexer->token->value);
  2289.     }
  2290.     /**
  2291.      * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
  2292.      *
  2293.      * @return AST\ArithmeticExpression
  2294.      */
  2295.     public function ArithmeticExpression()
  2296.     {
  2297.         $expr = new AST\ArithmeticExpression();
  2298.         if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) {
  2299.             $peek $this->lexer->glimpse();
  2300.             assert($peek !== null);
  2301.             if ($peek->type === TokenType::T_SELECT) {
  2302.                 $this->match(TokenType::T_OPEN_PARENTHESIS);
  2303.                 $expr->subselect $this->Subselect();
  2304.                 $this->match(TokenType::T_CLOSE_PARENTHESIS);
  2305.                 return $expr;
  2306.             }
  2307.         }
  2308.         $expr->simpleArithmeticExpression $this->SimpleArithmeticExpression();
  2309.         return $expr;
  2310.     }
  2311.     /**
  2312.      * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
  2313.      *
  2314.      * @return AST\SimpleArithmeticExpression|AST\ArithmeticTerm
  2315.      */
  2316.     public function SimpleArithmeticExpression()
  2317.     {
  2318.         $terms   = [];
  2319.         $terms[] = $this->ArithmeticTerm();
  2320.         while (($isPlus $this->lexer->isNextToken(TokenType::T_PLUS)) || $this->lexer->isNextToken(TokenType::T_MINUS)) {
  2321.             $this->match($isPlus TokenType::T_PLUS TokenType::T_MINUS);
  2322.             assert($this->lexer->token !== null);
  2323.             $terms[] = $this->lexer->token->value;
  2324.             $terms[] = $this->ArithmeticTerm();
  2325.         }
  2326.         // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
  2327.         // if only one AST\ArithmeticTerm is defined
  2328.         if (count($terms) === 1) {
  2329.             return $terms[0];
  2330.         }
  2331.         return new AST\SimpleArithmeticExpression($terms);
  2332.     }
  2333.     /**
  2334.      * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
  2335.      *
  2336.      * @return AST\ArithmeticTerm
  2337.      */
  2338.     public function ArithmeticTerm()
  2339.     {
  2340.         $factors   = [];
  2341.         $factors[] = $this->ArithmeticFactor();
  2342.         while (($isMult $this->lexer->isNextToken(TokenType::T_MULTIPLY)) || $this->lexer->isNextToken(TokenType::T_DIVIDE)) {
  2343.             $this->match($isMult TokenType::T_MULTIPLY TokenType::T_DIVIDE);
  2344.             assert($this->lexer->token !== null);
  2345.             $factors[] = $this->lexer->token->value;
  2346.             $factors[] = $this->ArithmeticFactor();
  2347.         }
  2348.         // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
  2349.         // if only one AST\ArithmeticFactor is defined
  2350.         if (count($factors) === 1) {
  2351.             return $factors[0];
  2352.         }
  2353.         return new AST\ArithmeticTerm($factors);
  2354.     }
  2355.     /**
  2356.      * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
  2357.      *
  2358.      * @return AST\ArithmeticFactor
  2359.      */
  2360.     public function ArithmeticFactor()
  2361.     {
  2362.         $sign null;
  2363.         $isPlus $this->lexer->isNextToken(TokenType::T_PLUS);
  2364.         if ($isPlus || $this->lexer->isNextToken(TokenType::T_MINUS)) {
  2365.             $this->match($isPlus TokenType::T_PLUS TokenType::T_MINUS);
  2366.             $sign $isPlus;
  2367.         }
  2368.         $primary $this->ArithmeticPrimary();
  2369.         // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
  2370.         // if only one AST\ArithmeticPrimary is defined
  2371.         if ($sign === null) {
  2372.             return $primary;
  2373.         }
  2374.         return new AST\ArithmeticFactor($primary$sign);
  2375.     }
  2376.     /**
  2377.      * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
  2378.      *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
  2379.      *          | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
  2380.      *          | InputParameter | CaseExpression
  2381.      *
  2382.      * @return AST\Node|string
  2383.      */
  2384.     public function ArithmeticPrimary()
  2385.     {
  2386.         if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) {
  2387.             $this->match(TokenType::T_OPEN_PARENTHESIS);
  2388.             $expr $this->SimpleArithmeticExpression();
  2389.             $this->match(TokenType::T_CLOSE_PARENTHESIS);
  2390.             return new AST\ParenthesisExpression($expr);
  2391.         }
  2392.         if ($this->lexer->lookahead === null) {
  2393.             $this->syntaxError('ArithmeticPrimary');
  2394.         }
  2395.         switch ($this->lexer->lookahead->type) {
  2396.             case TokenType::T_COALESCE:
  2397.             case TokenType::T_NULLIF:
  2398.             case TokenType::T_CASE:
  2399.                 return $this->CaseExpression();
  2400.             case TokenType::T_IDENTIFIER:
  2401.                 $peek $this->lexer->glimpse();
  2402.                 if ($peek !== null && $peek->value === '(') {
  2403.                     return $this->FunctionDeclaration();
  2404.                 }
  2405.                 if ($peek !== null && $peek->value === '.') {
  2406.                     return $this->SingleValuedPathExpression();
  2407.                 }
  2408.                 if (isset($this->queryComponents[$this->lexer->lookahead->value]['resultVariable'])) {
  2409.                     return $this->ResultVariable();
  2410.                 }
  2411.                 return $this->StateFieldPathExpression();
  2412.             case TokenType::T_INPUT_PARAMETER:
  2413.                 return $this->InputParameter();
  2414.             default:
  2415.                 $peek $this->lexer->glimpse();
  2416.                 if ($peek !== null && $peek->value === '(') {
  2417.                     return $this->FunctionDeclaration();
  2418.                 }
  2419.                 return $this->Literal();
  2420.         }
  2421.     }
  2422.     /**
  2423.      * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
  2424.      *
  2425.      * @return AST\Subselect|AST\Node|string
  2426.      */
  2427.     public function StringExpression()
  2428.     {
  2429.         $peek $this->lexer->glimpse();
  2430.         assert($peek !== null);
  2431.         // Subselect
  2432.         if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS) && $peek->type === TokenType::T_SELECT) {
  2433.             $this->match(TokenType::T_OPEN_PARENTHESIS);
  2434.             $expr $this->Subselect();
  2435.             $this->match(TokenType::T_CLOSE_PARENTHESIS);
  2436.             return $expr;
  2437.         }
  2438.         assert($this->lexer->lookahead !== null);
  2439.         // ResultVariable (string)
  2440.         if (
  2441.             $this->lexer->isNextToken(TokenType::T_IDENTIFIER) &&
  2442.             isset($this->queryComponents[$this->lexer->lookahead->value]['resultVariable'])
  2443.         ) {
  2444.             return $this->ResultVariable();
  2445.         }
  2446.         return $this->StringPrimary();
  2447.     }
  2448.     /**
  2449.      * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
  2450.      *
  2451.      * @return AST\Node
  2452.      */
  2453.     public function StringPrimary()
  2454.     {
  2455.         assert($this->lexer->lookahead !== null);
  2456.         $lookaheadType $this->lexer->lookahead->type;
  2457.         switch ($lookaheadType) {
  2458.             case TokenType::T_IDENTIFIER:
  2459.                 $peek $this->lexer->glimpse();
  2460.                 assert($peek !== null);
  2461.                 if ($peek->value === '.') {
  2462.                     return $this->StateFieldPathExpression();
  2463.                 }
  2464.                 if ($peek->value === '(') {
  2465.                     // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
  2466.                     return $this->FunctionDeclaration();
  2467.                 }
  2468.                 $this->syntaxError("'.' or '('");
  2469.                 break;
  2470.             case TokenType::T_STRING:
  2471.                 $this->match(TokenType::T_STRING);
  2472.                 assert($this->lexer->token !== null);
  2473.                 return new AST\Literal(AST\Literal::STRING$this->lexer->token->value);
  2474.             case TokenType::T_INPUT_PARAMETER:
  2475.                 return $this->InputParameter();
  2476.             case TokenType::T_CASE:
  2477.             case TokenType::T_COALESCE:
  2478.             case TokenType::T_NULLIF:
  2479.                 return $this->CaseExpression();
  2480.             default:
  2481.                 assert($lookaheadType !== null);
  2482.                 if ($this->isAggregateFunction($lookaheadType)) {
  2483.                     return $this->AggregateExpression();
  2484.                 }
  2485.         }
  2486.         $this->syntaxError(
  2487.             'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
  2488.         );
  2489.     }
  2490.     /**
  2491.      * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
  2492.      *
  2493.      * @return AST\InputParameter|AST\PathExpression
  2494.      */
  2495.     public function EntityExpression()
  2496.     {
  2497.         $glimpse $this->lexer->glimpse();
  2498.         assert($glimpse !== null);
  2499.         if ($this->lexer->isNextToken(TokenType::T_IDENTIFIER) && $glimpse->value === '.') {
  2500.             return $this->SingleValuedAssociationPathExpression();
  2501.         }
  2502.         return $this->SimpleEntityExpression();
  2503.     }
  2504.     /**
  2505.      * SimpleEntityExpression ::= IdentificationVariable | InputParameter
  2506.      *
  2507.      * @return AST\InputParameter|AST\PathExpression
  2508.      */
  2509.     public function SimpleEntityExpression()
  2510.     {
  2511.         if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
  2512.             return $this->InputParameter();
  2513.         }
  2514.         return $this->StateFieldPathExpression();
  2515.     }
  2516.     /**
  2517.      * AggregateExpression ::=
  2518.      *  ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
  2519.      *
  2520.      * @return AST\AggregateExpression
  2521.      */
  2522.     public function AggregateExpression()
  2523.     {
  2524.         assert($this->lexer->lookahead !== null);
  2525.         $lookaheadType $this->lexer->lookahead->type;
  2526.         $isDistinct    false;
  2527.         if (! in_array($lookaheadType, [TokenType::T_COUNTTokenType::T_AVGTokenType::T_MAXTokenType::T_MINTokenType::T_SUM], true)) {
  2528.             $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
  2529.         }
  2530.         $this->match($lookaheadType);
  2531.         assert($this->lexer->token !== null);
  2532.         $functionName $this->lexer->token->value;
  2533.         $this->match(TokenType::T_OPEN_PARENTHESIS);
  2534.         if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) {
  2535.             $this->match(TokenType::T_DISTINCT);
  2536.             $isDistinct true;
  2537.         }
  2538.         $pathExp $this->SimpleArithmeticExpression();
  2539.         $this->match(TokenType::T_CLOSE_PARENTHESIS);
  2540.         return new AST\AggregateExpression($functionName$pathExp$isDistinct);
  2541.     }
  2542.     /**
  2543.      * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
  2544.      *
  2545.      * @return AST\QuantifiedExpression
  2546.      */
  2547.     public function QuantifiedExpression()
  2548.     {
  2549.         assert($this->lexer->lookahead !== null);
  2550.         $lookaheadType $this->lexer->lookahead->type;
  2551.         $value         $this->lexer->lookahead->value;
  2552.         if (! in_array($lookaheadType, [TokenType::T_ALLTokenType::T_ANYTokenType::T_SOME], true)) {
  2553.             $this->syntaxError('ALL, ANY or SOME');
  2554.         }
  2555.         $this->match($lookaheadType);
  2556.         $this->match(TokenType::T_OPEN_PARENTHESIS);
  2557.         $qExpr       = new AST\QuantifiedExpression($this->Subselect());
  2558.         $qExpr->type $value;
  2559.         $this->match(TokenType::T_CLOSE_PARENTHESIS);
  2560.         return $qExpr;
  2561.     }
  2562.     /**
  2563.      * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
  2564.      *
  2565.      * @return AST\BetweenExpression
  2566.      */
  2567.     public function BetweenExpression()
  2568.     {
  2569.         $not        false;
  2570.         $arithExpr1 $this->ArithmeticExpression();
  2571.         if ($this->lexer->isNextToken(TokenType::T_NOT)) {
  2572.             $this->match(TokenType::T_NOT);
  2573.             $not true;
  2574.         }
  2575.         $this->match(TokenType::T_BETWEEN);
  2576.         $arithExpr2 $this->ArithmeticExpression();
  2577.         $this->match(TokenType::T_AND);
  2578.         $arithExpr3 $this->ArithmeticExpression();
  2579.         return new AST\BetweenExpression($arithExpr1$arithExpr2$arithExpr3$not);
  2580.     }
  2581.     /**
  2582.      * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
  2583.      *
  2584.      * @return AST\ComparisonExpression
  2585.      */
  2586.     public function ComparisonExpression()
  2587.     {
  2588.         $this->lexer->glimpse();
  2589.         $leftExpr  $this->ArithmeticExpression();
  2590.         $operator  $this->ComparisonOperator();
  2591.         $rightExpr $this->isNextAllAnySome()
  2592.             ? $this->QuantifiedExpression()
  2593.             : $this->ArithmeticExpression();
  2594.         return new AST\ComparisonExpression($leftExpr$operator$rightExpr);
  2595.     }
  2596.     /**
  2597.      * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
  2598.      *
  2599.      * @return AST\InListExpression|AST\InSubselectExpression
  2600.      */
  2601.     public function InExpression()
  2602.     {
  2603.         $expression $this->ArithmeticExpression();
  2604.         $not false;
  2605.         if ($this->lexer->isNextToken(TokenType::T_NOT)) {
  2606.             $this->match(TokenType::T_NOT);
  2607.             $not true;
  2608.         }
  2609.         $this->match(TokenType::T_IN);
  2610.         $this->match(TokenType::T_OPEN_PARENTHESIS);
  2611.         if ($this->lexer->isNextToken(TokenType::T_SELECT)) {
  2612.             $inExpression = new AST\InSubselectExpression(
  2613.                 $expression,
  2614.                 $this->Subselect(),
  2615.                 $not
  2616.             );
  2617.         } else {
  2618.             $literals = [$this->InParameter()];
  2619.             while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
  2620.                 $this->match(TokenType::T_COMMA);
  2621.                 $literals[] = $this->InParameter();
  2622.             }
  2623.             $inExpression = new AST\InListExpression(
  2624.                 $expression,
  2625.                 $literals,
  2626.                 $not
  2627.             );
  2628.         }
  2629.         $this->match(TokenType::T_CLOSE_PARENTHESIS);
  2630.         return $inExpression;
  2631.     }
  2632.     /**
  2633.      * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
  2634.      *
  2635.      * @return AST\InstanceOfExpression
  2636.      */
  2637.     public function InstanceOfExpression()
  2638.     {
  2639.         $identificationVariable $this->IdentificationVariable();
  2640.         $not false;
  2641.         if ($this->lexer->isNextToken(TokenType::T_NOT)) {
  2642.             $this->match(TokenType::T_NOT);
  2643.             $not true;
  2644.         }
  2645.         $this->match(TokenType::T_INSTANCE);
  2646.         $this->match(TokenType::T_OF);
  2647.         $exprValues $this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)
  2648.             ? $this->InstanceOfParameterList()
  2649.             : [$this->InstanceOfParameter()];
  2650.         return new AST\InstanceOfExpression(
  2651.             $identificationVariable,
  2652.             $exprValues,
  2653.             $not
  2654.         );
  2655.     }
  2656.     /** @return non-empty-list<AST\InputParameter|string> */
  2657.     public function InstanceOfParameterList(): array
  2658.     {
  2659.         $this->match(TokenType::T_OPEN_PARENTHESIS);
  2660.         $exprValues = [$this->InstanceOfParameter()];
  2661.         while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
  2662.             $this->match(TokenType::T_COMMA);
  2663.             $exprValues[] = $this->InstanceOfParameter();
  2664.         }
  2665.         $this->match(TokenType::T_CLOSE_PARENTHESIS);
  2666.         return $exprValues;
  2667.     }
  2668.     /**
  2669.      * InstanceOfParameter ::= AbstractSchemaName | InputParameter
  2670.      *
  2671.      * @return AST\InputParameter|string
  2672.      */
  2673.     public function InstanceOfParameter()
  2674.     {
  2675.         if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
  2676.             $this->match(TokenType::T_INPUT_PARAMETER);
  2677.             assert($this->lexer->token !== null);
  2678.             return new AST\InputParameter($this->lexer->token->value);
  2679.         }
  2680.         $abstractSchemaName $this->AbstractSchemaName();
  2681.         $this->validateAbstractSchemaName($abstractSchemaName);
  2682.         return $abstractSchemaName;
  2683.     }
  2684.     /**
  2685.      * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
  2686.      *
  2687.      * @return AST\LikeExpression
  2688.      */
  2689.     public function LikeExpression()
  2690.     {
  2691.         $stringExpr $this->StringExpression();
  2692.         $not        false;
  2693.         if ($this->lexer->isNextToken(TokenType::T_NOT)) {
  2694.             $this->match(TokenType::T_NOT);
  2695.             $not true;
  2696.         }
  2697.         $this->match(TokenType::T_LIKE);
  2698.         if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
  2699.             $this->match(TokenType::T_INPUT_PARAMETER);
  2700.             assert($this->lexer->token !== null);
  2701.             $stringPattern = new AST\InputParameter($this->lexer->token->value);
  2702.         } else {
  2703.             $stringPattern $this->StringPrimary();
  2704.         }
  2705.         $escapeChar null;
  2706.         if ($this->lexer->lookahead !== null && $this->lexer->lookahead->type === TokenType::T_ESCAPE) {
  2707.             $this->match(TokenType::T_ESCAPE);
  2708.             $this->match(TokenType::T_STRING);
  2709.             assert($this->lexer->token !== null);
  2710.             $escapeChar = new AST\Literal(AST\Literal::STRING$this->lexer->token->value);
  2711.         }
  2712.         return new AST\LikeExpression($stringExpr$stringPattern$escapeChar$not);
  2713.     }
  2714.     /**
  2715.      * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
  2716.      *
  2717.      * @return AST\NullComparisonExpression
  2718.      */
  2719.     public function NullComparisonExpression()
  2720.     {
  2721.         switch (true) {
  2722.             case $this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER):
  2723.                 $this->match(TokenType::T_INPUT_PARAMETER);
  2724.                 assert($this->lexer->token !== null);
  2725.                 $expr = new AST\InputParameter($this->lexer->token->value);
  2726.                 break;
  2727.             case $this->lexer->isNextToken(TokenType::T_NULLIF):
  2728.                 $expr $this->NullIfExpression();
  2729.                 break;
  2730.             case $this->lexer->isNextToken(TokenType::T_COALESCE):
  2731.                 $expr $this->CoalesceExpression();
  2732.                 break;
  2733.             case $this->isFunction():
  2734.                 $expr $this->FunctionDeclaration();
  2735.                 break;
  2736.             default:
  2737.                 // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
  2738.                 $glimpse $this->lexer->glimpse();
  2739.                 assert($glimpse !== null);
  2740.                 if ($glimpse->type === TokenType::T_DOT) {
  2741.                     $expr $this->SingleValuedPathExpression();
  2742.                     // Leave switch statement
  2743.                     break;
  2744.                 }
  2745.                 assert($this->lexer->lookahead !== null);
  2746.                 $lookaheadValue $this->lexer->lookahead->value;
  2747.                 // Validate existing component
  2748.                 if (! isset($this->queryComponents[$lookaheadValue])) {
  2749.                     $this->semanticalError('Cannot add having condition on undefined result variable.');
  2750.                 }
  2751.                 // Validate SingleValuedPathExpression (ie.: "product")
  2752.                 if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
  2753.                     $expr $this->SingleValuedPathExpression();
  2754.                     break;
  2755.                 }
  2756.                 // Validating ResultVariable
  2757.                 if (! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
  2758.                     $this->semanticalError('Cannot add having condition on a non result variable.');
  2759.                 }
  2760.                 $expr $this->ResultVariable();
  2761.                 break;
  2762.         }
  2763.         $this->match(TokenType::T_IS);
  2764.         $not false;
  2765.         if ($this->lexer->isNextToken(TokenType::T_NOT)) {
  2766.             $this->match(TokenType::T_NOT);
  2767.             $not true;
  2768.         }
  2769.         $this->match(TokenType::T_NULL);
  2770.         return new AST\NullComparisonExpression($expr$not);
  2771.     }
  2772.     /**
  2773.      * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
  2774.      *
  2775.      * @return AST\ExistsExpression
  2776.      */
  2777.     public function ExistsExpression()
  2778.     {
  2779.         $not false;
  2780.         if ($this->lexer->isNextToken(TokenType::T_NOT)) {
  2781.             $this->match(TokenType::T_NOT);
  2782.             $not true;
  2783.         }
  2784.         $this->match(TokenType::T_EXISTS);
  2785.         $this->match(TokenType::T_OPEN_PARENTHESIS);
  2786.         $subselect $this->Subselect();
  2787.         $this->match(TokenType::T_CLOSE_PARENTHESIS);
  2788.         return new AST\ExistsExpression($subselect$not);
  2789.     }
  2790.     /**
  2791.      * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
  2792.      *
  2793.      * @return string
  2794.      */
  2795.     public function ComparisonOperator()
  2796.     {
  2797.         assert($this->lexer->lookahead !== null);
  2798.         switch ($this->lexer->lookahead->value) {
  2799.             case '=':
  2800.                 $this->match(TokenType::T_EQUALS);
  2801.                 return '=';
  2802.             case '<':
  2803.                 $this->match(TokenType::T_LOWER_THAN);
  2804.                 $operator '<';
  2805.                 if ($this->lexer->isNextToken(TokenType::T_EQUALS)) {
  2806.                     $this->match(TokenType::T_EQUALS);
  2807.                     $operator .= '=';
  2808.                 } elseif ($this->lexer->isNextToken(TokenType::T_GREATER_THAN)) {
  2809.                     $this->match(TokenType::T_GREATER_THAN);
  2810.                     $operator .= '>';
  2811.                 }
  2812.                 return $operator;
  2813.             case '>':
  2814.                 $this->match(TokenType::T_GREATER_THAN);
  2815.                 $operator '>';
  2816.                 if ($this->lexer->isNextToken(TokenType::T_EQUALS)) {
  2817.                     $this->match(TokenType::T_EQUALS);
  2818.                     $operator .= '=';
  2819.                 }
  2820.                 return $operator;
  2821.             case '!':
  2822.                 $this->match(TokenType::T_NEGATE);
  2823.                 $this->match(TokenType::T_EQUALS);
  2824.                 return '<>';
  2825.             default:
  2826.                 $this->syntaxError('=, <, <=, <>, >, >=, !=');
  2827.         }
  2828.     }
  2829.     /**
  2830.      * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
  2831.      *
  2832.      * @return Functions\FunctionNode
  2833.      */
  2834.     public function FunctionDeclaration()
  2835.     {
  2836.         assert($this->lexer->lookahead !== null);
  2837.         $token    $this->lexer->lookahead;
  2838.         $funcName strtolower($token->value);
  2839.         $customFunctionDeclaration $this->CustomFunctionDeclaration();
  2840.         // Check for custom functions functions first!
  2841.         switch (true) {
  2842.             case $customFunctionDeclaration !== null:
  2843.                 return $customFunctionDeclaration;
  2844.             case isset(self::$stringFunctions[$funcName]):
  2845.                 return $this->FunctionsReturningStrings();
  2846.             case isset(self::$numericFunctions[$funcName]):
  2847.                 return $this->FunctionsReturningNumerics();
  2848.             case isset(self::$datetimeFunctions[$funcName]):
  2849.                 return $this->FunctionsReturningDatetime();
  2850.             default:
  2851.                 $this->syntaxError('known function'$token);
  2852.         }
  2853.     }
  2854.     /**
  2855.      * Helper function for FunctionDeclaration grammar rule.
  2856.      */
  2857.     private function CustomFunctionDeclaration(): ?Functions\FunctionNode
  2858.     {
  2859.         assert($this->lexer->lookahead !== null);
  2860.         $token    $this->lexer->lookahead;
  2861.         $funcName strtolower($token->value);
  2862.         // Check for custom functions afterwards
  2863.         $config $this->em->getConfiguration();
  2864.         switch (true) {
  2865.             case $config->getCustomStringFunction($funcName) !== null:
  2866.                 return $this->CustomFunctionsReturningStrings();
  2867.             case $config->getCustomNumericFunction($funcName) !== null:
  2868.                 return $this->CustomFunctionsReturningNumerics();
  2869.             case $config->getCustomDatetimeFunction($funcName) !== null:
  2870.                 return $this->CustomFunctionsReturningDatetime();
  2871.             default:
  2872.                 return null;
  2873.         }
  2874.     }
  2875.     /**
  2876.      * FunctionsReturningNumerics ::=
  2877.      *      "LENGTH" "(" StringPrimary ")" |
  2878.      *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
  2879.      *      "ABS" "(" SimpleArithmeticExpression ")" |
  2880.      *      "SQRT" "(" SimpleArithmeticExpression ")" |
  2881.      *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
  2882.      *      "SIZE" "(" CollectionValuedPathExpression ")" |
  2883.      *      "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
  2884.      *      "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
  2885.      *      "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
  2886.      *
  2887.      * @return Functions\FunctionNode
  2888.      */
  2889.     public function FunctionsReturningNumerics()
  2890.     {
  2891.         assert($this->lexer->lookahead !== null);
  2892.         $funcNameLower strtolower($this->lexer->lookahead->value);
  2893.         $funcClass     self::$numericFunctions[$funcNameLower];
  2894.         $function = new $funcClass($funcNameLower);
  2895.         $function->parse($this);
  2896.         return $function;
  2897.     }
  2898.     /** @return Functions\FunctionNode */
  2899.     public function CustomFunctionsReturningNumerics()
  2900.     {
  2901.         assert($this->lexer->lookahead !== null);
  2902.         // getCustomNumericFunction is case-insensitive
  2903.         $functionName  strtolower($this->lexer->lookahead->value);
  2904.         $functionClass $this->em->getConfiguration()->getCustomNumericFunction($functionName);
  2905.         assert($functionClass !== null);
  2906.         $function is_string($functionClass)
  2907.             ? new $functionClass($functionName)
  2908.             : $functionClass($functionName);
  2909.         $function->parse($this);
  2910.         return $function;
  2911.     }
  2912.     /**
  2913.      * FunctionsReturningDateTime ::=
  2914.      *     "CURRENT_DATE" |
  2915.      *     "CURRENT_TIME" |
  2916.      *     "CURRENT_TIMESTAMP" |
  2917.      *     "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
  2918.      *     "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
  2919.      *
  2920.      * @return Functions\FunctionNode
  2921.      */
  2922.     public function FunctionsReturningDatetime()
  2923.     {
  2924.         assert($this->lexer->lookahead !== null);
  2925.         $funcNameLower strtolower($this->lexer->lookahead->value);
  2926.         $funcClass     self::$datetimeFunctions[$funcNameLower];
  2927.         $function = new $funcClass($funcNameLower);
  2928.         $function->parse($this);
  2929.         return $function;
  2930.     }
  2931.     /** @return Functions\FunctionNode */
  2932.     public function CustomFunctionsReturningDatetime()
  2933.     {
  2934.         assert($this->lexer->lookahead !== null);
  2935.         // getCustomDatetimeFunction is case-insensitive
  2936.         $functionName  $this->lexer->lookahead->value;
  2937.         $functionClass $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
  2938.         assert($functionClass !== null);
  2939.         $function is_string($functionClass)
  2940.             ? new $functionClass($functionName)
  2941.             : $functionClass($functionName);
  2942.         $function->parse($this);
  2943.         return $function;
  2944.     }
  2945.     /**
  2946.      * FunctionsReturningStrings ::=
  2947.      *   "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" |
  2948.      *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
  2949.      *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
  2950.      *   "LOWER" "(" StringPrimary ")" |
  2951.      *   "UPPER" "(" StringPrimary ")" |
  2952.      *   "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
  2953.      *
  2954.      * @return Functions\FunctionNode
  2955.      */
  2956.     public function FunctionsReturningStrings()
  2957.     {
  2958.         assert($this->lexer->lookahead !== null);
  2959.         $funcNameLower strtolower($this->lexer->lookahead->value);
  2960.         $funcClass     self::$stringFunctions[$funcNameLower];
  2961.         $function = new $funcClass($funcNameLower);
  2962.         $function->parse($this);
  2963.         return $function;
  2964.     }
  2965.     /** @return Functions\FunctionNode */
  2966.     public function CustomFunctionsReturningStrings()
  2967.     {
  2968.         assert($this->lexer->lookahead !== null);
  2969.         // getCustomStringFunction is case-insensitive
  2970.         $functionName  $this->lexer->lookahead->value;
  2971.         $functionClass $this->em->getConfiguration()->getCustomStringFunction($functionName);
  2972.         assert($functionClass !== null);
  2973.         $function is_string($functionClass)
  2974.             ? new $functionClass($functionName)
  2975.             : $functionClass($functionName);
  2976.         $function->parse($this);
  2977.         return $function;
  2978.     }
  2979.     private function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata
  2980.     {
  2981.         if (! isset($this->queryComponents[$dqlAlias]['metadata'])) {
  2982.             throw new LogicException(sprintf('No metadata for DQL alias: %s'$dqlAlias));
  2983.         }
  2984.         return $this->queryComponents[$dqlAlias]['metadata'];
  2985.     }
  2986. }