vendor/doctrine/orm/lib/Doctrine/ORM/Query/Parser.php line 3514

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