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

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