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

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. * @phpstan-import-type AssociationMapping from ClassMetadata
  35. * @phpstan-type DqlToken = Token<TokenType::T_*, string>
  36. * @phpstan-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. /** @phpstan-var list<array{token: DqlToken|null, expression: mixed, nestingLevel: int}> */
  97. private $deferredIdentificationVariables = [];
  98. /** @phpstan-var list<array{token: DqlToken|null, expression: AST\PartialObjectExpression, nestingLevel: int}> */
  99. private $deferredPartialObjectExpressions = [];
  100. /** @phpstan-var list<array{token: DqlToken|null, expression: AST\PathExpression, nestingLevel: int}> */
  101. private $deferredPathExpressions = [];
  102. /** @phpstan-var list<array{token: DqlToken|null, expression: mixed, nestingLevel: int}> */
  103. private $deferredResultVariables = [];
  104. /** @phpstan-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. * @phpstan-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. /** @phpstan-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->selectExpressions, true);
  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. * @phpstan-param DqlToken|null $token
  393. *
  394. * @return void
  395. * @phpstan-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($message, QueryException::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. * @phpstan-param DqlToken|null $token
  416. *
  417. * @return void
  418. * @phpstan-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', 42, 0);
  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 > 0 ? $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($message, QueryException::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. * @phpstan-return DqlToken|null
  448. */
  449. private function peekBeyondClosingParenthesis(bool $resetPeek = true)
  450. {
  451. $token = $this->lexer->peek();
  452. $numUnmatched = 1;
  453. while ($numUnmatched > 0 && $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. * @phpstan-param DqlToken|null $token
  475. */
  476. private function isMathOperator($token): bool
  477. {
  478. return $token !== null && in_array($token->type, [TokenType::T_PLUS, TokenType::T_MINUS, TokenType::T_DIVIDE, TokenType::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. * @phpstan-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_AVG, TokenType::T_MIN, TokenType::T_MAX, TokenType::T_SUM, TokenType::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_ALL, TokenType::T_ANY, TokenType::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($fromClassName, 0, strrpos($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($schemaName, true) || interface_exists($schemaName, true))) {
  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. * @phpstan-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_NOT, TokenType::T_BETWEEN, TokenType::T_LIKE, TokenType::T_IN, TokenType::T_IS, TokenType::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_COUNT, TokenType::T_AVG, TokenType::T_MAX, TokenType::T_MIN, TokenType::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_ALL, TokenType::T_ANY, TokenType::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. }