summaryrefslogtreecommitdiff
path: root/vendor/doctrine/orm/src/Query
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2024-08-13 23:45:21 +0200
committerpolo <ordipolo@gmx.fr>2024-08-13 23:45:21 +0200
commitbf6655a534a6775d30cafa67bd801276bda1d98d (patch)
treec6381e3f6c81c33eab72508f410b165ba05f7e9c /vendor/doctrine/orm/src/Query
parent94d67a4b51f8e62e7d518cce26a526ae1ec48278 (diff)
downloadAppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.zip
VERSION 0.2 doctrine ORM et entités
Diffstat (limited to 'vendor/doctrine/orm/src/Query')
-rw-r--r--vendor/doctrine/orm/src/Query/AST/ASTException.php20
-rw-r--r--vendor/doctrine/orm/src/Query/AST/AggregateExpression.php23
-rw-r--r--vendor/doctrine/orm/src/Query/AST/ArithmeticExpression.php34
-rw-r--r--vendor/doctrine/orm/src/Query/AST/ArithmeticFactor.php36
-rw-r--r--vendor/doctrine/orm/src/Query/AST/ArithmeticTerm.php25
-rw-r--r--vendor/doctrine/orm/src/Query/AST/BetweenExpression.php23
-rw-r--r--vendor/doctrine/orm/src/Query/AST/CoalesceExpression.php25
-rw-r--r--vendor/doctrine/orm/src/Query/AST/CollectionMemberExpression.php27
-rw-r--r--vendor/doctrine/orm/src/Query/AST/ComparisonExpression.php32
-rw-r--r--vendor/doctrine/orm/src/Query/AST/ConditionalExpression.php25
-rw-r--r--vendor/doctrine/orm/src/Query/AST/ConditionalFactor.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/ConditionalPrimary.php34
-rw-r--r--vendor/doctrine/orm/src/Query/AST/ConditionalTerm.php25
-rw-r--r--vendor/doctrine/orm/src/Query/AST/DeleteClause.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/DeleteStatement.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/EmptyCollectionComparisonExpression.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/ExistsExpression.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/FromClause.php25
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/AbsFunction.php37
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/AvgFunction.php27
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/BitAndFunction.php43
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/BitOrFunction.php43
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/ConcatFunction.php58
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/CountFunction.php35
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/CurrentDateFunction.php29
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimeFunction.php29
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimestampFunction.php29
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/DateAddFunction.php83
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/DateDiffFunction.php41
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/DateSubFunction.php62
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/FunctionNode.php32
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/IdentityFunction.php90
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/LengthFunction.php45
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/LocateFunction.php62
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/LowerFunction.php40
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/MaxFunction.php27
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/MinFunction.php27
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/ModFunction.php43
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/SizeFunction.php113
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/SqrtFunction.php40
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/SubstringFunction.php58
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/SumFunction.php27
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/TrimFunction.php119
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Functions/UpperFunction.php40
-rw-r--r--vendor/doctrine/orm/src/Query/AST/GeneralCaseExpression.php27
-rw-r--r--vendor/doctrine/orm/src/Query/AST/GroupByClause.php20
-rw-r--r--vendor/doctrine/orm/src/Query/AST/HavingClause.php19
-rw-r--r--vendor/doctrine/orm/src/Query/AST/IdentificationVariableDeclaration.php28
-rw-r--r--vendor/doctrine/orm/src/Query/AST/InListExpression.php23
-rw-r--r--vendor/doctrine/orm/src/Query/AST/InSubselectExpression.php22
-rw-r--r--vendor/doctrine/orm/src/Query/AST/IndexBy.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/InputParameter.php35
-rw-r--r--vendor/doctrine/orm/src/Query/AST/InstanceOfExpression.php29
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Join.php34
-rw-r--r--vendor/doctrine/orm/src/Query/AST/JoinAssociationDeclaration.php27
-rw-r--r--vendor/doctrine/orm/src/Query/AST/JoinAssociationPathExpression.php19
-rw-r--r--vendor/doctrine/orm/src/Query/AST/JoinClassPathExpression.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/JoinVariableDeclaration.php24
-rw-r--r--vendor/doctrine/orm/src/Query/AST/LikeExpression.php29
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Literal.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/NewObjectExpression.php25
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Node.php85
-rw-r--r--vendor/doctrine/orm/src/Query/AST/NullComparisonExpression.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/NullIfExpression.php24
-rw-r--r--vendor/doctrine/orm/src/Query/AST/OrderByClause.php25
-rw-r--r--vendor/doctrine/orm/src/Query/AST/OrderByItem.php38
-rw-r--r--vendor/doctrine/orm/src/Query/AST/ParenthesisExpression.php22
-rw-r--r--vendor/doctrine/orm/src/Query/AST/PathExpression.php39
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Phase2OptimizableConditional.php17
-rw-r--r--vendor/doctrine/orm/src/Query/AST/QuantifiedExpression.php43
-rw-r--r--vendor/doctrine/orm/src/Query/AST/RangeVariableDeclaration.php27
-rw-r--r--vendor/doctrine/orm/src/Query/AST/SelectClause.php27
-rw-r--r--vendor/doctrine/orm/src/Query/AST/SelectExpression.php28
-rw-r--r--vendor/doctrine/orm/src/Query/AST/SelectStatement.php32
-rw-r--r--vendor/doctrine/orm/src/Query/AST/SimpleArithmeticExpression.php25
-rw-r--r--vendor/doctrine/orm/src/Query/AST/SimpleCaseExpression.php28
-rw-r--r--vendor/doctrine/orm/src/Query/AST/SimpleSelectClause.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/SimpleSelectExpression.php27
-rw-r--r--vendor/doctrine/orm/src/Query/AST/SimpleWhenClause.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/Subselect.php32
-rw-r--r--vendor/doctrine/orm/src/Query/AST/SubselectFromClause.php25
-rw-r--r--vendor/doctrine/orm/src/Query/AST/SubselectIdentificationVariableDeclaration.php19
-rw-r--r--vendor/doctrine/orm/src/Query/AST/TypedExpression.php15
-rw-r--r--vendor/doctrine/orm/src/Query/AST/UpdateClause.php29
-rw-r--r--vendor/doctrine/orm/src/Query/AST/UpdateItem.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/UpdateStatement.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/WhenClause.php26
-rw-r--r--vendor/doctrine/orm/src/Query/AST/WhereClause.php24
-rw-r--r--vendor/doctrine/orm/src/Query/Exec/AbstractSqlExecutor.php61
-rw-r--r--vendor/doctrine/orm/src/Query/Exec/MultiTableDeleteExecutor.php131
-rw-r--r--vendor/doctrine/orm/src/Query/Exec/MultiTableUpdateExecutor.php180
-rw-r--r--vendor/doctrine/orm/src/Query/Exec/SingleSelectExecutor.php31
-rw-r--r--vendor/doctrine/orm/src/Query/Exec/SingleTableDeleteUpdateExecutor.php42
-rw-r--r--vendor/doctrine/orm/src/Query/Expr.php615
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/Andx.php32
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/Base.php96
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/Comparison.php47
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/Composite.php50
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/From.php48
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/Func.php48
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/GroupBy.php25
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/Join.php77
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/Literal.php25
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/Math.php59
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/OrderBy.php60
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/Orx.php32
-rw-r--r--vendor/doctrine/orm/src/Query/Expr/Select.php28
-rw-r--r--vendor/doctrine/orm/src/Query/Filter/FilterException.php23
-rw-r--r--vendor/doctrine/orm/src/Query/Filter/SQLFilter.php174
-rw-r--r--vendor/doctrine/orm/src/Query/FilterCollection.php260
-rw-r--r--vendor/doctrine/orm/src/Query/Lexer.php150
-rw-r--r--vendor/doctrine/orm/src/Query/Parameter.php89
-rw-r--r--vendor/doctrine/orm/src/Query/ParameterTypeInferer.php77
-rw-r--r--vendor/doctrine/orm/src/Query/Parser.php3269
-rw-r--r--vendor/doctrine/orm/src/Query/ParserResult.php118
-rw-r--r--vendor/doctrine/orm/src/Query/Printer.php64
-rw-r--r--vendor/doctrine/orm/src/Query/QueryException.php155
-rw-r--r--vendor/doctrine/orm/src/Query/QueryExpressionVisitor.php180
-rw-r--r--vendor/doctrine/orm/src/Query/ResultSetMapping.php547
-rw-r--r--vendor/doctrine/orm/src/Query/ResultSetMappingBuilder.php281
-rw-r--r--vendor/doctrine/orm/src/Query/SqlWalker.php2264
-rw-r--r--vendor/doctrine/orm/src/Query/TokenType.php91
-rw-r--r--vendor/doctrine/orm/src/Query/TreeWalker.php44
-rw-r--r--vendor/doctrine/orm/src/Query/TreeWalkerAdapter.php90
-rw-r--r--vendor/doctrine/orm/src/Query/TreeWalkerChain.php88
125 files changed, 12640 insertions, 0 deletions
diff --git a/vendor/doctrine/orm/src/Query/AST/ASTException.php b/vendor/doctrine/orm/src/Query/AST/ASTException.php
new file mode 100644
index 0000000..1ef890a
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/ASTException.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\QueryException;
8
9use function get_debug_type;
10
11/**
12 * Base exception class for AST exceptions.
13 */
14class ASTException extends QueryException
15{
16 public static function noDispatchForNode(Node $node): self
17 {
18 return new self('Double-dispatch for node ' . get_debug_type($node) . ' is not supported.');
19 }
20}
diff --git a/vendor/doctrine/orm/src/Query/AST/AggregateExpression.php b/vendor/doctrine/orm/src/Query/AST/AggregateExpression.php
new file mode 100644
index 0000000..468c65c
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/AggregateExpression.php
@@ -0,0 +1,23 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9class AggregateExpression extends Node
10{
11 /** @param bool $isDistinct Some aggregate expressions support distinct, eg COUNT. */
12 public function __construct(
13 public string $functionName,
14 public Node|string $pathExpression,
15 public bool $isDistinct,
16 ) {
17 }
18
19 public function dispatch(SqlWalker $walker): string
20 {
21 return $walker->walkAggregateExpression($this);
22 }
23}
diff --git a/vendor/doctrine/orm/src/Query/AST/ArithmeticExpression.php b/vendor/doctrine/orm/src/Query/AST/ArithmeticExpression.php
new file mode 100644
index 0000000..a819e05
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/ArithmeticExpression.php
@@ -0,0 +1,34 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
11 *
12 * @link www.doctrine-project.org
13 */
14class ArithmeticExpression extends Node
15{
16 public Node|string|null $simpleArithmeticExpression = null;
17
18 public Subselect|null $subselect = null;
19
20 public function isSimpleArithmeticExpression(): bool
21 {
22 return (bool) $this->simpleArithmeticExpression;
23 }
24
25 public function isSubselect(): bool
26 {
27 return (bool) $this->subselect;
28 }
29
30 public function dispatch(SqlWalker $walker): string
31 {
32 return $walker->walkArithmeticExpression($this);
33 }
34}
diff --git a/vendor/doctrine/orm/src/Query/AST/ArithmeticFactor.php b/vendor/doctrine/orm/src/Query/AST/ArithmeticFactor.php
new file mode 100644
index 0000000..278a921
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/ArithmeticFactor.php
@@ -0,0 +1,36 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
11 *
12 * @link www.doctrine-project.org
13 */
14class ArithmeticFactor extends Node
15{
16 public function __construct(
17 public mixed $arithmeticPrimary,
18 public bool|null $sign = null,
19 ) {
20 }
21
22 public function isPositiveSigned(): bool
23 {
24 return $this->sign === true;
25 }
26
27 public function isNegativeSigned(): bool
28 {
29 return $this->sign === false;
30 }
31
32 public function dispatch(SqlWalker $walker): string
33 {
34 return $walker->walkArithmeticFactor($this);
35 }
36}
diff --git a/vendor/doctrine/orm/src/Query/AST/ArithmeticTerm.php b/vendor/doctrine/orm/src/Query/AST/ArithmeticTerm.php
new file mode 100644
index 0000000..b233612
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/ArithmeticTerm.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
11 *
12 * @link www.doctrine-project.org
13 */
14class ArithmeticTerm extends Node
15{
16 /** @param mixed[] $arithmeticFactors */
17 public function __construct(public array $arithmeticFactors)
18 {
19 }
20
21 public function dispatch(SqlWalker $walker): string
22 {
23 return $walker->walkArithmeticTerm($this);
24 }
25}
diff --git a/vendor/doctrine/orm/src/Query/AST/BetweenExpression.php b/vendor/doctrine/orm/src/Query/AST/BetweenExpression.php
new file mode 100644
index 0000000..c13292b
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/BetweenExpression.php
@@ -0,0 +1,23 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9class BetweenExpression extends Node
10{
11 public function __construct(
12 public ArithmeticExpression $expression,
13 public ArithmeticExpression $leftBetweenExpression,
14 public ArithmeticExpression $rightBetweenExpression,
15 public bool $not = false,
16 ) {
17 }
18
19 public function dispatch(SqlWalker $walker): string
20 {
21 return $walker->walkBetweenExpression($this);
22 }
23}
diff --git a/vendor/doctrine/orm/src/Query/AST/CoalesceExpression.php b/vendor/doctrine/orm/src/Query/AST/CoalesceExpression.php
new file mode 100644
index 0000000..89f025f
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/CoalesceExpression.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
11 *
12 * @link www.doctrine-project.org
13 */
14class CoalesceExpression extends Node
15{
16 /** @param mixed[] $scalarExpressions */
17 public function __construct(public array $scalarExpressions)
18 {
19 }
20
21 public function dispatch(SqlWalker $walker): string
22 {
23 return $walker->walkCoalesceExpression($this);
24 }
25}
diff --git a/vendor/doctrine/orm/src/Query/AST/CollectionMemberExpression.php b/vendor/doctrine/orm/src/Query/AST/CollectionMemberExpression.php
new file mode 100644
index 0000000..a62a191
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/CollectionMemberExpression.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
11 *
12 * @link www.doctrine-project.org
13 */
14class CollectionMemberExpression extends Node
15{
16 public function __construct(
17 public mixed $entityExpression,
18 public PathExpression $collectionValuedPathExpression,
19 public bool $not = false,
20 ) {
21 }
22
23 public function dispatch(SqlWalker $walker): string
24 {
25 return $walker->walkCollectionMemberExpression($this);
26 }
27}
diff --git a/vendor/doctrine/orm/src/Query/AST/ComparisonExpression.php b/vendor/doctrine/orm/src/Query/AST/ComparisonExpression.php
new file mode 100644
index 0000000..a7d91f9
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/ComparisonExpression.php
@@ -0,0 +1,32 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) |
11 * StringExpression ComparisonOperator (StringExpression | QuantifiedExpression) |
12 * BooleanExpression ("=" | "<>" | "!=") (BooleanExpression | QuantifiedExpression) |
13 * EnumExpression ("=" | "<>" | "!=") (EnumExpression | QuantifiedExpression) |
14 * DatetimeExpression ComparisonOperator (DatetimeExpression | QuantifiedExpression) |
15 * EntityExpression ("=" | "<>") (EntityExpression | QuantifiedExpression)
16 *
17 * @link www.doctrine-project.org
18 */
19class ComparisonExpression extends Node
20{
21 public function __construct(
22 public Node|string $leftExpression,
23 public string $operator,
24 public Node|string $rightExpression,
25 ) {
26 }
27
28 public function dispatch(SqlWalker $walker): string
29 {
30 return $walker->walkComparisonExpression($this);
31 }
32}
diff --git a/vendor/doctrine/orm/src/Query/AST/ConditionalExpression.php b/vendor/doctrine/orm/src/Query/AST/ConditionalExpression.php
new file mode 100644
index 0000000..26a98e5
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/ConditionalExpression.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
11 *
12 * @link www.doctrine-project.org
13 */
14class ConditionalExpression extends Node
15{
16 /** @param mixed[] $conditionalTerms */
17 public function __construct(public array $conditionalTerms)
18 {
19 }
20
21 public function dispatch(SqlWalker $walker): string
22 {
23 return $walker->walkConditionalExpression($this);
24 }
25}
diff --git a/vendor/doctrine/orm/src/Query/AST/ConditionalFactor.php b/vendor/doctrine/orm/src/Query/AST/ConditionalFactor.php
new file mode 100644
index 0000000..7881743
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/ConditionalFactor.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * ConditionalFactor ::= ["NOT"] ConditionalPrimary
11 *
12 * @link www.doctrine-project.org
13 */
14class ConditionalFactor extends Node implements Phase2OptimizableConditional
15{
16 public function __construct(
17 public ConditionalPrimary $conditionalPrimary,
18 public bool $not = false,
19 ) {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkConditionalFactor($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/ConditionalPrimary.php b/vendor/doctrine/orm/src/Query/AST/ConditionalPrimary.php
new file mode 100644
index 0000000..9344cd9
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/ConditionalPrimary.php
@@ -0,0 +1,34 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
11 *
12 * @link www.doctrine-project.org
13 */
14class ConditionalPrimary extends Node implements Phase2OptimizableConditional
15{
16 public Node|null $simpleConditionalExpression = null;
17
18 public ConditionalExpression|Phase2OptimizableConditional|null $conditionalExpression = null;
19
20 public function isSimpleConditionalExpression(): bool
21 {
22 return (bool) $this->simpleConditionalExpression;
23 }
24
25 public function isConditionalExpression(): bool
26 {
27 return (bool) $this->conditionalExpression;
28 }
29
30 public function dispatch(SqlWalker $walker): string
31 {
32 return $walker->walkConditionalPrimary($this);
33 }
34}
diff --git a/vendor/doctrine/orm/src/Query/AST/ConditionalTerm.php b/vendor/doctrine/orm/src/Query/AST/ConditionalTerm.php
new file mode 100644
index 0000000..dcea50b
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/ConditionalTerm.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
11 *
12 * @link www.doctrine-project.org
13 */
14class ConditionalTerm extends Node implements Phase2OptimizableConditional
15{
16 /** @param mixed[] $conditionalFactors */
17 public function __construct(public array $conditionalFactors)
18 {
19 }
20
21 public function dispatch(SqlWalker $walker): string
22 {
23 return $walker->walkConditionalTerm($this);
24 }
25}
diff --git a/vendor/doctrine/orm/src/Query/AST/DeleteClause.php b/vendor/doctrine/orm/src/Query/AST/DeleteClause.php
new file mode 100644
index 0000000..25e9085
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/DeleteClause.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable]
11 *
12 * @link www.doctrine-project.org
13 */
14class DeleteClause extends Node
15{
16 public string $aliasIdentificationVariable;
17
18 public function __construct(public string $abstractSchemaName)
19 {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkDeleteClause($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/DeleteStatement.php b/vendor/doctrine/orm/src/Query/AST/DeleteStatement.php
new file mode 100644
index 0000000..f367d09
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/DeleteStatement.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * DeleteStatement = DeleteClause [WhereClause]
11 *
12 * @link www.doctrine-project.org
13 */
14class DeleteStatement extends Node
15{
16 public WhereClause|null $whereClause = null;
17
18 public function __construct(public DeleteClause $deleteClause)
19 {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkDeleteStatement($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/EmptyCollectionComparisonExpression.php b/vendor/doctrine/orm/src/Query/AST/EmptyCollectionComparisonExpression.php
new file mode 100644
index 0000000..9978800
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/EmptyCollectionComparisonExpression.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
11 *
12 * @link www.doctrine-project.org
13 */
14class EmptyCollectionComparisonExpression extends Node
15{
16 public function __construct(
17 public PathExpression $expression,
18 public bool $not = false,
19 ) {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkEmptyCollectionComparisonExpression($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/ExistsExpression.php b/vendor/doctrine/orm/src/Query/AST/ExistsExpression.php
new file mode 100644
index 0000000..72757f4
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/ExistsExpression.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
11 *
12 * @link www.doctrine-project.org
13 */
14class ExistsExpression extends Node
15{
16 public function __construct(
17 public Subselect $subselect,
18 public bool $not = false,
19 ) {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkExistsExpression($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/FromClause.php b/vendor/doctrine/orm/src/Query/AST/FromClause.php
new file mode 100644
index 0000000..0b74393
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/FromClause.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}
11 *
12 * @link www.doctrine-project.org
13 */
14class FromClause extends Node
15{
16 /** @param mixed[] $identificationVariableDeclarations */
17 public function __construct(public array $identificationVariableDeclarations)
18 {
19 }
20
21 public function dispatch(SqlWalker $walker): string
22 {
23 return $walker->walkFromClause($this);
24 }
25}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/AbsFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/AbsFunction.php
new file mode 100644
index 0000000..4edff06
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/AbsFunction.php
@@ -0,0 +1,37 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12/**
13 * "ABS" "(" SimpleArithmeticExpression ")"
14 *
15 * @link www.doctrine-project.org
16 */
17class AbsFunction extends FunctionNode
18{
19 public Node|string $simpleArithmeticExpression;
20
21 public function getSql(SqlWalker $sqlWalker): string
22 {
23 return 'ABS(' . $sqlWalker->walkSimpleArithmeticExpression(
24 $this->simpleArithmeticExpression,
25 ) . ')';
26 }
27
28 public function parse(Parser $parser): void
29 {
30 $parser->match(TokenType::T_IDENTIFIER);
31 $parser->match(TokenType::T_OPEN_PARENTHESIS);
32
33 $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
34
35 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
36 }
37}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/AvgFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/AvgFunction.php
new file mode 100644
index 0000000..ba7b7f3
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/AvgFunction.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\AggregateExpression;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10
11/**
12 * "AVG" "(" ["DISTINCT"] StringPrimary ")"
13 */
14final class AvgFunction extends FunctionNode
15{
16 private AggregateExpression $aggregateExpression;
17
18 public function getSql(SqlWalker $sqlWalker): string
19 {
20 return $this->aggregateExpression->dispatch($sqlWalker);
21 }
22
23 public function parse(Parser $parser): void
24 {
25 $this->aggregateExpression = $parser->AggregateExpression();
26 }
27}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/BitAndFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/BitAndFunction.php
new file mode 100644
index 0000000..f2d3146
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/BitAndFunction.php
@@ -0,0 +1,43 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12/**
13 * "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
14 *
15 * @link www.doctrine-project.org
16 */
17class BitAndFunction extends FunctionNode
18{
19 public Node $firstArithmetic;
20 public Node $secondArithmetic;
21
22 public function getSql(SqlWalker $sqlWalker): string
23 {
24 $platform = $sqlWalker->getConnection()->getDatabasePlatform();
25
26 return $platform->getBitAndComparisonExpression(
27 $this->firstArithmetic->dispatch($sqlWalker),
28 $this->secondArithmetic->dispatch($sqlWalker),
29 );
30 }
31
32 public function parse(Parser $parser): void
33 {
34 $parser->match(TokenType::T_IDENTIFIER);
35 $parser->match(TokenType::T_OPEN_PARENTHESIS);
36
37 $this->firstArithmetic = $parser->ArithmeticPrimary();
38 $parser->match(TokenType::T_COMMA);
39 $this->secondArithmetic = $parser->ArithmeticPrimary();
40
41 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
42 }
43}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/BitOrFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/BitOrFunction.php
new file mode 100644
index 0000000..f3f84da
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/BitOrFunction.php
@@ -0,0 +1,43 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12/**
13 * "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
14 *
15 * @link www.doctrine-project.org
16 */
17class BitOrFunction extends FunctionNode
18{
19 public Node $firstArithmetic;
20 public Node $secondArithmetic;
21
22 public function getSql(SqlWalker $sqlWalker): string
23 {
24 $platform = $sqlWalker->getConnection()->getDatabasePlatform();
25
26 return $platform->getBitOrComparisonExpression(
27 $this->firstArithmetic->dispatch($sqlWalker),
28 $this->secondArithmetic->dispatch($sqlWalker),
29 );
30 }
31
32 public function parse(Parser $parser): void
33 {
34 $parser->match(TokenType::T_IDENTIFIER);
35 $parser->match(TokenType::T_OPEN_PARENTHESIS);
36
37 $this->firstArithmetic = $parser->ArithmeticPrimary();
38 $parser->match(TokenType::T_COMMA);
39 $this->secondArithmetic = $parser->ArithmeticPrimary();
40
41 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
42 }
43}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/ConcatFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/ConcatFunction.php
new file mode 100644
index 0000000..5b8d696
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/ConcatFunction.php
@@ -0,0 +1,58 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12/**
13 * "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary }* ")"
14 *
15 * @link www.doctrine-project.org
16 */
17class ConcatFunction extends FunctionNode
18{
19 public Node $firstStringPrimary;
20 public Node $secondStringPrimary;
21
22 /** @psalm-var list<Node> */
23 public array $concatExpressions = [];
24
25 public function getSql(SqlWalker $sqlWalker): string
26 {
27 $platform = $sqlWalker->getConnection()->getDatabasePlatform();
28
29 $args = [];
30
31 foreach ($this->concatExpressions as $expression) {
32 $args[] = $sqlWalker->walkStringPrimary($expression);
33 }
34
35 return $platform->getConcatExpression(...$args);
36 }
37
38 public function parse(Parser $parser): void
39 {
40 $parser->match(TokenType::T_IDENTIFIER);
41 $parser->match(TokenType::T_OPEN_PARENTHESIS);
42
43 $this->firstStringPrimary = $parser->StringPrimary();
44 $this->concatExpressions[] = $this->firstStringPrimary;
45
46 $parser->match(TokenType::T_COMMA);
47
48 $this->secondStringPrimary = $parser->StringPrimary();
49 $this->concatExpressions[] = $this->secondStringPrimary;
50
51 while ($parser->getLexer()->isNextToken(TokenType::T_COMMA)) {
52 $parser->match(TokenType::T_COMMA);
53 $this->concatExpressions[] = $parser->StringPrimary();
54 }
55
56 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
57 }
58}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/CountFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/CountFunction.php
new file mode 100644
index 0000000..dc926a5
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/CountFunction.php
@@ -0,0 +1,35 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\DBAL\Types\Type;
8use Doctrine\DBAL\Types\Types;
9use Doctrine\ORM\Query\AST\AggregateExpression;
10use Doctrine\ORM\Query\AST\TypedExpression;
11use Doctrine\ORM\Query\Parser;
12use Doctrine\ORM\Query\SqlWalker;
13
14/**
15 * "COUNT" "(" ["DISTINCT"] StringPrimary ")"
16 */
17final class CountFunction extends FunctionNode implements TypedExpression
18{
19 private AggregateExpression $aggregateExpression;
20
21 public function getSql(SqlWalker $sqlWalker): string
22 {
23 return $this->aggregateExpression->dispatch($sqlWalker);
24 }
25
26 public function parse(Parser $parser): void
27 {
28 $this->aggregateExpression = $parser->AggregateExpression();
29 }
30
31 public function getReturnType(): Type
32 {
33 return Type::getType(Types::INTEGER);
34 }
35}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/CurrentDateFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentDateFunction.php
new file mode 100644
index 0000000..cec9632
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentDateFunction.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\Parser;
8use Doctrine\ORM\Query\SqlWalker;
9use Doctrine\ORM\Query\TokenType;
10
11/**
12 * "CURRENT_DATE"
13 *
14 * @link www.doctrine-project.org
15 */
16class CurrentDateFunction extends FunctionNode
17{
18 public function getSql(SqlWalker $sqlWalker): string
19 {
20 return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentDateSQL();
21 }
22
23 public function parse(Parser $parser): void
24 {
25 $parser->match(TokenType::T_IDENTIFIER);
26 $parser->match(TokenType::T_OPEN_PARENTHESIS);
27 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
28 }
29}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimeFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimeFunction.php
new file mode 100644
index 0000000..6473fce
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimeFunction.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\Parser;
8use Doctrine\ORM\Query\SqlWalker;
9use Doctrine\ORM\Query\TokenType;
10
11/**
12 * "CURRENT_TIME"
13 *
14 * @link www.doctrine-project.org
15 */
16class CurrentTimeFunction extends FunctionNode
17{
18 public function getSql(SqlWalker $sqlWalker): string
19 {
20 return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentTimeSQL();
21 }
22
23 public function parse(Parser $parser): void
24 {
25 $parser->match(TokenType::T_IDENTIFIER);
26 $parser->match(TokenType::T_OPEN_PARENTHESIS);
27 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
28 }
29}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimestampFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimestampFunction.php
new file mode 100644
index 0000000..edcd27c
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/CurrentTimestampFunction.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\Parser;
8use Doctrine\ORM\Query\SqlWalker;
9use Doctrine\ORM\Query\TokenType;
10
11/**
12 * "CURRENT_TIMESTAMP"
13 *
14 * @link www.doctrine-project.org
15 */
16class CurrentTimestampFunction extends FunctionNode
17{
18 public function getSql(SqlWalker $sqlWalker): string
19 {
20 return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentTimestampSQL();
21 }
22
23 public function parse(Parser $parser): void
24 {
25 $parser->match(TokenType::T_IDENTIFIER);
26 $parser->match(TokenType::T_OPEN_PARENTHESIS);
27 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
28 }
29}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/DateAddFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/DateAddFunction.php
new file mode 100644
index 0000000..12920dc
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/DateAddFunction.php
@@ -0,0 +1,83 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\ASTException;
8use Doctrine\ORM\Query\AST\Node;
9use Doctrine\ORM\Query\Parser;
10use Doctrine\ORM\Query\QueryException;
11use Doctrine\ORM\Query\SqlWalker;
12use Doctrine\ORM\Query\TokenType;
13
14use function strtolower;
15
16/**
17 * "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
18 *
19 * @link www.doctrine-project.org
20 */
21class DateAddFunction extends FunctionNode
22{
23 public Node $firstDateExpression;
24 public Node $intervalExpression;
25 public Node $unit;
26
27 public function getSql(SqlWalker $sqlWalker): string
28 {
29 return match (strtolower((string) $this->unit->value)) {
30 'second' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddSecondsExpression(
31 $this->firstDateExpression->dispatch($sqlWalker),
32 $this->dispatchIntervalExpression($sqlWalker),
33 ),
34 'minute' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddMinutesExpression(
35 $this->firstDateExpression->dispatch($sqlWalker),
36 $this->dispatchIntervalExpression($sqlWalker),
37 ),
38 'hour' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddHourExpression(
39 $this->firstDateExpression->dispatch($sqlWalker),
40 $this->dispatchIntervalExpression($sqlWalker),
41 ),
42 'day' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddDaysExpression(
43 $this->firstDateExpression->dispatch($sqlWalker),
44 $this->dispatchIntervalExpression($sqlWalker),
45 ),
46 'week' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddWeeksExpression(
47 $this->firstDateExpression->dispatch($sqlWalker),
48 $this->dispatchIntervalExpression($sqlWalker),
49 ),
50 'month' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddMonthExpression(
51 $this->firstDateExpression->dispatch($sqlWalker),
52 $this->dispatchIntervalExpression($sqlWalker),
53 ),
54 'year' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddYearsExpression(
55 $this->firstDateExpression->dispatch($sqlWalker),
56 $this->dispatchIntervalExpression($sqlWalker),
57 ),
58 default => throw QueryException::semanticalError(
59 'DATE_ADD() only supports units of type second, minute, hour, day, week, month and year.',
60 ),
61 };
62 }
63
64 /** @throws ASTException */
65 private function dispatchIntervalExpression(SqlWalker $sqlWalker): string
66 {
67 return $this->intervalExpression->dispatch($sqlWalker);
68 }
69
70 public function parse(Parser $parser): void
71 {
72 $parser->match(TokenType::T_IDENTIFIER);
73 $parser->match(TokenType::T_OPEN_PARENTHESIS);
74
75 $this->firstDateExpression = $parser->ArithmeticPrimary();
76 $parser->match(TokenType::T_COMMA);
77 $this->intervalExpression = $parser->ArithmeticPrimary();
78 $parser->match(TokenType::T_COMMA);
79 $this->unit = $parser->StringPrimary();
80
81 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
82 }
83}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/DateDiffFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/DateDiffFunction.php
new file mode 100644
index 0000000..55598c0
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/DateDiffFunction.php
@@ -0,0 +1,41 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12/**
13 * "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
14 *
15 * @link www.doctrine-project.org
16 */
17class DateDiffFunction extends FunctionNode
18{
19 public Node $date1;
20 public Node $date2;
21
22 public function getSql(SqlWalker $sqlWalker): string
23 {
24 return $sqlWalker->getConnection()->getDatabasePlatform()->getDateDiffExpression(
25 $this->date1->dispatch($sqlWalker),
26 $this->date2->dispatch($sqlWalker),
27 );
28 }
29
30 public function parse(Parser $parser): void
31 {
32 $parser->match(TokenType::T_IDENTIFIER);
33 $parser->match(TokenType::T_OPEN_PARENTHESIS);
34
35 $this->date1 = $parser->ArithmeticPrimary();
36 $parser->match(TokenType::T_COMMA);
37 $this->date2 = $parser->ArithmeticPrimary();
38
39 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
40 }
41}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/DateSubFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/DateSubFunction.php
new file mode 100644
index 0000000..5363680
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/DateSubFunction.php
@@ -0,0 +1,62 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\ASTException;
8use Doctrine\ORM\Query\QueryException;
9use Doctrine\ORM\Query\SqlWalker;
10
11use function strtolower;
12
13/**
14 * "DATE_SUB(date1, interval, unit)"
15 *
16 * @link www.doctrine-project.org
17 */
18class DateSubFunction extends DateAddFunction
19{
20 public function getSql(SqlWalker $sqlWalker): string
21 {
22 return match (strtolower((string) $this->unit->value)) {
23 'second' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubSecondsExpression(
24 $this->firstDateExpression->dispatch($sqlWalker),
25 $this->dispatchIntervalExpression($sqlWalker),
26 ),
27 'minute' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubMinutesExpression(
28 $this->firstDateExpression->dispatch($sqlWalker),
29 $this->dispatchIntervalExpression($sqlWalker),
30 ),
31 'hour' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubHourExpression(
32 $this->firstDateExpression->dispatch($sqlWalker),
33 $this->dispatchIntervalExpression($sqlWalker),
34 ),
35 'day' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubDaysExpression(
36 $this->firstDateExpression->dispatch($sqlWalker),
37 $this->dispatchIntervalExpression($sqlWalker),
38 ),
39 'week' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubWeeksExpression(
40 $this->firstDateExpression->dispatch($sqlWalker),
41 $this->dispatchIntervalExpression($sqlWalker),
42 ),
43 'month' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubMonthExpression(
44 $this->firstDateExpression->dispatch($sqlWalker),
45 $this->dispatchIntervalExpression($sqlWalker),
46 ),
47 'year' => $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubYearsExpression(
48 $this->firstDateExpression->dispatch($sqlWalker),
49 $this->dispatchIntervalExpression($sqlWalker),
50 ),
51 default => throw QueryException::semanticalError(
52 'DATE_SUB() only supports units of type second, minute, hour, day, week, month and year.',
53 ),
54 };
55 }
56
57 /** @throws ASTException */
58 private function dispatchIntervalExpression(SqlWalker $sqlWalker): string
59 {
60 return $this->intervalExpression->dispatch($sqlWalker);
61 }
62}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/FunctionNode.php b/vendor/doctrine/orm/src/Query/AST/Functions/FunctionNode.php
new file mode 100644
index 0000000..4cc549e
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/FunctionNode.php
@@ -0,0 +1,32 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10
11/**
12 * Abstract Function Node.
13 *
14 * @link www.doctrine-project.org
15 *
16 * @psalm-consistent-constructor
17 */
18abstract class FunctionNode extends Node
19{
20 public function __construct(public string $name)
21 {
22 }
23
24 abstract public function getSql(SqlWalker $sqlWalker): string;
25
26 public function dispatch(SqlWalker $sqlWalker): string
27 {
28 return $sqlWalker->walkFunction($this);
29 }
30
31 abstract public function parse(Parser $parser): void;
32}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/IdentityFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/IdentityFunction.php
new file mode 100644
index 0000000..1dd1bf5
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/IdentityFunction.php
@@ -0,0 +1,90 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\PathExpression;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\QueryException;
10use Doctrine\ORM\Query\SqlWalker;
11use Doctrine\ORM\Query\TokenType;
12
13use function assert;
14use function reset;
15use function sprintf;
16
17/**
18 * "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
19 *
20 * @link www.doctrine-project.org
21 */
22class IdentityFunction extends FunctionNode
23{
24 public PathExpression $pathExpression;
25
26 public string|null $fieldMapping = null;
27
28 public function getSql(SqlWalker $sqlWalker): string
29 {
30 assert($this->pathExpression->field !== null);
31 $entityManager = $sqlWalker->getEntityManager();
32 $platform = $entityManager->getConnection()->getDatabasePlatform();
33 $quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy();
34 $dqlAlias = $this->pathExpression->identificationVariable;
35 $assocField = $this->pathExpression->field;
36 $assoc = $sqlWalker->getMetadataForDqlAlias($dqlAlias)->associationMappings[$assocField];
37 $targetEntity = $entityManager->getClassMetadata($assoc->targetEntity);
38
39 assert($assoc->isToOneOwningSide());
40 $joinColumn = reset($assoc->joinColumns);
41
42 if ($this->fieldMapping !== null) {
43 if (! isset($targetEntity->fieldMappings[$this->fieldMapping])) {
44 throw new QueryException(sprintf('Undefined reference field mapping "%s"', $this->fieldMapping));
45 }
46
47 $field = $targetEntity->fieldMappings[$this->fieldMapping];
48 $joinColumn = null;
49
50 foreach ($assoc->joinColumns as $mapping) {
51 if ($mapping->referencedColumnName === $field->columnName) {
52 $joinColumn = $mapping;
53
54 break;
55 }
56 }
57
58 if ($joinColumn === null) {
59 throw new QueryException(sprintf('Unable to resolve the reference field mapping "%s"', $this->fieldMapping));
60 }
61 }
62
63 // The table with the relation may be a subclass, so get the table name from the association definition
64 $tableName = $entityManager->getClassMetadata($assoc->sourceEntity)->getTableName();
65
66 $tableAlias = $sqlWalker->getSQLTableAlias($tableName, $dqlAlias);
67 $columnName = $quoteStrategy->getJoinColumnName($joinColumn, $targetEntity, $platform);
68
69 return $tableAlias . '.' . $columnName;
70 }
71
72 public function parse(Parser $parser): void
73 {
74 $parser->match(TokenType::T_IDENTIFIER);
75 $parser->match(TokenType::T_OPEN_PARENTHESIS);
76
77 $this->pathExpression = $parser->SingleValuedAssociationPathExpression();
78
79 if ($parser->getLexer()->isNextToken(TokenType::T_COMMA)) {
80 $parser->match(TokenType::T_COMMA);
81 $parser->match(TokenType::T_STRING);
82
83 $token = $parser->getLexer()->token;
84 assert($token !== null);
85 $this->fieldMapping = $token->value;
86 }
87
88 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
89 }
90}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/LengthFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/LengthFunction.php
new file mode 100644
index 0000000..3994918
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/LengthFunction.php
@@ -0,0 +1,45 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\DBAL\Types\Type;
8use Doctrine\DBAL\Types\Types;
9use Doctrine\ORM\Query\AST\Node;
10use Doctrine\ORM\Query\AST\TypedExpression;
11use Doctrine\ORM\Query\Parser;
12use Doctrine\ORM\Query\SqlWalker;
13use Doctrine\ORM\Query\TokenType;
14
15/**
16 * "LENGTH" "(" StringPrimary ")"
17 *
18 * @link www.doctrine-project.org
19 */
20class LengthFunction extends FunctionNode implements TypedExpression
21{
22 public Node $stringPrimary;
23
24 public function getSql(SqlWalker $sqlWalker): string
25 {
26 return $sqlWalker->getConnection()->getDatabasePlatform()->getLengthExpression(
27 $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary),
28 );
29 }
30
31 public function parse(Parser $parser): void
32 {
33 $parser->match(TokenType::T_IDENTIFIER);
34 $parser->match(TokenType::T_OPEN_PARENTHESIS);
35
36 $this->stringPrimary = $parser->StringPrimary();
37
38 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
39 }
40
41 public function getReturnType(): Type
42 {
43 return Type::getType(Types::INTEGER);
44 }
45}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/LocateFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/LocateFunction.php
new file mode 100644
index 0000000..c0d3b4a
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/LocateFunction.php
@@ -0,0 +1,62 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12/**
13 * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")"
14 *
15 * @link www.doctrine-project.org
16 */
17class LocateFunction extends FunctionNode
18{
19 public Node|string $firstStringPrimary;
20 public Node|string $secondStringPrimary;
21
22 public Node|string|bool $simpleArithmeticExpression = false;
23
24 public function getSql(SqlWalker $sqlWalker): string
25 {
26 $platform = $sqlWalker->getConnection()->getDatabasePlatform();
27
28 $firstString = $sqlWalker->walkStringPrimary($this->firstStringPrimary);
29 $secondString = $sqlWalker->walkStringPrimary($this->secondStringPrimary);
30
31 if ($this->simpleArithmeticExpression) {
32 return $platform->getLocateExpression(
33 $secondString,
34 $firstString,
35 $sqlWalker->walkSimpleArithmeticExpression($this->simpleArithmeticExpression),
36 );
37 }
38
39 return $platform->getLocateExpression($secondString, $firstString);
40 }
41
42 public function parse(Parser $parser): void
43 {
44 $parser->match(TokenType::T_IDENTIFIER);
45 $parser->match(TokenType::T_OPEN_PARENTHESIS);
46
47 $this->firstStringPrimary = $parser->StringPrimary();
48
49 $parser->match(TokenType::T_COMMA);
50
51 $this->secondStringPrimary = $parser->StringPrimary();
52
53 $lexer = $parser->getLexer();
54 if ($lexer->isNextToken(TokenType::T_COMMA)) {
55 $parser->match(TokenType::T_COMMA);
56
57 $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
58 }
59
60 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
61 }
62}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/LowerFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/LowerFunction.php
new file mode 100644
index 0000000..8ae337a
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/LowerFunction.php
@@ -0,0 +1,40 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12use function sprintf;
13
14/**
15 * "LOWER" "(" StringPrimary ")"
16 *
17 * @link www.doctrine-project.org
18 */
19class LowerFunction extends FunctionNode
20{
21 public Node $stringPrimary;
22
23 public function getSql(SqlWalker $sqlWalker): string
24 {
25 return sprintf(
26 'LOWER(%s)',
27 $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary),
28 );
29 }
30
31 public function parse(Parser $parser): void
32 {
33 $parser->match(TokenType::T_IDENTIFIER);
34 $parser->match(TokenType::T_OPEN_PARENTHESIS);
35
36 $this->stringPrimary = $parser->StringPrimary();
37
38 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
39 }
40}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/MaxFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/MaxFunction.php
new file mode 100644
index 0000000..8a6eecf
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/MaxFunction.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\AggregateExpression;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10
11/**
12 * "MAX" "(" ["DISTINCT"] StringPrimary ")"
13 */
14final class MaxFunction extends FunctionNode
15{
16 private AggregateExpression $aggregateExpression;
17
18 public function getSql(SqlWalker $sqlWalker): string
19 {
20 return $this->aggregateExpression->dispatch($sqlWalker);
21 }
22
23 public function parse(Parser $parser): void
24 {
25 $this->aggregateExpression = $parser->AggregateExpression();
26 }
27}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/MinFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/MinFunction.php
new file mode 100644
index 0000000..98d73a2
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/MinFunction.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\AggregateExpression;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10
11/**
12 * "MIN" "(" ["DISTINCT"] StringPrimary ")"
13 */
14final class MinFunction extends FunctionNode
15{
16 private AggregateExpression $aggregateExpression;
17
18 public function getSql(SqlWalker $sqlWalker): string
19 {
20 return $this->aggregateExpression->dispatch($sqlWalker);
21 }
22
23 public function parse(Parser $parser): void
24 {
25 $this->aggregateExpression = $parser->AggregateExpression();
26 }
27}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/ModFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/ModFunction.php
new file mode 100644
index 0000000..7c1af0b
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/ModFunction.php
@@ -0,0 +1,43 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12/**
13 * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")"
14 *
15 * @link www.doctrine-project.org
16 */
17class ModFunction extends FunctionNode
18{
19 public Node|string $firstSimpleArithmeticExpression;
20 public Node|string $secondSimpleArithmeticExpression;
21
22 public function getSql(SqlWalker $sqlWalker): string
23 {
24 return $sqlWalker->getConnection()->getDatabasePlatform()->getModExpression(
25 $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression),
26 $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression),
27 );
28 }
29
30 public function parse(Parser $parser): void
31 {
32 $parser->match(TokenType::T_IDENTIFIER);
33 $parser->match(TokenType::T_OPEN_PARENTHESIS);
34
35 $this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
36
37 $parser->match(TokenType::T_COMMA);
38
39 $this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
40
41 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
42 }
43}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/SizeFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/SizeFunction.php
new file mode 100644
index 0000000..87ee713
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/SizeFunction.php
@@ -0,0 +1,113 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\PathExpression;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12use function assert;
13
14/**
15 * "SIZE" "(" CollectionValuedPathExpression ")"
16 *
17 * @link www.doctrine-project.org
18 */
19class SizeFunction extends FunctionNode
20{
21 public PathExpression $collectionPathExpression;
22
23 /**
24 * @inheritdoc
25 * @todo If the collection being counted is already joined, the SQL can be simpler (more efficient).
26 */
27 public function getSql(SqlWalker $sqlWalker): string
28 {
29 assert($this->collectionPathExpression->field !== null);
30 $entityManager = $sqlWalker->getEntityManager();
31 $platform = $entityManager->getConnection()->getDatabasePlatform();
32 $quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy();
33 $dqlAlias = $this->collectionPathExpression->identificationVariable;
34 $assocField = $this->collectionPathExpression->field;
35
36 $class = $sqlWalker->getMetadataForDqlAlias($dqlAlias);
37 $assoc = $class->associationMappings[$assocField];
38 $sql = 'SELECT COUNT(*) FROM ';
39
40 if ($assoc->isOneToMany()) {
41 $targetClass = $entityManager->getClassMetadata($assoc->targetEntity);
42 $targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->getTableName());
43 $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias);
44
45 $sql .= $quoteStrategy->getTableName($targetClass, $platform) . ' ' . $targetTableAlias . ' WHERE ';
46
47 $owningAssoc = $targetClass->associationMappings[$assoc->mappedBy];
48 assert($owningAssoc->isManyToOne());
49
50 $first = true;
51
52 foreach ($owningAssoc->targetToSourceKeyColumns as $targetColumn => $sourceColumn) {
53 if ($first) {
54 $first = false;
55 } else {
56 $sql .= ' AND ';
57 }
58
59 $sql .= $targetTableAlias . '.' . $sourceColumn
60 . ' = '
61 . $sourceTableAlias . '.' . $quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $platform);
62 }
63 } else { // many-to-many
64 assert($assoc->isManyToMany());
65 $owningAssoc = $entityManager->getMetadataFactory()->getOwningSide($assoc);
66 $joinTable = $owningAssoc->joinTable;
67
68 // SQL table aliases
69 $joinTableAlias = $sqlWalker->getSQLTableAlias($joinTable->name);
70 $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias);
71
72 // join to target table
73 $targetClass = $entityManager->getClassMetadata($assoc->targetEntity);
74 $sql .= $quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $platform) . ' ' . $joinTableAlias . ' WHERE ';
75
76 $joinColumns = $assoc->isOwningSide()
77 ? $joinTable->joinColumns
78 : $joinTable->inverseJoinColumns;
79
80 $first = true;
81
82 foreach ($joinColumns as $joinColumn) {
83 if ($first) {
84 $first = false;
85 } else {
86 $sql .= ' AND ';
87 }
88
89 $sourceColumnName = $quoteStrategy->getColumnName(
90 $class->fieldNames[$joinColumn->referencedColumnName],
91 $class,
92 $platform,
93 );
94
95 $sql .= $joinTableAlias . '.' . $joinColumn->name
96 . ' = '
97 . $sourceTableAlias . '.' . $sourceColumnName;
98 }
99 }
100
101 return '(' . $sql . ')';
102 }
103
104 public function parse(Parser $parser): void
105 {
106 $parser->match(TokenType::T_IDENTIFIER);
107 $parser->match(TokenType::T_OPEN_PARENTHESIS);
108
109 $this->collectionPathExpression = $parser->CollectionValuedPathExpression();
110
111 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
112 }
113}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/SqrtFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/SqrtFunction.php
new file mode 100644
index 0000000..e643663
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/SqrtFunction.php
@@ -0,0 +1,40 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12use function sprintf;
13
14/**
15 * "SQRT" "(" SimpleArithmeticExpression ")"
16 *
17 * @link www.doctrine-project.org
18 */
19class SqrtFunction extends FunctionNode
20{
21 public Node|string $simpleArithmeticExpression;
22
23 public function getSql(SqlWalker $sqlWalker): string
24 {
25 return sprintf(
26 'SQRT(%s)',
27 $sqlWalker->walkSimpleArithmeticExpression($this->simpleArithmeticExpression),
28 );
29 }
30
31 public function parse(Parser $parser): void
32 {
33 $parser->match(TokenType::T_IDENTIFIER);
34 $parser->match(TokenType::T_OPEN_PARENTHESIS);
35
36 $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
37
38 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
39 }
40}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/SubstringFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/SubstringFunction.php
new file mode 100644
index 0000000..5744f08
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/SubstringFunction.php
@@ -0,0 +1,58 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12/**
13 * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")"
14 *
15 * @link www.doctrine-project.org
16 */
17class SubstringFunction extends FunctionNode
18{
19 public Node $stringPrimary;
20
21 public Node|string $firstSimpleArithmeticExpression;
22 public Node|string|null $secondSimpleArithmeticExpression = null;
23
24 public function getSql(SqlWalker $sqlWalker): string
25 {
26 $optionalSecondSimpleArithmeticExpression = null;
27 if ($this->secondSimpleArithmeticExpression !== null) {
28 $optionalSecondSimpleArithmeticExpression = $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression);
29 }
30
31 return $sqlWalker->getConnection()->getDatabasePlatform()->getSubstringExpression(
32 $sqlWalker->walkStringPrimary($this->stringPrimary),
33 $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression),
34 $optionalSecondSimpleArithmeticExpression,
35 );
36 }
37
38 public function parse(Parser $parser): void
39 {
40 $parser->match(TokenType::T_IDENTIFIER);
41 $parser->match(TokenType::T_OPEN_PARENTHESIS);
42
43 $this->stringPrimary = $parser->StringPrimary();
44
45 $parser->match(TokenType::T_COMMA);
46
47 $this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
48
49 $lexer = $parser->getLexer();
50 if ($lexer->isNextToken(TokenType::T_COMMA)) {
51 $parser->match(TokenType::T_COMMA);
52
53 $this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
54 }
55
56 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
57 }
58}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/SumFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/SumFunction.php
new file mode 100644
index 0000000..588dce9
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/SumFunction.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\AggregateExpression;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10
11/**
12 * "SUM" "(" ["DISTINCT"] StringPrimary ")"
13 */
14final class SumFunction extends FunctionNode
15{
16 private AggregateExpression $aggregateExpression;
17
18 public function getSql(SqlWalker $sqlWalker): string
19 {
20 return $this->aggregateExpression->dispatch($sqlWalker);
21 }
22
23 public function parse(Parser $parser): void
24 {
25 $this->aggregateExpression = $parser->AggregateExpression();
26 }
27}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/TrimFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/TrimFunction.php
new file mode 100644
index 0000000..e0a3e99
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/TrimFunction.php
@@ -0,0 +1,119 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\DBAL\Platforms\TrimMode;
8use Doctrine\ORM\Query\AST\Node;
9use Doctrine\ORM\Query\Parser;
10use Doctrine\ORM\Query\SqlWalker;
11use Doctrine\ORM\Query\TokenType;
12
13use function assert;
14use function strcasecmp;
15
16/**
17 * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")"
18 *
19 * @link www.doctrine-project.org
20 */
21class TrimFunction extends FunctionNode
22{
23 public bool $leading = false;
24 public bool $trailing = false;
25 public bool $both = false;
26 public string|false $trimChar = false;
27 public Node $stringPrimary;
28
29 public function getSql(SqlWalker $sqlWalker): string
30 {
31 $stringPrimary = $sqlWalker->walkStringPrimary($this->stringPrimary);
32 $platform = $sqlWalker->getConnection()->getDatabasePlatform();
33 $trimMode = $this->getTrimMode();
34
35 if ($this->trimChar !== false) {
36 return $platform->getTrimExpression(
37 $stringPrimary,
38 $trimMode,
39 $platform->quoteStringLiteral($this->trimChar),
40 );
41 }
42
43 return $platform->getTrimExpression($stringPrimary, $trimMode);
44 }
45
46 public function parse(Parser $parser): void
47 {
48 $lexer = $parser->getLexer();
49
50 $parser->match(TokenType::T_IDENTIFIER);
51 $parser->match(TokenType::T_OPEN_PARENTHESIS);
52
53 $this->parseTrimMode($parser);
54
55 if ($lexer->isNextToken(TokenType::T_STRING)) {
56 $parser->match(TokenType::T_STRING);
57
58 assert($lexer->token !== null);
59 $this->trimChar = $lexer->token->value;
60 }
61
62 if ($this->leading || $this->trailing || $this->both || ($this->trimChar !== false)) {
63 $parser->match(TokenType::T_FROM);
64 }
65
66 $this->stringPrimary = $parser->StringPrimary();
67
68 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
69 }
70
71 /** @psalm-return TrimMode::* */
72 private function getTrimMode(): TrimMode|int
73 {
74 if ($this->leading) {
75 return TrimMode::LEADING;
76 }
77
78 if ($this->trailing) {
79 return TrimMode::TRAILING;
80 }
81
82 if ($this->both) {
83 return TrimMode::BOTH;
84 }
85
86 return TrimMode::UNSPECIFIED;
87 }
88
89 private function parseTrimMode(Parser $parser): void
90 {
91 $lexer = $parser->getLexer();
92 assert($lexer->lookahead !== null);
93 $value = $lexer->lookahead->value;
94
95 if (strcasecmp('leading', $value) === 0) {
96 $parser->match(TokenType::T_LEADING);
97
98 $this->leading = true;
99
100 return;
101 }
102
103 if (strcasecmp('trailing', $value) === 0) {
104 $parser->match(TokenType::T_TRAILING);
105
106 $this->trailing = true;
107
108 return;
109 }
110
111 if (strcasecmp('both', $value) === 0) {
112 $parser->match(TokenType::T_BOTH);
113
114 $this->both = true;
115
116 return;
117 }
118 }
119}
diff --git a/vendor/doctrine/orm/src/Query/AST/Functions/UpperFunction.php b/vendor/doctrine/orm/src/Query/AST/Functions/UpperFunction.php
new file mode 100644
index 0000000..1ecef66
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Functions/UpperFunction.php
@@ -0,0 +1,40 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST\Functions;
6
7use Doctrine\ORM\Query\AST\Node;
8use Doctrine\ORM\Query\Parser;
9use Doctrine\ORM\Query\SqlWalker;
10use Doctrine\ORM\Query\TokenType;
11
12use function sprintf;
13
14/**
15 * "UPPER" "(" StringPrimary ")"
16 *
17 * @link www.doctrine-project.org
18 */
19class UpperFunction extends FunctionNode
20{
21 public Node $stringPrimary;
22
23 public function getSql(SqlWalker $sqlWalker): string
24 {
25 return sprintf(
26 'UPPER(%s)',
27 $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary),
28 );
29 }
30
31 public function parse(Parser $parser): void
32 {
33 $parser->match(TokenType::T_IDENTIFIER);
34 $parser->match(TokenType::T_OPEN_PARENTHESIS);
35
36 $this->stringPrimary = $parser->StringPrimary();
37
38 $parser->match(TokenType::T_CLOSE_PARENTHESIS);
39 }
40}
diff --git a/vendor/doctrine/orm/src/Query/AST/GeneralCaseExpression.php b/vendor/doctrine/orm/src/Query/AST/GeneralCaseExpression.php
new file mode 100644
index 0000000..39d760a
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/GeneralCaseExpression.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
11 *
12 * @link www.doctrine-project.org
13 */
14class GeneralCaseExpression extends Node
15{
16 /** @param mixed[] $whenClauses */
17 public function __construct(
18 public array $whenClauses,
19 public mixed $elseScalarExpression = null,
20 ) {
21 }
22
23 public function dispatch(SqlWalker $walker): string
24 {
25 return $walker->walkGeneralCaseExpression($this);
26 }
27}
diff --git a/vendor/doctrine/orm/src/Query/AST/GroupByClause.php b/vendor/doctrine/orm/src/Query/AST/GroupByClause.php
new file mode 100644
index 0000000..eb0f1b9
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/GroupByClause.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9class GroupByClause extends Node
10{
11 /** @param mixed[] $groupByItems */
12 public function __construct(public array $groupByItems)
13 {
14 }
15
16 public function dispatch(SqlWalker $walker): string
17 {
18 return $walker->walkGroupByClause($this);
19 }
20}
diff --git a/vendor/doctrine/orm/src/Query/AST/HavingClause.php b/vendor/doctrine/orm/src/Query/AST/HavingClause.php
new file mode 100644
index 0000000..0d4d821
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/HavingClause.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9class HavingClause extends Node
10{
11 public function __construct(public ConditionalExpression|Phase2OptimizableConditional $conditionalExpression)
12 {
13 }
14
15 public function dispatch(SqlWalker $walker): string
16 {
17 return $walker->walkHavingClause($this);
18 }
19}
diff --git a/vendor/doctrine/orm/src/Query/AST/IdentificationVariableDeclaration.php b/vendor/doctrine/orm/src/Query/AST/IdentificationVariableDeclaration.php
new file mode 100644
index 0000000..c4c7cca
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/IdentificationVariableDeclaration.php
@@ -0,0 +1,28 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
11 *
12 * @link www.doctrine-project.org
13 */
14class IdentificationVariableDeclaration extends Node
15{
16 /** @param mixed[] $joins */
17 public function __construct(
18 public RangeVariableDeclaration|null $rangeVariableDeclaration = null,
19 public IndexBy|null $indexBy = null,
20 public array $joins = [],
21 ) {
22 }
23
24 public function dispatch(SqlWalker $walker): string
25 {
26 return $walker->walkIdentificationVariableDeclaration($this);
27 }
28}
diff --git a/vendor/doctrine/orm/src/Query/AST/InListExpression.php b/vendor/doctrine/orm/src/Query/AST/InListExpression.php
new file mode 100644
index 0000000..dc0f32b
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/InListExpression.php
@@ -0,0 +1,23 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9class InListExpression extends Node
10{
11 /** @param non-empty-list<mixed> $literals */
12 public function __construct(
13 public ArithmeticExpression $expression,
14 public array $literals,
15 public bool $not = false,
16 ) {
17 }
18
19 public function dispatch(SqlWalker $walker): string
20 {
21 return $walker->walkInListExpression($this);
22 }
23}
diff --git a/vendor/doctrine/orm/src/Query/AST/InSubselectExpression.php b/vendor/doctrine/orm/src/Query/AST/InSubselectExpression.php
new file mode 100644
index 0000000..1128285
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/InSubselectExpression.php
@@ -0,0 +1,22 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9class InSubselectExpression extends Node
10{
11 public function __construct(
12 public ArithmeticExpression $expression,
13 public Subselect $subselect,
14 public bool $not = false,
15 ) {
16 }
17
18 public function dispatch(SqlWalker $walker): string
19 {
20 return $walker->walkInSubselectExpression($this);
21 }
22}
diff --git a/vendor/doctrine/orm/src/Query/AST/IndexBy.php b/vendor/doctrine/orm/src/Query/AST/IndexBy.php
new file mode 100644
index 0000000..3d90265
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/IndexBy.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * IndexBy ::= "INDEX" "BY" SingleValuedPathExpression
11 *
12 * @link www.doctrine-project.org
13 */
14class IndexBy extends Node
15{
16 public function __construct(public PathExpression $singleValuedPathExpression)
17 {
18 }
19
20 public function dispatch(SqlWalker $walker): string
21 {
22 $walker->walkIndexBy($this);
23
24 return '';
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/InputParameter.php b/vendor/doctrine/orm/src/Query/AST/InputParameter.php
new file mode 100644
index 0000000..a8e0a3b
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/InputParameter.php
@@ -0,0 +1,35 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\QueryException;
8use Doctrine\ORM\Query\SqlWalker;
9
10use function is_numeric;
11use function strlen;
12use function substr;
13
14class InputParameter extends Node
15{
16 public bool $isNamed;
17 public string $name;
18
19 /** @throws QueryException */
20 public function __construct(string $value)
21 {
22 if (strlen($value) === 1) {
23 throw QueryException::invalidParameterFormat($value);
24 }
25
26 $param = substr($value, 1);
27 $this->isNamed = ! is_numeric($param);
28 $this->name = $param;
29 }
30
31 public function dispatch(SqlWalker $walker): string
32 {
33 return $walker->walkInputParameter($this);
34 }
35}
diff --git a/vendor/doctrine/orm/src/Query/AST/InstanceOfExpression.php b/vendor/doctrine/orm/src/Query/AST/InstanceOfExpression.php
new file mode 100644
index 0000000..3a4e75f
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/InstanceOfExpression.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
11 * InstanceOfParameter ::= AbstractSchemaName | InputParameter
12 *
13 * @link www.doctrine-project.org
14 */
15class InstanceOfExpression extends Node
16{
17 /** @param non-empty-list<InputParameter|string> $value */
18 public function __construct(
19 public string $identificationVariable,
20 public array $value,
21 public bool $not = false,
22 ) {
23 }
24
25 public function dispatch(SqlWalker $walker): string
26 {
27 return $walker->walkInstanceOfExpression($this);
28 }
29}
diff --git a/vendor/doctrine/orm/src/Query/AST/Join.php b/vendor/doctrine/orm/src/Query/AST/Join.php
new file mode 100644
index 0000000..34ce830
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Join.php
@@ -0,0 +1,34 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
11 * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression]
12 *
13 * @link www.doctrine-project.org
14 */
15class Join extends Node
16{
17 final public const JOIN_TYPE_LEFT = 1;
18 final public const JOIN_TYPE_LEFTOUTER = 2;
19 final public const JOIN_TYPE_INNER = 3;
20
21 public ConditionalExpression|Phase2OptimizableConditional|null $conditionalExpression = null;
22
23 /** @psalm-param self::JOIN_TYPE_* $joinType */
24 public function __construct(
25 public int $joinType,
26 public Node|null $joinAssociationDeclaration = null,
27 ) {
28 }
29
30 public function dispatch(SqlWalker $walker): string
31 {
32 return $walker->walkJoin($this);
33 }
34}
diff --git a/vendor/doctrine/orm/src/Query/AST/JoinAssociationDeclaration.php b/vendor/doctrine/orm/src/Query/AST/JoinAssociationDeclaration.php
new file mode 100644
index 0000000..e08d7f5
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/JoinAssociationDeclaration.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable
11 *
12 * @link www.doctrine-project.org
13 */
14class JoinAssociationDeclaration extends Node
15{
16 public function __construct(
17 public JoinAssociationPathExpression $joinAssociationPathExpression,
18 public string $aliasIdentificationVariable,
19 public IndexBy|null $indexBy,
20 ) {
21 }
22
23 public function dispatch(SqlWalker $walker): string
24 {
25 return $walker->walkJoinAssociationDeclaration($this);
26 }
27}
diff --git a/vendor/doctrine/orm/src/Query/AST/JoinAssociationPathExpression.php b/vendor/doctrine/orm/src/Query/AST/JoinAssociationPathExpression.php
new file mode 100644
index 0000000..230be36
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/JoinAssociationPathExpression.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7/**
8 * JoinAssociationPathExpression ::= IdentificationVariable "." (SingleValuedAssociationField | CollectionValuedAssociationField)
9 *
10 * @link www.doctrine-project.org
11 */
12class JoinAssociationPathExpression extends Node
13{
14 public function __construct(
15 public string $identificationVariable,
16 public string $associationField,
17 ) {
18 }
19}
diff --git a/vendor/doctrine/orm/src/Query/AST/JoinClassPathExpression.php b/vendor/doctrine/orm/src/Query/AST/JoinClassPathExpression.php
new file mode 100644
index 0000000..cc92782
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/JoinClassPathExpression.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * JoinClassPathExpression ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
11 *
12 * @link www.doctrine-project.org
13 */
14class JoinClassPathExpression extends Node
15{
16 public function __construct(
17 public mixed $abstractSchemaName,
18 public mixed $aliasIdentificationVariable,
19 ) {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkJoinPathExpression($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/JoinVariableDeclaration.php b/vendor/doctrine/orm/src/Query/AST/JoinVariableDeclaration.php
new file mode 100644
index 0000000..bf76695
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/JoinVariableDeclaration.php
@@ -0,0 +1,24 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * JoinVariableDeclaration ::= Join [IndexBy]
11 *
12 * @link www.doctrine-project.org
13 */
14class JoinVariableDeclaration extends Node
15{
16 public function __construct(public Join $join, public IndexBy|null $indexBy)
17 {
18 }
19
20 public function dispatch(SqlWalker $walker): string
21 {
22 return $walker->walkJoinVariableDeclaration($this);
23 }
24}
diff --git a/vendor/doctrine/orm/src/Query/AST/LikeExpression.php b/vendor/doctrine/orm/src/Query/AST/LikeExpression.php
new file mode 100644
index 0000000..e3f67f8
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/LikeExpression.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\AST\Functions\FunctionNode;
8use Doctrine\ORM\Query\SqlWalker;
9
10/**
11 * LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char]
12 *
13 * @link www.doctrine-project.org
14 */
15class LikeExpression extends Node
16{
17 public function __construct(
18 public Node|string $stringExpression,
19 public InputParameter|FunctionNode|PathExpression|Literal $stringPattern,
20 public Literal|null $escapeChar = null,
21 public bool $not = false,
22 ) {
23 }
24
25 public function dispatch(SqlWalker $walker): string
26 {
27 return $walker->walkLikeExpression($this);
28 }
29}
diff --git a/vendor/doctrine/orm/src/Query/AST/Literal.php b/vendor/doctrine/orm/src/Query/AST/Literal.php
new file mode 100644
index 0000000..9ec2036
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Literal.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9class Literal extends Node
10{
11 final public const STRING = 1;
12 final public const BOOLEAN = 2;
13 final public const NUMERIC = 3;
14
15 /** @psalm-param self::* $type */
16 public function __construct(
17 public int $type,
18 public mixed $value,
19 ) {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkLiteral($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/NewObjectExpression.php b/vendor/doctrine/orm/src/Query/AST/NewObjectExpression.php
new file mode 100644
index 0000000..7383c48
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/NewObjectExpression.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")"
11 *
12 * @link www.doctrine-project.org
13 */
14class NewObjectExpression extends Node
15{
16 /** @param mixed[] $args */
17 public function __construct(public string $className, public array $args)
18 {
19 }
20
21 public function dispatch(SqlWalker $walker): string
22 {
23 return $walker->walkNewObject($this);
24 }
25}
diff --git a/vendor/doctrine/orm/src/Query/AST/Node.php b/vendor/doctrine/orm/src/Query/AST/Node.php
new file mode 100644
index 0000000..cdb5855
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Node.php
@@ -0,0 +1,85 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8use Stringable;
9
10use function get_debug_type;
11use function get_object_vars;
12use function is_array;
13use function is_object;
14use function str_repeat;
15use function var_export;
16
17use const PHP_EOL;
18
19/**
20 * Abstract class of an AST node.
21 *
22 * @link www.doctrine-project.org
23 */
24abstract class Node implements Stringable
25{
26 /**
27 * Double-dispatch method, supposed to dispatch back to the walker.
28 *
29 * Implementation is not mandatory for all nodes.
30 *
31 * @throws ASTException
32 */
33 public function dispatch(SqlWalker $walker): string
34 {
35 throw ASTException::noDispatchForNode($this);
36 }
37
38 /**
39 * Dumps the AST Node into a string representation for information purpose only.
40 */
41 public function __toString(): string
42 {
43 return $this->dump($this);
44 }
45
46 public function dump(mixed $value): string
47 {
48 static $ident = 0;
49
50 $str = '';
51
52 if ($value instanceof Node) {
53 $str .= get_debug_type($value) . '(' . PHP_EOL;
54 $props = get_object_vars($value);
55
56 foreach ($props as $name => $prop) {
57 $ident += 4;
58 $str .= str_repeat(' ', $ident) . '"' . $name . '": '
59 . $this->dump($prop) . ',' . PHP_EOL;
60 $ident -= 4;
61 }
62
63 $str .= str_repeat(' ', $ident) . ')';
64 } elseif (is_array($value)) {
65 $ident += 4;
66 $str .= 'array(';
67 $some = false;
68
69 foreach ($value as $k => $v) {
70 $str .= PHP_EOL . str_repeat(' ', $ident) . '"'
71 . $k . '" => ' . $this->dump($v) . ',';
72 $some = true;
73 }
74
75 $ident -= 4;
76 $str .= ($some ? PHP_EOL . str_repeat(' ', $ident) : '') . ')';
77 } elseif (is_object($value)) {
78 $str .= 'instanceof(' . get_debug_type($value) . ')';
79 } else {
80 $str .= var_export($value, true);
81 }
82
83 return $str;
84 }
85}
diff --git a/vendor/doctrine/orm/src/Query/AST/NullComparisonExpression.php b/vendor/doctrine/orm/src/Query/AST/NullComparisonExpression.php
new file mode 100644
index 0000000..e60cb04
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/NullComparisonExpression.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
11 *
12 * @link www.doctrine-project.org
13 */
14class NullComparisonExpression extends Node
15{
16 public function __construct(
17 public Node|string $expression,
18 public bool $not = false,
19 ) {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkNullComparisonExpression($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/NullIfExpression.php b/vendor/doctrine/orm/src/Query/AST/NullIfExpression.php
new file mode 100644
index 0000000..6fffeeb
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/NullIfExpression.php
@@ -0,0 +1,24 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
11 *
12 * @link www.doctrine-project.org
13 */
14class NullIfExpression extends Node
15{
16 public function __construct(public mixed $firstExpression, public mixed $secondExpression)
17 {
18 }
19
20 public function dispatch(SqlWalker $walker): string
21 {
22 return $walker->walkNullIfExpression($this);
23 }
24}
diff --git a/vendor/doctrine/orm/src/Query/AST/OrderByClause.php b/vendor/doctrine/orm/src/Query/AST/OrderByClause.php
new file mode 100644
index 0000000..f6d7a67
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/OrderByClause.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
11 *
12 * @link www.doctrine-project.org
13 */
14class OrderByClause extends Node
15{
16 /** @param OrderByItem[] $orderByItems */
17 public function __construct(public array $orderByItems)
18 {
19 }
20
21 public function dispatch(SqlWalker $walker): string
22 {
23 return $walker->walkOrderByClause($this);
24 }
25}
diff --git a/vendor/doctrine/orm/src/Query/AST/OrderByItem.php b/vendor/doctrine/orm/src/Query/AST/OrderByItem.php
new file mode 100644
index 0000000..64b3f40
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/OrderByItem.php
@@ -0,0 +1,38 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9use function strtoupper;
10
11/**
12 * OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"]
13 *
14 * @link www.doctrine-project.org
15 */
16class OrderByItem extends Node
17{
18 public string $type;
19
20 public function __construct(public mixed $expression)
21 {
22 }
23
24 public function isAsc(): bool
25 {
26 return strtoupper($this->type) === 'ASC';
27 }
28
29 public function isDesc(): bool
30 {
31 return strtoupper($this->type) === 'DESC';
32 }
33
34 public function dispatch(SqlWalker $walker): string
35 {
36 return $walker->walkOrderByItem($this);
37 }
38}
diff --git a/vendor/doctrine/orm/src/Query/AST/ParenthesisExpression.php b/vendor/doctrine/orm/src/Query/AST/ParenthesisExpression.php
new file mode 100644
index 0000000..cda6d19
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/ParenthesisExpression.php
@@ -0,0 +1,22 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * ParenthesisExpression ::= "(" ArithmeticPrimary ")"
11 */
12class ParenthesisExpression extends Node
13{
14 public function __construct(public Node $expression)
15 {
16 }
17
18 public function dispatch(SqlWalker $walker): string
19 {
20 return $walker->walkParenthesisExpression($this);
21 }
22}
diff --git a/vendor/doctrine/orm/src/Query/AST/PathExpression.php b/vendor/doctrine/orm/src/Query/AST/PathExpression.php
new file mode 100644
index 0000000..4a56fcd
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/PathExpression.php
@@ -0,0 +1,39 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
11 * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
12 * StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression
13 * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
14 * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
15 * StateField ::= {EmbeddedClassStateField "."}* SimpleStateField
16 * SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField
17 */
18class PathExpression extends Node
19{
20 final public const TYPE_COLLECTION_VALUED_ASSOCIATION = 2;
21 final public const TYPE_SINGLE_VALUED_ASSOCIATION = 4;
22 final public const TYPE_STATE_FIELD = 8;
23
24 /** @psalm-var self::TYPE_*|null */
25 public int|null $type = null;
26
27 /** @psalm-param int-mask-of<self::TYPE_*> $expectedType */
28 public function __construct(
29 public int $expectedType,
30 public string $identificationVariable,
31 public string|null $field = null,
32 ) {
33 }
34
35 public function dispatch(SqlWalker $walker): string
36 {
37 return $walker->walkPathExpression($this);
38 }
39}
diff --git a/vendor/doctrine/orm/src/Query/AST/Phase2OptimizableConditional.php b/vendor/doctrine/orm/src/Query/AST/Phase2OptimizableConditional.php
new file mode 100644
index 0000000..276f8f8
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Phase2OptimizableConditional.php
@@ -0,0 +1,17 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7/**
8 * Marks types that can be used in place of a ConditionalExpression as a phase
9 * 2 optimization.
10 *
11 * @internal
12 *
13 * @psalm-inheritors ConditionalPrimary|ConditionalFactor|ConditionalTerm
14 */
15interface Phase2OptimizableConditional
16{
17}
diff --git a/vendor/doctrine/orm/src/Query/AST/QuantifiedExpression.php b/vendor/doctrine/orm/src/Query/AST/QuantifiedExpression.php
new file mode 100644
index 0000000..90331cd
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/QuantifiedExpression.php
@@ -0,0 +1,43 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9use function strtoupper;
10
11/**
12 * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
13 *
14 * @link www.doctrine-project.org
15 */
16class QuantifiedExpression extends Node
17{
18 public string $type;
19
20 public function __construct(public Subselect $subselect)
21 {
22 }
23
24 public function isAll(): bool
25 {
26 return strtoupper($this->type) === 'ALL';
27 }
28
29 public function isAny(): bool
30 {
31 return strtoupper($this->type) === 'ANY';
32 }
33
34 public function isSome(): bool
35 {
36 return strtoupper($this->type) === 'SOME';
37 }
38
39 public function dispatch(SqlWalker $walker): string
40 {
41 return $walker->walkQuantifiedExpression($this);
42 }
43}
diff --git a/vendor/doctrine/orm/src/Query/AST/RangeVariableDeclaration.php b/vendor/doctrine/orm/src/Query/AST/RangeVariableDeclaration.php
new file mode 100644
index 0000000..59bd5c8
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/RangeVariableDeclaration.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
11 *
12 * @link www.doctrine-project.org
13 */
14class RangeVariableDeclaration extends Node
15{
16 public function __construct(
17 public string $abstractSchemaName,
18 public string $aliasIdentificationVariable,
19 public bool $isRoot = true,
20 ) {
21 }
22
23 public function dispatch(SqlWalker $walker): string
24 {
25 return $walker->walkRangeVariableDeclaration($this);
26 }
27}
diff --git a/vendor/doctrine/orm/src/Query/AST/SelectClause.php b/vendor/doctrine/orm/src/Query/AST/SelectClause.php
new file mode 100644
index 0000000..ad50e67
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/SelectClause.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * SelectClause = "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
11 *
12 * @link www.doctrine-project.org
13 */
14class SelectClause extends Node
15{
16 /** @param mixed[] $selectExpressions */
17 public function __construct(
18 public array $selectExpressions,
19 public bool $isDistinct,
20 ) {
21 }
22
23 public function dispatch(SqlWalker $walker): string
24 {
25 return $walker->walkSelectClause($this);
26 }
27}
diff --git a/vendor/doctrine/orm/src/Query/AST/SelectExpression.php b/vendor/doctrine/orm/src/Query/AST/SelectExpression.php
new file mode 100644
index 0000000..f09f3cd
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/SelectExpression.php
@@ -0,0 +1,28 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression |
11 * (AggregateExpression | "(" Subselect ")") [["AS"] ["HIDDEN"] FieldAliasIdentificationVariable]
12 *
13 * @link www.doctrine-project.org
14 */
15class SelectExpression extends Node
16{
17 public function __construct(
18 public mixed $expression,
19 public string|null $fieldIdentificationVariable,
20 public bool $hiddenAliasResultVariable = false,
21 ) {
22 }
23
24 public function dispatch(SqlWalker $walker): string
25 {
26 return $walker->walkSelectExpression($this);
27 }
28}
diff --git a/vendor/doctrine/orm/src/Query/AST/SelectStatement.php b/vendor/doctrine/orm/src/Query/AST/SelectStatement.php
new file mode 100644
index 0000000..399462f
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/SelectStatement.php
@@ -0,0 +1,32 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * SelectStatement = SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
11 *
12 * @link www.doctrine-project.org
13 */
14class SelectStatement extends Node
15{
16 public WhereClause|null $whereClause = null;
17
18 public GroupByClause|null $groupByClause = null;
19
20 public HavingClause|null $havingClause = null;
21
22 public OrderByClause|null $orderByClause = null;
23
24 public function __construct(public SelectClause $selectClause, public FromClause $fromClause)
25 {
26 }
27
28 public function dispatch(SqlWalker $walker): string
29 {
30 return $walker->walkSelectStatement($this);
31 }
32}
diff --git a/vendor/doctrine/orm/src/Query/AST/SimpleArithmeticExpression.php b/vendor/doctrine/orm/src/Query/AST/SimpleArithmeticExpression.php
new file mode 100644
index 0000000..ae7ca44
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/SimpleArithmeticExpression.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
11 *
12 * @link www.doctrine-project.org
13 */
14class SimpleArithmeticExpression extends Node
15{
16 /** @param mixed[] $arithmeticTerms */
17 public function __construct(public array $arithmeticTerms)
18 {
19 }
20
21 public function dispatch(SqlWalker $walker): string
22 {
23 return $walker->walkSimpleArithmeticExpression($this);
24 }
25}
diff --git a/vendor/doctrine/orm/src/Query/AST/SimpleCaseExpression.php b/vendor/doctrine/orm/src/Query/AST/SimpleCaseExpression.php
new file mode 100644
index 0000000..b3764ba
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/SimpleCaseExpression.php
@@ -0,0 +1,28 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
11 *
12 * @link www.doctrine-project.org
13 */
14class SimpleCaseExpression extends Node
15{
16 /** @param mixed[] $simpleWhenClauses */
17 public function __construct(
18 public PathExpression|null $caseOperand = null,
19 public array $simpleWhenClauses = [],
20 public mixed $elseScalarExpression = null,
21 ) {
22 }
23
24 public function dispatch(SqlWalker $walker): string
25 {
26 return $walker->walkSimpleCaseExpression($this);
27 }
28}
diff --git a/vendor/doctrine/orm/src/Query/AST/SimpleSelectClause.php b/vendor/doctrine/orm/src/Query/AST/SimpleSelectClause.php
new file mode 100644
index 0000000..0259e3b
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/SimpleSelectClause.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
11 *
12 * @link www.doctrine-project.org
13 */
14class SimpleSelectClause extends Node
15{
16 public function __construct(
17 public SimpleSelectExpression $simpleSelectExpression,
18 public bool $isDistinct = false,
19 ) {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkSimpleSelectClause($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/SimpleSelectExpression.php b/vendor/doctrine/orm/src/Query/AST/SimpleSelectExpression.php
new file mode 100644
index 0000000..97e8f08
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/SimpleSelectExpression.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable
11 * | (AggregateExpression [["AS"] FieldAliasIdentificationVariable])
12 *
13 * @link www.doctrine-project.org
14 */
15class SimpleSelectExpression extends Node
16{
17 public string|null $fieldIdentificationVariable = null;
18
19 public function __construct(public Node|string $expression)
20 {
21 }
22
23 public function dispatch(SqlWalker $walker): string
24 {
25 return $walker->walkSimpleSelectExpression($this);
26 }
27}
diff --git a/vendor/doctrine/orm/src/Query/AST/SimpleWhenClause.php b/vendor/doctrine/orm/src/Query/AST/SimpleWhenClause.php
new file mode 100644
index 0000000..892165a
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/SimpleWhenClause.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
11 *
12 * @link www.doctrine-project.org
13 */
14class SimpleWhenClause extends Node
15{
16 public function __construct(
17 public mixed $caseScalarExpression = null,
18 public mixed $thenScalarExpression = null,
19 ) {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkWhenClauseExpression($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/Subselect.php b/vendor/doctrine/orm/src/Query/AST/Subselect.php
new file mode 100644
index 0000000..8ff8595
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/Subselect.php
@@ -0,0 +1,32 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
11 *
12 * @link www.doctrine-project.org
13 */
14class Subselect extends Node
15{
16 public WhereClause|null $whereClause = null;
17
18 public GroupByClause|null $groupByClause = null;
19
20 public HavingClause|null $havingClause = null;
21
22 public OrderByClause|null $orderByClause = null;
23
24 public function __construct(public SimpleSelectClause $simpleSelectClause, public SubselectFromClause $subselectFromClause)
25 {
26 }
27
28 public function dispatch(SqlWalker $walker): string
29 {
30 return $walker->walkSubselect($this);
31 }
32}
diff --git a/vendor/doctrine/orm/src/Query/AST/SubselectFromClause.php b/vendor/doctrine/orm/src/Query/AST/SubselectFromClause.php
new file mode 100644
index 0000000..7cf01e2
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/SubselectFromClause.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
11 *
12 * @link www.doctrine-project.org
13 */
14class SubselectFromClause extends Node
15{
16 /** @param mixed[] $identificationVariableDeclarations */
17 public function __construct(public array $identificationVariableDeclarations)
18 {
19 }
20
21 public function dispatch(SqlWalker $walker): string
22 {
23 return $walker->walkSubselectFromClause($this);
24 }
25}
diff --git a/vendor/doctrine/orm/src/Query/AST/SubselectIdentificationVariableDeclaration.php b/vendor/doctrine/orm/src/Query/AST/SubselectIdentificationVariableDeclaration.php
new file mode 100644
index 0000000..eadf6bc
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/SubselectIdentificationVariableDeclaration.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7/**
8 * SubselectIdentificationVariableDeclaration ::= AssociationPathExpression ["AS"] AliasIdentificationVariable
9 *
10 * @link www.doctrine-project.org
11 */
12class SubselectIdentificationVariableDeclaration
13{
14 public function __construct(
15 public PathExpression $associationPathExpression,
16 public string $aliasIdentificationVariable,
17 ) {
18 }
19}
diff --git a/vendor/doctrine/orm/src/Query/AST/TypedExpression.php b/vendor/doctrine/orm/src/Query/AST/TypedExpression.php
new file mode 100644
index 0000000..d7cf76f
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/TypedExpression.php
@@ -0,0 +1,15 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\DBAL\Types\Type;
8
9/**
10 * Provides an API for resolving the type of a Node
11 */
12interface TypedExpression
13{
14 public function getReturnType(): Type;
15}
diff --git a/vendor/doctrine/orm/src/Query/AST/UpdateClause.php b/vendor/doctrine/orm/src/Query/AST/UpdateClause.php
new file mode 100644
index 0000000..cafcff0
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/UpdateClause.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}*
11 *
12 * @link www.doctrine-project.org
13 */
14class UpdateClause extends Node
15{
16 public string $aliasIdentificationVariable;
17
18 /** @param mixed[] $updateItems */
19 public function __construct(
20 public string $abstractSchemaName,
21 public array $updateItems,
22 ) {
23 }
24
25 public function dispatch(SqlWalker $walker): string
26 {
27 return $walker->walkUpdateClause($this);
28 }
29}
diff --git a/vendor/doctrine/orm/src/Query/AST/UpdateItem.php b/vendor/doctrine/orm/src/Query/AST/UpdateItem.php
new file mode 100644
index 0000000..b540593
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/UpdateItem.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * UpdateItem ::= [IdentificationVariable "."] {StateField | SingleValuedAssociationField} "=" NewValue
11 * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
12 * EnumPrimary | SimpleEntityExpression | "NULL"
13 *
14 * @link www.doctrine-project.org
15 */
16class UpdateItem extends Node
17{
18 public function __construct(public PathExpression $pathExpression, public InputParameter|ArithmeticExpression|null $newValue)
19 {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkUpdateItem($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/UpdateStatement.php b/vendor/doctrine/orm/src/Query/AST/UpdateStatement.php
new file mode 100644
index 0000000..7ea5076
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/UpdateStatement.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * UpdateStatement = UpdateClause [WhereClause]
11 *
12 * @link www.doctrine-project.org
13 */
14class UpdateStatement extends Node
15{
16 public WhereClause|null $whereClause = null;
17
18 public function __construct(public UpdateClause $updateClause)
19 {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkUpdateStatement($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/WhenClause.php b/vendor/doctrine/orm/src/Query/AST/WhenClause.php
new file mode 100644
index 0000000..9bf194e
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/WhenClause.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
11 *
12 * @link www.doctrine-project.org
13 */
14class WhenClause extends Node
15{
16 public function __construct(
17 public ConditionalExpression|Phase2OptimizableConditional $caseConditionExpression,
18 public mixed $thenScalarExpression = null,
19 ) {
20 }
21
22 public function dispatch(SqlWalker $walker): string
23 {
24 return $walker->walkWhenClauseExpression($this);
25 }
26}
diff --git a/vendor/doctrine/orm/src/Query/AST/WhereClause.php b/vendor/doctrine/orm/src/Query/AST/WhereClause.php
new file mode 100644
index 0000000..e4d7b66
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/AST/WhereClause.php
@@ -0,0 +1,24 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\AST;
6
7use Doctrine\ORM\Query\SqlWalker;
8
9/**
10 * WhereClause ::= "WHERE" ConditionalExpression
11 *
12 * @link www.doctrine-project.org
13 */
14class WhereClause extends Node
15{
16 public function __construct(public ConditionalExpression|Phase2OptimizableConditional $conditionalExpression)
17 {
18 }
19
20 public function dispatch(SqlWalker $walker): string
21 {
22 return $walker->walkWhereClause($this);
23 }
24}
diff --git a/vendor/doctrine/orm/src/Query/Exec/AbstractSqlExecutor.php b/vendor/doctrine/orm/src/Query/Exec/AbstractSqlExecutor.php
new file mode 100644
index 0000000..101bf26
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Exec/AbstractSqlExecutor.php
@@ -0,0 +1,61 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Exec;
6
7use Doctrine\DBAL\ArrayParameterType;
8use Doctrine\DBAL\Cache\QueryCacheProfile;
9use Doctrine\DBAL\Connection;
10use Doctrine\DBAL\ParameterType;
11use Doctrine\DBAL\Result;
12use Doctrine\DBAL\Types\Type;
13
14/**
15 * Base class for SQL statement executors.
16 *
17 * @link http://www.doctrine-project.org
18 *
19 * @todo Rename: AbstractSQLExecutor
20 * @psalm-type WrapperParameterType = string|Type|ParameterType::*|ArrayParameterType::*
21 * @psalm-type WrapperParameterTypeArray = array<int<0, max>, WrapperParameterType>|array<string, WrapperParameterType>
22 */
23abstract class AbstractSqlExecutor
24{
25 /** @var list<string>|string */
26 protected array|string $sqlStatements;
27
28 protected QueryCacheProfile|null $queryCacheProfile = null;
29
30 /**
31 * Gets the SQL statements that are executed by the executor.
32 *
33 * @return list<string>|string All the SQL update statements.
34 */
35 public function getSqlStatements(): array|string
36 {
37 return $this->sqlStatements;
38 }
39
40 public function setQueryCacheProfile(QueryCacheProfile $qcp): void
41 {
42 $this->queryCacheProfile = $qcp;
43 }
44
45 /**
46 * Do not use query cache
47 */
48 public function removeQueryCacheProfile(): void
49 {
50 $this->queryCacheProfile = null;
51 }
52
53 /**
54 * Executes all sql statements.
55 *
56 * @param Connection $conn The database connection that is used to execute the queries.
57 * @param list<mixed>|array<string, mixed> $params The parameters.
58 * @psalm-param WrapperParameterTypeArray $types The parameter types.
59 */
60 abstract public function execute(Connection $conn, array $params, array $types): Result|int;
61}
diff --git a/vendor/doctrine/orm/src/Query/Exec/MultiTableDeleteExecutor.php b/vendor/doctrine/orm/src/Query/Exec/MultiTableDeleteExecutor.php
new file mode 100644
index 0000000..6096462
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Exec/MultiTableDeleteExecutor.php
@@ -0,0 +1,131 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Exec;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
9use Doctrine\DBAL\Types\Type;
10use Doctrine\ORM\Query\AST;
11use Doctrine\ORM\Query\AST\DeleteStatement;
12use Doctrine\ORM\Query\SqlWalker;
13use Doctrine\ORM\Utility\PersisterHelper;
14use Throwable;
15
16use function array_reverse;
17use function implode;
18
19/**
20 * Executes the SQL statements for bulk DQL DELETE statements on classes in
21 * Class Table Inheritance (JOINED).
22 *
23 * @link http://www.doctrine-project.org
24 */
25class MultiTableDeleteExecutor extends AbstractSqlExecutor
26{
27 private readonly string $createTempTableSql;
28 private readonly string $dropTempTableSql;
29 private readonly string $insertSql;
30
31 /**
32 * Initializes a new <tt>MultiTableDeleteExecutor</tt>.
33 *
34 * Internal note: Any SQL construction and preparation takes place in the constructor for
35 * best performance. With a query cache the executor will be cached.
36 *
37 * @param DeleteStatement $AST The root AST node of the DQL query.
38 * @param SqlWalker $sqlWalker The walker used for SQL generation from the AST.
39 */
40 public function __construct(AST\Node $AST, SqlWalker $sqlWalker)
41 {
42 $em = $sqlWalker->getEntityManager();
43 $conn = $em->getConnection();
44 $platform = $conn->getDatabasePlatform();
45 $quoteStrategy = $em->getConfiguration()->getQuoteStrategy();
46
47 if ($conn instanceof PrimaryReadReplicaConnection) {
48 $conn->ensureConnectedToPrimary();
49 }
50
51 $primaryClass = $em->getClassMetadata($AST->deleteClause->abstractSchemaName);
52 $primaryDqlAlias = $AST->deleteClause->aliasIdentificationVariable;
53 $rootClass = $em->getClassMetadata($primaryClass->rootEntityName);
54
55 $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName());
56 $idColumnNames = $rootClass->getIdentifierColumnNames();
57 $idColumnList = implode(', ', $idColumnNames);
58
59 // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
60 $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $primaryDqlAlias);
61
62 $insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
63 . ' SELECT t0.' . implode(', t0.', $idColumnNames);
64
65 $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $primaryDqlAlias);
66 $fromClause = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]);
67 $insertSql .= $sqlWalker->walkFromClause($fromClause);
68
69 // Append WHERE clause, if there is one.
70 if ($AST->whereClause) {
71 $insertSql .= $sqlWalker->walkWhereClause($AST->whereClause);
72 }
73
74 $this->insertSql = $insertSql;
75
76 // 2. Create ID subselect statement used in DELETE ... WHERE ... IN (subselect)
77 $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable;
78
79 // 3. Create and store DELETE statements
80 $classNames = [...$primaryClass->parentClasses, ...[$primaryClass->name], ...$primaryClass->subClasses];
81 foreach (array_reverse($classNames) as $className) {
82 $tableName = $quoteStrategy->getTableName($em->getClassMetadata($className), $platform);
83 $this->sqlStatements[] = 'DELETE FROM ' . $tableName
84 . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')';
85 }
86
87 // 4. Store DDL for temporary identifier table.
88 $columnDefinitions = [];
89 foreach ($idColumnNames as $idColumnName) {
90 $columnDefinitions[$idColumnName] = [
91 'name' => $idColumnName,
92 'notnull' => true,
93 'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $em)),
94 ];
95 }
96
97 $this->createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
98 . $platform->getColumnDeclarationListSQL($columnDefinitions) . ', PRIMARY KEY(' . implode(',', $idColumnNames) . '))';
99 $this->dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
100 }
101
102 /**
103 * {@inheritDoc}
104 */
105 public function execute(Connection $conn, array $params, array $types): int
106 {
107 // Create temporary id table
108 $conn->executeStatement($this->createTempTableSql);
109
110 try {
111 // Insert identifiers
112 $numDeleted = $conn->executeStatement($this->insertSql, $params, $types);
113
114 // Execute DELETE statements
115 foreach ($this->sqlStatements as $sql) {
116 $conn->executeStatement($sql);
117 }
118 } catch (Throwable $exception) {
119 // FAILURE! Drop temporary table to avoid possible collisions
120 $conn->executeStatement($this->dropTempTableSql);
121
122 // Re-throw exception
123 throw $exception;
124 }
125
126 // Drop temporary table
127 $conn->executeStatement($this->dropTempTableSql);
128
129 return $numDeleted;
130 }
131}
diff --git a/vendor/doctrine/orm/src/Query/Exec/MultiTableUpdateExecutor.php b/vendor/doctrine/orm/src/Query/Exec/MultiTableUpdateExecutor.php
new file mode 100644
index 0000000..dab1b61
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Exec/MultiTableUpdateExecutor.php
@@ -0,0 +1,180 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Exec;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
9use Doctrine\DBAL\Types\Type;
10use Doctrine\ORM\Query\AST;
11use Doctrine\ORM\Query\AST\UpdateStatement;
12use Doctrine\ORM\Query\ParameterTypeInferer;
13use Doctrine\ORM\Query\SqlWalker;
14use Doctrine\ORM\Utility\PersisterHelper;
15
16use function array_reverse;
17use function array_slice;
18use function implode;
19
20/**
21 * Executes the SQL statements for bulk DQL UPDATE statements on classes in
22 * Class Table Inheritance (JOINED).
23 */
24class MultiTableUpdateExecutor extends AbstractSqlExecutor
25{
26 private readonly string $createTempTableSql;
27 private readonly string $dropTempTableSql;
28 private readonly string $insertSql;
29
30 /** @var mixed[] */
31 private array $sqlParameters = [];
32 private int $numParametersInUpdateClause = 0;
33
34 /**
35 * Initializes a new <tt>MultiTableUpdateExecutor</tt>.
36 *
37 * Internal note: Any SQL construction and preparation takes place in the constructor for
38 * best performance. With a query cache the executor will be cached.
39 *
40 * @param UpdateStatement $AST The root AST node of the DQL query.
41 * @param SqlWalker $sqlWalker The walker used for SQL generation from the AST.
42 */
43 public function __construct(AST\Node $AST, SqlWalker $sqlWalker)
44 {
45 $em = $sqlWalker->getEntityManager();
46 $conn = $em->getConnection();
47 $platform = $conn->getDatabasePlatform();
48 $quoteStrategy = $em->getConfiguration()->getQuoteStrategy();
49 $this->sqlStatements = [];
50
51 if ($conn instanceof PrimaryReadReplicaConnection) {
52 $conn->ensureConnectedToPrimary();
53 }
54
55 $updateClause = $AST->updateClause;
56 $primaryClass = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName);
57 $rootClass = $em->getClassMetadata($primaryClass->rootEntityName);
58
59 $updateItems = $updateClause->updateItems;
60
61 $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName());
62 $idColumnNames = $rootClass->getIdentifierColumnNames();
63 $idColumnList = implode(', ', $idColumnNames);
64
65 // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
66 $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $updateClause->aliasIdentificationVariable);
67
68 $insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
69 . ' SELECT t0.' . implode(', t0.', $idColumnNames);
70
71 $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $updateClause->aliasIdentificationVariable);
72 $fromClause = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]);
73
74 $insertSql .= $sqlWalker->walkFromClause($fromClause);
75
76 // 2. Create ID subselect statement used in UPDATE ... WHERE ... IN (subselect)
77 $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable;
78
79 // 3. Create and store UPDATE statements
80 $classNames = [...$primaryClass->parentClasses, ...[$primaryClass->name], ...$primaryClass->subClasses];
81
82 foreach (array_reverse($classNames) as $className) {
83 $affected = false;
84 $class = $em->getClassMetadata($className);
85 $updateSql = 'UPDATE ' . $quoteStrategy->getTableName($class, $platform) . ' SET ';
86
87 $sqlParameters = [];
88 foreach ($updateItems as $updateItem) {
89 $field = $updateItem->pathExpression->field;
90
91 if (
92 (isset($class->fieldMappings[$field]) && ! isset($class->fieldMappings[$field]->inherited)) ||
93 (isset($class->associationMappings[$field]) && ! isset($class->associationMappings[$field]->inherited))
94 ) {
95 $newValue = $updateItem->newValue;
96
97 if (! $affected) {
98 $affected = true;
99 } else {
100 $updateSql .= ', ';
101 }
102
103 $updateSql .= $sqlWalker->walkUpdateItem($updateItem);
104
105 if ($newValue instanceof AST\InputParameter) {
106 $sqlParameters[] = $newValue->name;
107
108 ++$this->numParametersInUpdateClause;
109 }
110 }
111 }
112
113 if ($affected) {
114 $this->sqlParameters[] = $sqlParameters;
115 $this->sqlStatements[] = $updateSql . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')';
116 }
117 }
118
119 // Append WHERE clause to insertSql, if there is one.
120 if ($AST->whereClause) {
121 $insertSql .= $sqlWalker->walkWhereClause($AST->whereClause);
122 }
123
124 $this->insertSql = $insertSql;
125
126 // 4. Store DDL for temporary identifier table.
127 $columnDefinitions = [];
128
129 foreach ($idColumnNames as $idColumnName) {
130 $columnDefinitions[$idColumnName] = [
131 'name' => $idColumnName,
132 'notnull' => true,
133 'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $em)),
134 ];
135 }
136
137 $this->createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
138 . $platform->getColumnDeclarationListSQL($columnDefinitions) . ', PRIMARY KEY(' . implode(',', $idColumnNames) . '))';
139
140 $this->dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
141 }
142
143 /**
144 * {@inheritDoc}
145 */
146 public function execute(Connection $conn, array $params, array $types): int
147 {
148 // Create temporary id table
149 $conn->executeStatement($this->createTempTableSql);
150
151 try {
152 // Insert identifiers. Parameters from the update clause are cut off.
153 $numUpdated = $conn->executeStatement(
154 $this->insertSql,
155 array_slice($params, $this->numParametersInUpdateClause),
156 array_slice($types, $this->numParametersInUpdateClause),
157 );
158
159 // Execute UPDATE statements
160 foreach ($this->sqlStatements as $key => $statement) {
161 $paramValues = [];
162 $paramTypes = [];
163
164 if (isset($this->sqlParameters[$key])) {
165 foreach ($this->sqlParameters[$key] as $parameterKey => $parameterName) {
166 $paramValues[] = $params[$parameterKey];
167 $paramTypes[] = $types[$parameterKey] ?? ParameterTypeInferer::inferType($params[$parameterKey]);
168 }
169 }
170
171 $conn->executeStatement($statement, $paramValues, $paramTypes);
172 }
173 } finally {
174 // Drop temporary table
175 $conn->executeStatement($this->dropTempTableSql);
176 }
177
178 return $numUpdated;
179 }
180}
diff --git a/vendor/doctrine/orm/src/Query/Exec/SingleSelectExecutor.php b/vendor/doctrine/orm/src/Query/Exec/SingleSelectExecutor.php
new file mode 100644
index 0000000..5445edb
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Exec/SingleSelectExecutor.php
@@ -0,0 +1,31 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Exec;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Result;
9use Doctrine\ORM\Query\AST\SelectStatement;
10use Doctrine\ORM\Query\SqlWalker;
11
12/**
13 * Executor that executes the SQL statement for simple DQL SELECT statements.
14 *
15 * @link www.doctrine-project.org
16 */
17class SingleSelectExecutor extends AbstractSqlExecutor
18{
19 public function __construct(SelectStatement $AST, SqlWalker $sqlWalker)
20 {
21 $this->sqlStatements = $sqlWalker->walkSelectStatement($AST);
22 }
23
24 /**
25 * {@inheritDoc}
26 */
27 public function execute(Connection $conn, array $params, array $types): Result
28 {
29 return $conn->executeQuery($this->sqlStatements, $params, $types, $this->queryCacheProfile);
30 }
31}
diff --git a/vendor/doctrine/orm/src/Query/Exec/SingleTableDeleteUpdateExecutor.php b/vendor/doctrine/orm/src/Query/Exec/SingleTableDeleteUpdateExecutor.php
new file mode 100644
index 0000000..66696db
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Exec/SingleTableDeleteUpdateExecutor.php
@@ -0,0 +1,42 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Exec;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
9use Doctrine\ORM\Query\AST;
10use Doctrine\ORM\Query\SqlWalker;
11
12/**
13 * Executor that executes the SQL statements for DQL DELETE/UPDATE statements on classes
14 * that are mapped to a single table.
15 *
16 * @link www.doctrine-project.org
17 *
18 * @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor.
19 */
20class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor
21{
22 public function __construct(AST\Node $AST, SqlWalker $sqlWalker)
23 {
24 if ($AST instanceof AST\UpdateStatement) {
25 $this->sqlStatements = $sqlWalker->walkUpdateStatement($AST);
26 } elseif ($AST instanceof AST\DeleteStatement) {
27 $this->sqlStatements = $sqlWalker->walkDeleteStatement($AST);
28 }
29 }
30
31 /**
32 * {@inheritDoc}
33 */
34 public function execute(Connection $conn, array $params, array $types): int
35 {
36 if ($conn instanceof PrimaryReadReplicaConnection) {
37 $conn->ensureConnectedToPrimary();
38 }
39
40 return $conn->executeStatement($this->sqlStatements, $params, $types);
41 }
42}
diff --git a/vendor/doctrine/orm/src/Query/Expr.php b/vendor/doctrine/orm/src/Query/Expr.php
new file mode 100644
index 0000000..65f3082
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr.php
@@ -0,0 +1,615 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use Doctrine\ORM\Internal\NoUnknownNamedArguments;
8use Traversable;
9
10use function implode;
11use function is_bool;
12use function is_float;
13use function is_int;
14use function is_iterable;
15use function iterator_to_array;
16use function str_replace;
17
18/**
19 * This class is used to generate DQL expressions via a set of PHP static functions.
20 *
21 * @link www.doctrine-project.org
22 *
23 * @todo Rename: ExpressionBuilder
24 */
25class Expr
26{
27 use NoUnknownNamedArguments;
28
29 /**
30 * Creates a conjunction of the given boolean expressions.
31 *
32 * Example:
33 *
34 * [php]
35 * // (u.type = ?1) AND (u.role = ?2)
36 * $expr->andX($expr->eq('u.type', ':1'), $expr->eq('u.role', ':2'));
37 *
38 * @param Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x Optional clause. Defaults to null,
39 * but requires at least one defined
40 * when converting to string.
41 */
42 public function andX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Andx
43 {
44 self::validateVariadicParameter($x);
45
46 return new Expr\Andx($x);
47 }
48
49 /**
50 * Creates a disjunction of the given boolean expressions.
51 *
52 * Example:
53 *
54 * [php]
55 * // (u.type = ?1) OR (u.role = ?2)
56 * $q->where($q->expr()->orX('u.type = ?1', 'u.role = ?2'));
57 *
58 * @param Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x Optional clause. Defaults to null,
59 * but requires at least one defined
60 * when converting to string.
61 */
62 public function orX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Orx
63 {
64 self::validateVariadicParameter($x);
65
66 return new Expr\Orx($x);
67 }
68
69 /**
70 * Creates an ASCending order expression.
71 */
72 public function asc(mixed $expr): Expr\OrderBy
73 {
74 return new Expr\OrderBy($expr, 'ASC');
75 }
76
77 /**
78 * Creates a DESCending order expression.
79 */
80 public function desc(mixed $expr): Expr\OrderBy
81 {
82 return new Expr\OrderBy($expr, 'DESC');
83 }
84
85 /**
86 * Creates an equality comparison expression with the given arguments.
87 *
88 * First argument is considered the left expression and the second is the right expression.
89 * When converted to string, it will generated a <left expr> = <right expr>. Example:
90 *
91 * [php]
92 * // u.id = ?1
93 * $expr->eq('u.id', '?1');
94 *
95 * @param mixed $x Left expression.
96 * @param mixed $y Right expression.
97 */
98 public function eq(mixed $x, mixed $y): Expr\Comparison
99 {
100 return new Expr\Comparison($x, Expr\Comparison::EQ, $y);
101 }
102
103 /**
104 * Creates an instance of Expr\Comparison, with the given arguments.
105 * First argument is considered the left expression and the second is the right expression.
106 * When converted to string, it will generated a <left expr> <> <right expr>. Example:
107 *
108 * [php]
109 * // u.id <> ?1
110 * $q->where($q->expr()->neq('u.id', '?1'));
111 *
112 * @param mixed $x Left expression.
113 * @param mixed $y Right expression.
114 */
115 public function neq(mixed $x, mixed $y): Expr\Comparison
116 {
117 return new Expr\Comparison($x, Expr\Comparison::NEQ, $y);
118 }
119
120 /**
121 * Creates an instance of Expr\Comparison, with the given arguments.
122 * First argument is considered the left expression and the second is the right expression.
123 * When converted to string, it will generated a <left expr> < <right expr>. Example:
124 *
125 * [php]
126 * // u.id < ?1
127 * $q->where($q->expr()->lt('u.id', '?1'));
128 *
129 * @param mixed $x Left expression.
130 * @param mixed $y Right expression.
131 */
132 public function lt(mixed $x, mixed $y): Expr\Comparison
133 {
134 return new Expr\Comparison($x, Expr\Comparison::LT, $y);
135 }
136
137 /**
138 * Creates an instance of Expr\Comparison, with the given arguments.
139 * First argument is considered the left expression and the second is the right expression.
140 * When converted to string, it will generated a <left expr> <= <right expr>. Example:
141 *
142 * [php]
143 * // u.id <= ?1
144 * $q->where($q->expr()->lte('u.id', '?1'));
145 *
146 * @param mixed $x Left expression.
147 * @param mixed $y Right expression.
148 */
149 public function lte(mixed $x, mixed $y): Expr\Comparison
150 {
151 return new Expr\Comparison($x, Expr\Comparison::LTE, $y);
152 }
153
154 /**
155 * Creates an instance of Expr\Comparison, with the given arguments.
156 * First argument is considered the left expression and the second is the right expression.
157 * When converted to string, it will generated a <left expr> > <right expr>. Example:
158 *
159 * [php]
160 * // u.id > ?1
161 * $q->where($q->expr()->gt('u.id', '?1'));
162 *
163 * @param mixed $x Left expression.
164 * @param mixed $y Right expression.
165 */
166 public function gt(mixed $x, mixed $y): Expr\Comparison
167 {
168 return new Expr\Comparison($x, Expr\Comparison::GT, $y);
169 }
170
171 /**
172 * Creates an instance of Expr\Comparison, with the given arguments.
173 * First argument is considered the left expression and the second is the right expression.
174 * When converted to string, it will generated a <left expr> >= <right expr>. Example:
175 *
176 * [php]
177 * // u.id >= ?1
178 * $q->where($q->expr()->gte('u.id', '?1'));
179 *
180 * @param mixed $x Left expression.
181 * @param mixed $y Right expression.
182 */
183 public function gte(mixed $x, mixed $y): Expr\Comparison
184 {
185 return new Expr\Comparison($x, Expr\Comparison::GTE, $y);
186 }
187
188 /**
189 * Creates an instance of AVG() function, with the given argument.
190 *
191 * @param mixed $x Argument to be used in AVG() function.
192 */
193 public function avg(mixed $x): Expr\Func
194 {
195 return new Expr\Func('AVG', [$x]);
196 }
197
198 /**
199 * Creates an instance of MAX() function, with the given argument.
200 *
201 * @param mixed $x Argument to be used in MAX() function.
202 */
203 public function max(mixed $x): Expr\Func
204 {
205 return new Expr\Func('MAX', [$x]);
206 }
207
208 /**
209 * Creates an instance of MIN() function, with the given argument.
210 *
211 * @param mixed $x Argument to be used in MIN() function.
212 */
213 public function min(mixed $x): Expr\Func
214 {
215 return new Expr\Func('MIN', [$x]);
216 }
217
218 /**
219 * Creates an instance of COUNT() function, with the given argument.
220 *
221 * @param mixed $x Argument to be used in COUNT() function.
222 */
223 public function count(mixed $x): Expr\Func
224 {
225 return new Expr\Func('COUNT', [$x]);
226 }
227
228 /**
229 * Creates an instance of COUNT(DISTINCT) function, with the given argument.
230 *
231 * @param mixed ...$x Argument to be used in COUNT(DISTINCT) function.
232 */
233 public function countDistinct(mixed ...$x): string
234 {
235 self::validateVariadicParameter($x);
236
237 return 'COUNT(DISTINCT ' . implode(', ', $x) . ')';
238 }
239
240 /**
241 * Creates an instance of EXISTS() function, with the given DQL Subquery.
242 *
243 * @param mixed $subquery DQL Subquery to be used in EXISTS() function.
244 */
245 public function exists(mixed $subquery): Expr\Func
246 {
247 return new Expr\Func('EXISTS', [$subquery]);
248 }
249
250 /**
251 * Creates an instance of ALL() function, with the given DQL Subquery.
252 *
253 * @param mixed $subquery DQL Subquery to be used in ALL() function.
254 */
255 public function all(mixed $subquery): Expr\Func
256 {
257 return new Expr\Func('ALL', [$subquery]);
258 }
259
260 /**
261 * Creates a SOME() function expression with the given DQL subquery.
262 *
263 * @param mixed $subquery DQL Subquery to be used in SOME() function.
264 */
265 public function some(mixed $subquery): Expr\Func
266 {
267 return new Expr\Func('SOME', [$subquery]);
268 }
269
270 /**
271 * Creates an ANY() function expression with the given DQL subquery.
272 *
273 * @param mixed $subquery DQL Subquery to be used in ANY() function.
274 */
275 public function any(mixed $subquery): Expr\Func
276 {
277 return new Expr\Func('ANY', [$subquery]);
278 }
279
280 /**
281 * Creates a negation expression of the given restriction.
282 *
283 * @param mixed $restriction Restriction to be used in NOT() function.
284 */
285 public function not(mixed $restriction): Expr\Func
286 {
287 return new Expr\Func('NOT', [$restriction]);
288 }
289
290 /**
291 * Creates an ABS() function expression with the given argument.
292 *
293 * @param mixed $x Argument to be used in ABS() function.
294 */
295 public function abs(mixed $x): Expr\Func
296 {
297 return new Expr\Func('ABS', [$x]);
298 }
299
300 /**
301 * Creates a MOD($x, $y) function expression to return the remainder of $x divided by $y.
302 */
303 public function mod(mixed $x, mixed $y): Expr\Func
304 {
305 return new Expr\Func('MOD', [$x, $y]);
306 }
307
308 /**
309 * Creates a product mathematical expression with the given arguments.
310 *
311 * First argument is considered the left expression and the second is the right expression.
312 * When converted to string, it will generated a <left expr> * <right expr>. Example:
313 *
314 * [php]
315 * // u.salary * u.percentAnnualSalaryIncrease
316 * $q->expr()->prod('u.salary', 'u.percentAnnualSalaryIncrease')
317 *
318 * @param mixed $x Left expression.
319 * @param mixed $y Right expression.
320 */
321 public function prod(mixed $x, mixed $y): Expr\Math
322 {
323 return new Expr\Math($x, '*', $y);
324 }
325
326 /**
327 * Creates a difference mathematical expression with the given arguments.
328 * First argument is considered the left expression and the second is the right expression.
329 * When converted to string, it will generated a <left expr> - <right expr>. Example:
330 *
331 * [php]
332 * // u.monthlySubscriptionCount - 1
333 * $q->expr()->diff('u.monthlySubscriptionCount', '1')
334 *
335 * @param mixed $x Left expression.
336 * @param mixed $y Right expression.
337 */
338 public function diff(mixed $x, mixed $y): Expr\Math
339 {
340 return new Expr\Math($x, '-', $y);
341 }
342
343 /**
344 * Creates a sum mathematical expression with the given arguments.
345 * First argument is considered the left expression and the second is the right expression.
346 * When converted to string, it will generated a <left expr> + <right expr>. Example:
347 *
348 * [php]
349 * // u.numChildren + 1
350 * $q->expr()->sum('u.numChildren', '1')
351 *
352 * @param mixed $x Left expression.
353 * @param mixed $y Right expression.
354 */
355 public function sum(mixed $x, mixed $y): Expr\Math
356 {
357 return new Expr\Math($x, '+', $y);
358 }
359
360 /**
361 * Creates a quotient mathematical expression with the given arguments.
362 * First argument is considered the left expression and the second is the right expression.
363 * When converted to string, it will generated a <left expr> / <right expr>. Example:
364 *
365 * [php]
366 * // u.total / u.period
367 * $expr->quot('u.total', 'u.period')
368 *
369 * @param mixed $x Left expression.
370 * @param mixed $y Right expression.
371 */
372 public function quot(mixed $x, mixed $y): Expr\Math
373 {
374 return new Expr\Math($x, '/', $y);
375 }
376
377 /**
378 * Creates a SQRT() function expression with the given argument.
379 *
380 * @param mixed $x Argument to be used in SQRT() function.
381 */
382 public function sqrt(mixed $x): Expr\Func
383 {
384 return new Expr\Func('SQRT', [$x]);
385 }
386
387 /**
388 * Creates an IN() expression with the given arguments.
389 *
390 * @param string $x Field in string format to be restricted by IN() function.
391 * @param mixed $y Argument to be used in IN() function.
392 */
393 public function in(string $x, mixed $y): Expr\Func
394 {
395 if (is_iterable($y)) {
396 if ($y instanceof Traversable) {
397 $y = iterator_to_array($y);
398 }
399
400 foreach ($y as &$literal) {
401 if (! ($literal instanceof Expr\Literal)) {
402 $literal = $this->quoteLiteral($literal);
403 }
404 }
405 }
406
407 return new Expr\Func($x . ' IN', (array) $y);
408 }
409
410 /**
411 * Creates a NOT IN() expression with the given arguments.
412 *
413 * @param string $x Field in string format to be restricted by NOT IN() function.
414 * @param mixed $y Argument to be used in NOT IN() function.
415 */
416 public function notIn(string $x, mixed $y): Expr\Func
417 {
418 if (is_iterable($y)) {
419 if ($y instanceof Traversable) {
420 $y = iterator_to_array($y);
421 }
422
423 foreach ($y as &$literal) {
424 if (! ($literal instanceof Expr\Literal)) {
425 $literal = $this->quoteLiteral($literal);
426 }
427 }
428 }
429
430 return new Expr\Func($x . ' NOT IN', (array) $y);
431 }
432
433 /**
434 * Creates an IS NULL expression with the given arguments.
435 *
436 * @param string $x Field in string format to be restricted by IS NULL.
437 */
438 public function isNull(string $x): string
439 {
440 return $x . ' IS NULL';
441 }
442
443 /**
444 * Creates an IS NOT NULL expression with the given arguments.
445 *
446 * @param string $x Field in string format to be restricted by IS NOT NULL.
447 */
448 public function isNotNull(string $x): string
449 {
450 return $x . ' IS NOT NULL';
451 }
452
453 /**
454 * Creates a LIKE() comparison expression with the given arguments.
455 *
456 * @param string $x Field in string format to be inspected by LIKE() comparison.
457 * @param mixed $y Argument to be used in LIKE() comparison.
458 */
459 public function like(string $x, mixed $y): Expr\Comparison
460 {
461 return new Expr\Comparison($x, 'LIKE', $y);
462 }
463
464 /**
465 * Creates a NOT LIKE() comparison expression with the given arguments.
466 *
467 * @param string $x Field in string format to be inspected by LIKE() comparison.
468 * @param mixed $y Argument to be used in LIKE() comparison.
469 */
470 public function notLike(string $x, mixed $y): Expr\Comparison
471 {
472 return new Expr\Comparison($x, 'NOT LIKE', $y);
473 }
474
475 /**
476 * Creates a CONCAT() function expression with the given arguments.
477 *
478 * @param mixed ...$x Arguments to be used in CONCAT() function.
479 */
480 public function concat(mixed ...$x): Expr\Func
481 {
482 self::validateVariadicParameter($x);
483
484 return new Expr\Func('CONCAT', $x);
485 }
486
487 /**
488 * Creates a SUBSTRING() function expression with the given arguments.
489 *
490 * @param mixed $x Argument to be used as string to be cropped by SUBSTRING() function.
491 * @param int $from Initial offset to start cropping string. May accept negative values.
492 * @param int|null $len Length of crop. May accept negative values.
493 */
494 public function substring(mixed $x, int $from, int|null $len = null): Expr\Func
495 {
496 $args = [$x, $from];
497 if ($len !== null) {
498 $args[] = $len;
499 }
500
501 return new Expr\Func('SUBSTRING', $args);
502 }
503
504 /**
505 * Creates a LOWER() function expression with the given argument.
506 *
507 * @param mixed $x Argument to be used in LOWER() function.
508 *
509 * @return Expr\Func A LOWER function expression.
510 */
511 public function lower(mixed $x): Expr\Func
512 {
513 return new Expr\Func('LOWER', [$x]);
514 }
515
516 /**
517 * Creates an UPPER() function expression with the given argument.
518 *
519 * @param mixed $x Argument to be used in UPPER() function.
520 *
521 * @return Expr\Func An UPPER function expression.
522 */
523 public function upper(mixed $x): Expr\Func
524 {
525 return new Expr\Func('UPPER', [$x]);
526 }
527
528 /**
529 * Creates a LENGTH() function expression with the given argument.
530 *
531 * @param mixed $x Argument to be used as argument of LENGTH() function.
532 *
533 * @return Expr\Func A LENGTH function expression.
534 */
535 public function length(mixed $x): Expr\Func
536 {
537 return new Expr\Func('LENGTH', [$x]);
538 }
539
540 /**
541 * Creates a literal expression of the given argument.
542 *
543 * @param scalar $literal Argument to be converted to literal.
544 */
545 public function literal(bool|string|int|float $literal): Expr\Literal
546 {
547 return new Expr\Literal($this->quoteLiteral($literal));
548 }
549
550 /**
551 * Quotes a literal value, if necessary, according to the DQL syntax.
552 *
553 * @param scalar $literal The literal value.
554 */
555 private function quoteLiteral(bool|string|int|float $literal): string
556 {
557 if (is_int($literal) || is_float($literal)) {
558 return (string) $literal;
559 }
560
561 if (is_bool($literal)) {
562 return $literal ? 'true' : 'false';
563 }
564
565 return "'" . str_replace("'", "''", $literal) . "'";
566 }
567
568 /**
569 * Creates an instance of BETWEEN() function, with the given argument.
570 *
571 * @param mixed $val Valued to be inspected by range values.
572 * @param int|string $x Starting range value to be used in BETWEEN() function.
573 * @param int|string $y End point value to be used in BETWEEN() function.
574 *
575 * @return string A BETWEEN expression.
576 */
577 public function between(mixed $val, int|string $x, int|string $y): string
578 {
579 return $val . ' BETWEEN ' . $x . ' AND ' . $y;
580 }
581
582 /**
583 * Creates an instance of TRIM() function, with the given argument.
584 *
585 * @param mixed $x Argument to be used as argument of TRIM() function.
586 *
587 * @return Expr\Func a TRIM expression.
588 */
589 public function trim(mixed $x): Expr\Func
590 {
591 return new Expr\Func('TRIM', $x);
592 }
593
594 /**
595 * Creates an instance of MEMBER OF function, with the given arguments.
596 *
597 * @param string $x Value to be checked
598 * @param string $y Value to be checked against
599 */
600 public function isMemberOf(string $x, string $y): Expr\Comparison
601 {
602 return new Expr\Comparison($x, 'MEMBER OF', $y);
603 }
604
605 /**
606 * Creates an instance of INSTANCE OF function, with the given arguments.
607 *
608 * @param string $x Value to be checked
609 * @param string $y Value to be checked against
610 */
611 public function isInstanceOf(string $x, string $y): Expr\Comparison
612 {
613 return new Expr\Comparison($x, 'INSTANCE OF', $y);
614 }
615}
diff --git a/vendor/doctrine/orm/src/Query/Expr/Andx.php b/vendor/doctrine/orm/src/Query/Expr/Andx.php
new file mode 100644
index 0000000..a20bcef
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/Andx.php
@@ -0,0 +1,32 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7/**
8 * Expression class for building DQL and parts.
9 *
10 * @link www.doctrine-project.org
11 */
12class Andx extends Composite
13{
14 protected string $separator = ' AND ';
15
16 /** @var string[] */
17 protected array $allowedClasses = [
18 Comparison::class,
19 Func::class,
20 Orx::class,
21 self::class,
22 ];
23
24 /** @psalm-var list<string|Comparison|Func|Orx|self> */
25 protected array $parts = [];
26
27 /** @psalm-return list<string|Comparison|Func|Orx|self> */
28 public function getParts(): array
29 {
30 return $this->parts;
31 }
32}
diff --git a/vendor/doctrine/orm/src/Query/Expr/Base.php b/vendor/doctrine/orm/src/Query/Expr/Base.php
new file mode 100644
index 0000000..e0f2572
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/Base.php
@@ -0,0 +1,96 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7use InvalidArgumentException;
8use Stringable;
9
10use function array_key_exists;
11use function count;
12use function get_debug_type;
13use function implode;
14use function in_array;
15use function is_array;
16use function is_string;
17use function sprintf;
18
19/**
20 * Abstract base Expr class for building DQL parts.
21 *
22 * @link www.doctrine-project.org
23 */
24abstract class Base implements Stringable
25{
26 protected string $preSeparator = '(';
27 protected string $separator = ', ';
28 protected string $postSeparator = ')';
29
30 /** @var list<class-string> */
31 protected array $allowedClasses = [];
32
33 /** @var list<string|Stringable> */
34 protected array $parts = [];
35
36 public function __construct(mixed $args = [])
37 {
38 if (is_array($args) && array_key_exists(0, $args) && is_array($args[0])) {
39 $args = $args[0];
40 }
41
42 $this->addMultiple($args);
43 }
44
45 /**
46 * @param string[]|object[]|string|object $args
47 * @psalm-param list<string|object>|string|object $args
48 *
49 * @return $this
50 */
51 public function addMultiple(array|string|object $args = []): static
52 {
53 foreach ((array) $args as $arg) {
54 $this->add($arg);
55 }
56
57 return $this;
58 }
59
60 /**
61 * @return $this
62 *
63 * @throws InvalidArgumentException
64 */
65 public function add(mixed $arg): static
66 {
67 if ($arg !== null && (! $arg instanceof self || $arg->count() > 0)) {
68 // If we decide to keep Expr\Base instances, we can use this check
69 if (! is_string($arg) && ! in_array($arg::class, $this->allowedClasses, true)) {
70 throw new InvalidArgumentException(sprintf(
71 "Expression of type '%s' not allowed in this context.",
72 get_debug_type($arg),
73 ));
74 }
75
76 $this->parts[] = $arg;
77 }
78
79 return $this;
80 }
81
82 /** @psalm-return 0|positive-int */
83 public function count(): int
84 {
85 return count($this->parts);
86 }
87
88 public function __toString(): string
89 {
90 if ($this->count() === 1) {
91 return (string) $this->parts[0];
92 }
93
94 return $this->preSeparator . implode($this->separator, $this->parts) . $this->postSeparator;
95 }
96}
diff --git a/vendor/doctrine/orm/src/Query/Expr/Comparison.php b/vendor/doctrine/orm/src/Query/Expr/Comparison.php
new file mode 100644
index 0000000..ec8ef21
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/Comparison.php
@@ -0,0 +1,47 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7use Stringable;
8
9/**
10 * Expression class for DQL comparison expressions.
11 *
12 * @link www.doctrine-project.org
13 */
14class Comparison implements Stringable
15{
16 final public const EQ = '=';
17 final public const NEQ = '<>';
18 final public const LT = '<';
19 final public const LTE = '<=';
20 final public const GT = '>';
21 final public const GTE = '>=';
22
23 /** Creates a comparison expression with the given arguments. */
24 public function __construct(protected mixed $leftExpr, protected string $operator, protected mixed $rightExpr)
25 {
26 }
27
28 public function getLeftExpr(): mixed
29 {
30 return $this->leftExpr;
31 }
32
33 public function getOperator(): string
34 {
35 return $this->operator;
36 }
37
38 public function getRightExpr(): mixed
39 {
40 return $this->rightExpr;
41 }
42
43 public function __toString(): string
44 {
45 return $this->leftExpr . ' ' . $this->operator . ' ' . $this->rightExpr;
46 }
47}
diff --git a/vendor/doctrine/orm/src/Query/Expr/Composite.php b/vendor/doctrine/orm/src/Query/Expr/Composite.php
new file mode 100644
index 0000000..f3007a7
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/Composite.php
@@ -0,0 +1,50 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7use Stringable;
8
9use function implode;
10use function is_object;
11use function preg_match;
12
13/**
14 * Expression class for building DQL and parts.
15 *
16 * @link www.doctrine-project.org
17 */
18class Composite extends Base
19{
20 public function __toString(): string
21 {
22 if ($this->count() === 1) {
23 return (string) $this->parts[0];
24 }
25
26 $components = [];
27
28 foreach ($this->parts as $part) {
29 $components[] = $this->processQueryPart($part);
30 }
31
32 return implode($this->separator, $components);
33 }
34
35 private function processQueryPart(string|Stringable $part): string
36 {
37 $queryPart = (string) $part;
38
39 if (is_object($part) && $part instanceof self && $part->count() > 1) {
40 return $this->preSeparator . $queryPart . $this->postSeparator;
41 }
42
43 // Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND")
44 if (preg_match('/\s(OR|AND)\s/i', $queryPart)) {
45 return $this->preSeparator . $queryPart . $this->postSeparator;
46 }
47
48 return $queryPart;
49 }
50}
diff --git a/vendor/doctrine/orm/src/Query/Expr/From.php b/vendor/doctrine/orm/src/Query/Expr/From.php
new file mode 100644
index 0000000..21af078
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/From.php
@@ -0,0 +1,48 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7use Stringable;
8
9/**
10 * Expression class for DQL from.
11 *
12 * @link www.doctrine-project.org
13 */
14class From implements Stringable
15{
16 /**
17 * @param class-string $from The class name.
18 * @param string $alias The alias of the class.
19 */
20 public function __construct(
21 protected string $from,
22 protected string $alias,
23 protected string|null $indexBy = null,
24 ) {
25 }
26
27 /** @return class-string */
28 public function getFrom(): string
29 {
30 return $this->from;
31 }
32
33 public function getAlias(): string
34 {
35 return $this->alias;
36 }
37
38 public function getIndexBy(): string|null
39 {
40 return $this->indexBy;
41 }
42
43 public function __toString(): string
44 {
45 return $this->from . ' ' . $this->alias .
46 ($this->indexBy ? ' INDEX BY ' . $this->indexBy : '');
47 }
48}
diff --git a/vendor/doctrine/orm/src/Query/Expr/Func.php b/vendor/doctrine/orm/src/Query/Expr/Func.php
new file mode 100644
index 0000000..cd9e8e0
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/Func.php
@@ -0,0 +1,48 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7use Stringable;
8
9use function implode;
10
11/**
12 * Expression class for generating DQL functions.
13 *
14 * @link www.doctrine-project.org
15 */
16class Func implements Stringable
17{
18 /** @var mixed[] */
19 protected array $arguments;
20
21 /**
22 * Creates a function, with the given argument.
23 *
24 * @psalm-param list<mixed>|mixed $arguments
25 */
26 public function __construct(
27 protected string $name,
28 mixed $arguments,
29 ) {
30 $this->arguments = (array) $arguments;
31 }
32
33 public function getName(): string
34 {
35 return $this->name;
36 }
37
38 /** @psalm-return list<mixed> */
39 public function getArguments(): array
40 {
41 return $this->arguments;
42 }
43
44 public function __toString(): string
45 {
46 return $this->name . '(' . implode(', ', $this->arguments) . ')';
47 }
48}
diff --git a/vendor/doctrine/orm/src/Query/Expr/GroupBy.php b/vendor/doctrine/orm/src/Query/Expr/GroupBy.php
new file mode 100644
index 0000000..fa4625a
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/GroupBy.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7/**
8 * Expression class for building DQL Group By parts.
9 *
10 * @link www.doctrine-project.org
11 */
12class GroupBy extends Base
13{
14 protected string $preSeparator = '';
15 protected string $postSeparator = '';
16
17 /** @psalm-var list<string> */
18 protected array $parts = [];
19
20 /** @psalm-return list<string> */
21 public function getParts(): array
22 {
23 return $this->parts;
24 }
25}
diff --git a/vendor/doctrine/orm/src/Query/Expr/Join.php b/vendor/doctrine/orm/src/Query/Expr/Join.php
new file mode 100644
index 0000000..c3b6dc9
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/Join.php
@@ -0,0 +1,77 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7use Stringable;
8
9use function strtoupper;
10
11/**
12 * Expression class for DQL join.
13 *
14 * @link www.doctrine-project.org
15 */
16class Join implements Stringable
17{
18 final public const INNER_JOIN = 'INNER';
19 final public const LEFT_JOIN = 'LEFT';
20
21 final public const ON = 'ON';
22 final public const WITH = 'WITH';
23
24 /**
25 * @psalm-param self::INNER_JOIN|self::LEFT_JOIN $joinType
26 * @psalm-param self::ON|self::WITH|null $conditionType
27 */
28 public function __construct(
29 protected string $joinType,
30 protected string $join,
31 protected string|null $alias = null,
32 protected string|null $conditionType = null,
33 protected string|Comparison|Composite|Func|null $condition = null,
34 protected string|null $indexBy = null,
35 ) {
36 }
37
38 /** @psalm-return self::INNER_JOIN|self::LEFT_JOIN */
39 public function getJoinType(): string
40 {
41 return $this->joinType;
42 }
43
44 public function getJoin(): string
45 {
46 return $this->join;
47 }
48
49 public function getAlias(): string|null
50 {
51 return $this->alias;
52 }
53
54 /** @psalm-return self::ON|self::WITH|null */
55 public function getConditionType(): string|null
56 {
57 return $this->conditionType;
58 }
59
60 public function getCondition(): string|Comparison|Composite|Func|null
61 {
62 return $this->condition;
63 }
64
65 public function getIndexBy(): string|null
66 {
67 return $this->indexBy;
68 }
69
70 public function __toString(): string
71 {
72 return strtoupper($this->joinType) . ' JOIN ' . $this->join
73 . ($this->alias ? ' ' . $this->alias : '')
74 . ($this->indexBy ? ' INDEX BY ' . $this->indexBy : '')
75 . ($this->condition ? ' ' . strtoupper($this->conditionType) . ' ' . $this->condition : '');
76 }
77}
diff --git a/vendor/doctrine/orm/src/Query/Expr/Literal.php b/vendor/doctrine/orm/src/Query/Expr/Literal.php
new file mode 100644
index 0000000..0c13030
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/Literal.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7/**
8 * Expression class for generating DQL functions.
9 *
10 * @link www.doctrine-project.org
11 */
12class Literal extends Base
13{
14 protected string $preSeparator = '';
15 protected string $postSeparator = '';
16
17 /** @psalm-var list<string> */
18 protected array $parts = [];
19
20 /** @psalm-return list<string> */
21 public function getParts(): array
22 {
23 return $this->parts;
24 }
25}
diff --git a/vendor/doctrine/orm/src/Query/Expr/Math.php b/vendor/doctrine/orm/src/Query/Expr/Math.php
new file mode 100644
index 0000000..05e0b39
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/Math.php
@@ -0,0 +1,59 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7use Stringable;
8
9/**
10 * Expression class for DQL math statements.
11 *
12 * @link www.doctrine-project.org
13 */
14class Math implements Stringable
15{
16 /**
17 * Creates a mathematical expression with the given arguments.
18 */
19 public function __construct(
20 protected mixed $leftExpr,
21 protected string $operator,
22 protected mixed $rightExpr,
23 ) {
24 }
25
26 public function getLeftExpr(): mixed
27 {
28 return $this->leftExpr;
29 }
30
31 public function getOperator(): string
32 {
33 return $this->operator;
34 }
35
36 public function getRightExpr(): mixed
37 {
38 return $this->rightExpr;
39 }
40
41 public function __toString(): string
42 {
43 // Adjusting Left Expression
44 $leftExpr = (string) $this->leftExpr;
45
46 if ($this->leftExpr instanceof Math) {
47 $leftExpr = '(' . $leftExpr . ')';
48 }
49
50 // Adjusting Right Expression
51 $rightExpr = (string) $this->rightExpr;
52
53 if ($this->rightExpr instanceof Math) {
54 $rightExpr = '(' . $rightExpr . ')';
55 }
56
57 return $leftExpr . ' ' . $this->operator . ' ' . $rightExpr;
58 }
59}
diff --git a/vendor/doctrine/orm/src/Query/Expr/OrderBy.php b/vendor/doctrine/orm/src/Query/Expr/OrderBy.php
new file mode 100644
index 0000000..ac9e160
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/OrderBy.php
@@ -0,0 +1,60 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7use Stringable;
8
9use function count;
10use function implode;
11
12/**
13 * Expression class for building DQL Order By parts.
14 *
15 * @link www.doctrine-project.org
16 */
17class OrderBy implements Stringable
18{
19 protected string $preSeparator = '';
20 protected string $separator = ', ';
21 protected string $postSeparator = '';
22
23 /** @var string[] */
24 protected array $allowedClasses = [];
25
26 /** @psalm-var list<string> */
27 protected array $parts = [];
28
29 public function __construct(
30 string|null $sort = null,
31 string|null $order = null,
32 ) {
33 if ($sort) {
34 $this->add($sort, $order);
35 }
36 }
37
38 public function add(string $sort, string|null $order = null): void
39 {
40 $order = ! $order ? 'ASC' : $order;
41 $this->parts[] = $sort . ' ' . $order;
42 }
43
44 /** @psalm-return 0|positive-int */
45 public function count(): int
46 {
47 return count($this->parts);
48 }
49
50 /** @psalm-return list<string> */
51 public function getParts(): array
52 {
53 return $this->parts;
54 }
55
56 public function __toString(): string
57 {
58 return $this->preSeparator . implode($this->separator, $this->parts) . $this->postSeparator;
59 }
60}
diff --git a/vendor/doctrine/orm/src/Query/Expr/Orx.php b/vendor/doctrine/orm/src/Query/Expr/Orx.php
new file mode 100644
index 0000000..2ae2332
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/Orx.php
@@ -0,0 +1,32 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7/**
8 * Expression class for building DQL OR clauses.
9 *
10 * @link www.doctrine-project.org
11 */
12class Orx extends Composite
13{
14 protected string $separator = ' OR ';
15
16 /** @var string[] */
17 protected array $allowedClasses = [
18 Comparison::class,
19 Func::class,
20 Andx::class,
21 self::class,
22 ];
23
24 /** @psalm-var list<string|Comparison|Func|Andx|self> */
25 protected array $parts = [];
26
27 /** @psalm-return list<string|Comparison|Func|Andx|self> */
28 public function getParts(): array
29 {
30 return $this->parts;
31 }
32}
diff --git a/vendor/doctrine/orm/src/Query/Expr/Select.php b/vendor/doctrine/orm/src/Query/Expr/Select.php
new file mode 100644
index 0000000..91b0b60
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Expr/Select.php
@@ -0,0 +1,28 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Expr;
6
7/**
8 * Expression class for building DQL select statements.
9 *
10 * @link www.doctrine-project.org
11 */
12class Select extends Base
13{
14 protected string $preSeparator = '';
15 protected string $postSeparator = '';
16
17 /** @var string[] */
18 protected array $allowedClasses = [Func::class];
19
20 /** @psalm-var list<string|Func> */
21 protected array $parts = [];
22
23 /** @psalm-return list<string|Func> */
24 public function getParts(): array
25 {
26 return $this->parts;
27 }
28}
diff --git a/vendor/doctrine/orm/src/Query/Filter/FilterException.php b/vendor/doctrine/orm/src/Query/Filter/FilterException.php
new file mode 100644
index 0000000..37f12bc
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Filter/FilterException.php
@@ -0,0 +1,23 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Filter;
6
7use Doctrine\ORM\Exception\ORMException;
8use LogicException;
9
10use function sprintf;
11
12class FilterException extends LogicException implements ORMException
13{
14 public static function cannotConvertListParameterIntoSingleValue(string $name): self
15 {
16 return new self(sprintf('Cannot convert list-based SQL filter parameter "%s" into a single value.', $name));
17 }
18
19 public static function cannotConvertSingleParameterIntoListValue(string $name): self
20 {
21 return new self(sprintf('Cannot convert single SQL filter parameter "%s" into a list value.', $name));
22 }
23}
diff --git a/vendor/doctrine/orm/src/Query/Filter/SQLFilter.php b/vendor/doctrine/orm/src/Query/Filter/SQLFilter.php
new file mode 100644
index 0000000..29f3775
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Filter/SQLFilter.php
@@ -0,0 +1,174 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query\Filter;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Types\Types;
9use Doctrine\ORM\EntityManagerInterface;
10use Doctrine\ORM\Mapping\ClassMetadata;
11use Doctrine\ORM\Query\ParameterTypeInferer;
12use InvalidArgumentException;
13use Stringable;
14
15use function array_map;
16use function implode;
17use function ksort;
18use function serialize;
19
20/**
21 * The base class that user defined filters should extend.
22 *
23 * Handles the setting and escaping of parameters.
24 *
25 * @abstract
26 */
27abstract class SQLFilter implements Stringable
28{
29 /**
30 * Parameters for the filter.
31 *
32 * @psalm-var array<string,array{type: string, value: mixed, is_list: bool}>
33 */
34 private array $parameters = [];
35
36 final public function __construct(
37 private readonly EntityManagerInterface $em,
38 ) {
39 }
40
41 /**
42 * Sets a parameter list that can be used by the filter.
43 *
44 * @param array<mixed> $values List of parameter values.
45 * @param string $type The parameter type. If specified, the given value will be run through
46 * the type conversion of this type.
47 *
48 * @return $this
49 */
50 final public function setParameterList(string $name, array $values, string $type = Types::STRING): static
51 {
52 $this->parameters[$name] = ['value' => $values, 'type' => $type, 'is_list' => true];
53
54 // Keep the parameters sorted for the hash
55 ksort($this->parameters);
56
57 // The filter collection of the EM is now dirty
58 $this->em->getFilters()->setFiltersStateDirty();
59
60 return $this;
61 }
62
63 /**
64 * Sets a parameter that can be used by the filter.
65 *
66 * @param string|null $type The parameter type. If specified, the given value will be run through
67 * the type conversion of this type. This is usually not needed for
68 * strings and numeric types.
69 *
70 * @return $this
71 */
72 final public function setParameter(string $name, mixed $value, string|null $type = null): static
73 {
74 if ($type === null) {
75 $type = ParameterTypeInferer::inferType($value);
76 }
77
78 $this->parameters[$name] = ['value' => $value, 'type' => $type, 'is_list' => false];
79
80 // Keep the parameters sorted for the hash
81 ksort($this->parameters);
82
83 // The filter collection of the EM is now dirty
84 $this->em->getFilters()->setFiltersStateDirty();
85
86 return $this;
87 }
88
89 /**
90 * Gets a parameter to use in a query.
91 *
92 * The function is responsible for the right output escaping to use the
93 * value in a query.
94 *
95 * @return string The SQL escaped parameter to use in a query.
96 *
97 * @throws InvalidArgumentException
98 */
99 final public function getParameter(string $name): string
100 {
101 if (! isset($this->parameters[$name])) {
102 throw new InvalidArgumentException("Parameter '" . $name . "' does not exist.");
103 }
104
105 if ($this->parameters[$name]['is_list']) {
106 throw FilterException::cannotConvertListParameterIntoSingleValue($name);
107 }
108
109 return $this->em->getConnection()->quote((string) $this->parameters[$name]['value']);
110 }
111
112 /**
113 * Gets a parameter to use in a query assuming it's a list of entries.
114 *
115 * The function is responsible for the right output escaping to use the
116 * value in a query, separating each entry by comma to inline it into
117 * an IN() query part.
118 *
119 * @throws InvalidArgumentException
120 */
121 final public function getParameterList(string $name): string
122 {
123 if (! isset($this->parameters[$name])) {
124 throw new InvalidArgumentException("Parameter '" . $name . "' does not exist.");
125 }
126
127 if ($this->parameters[$name]['is_list'] === false) {
128 throw FilterException::cannotConvertSingleParameterIntoListValue($name);
129 }
130
131 $param = $this->parameters[$name];
132 $connection = $this->em->getConnection();
133
134 $quoted = array_map(
135 static fn (mixed $value): string => $connection->quote((string) $value),
136 $param['value'],
137 );
138
139 return implode(',', $quoted);
140 }
141
142 /**
143 * Checks if a parameter was set for the filter.
144 */
145 final public function hasParameter(string $name): bool
146 {
147 return isset($this->parameters[$name]);
148 }
149
150 /**
151 * Returns as string representation of the SQLFilter parameters (the state).
152 */
153 final public function __toString(): string
154 {
155 return serialize($this->parameters);
156 }
157
158 /**
159 * Returns the database connection used by the entity manager
160 */
161 final protected function getConnection(): Connection
162 {
163 return $this->em->getConnection();
164 }
165
166 /**
167 * Gets the SQL query part to add to a query.
168 *
169 * @psalm-param ClassMetadata<object> $targetEntity
170 *
171 * @return string The constraint SQL if there is available, empty string otherwise.
172 */
173 abstract public function addFilterConstraint(ClassMetadata $targetEntity, string $targetTableAlias): string;
174}
diff --git a/vendor/doctrine/orm/src/Query/FilterCollection.php b/vendor/doctrine/orm/src/Query/FilterCollection.php
new file mode 100644
index 0000000..3d3c576
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/FilterCollection.php
@@ -0,0 +1,260 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use Doctrine\ORM\Configuration;
8use Doctrine\ORM\EntityManagerInterface;
9use Doctrine\ORM\Query\Filter\SQLFilter;
10use InvalidArgumentException;
11
12use function assert;
13use function ksort;
14
15/**
16 * Collection class for all the query filters.
17 */
18class FilterCollection
19{
20 /* Filter STATES */
21
22 /**
23 * A filter object is in CLEAN state when it has no changed parameters.
24 */
25 public const FILTERS_STATE_CLEAN = 1;
26
27 /**
28 * A filter object is in DIRTY state when it has changed parameters.
29 */
30 public const FILTERS_STATE_DIRTY = 2;
31
32 private readonly Configuration $config;
33
34 /**
35 * Instances of enabled filters.
36 *
37 * @var array<string, SQLFilter>
38 */
39 private array $enabledFilters = [];
40
41 /** The filter hash from the last time the query was parsed. */
42 private string $filterHash = '';
43
44 /**
45 * Instances of suspended filters.
46 *
47 * @var SQLFilter[]
48 * @psalm-var array<string, SQLFilter>
49 */
50 private array $suspendedFilters = [];
51
52 /**
53 * The current state of this filter.
54 *
55 * @psalm-var self::FILTERS_STATE_*
56 */
57 private int $filtersState = self::FILTERS_STATE_CLEAN;
58
59 public function __construct(
60 private readonly EntityManagerInterface $em,
61 ) {
62 $this->config = $em->getConfiguration();
63 }
64
65 /**
66 * Gets all the enabled filters.
67 *
68 * @return array<string, SQLFilter> The enabled filters.
69 */
70 public function getEnabledFilters(): array
71 {
72 return $this->enabledFilters;
73 }
74
75 /**
76 * Gets all the suspended filters.
77 *
78 * @return SQLFilter[] The suspended filters.
79 * @psalm-return array<string, SQLFilter>
80 */
81 public function getSuspendedFilters(): array
82 {
83 return $this->suspendedFilters;
84 }
85
86 /**
87 * Enables a filter from the collection.
88 *
89 * @throws InvalidArgumentException If the filter does not exist.
90 */
91 public function enable(string $name): SQLFilter
92 {
93 if (! $this->has($name)) {
94 throw new InvalidArgumentException("Filter '" . $name . "' does not exist.");
95 }
96
97 if (! $this->isEnabled($name)) {
98 $filterClass = $this->config->getFilterClassName($name);
99
100 assert($filterClass !== null);
101
102 $this->enabledFilters[$name] = new $filterClass($this->em);
103
104 // In case a suspended filter with the same name was forgotten
105 unset($this->suspendedFilters[$name]);
106
107 // Keep the enabled filters sorted for the hash
108 ksort($this->enabledFilters);
109
110 $this->setFiltersStateDirty();
111 }
112
113 return $this->enabledFilters[$name];
114 }
115
116 /**
117 * Disables a filter.
118 *
119 * @throws InvalidArgumentException If the filter does not exist.
120 */
121 public function disable(string $name): SQLFilter
122 {
123 // Get the filter to return it
124 $filter = $this->getFilter($name);
125
126 unset($this->enabledFilters[$name]);
127
128 $this->setFiltersStateDirty();
129
130 return $filter;
131 }
132
133 /**
134 * Suspend a filter.
135 *
136 * @param string $name Name of the filter.
137 *
138 * @return SQLFilter The suspended filter.
139 *
140 * @throws InvalidArgumentException If the filter does not exist.
141 */
142 public function suspend(string $name): SQLFilter
143 {
144 // Get the filter to return it
145 $filter = $this->getFilter($name);
146
147 $this->suspendedFilters[$name] = $filter;
148 unset($this->enabledFilters[$name]);
149
150 $this->setFiltersStateDirty();
151
152 return $filter;
153 }
154
155 /**
156 * Restore a disabled filter from the collection.
157 *
158 * @param string $name Name of the filter.
159 *
160 * @return SQLFilter The restored filter.
161 *
162 * @throws InvalidArgumentException If the filter does not exist.
163 */
164 public function restore(string $name): SQLFilter
165 {
166 if (! $this->isSuspended($name)) {
167 throw new InvalidArgumentException("Filter '" . $name . "' is not suspended.");
168 }
169
170 $this->enabledFilters[$name] = $this->suspendedFilters[$name];
171 unset($this->suspendedFilters[$name]);
172
173 // Keep the enabled filters sorted for the hash
174 ksort($this->enabledFilters);
175
176 $this->setFiltersStateDirty();
177
178 return $this->enabledFilters[$name];
179 }
180
181 /**
182 * Gets an enabled filter from the collection.
183 *
184 * @throws InvalidArgumentException If the filter is not enabled.
185 */
186 public function getFilter(string $name): SQLFilter
187 {
188 if (! $this->isEnabled($name)) {
189 throw new InvalidArgumentException("Filter '" . $name . "' is not enabled.");
190 }
191
192 return $this->enabledFilters[$name];
193 }
194
195 /**
196 * Checks whether filter with given name is defined.
197 */
198 public function has(string $name): bool
199 {
200 return $this->config->getFilterClassName($name) !== null;
201 }
202
203 /**
204 * Checks if a filter is enabled.
205 */
206 public function isEnabled(string $name): bool
207 {
208 return isset($this->enabledFilters[$name]);
209 }
210
211 /**
212 * Checks if a filter is suspended.
213 *
214 * @param string $name Name of the filter.
215 *
216 * @return bool True if the filter is suspended, false otherwise.
217 */
218 public function isSuspended(string $name): bool
219 {
220 return isset($this->suspendedFilters[$name]);
221 }
222
223 /**
224 * Checks if the filter collection is clean.
225 */
226 public function isClean(): bool
227 {
228 return $this->filtersState === self::FILTERS_STATE_CLEAN;
229 }
230
231 /**
232 * Generates a string of currently enabled filters to use for the cache id.
233 */
234 public function getHash(): string
235 {
236 // If there are only clean filters, the previous hash can be returned
237 if ($this->filtersState === self::FILTERS_STATE_CLEAN) {
238 return $this->filterHash;
239 }
240
241 $filterHash = '';
242
243 foreach ($this->enabledFilters as $name => $filter) {
244 $filterHash .= $name . $filter;
245 }
246
247 $this->filterHash = $filterHash;
248 $this->filtersState = self::FILTERS_STATE_CLEAN;
249
250 return $filterHash;
251 }
252
253 /**
254 * Sets the filter state to dirty.
255 */
256 public function setFiltersStateDirty(): void
257 {
258 $this->filtersState = self::FILTERS_STATE_DIRTY;
259 }
260}
diff --git a/vendor/doctrine/orm/src/Query/Lexer.php b/vendor/doctrine/orm/src/Query/Lexer.php
new file mode 100644
index 0000000..c446675
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Lexer.php
@@ -0,0 +1,150 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use Doctrine\Common\Lexer\AbstractLexer;
8
9use function constant;
10use function ctype_alpha;
11use function defined;
12use function is_numeric;
13use function str_contains;
14use function str_replace;
15use function stripos;
16use function strlen;
17use function strtoupper;
18use function substr;
19
20/**
21 * Scans a DQL query for tokens.
22 *
23 * @extends AbstractLexer<TokenType, string>
24 */
25class Lexer extends AbstractLexer
26{
27 /**
28 * Creates a new query scanner object.
29 *
30 * @param string $input A query string.
31 */
32 public function __construct(string $input)
33 {
34 $this->setInput($input);
35 }
36
37 /**
38 * {@inheritDoc}
39 */
40 protected function getCatchablePatterns(): array
41 {
42 return [
43 '[a-z_][a-z0-9_]*\:[a-z_][a-z0-9_]*(?:\\\[a-z_][a-z0-9_]*)*', // aliased name
44 '[a-z_\\\][a-z0-9_]*(?:\\\[a-z_][a-z0-9_]*)*', // identifier or qualified name
45 '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', // numbers
46 "'(?:[^']|'')*'", // quoted strings
47 '\?[0-9]*|:[a-z_][a-z0-9_]*', // parameters
48 ];
49 }
50
51 /**
52 * {@inheritDoc}
53 */
54 protected function getNonCatchablePatterns(): array
55 {
56 return ['\s+', '--.*', '(.)'];
57 }
58
59 protected function getType(string &$value): TokenType
60 {
61 $type = TokenType::T_NONE;
62
63 switch (true) {
64 // Recognize numeric values
65 case is_numeric($value):
66 if (str_contains($value, '.') || stripos($value, 'e') !== false) {
67 return TokenType::T_FLOAT;
68 }
69
70 return TokenType::T_INTEGER;
71
72 // Recognize quoted strings
73 case $value[0] === "'":
74 $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2));
75
76 return TokenType::T_STRING;
77
78 // Recognize identifiers, aliased or qualified names
79 case ctype_alpha($value[0]) || $value[0] === '_' || $value[0] === '\\':
80 $name = 'Doctrine\ORM\Query\TokenType::T_' . strtoupper($value);
81
82 if (defined($name)) {
83 $type = constant($name);
84
85 if ($type->value > 100) {
86 return $type;
87 }
88 }
89
90 if (str_contains($value, '\\')) {
91 return TokenType::T_FULLY_QUALIFIED_NAME;
92 }
93
94 return TokenType::T_IDENTIFIER;
95
96 // Recognize input parameters
97 case $value[0] === '?' || $value[0] === ':':
98 return TokenType::T_INPUT_PARAMETER;
99
100 // Recognize symbols
101 case $value === '.':
102 return TokenType::T_DOT;
103
104 case $value === ',':
105 return TokenType::T_COMMA;
106
107 case $value === '(':
108 return TokenType::T_OPEN_PARENTHESIS;
109
110 case $value === ')':
111 return TokenType::T_CLOSE_PARENTHESIS;
112
113 case $value === '=':
114 return TokenType::T_EQUALS;
115
116 case $value === '>':
117 return TokenType::T_GREATER_THAN;
118
119 case $value === '<':
120 return TokenType::T_LOWER_THAN;
121
122 case $value === '+':
123 return TokenType::T_PLUS;
124
125 case $value === '-':
126 return TokenType::T_MINUS;
127
128 case $value === '*':
129 return TokenType::T_MULTIPLY;
130
131 case $value === '/':
132 return TokenType::T_DIVIDE;
133
134 case $value === '!':
135 return TokenType::T_NEGATE;
136
137 case $value === '{':
138 return TokenType::T_OPEN_CURLY_BRACE;
139
140 case $value === '}':
141 return TokenType::T_CLOSE_CURLY_BRACE;
142
143 // Default
144 default:
145 // Do nothing
146 }
147
148 return $type;
149 }
150}
diff --git a/vendor/doctrine/orm/src/Query/Parameter.php b/vendor/doctrine/orm/src/Query/Parameter.php
new file mode 100644
index 0000000..43eb7a4
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Parameter.php
@@ -0,0 +1,89 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use function trim;
8
9/**
10 * Defines a Query Parameter.
11 *
12 * @link www.doctrine-project.org
13 */
14class Parameter
15{
16 /**
17 * Returns the internal representation of a parameter name.
18 */
19 public static function normalizeName(int|string $name): string
20 {
21 return trim((string) $name, ':');
22 }
23
24 /**
25 * The parameter name.
26 */
27 private readonly string $name;
28
29 /**
30 * The parameter value.
31 */
32 private mixed $value;
33
34 /**
35 * The parameter type.
36 */
37 private mixed $type;
38
39 /**
40 * Whether the parameter type was explicitly specified or not
41 */
42 private readonly bool $typeSpecified;
43
44 public function __construct(int|string $name, mixed $value, mixed $type = null)
45 {
46 $this->name = self::normalizeName($name);
47 $this->typeSpecified = $type !== null;
48
49 $this->setValue($value, $type);
50 }
51
52 /**
53 * Retrieves the Parameter name.
54 */
55 public function getName(): string
56 {
57 return $this->name;
58 }
59
60 /**
61 * Retrieves the Parameter value.
62 */
63 public function getValue(): mixed
64 {
65 return $this->value;
66 }
67
68 /**
69 * Retrieves the Parameter type.
70 */
71 public function getType(): mixed
72 {
73 return $this->type;
74 }
75
76 /**
77 * Defines the Parameter value.
78 */
79 public function setValue(mixed $value, mixed $type = null): void
80 {
81 $this->value = $value;
82 $this->type = $type ?: ParameterTypeInferer::inferType($value);
83 }
84
85 public function typeWasSpecified(): bool
86 {
87 return $this->typeSpecified;
88 }
89}
diff --git a/vendor/doctrine/orm/src/Query/ParameterTypeInferer.php b/vendor/doctrine/orm/src/Query/ParameterTypeInferer.php
new file mode 100644
index 0000000..dae28fa
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/ParameterTypeInferer.php
@@ -0,0 +1,77 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use BackedEnum;
8use DateInterval;
9use DateTimeImmutable;
10use DateTimeInterface;
11use Doctrine\DBAL\ArrayParameterType;
12use Doctrine\DBAL\ParameterType;
13use Doctrine\DBAL\Types\Types;
14
15use function current;
16use function is_array;
17use function is_bool;
18use function is_int;
19
20/**
21 * Provides an enclosed support for parameter inferring.
22 *
23 * @link www.doctrine-project.org
24 */
25final class ParameterTypeInferer
26{
27 /**
28 * Infers type of a given value, returning a compatible constant:
29 * - Type (\Doctrine\DBAL\Types\Type::*)
30 * - Connection (\Doctrine\DBAL\Connection::PARAM_*)
31 */
32 public static function inferType(mixed $value): ParameterType|ArrayParameterType|int|string
33 {
34 if (is_int($value)) {
35 return Types::INTEGER;
36 }
37
38 if (is_bool($value)) {
39 return Types::BOOLEAN;
40 }
41
42 if ($value instanceof DateTimeImmutable) {
43 return Types::DATETIME_IMMUTABLE;
44 }
45
46 if ($value instanceof DateTimeInterface) {
47 return Types::DATETIME_MUTABLE;
48 }
49
50 if ($value instanceof DateInterval) {
51 return Types::DATEINTERVAL;
52 }
53
54 if ($value instanceof BackedEnum) {
55 return is_int($value->value)
56 ? Types::INTEGER
57 : Types::STRING;
58 }
59
60 if (is_array($value)) {
61 $firstValue = current($value);
62 if ($firstValue instanceof BackedEnum) {
63 $firstValue = $firstValue->value;
64 }
65
66 return is_int($firstValue)
67 ? ArrayParameterType::INTEGER
68 : ArrayParameterType::STRING;
69 }
70
71 return ParameterType::STRING;
72 }
73
74 private function __construct()
75 {
76 }
77}
diff --git a/vendor/doctrine/orm/src/Query/Parser.php b/vendor/doctrine/orm/src/Query/Parser.php
new file mode 100644
index 0000000..e948f2c
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Parser.php
@@ -0,0 +1,3269 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use Doctrine\Common\Lexer\Token;
8use Doctrine\ORM\EntityManagerInterface;
9use Doctrine\ORM\Mapping\AssociationMapping;
10use Doctrine\ORM\Mapping\ClassMetadata;
11use Doctrine\ORM\Query;
12use Doctrine\ORM\Query\AST\Functions;
13use LogicException;
14use ReflectionClass;
15
16use function array_search;
17use function assert;
18use function class_exists;
19use function count;
20use function implode;
21use function in_array;
22use function interface_exists;
23use function is_string;
24use function sprintf;
25use function str_contains;
26use function strlen;
27use function strpos;
28use function strrpos;
29use function strtolower;
30use function substr;
31
32/**
33 * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
34 * Parses a DQL query, reports any errors in it, and generates an AST.
35 *
36 * @psalm-type DqlToken = Token<TokenType, string>
37 * @psalm-type QueryComponent = array{
38 * metadata?: ClassMetadata<object>,
39 * parent?: string|null,
40 * relation?: AssociationMapping|null,
41 * map?: string|null,
42 * resultVariable?: AST\Node|string,
43 * nestingLevel: int,
44 * token: DqlToken,
45 * }
46 */
47final class Parser
48{
49 /**
50 * @readonly Maps BUILT-IN string function names to AST class names.
51 * @psalm-var array<string, class-string<Functions\FunctionNode>>
52 */
53 private static array $stringFunctions = [
54 'concat' => Functions\ConcatFunction::class,
55 'substring' => Functions\SubstringFunction::class,
56 'trim' => Functions\TrimFunction::class,
57 'lower' => Functions\LowerFunction::class,
58 'upper' => Functions\UpperFunction::class,
59 'identity' => Functions\IdentityFunction::class,
60 ];
61
62 /**
63 * @readonly Maps BUILT-IN numeric function names to AST class names.
64 * @psalm-var array<string, class-string<Functions\FunctionNode>>
65 */
66 private static array $numericFunctions = [
67 'length' => Functions\LengthFunction::class,
68 'locate' => Functions\LocateFunction::class,
69 'abs' => Functions\AbsFunction::class,
70 'sqrt' => Functions\SqrtFunction::class,
71 'mod' => Functions\ModFunction::class,
72 'size' => Functions\SizeFunction::class,
73 'date_diff' => Functions\DateDiffFunction::class,
74 'bit_and' => Functions\BitAndFunction::class,
75 'bit_or' => Functions\BitOrFunction::class,
76
77 // Aggregate functions
78 'min' => Functions\MinFunction::class,
79 'max' => Functions\MaxFunction::class,
80 'avg' => Functions\AvgFunction::class,
81 'sum' => Functions\SumFunction::class,
82 'count' => Functions\CountFunction::class,
83 ];
84
85 /**
86 * @readonly Maps BUILT-IN datetime function names to AST class names.
87 * @psalm-var array<string, class-string<Functions\FunctionNode>>
88 */
89 private static array $datetimeFunctions = [
90 'current_date' => Functions\CurrentDateFunction::class,
91 'current_time' => Functions\CurrentTimeFunction::class,
92 'current_timestamp' => Functions\CurrentTimestampFunction::class,
93 'date_add' => Functions\DateAddFunction::class,
94 'date_sub' => Functions\DateSubFunction::class,
95 ];
96
97 /*
98 * Expressions that were encountered during parsing of identifiers and expressions
99 * and still need to be validated.
100 */
101
102 /** @psalm-var list<array{token: DqlToken|null, expression: mixed, nestingLevel: int}> */
103 private array $deferredIdentificationVariables = [];
104
105 /** @psalm-var list<array{token: DqlToken|null, expression: AST\PathExpression, nestingLevel: int}> */
106 private array $deferredPathExpressions = [];
107
108 /** @psalm-var list<array{token: DqlToken|null, expression: mixed, nestingLevel: int}> */
109 private array $deferredResultVariables = [];
110
111 /** @psalm-var list<array{token: DqlToken|null, expression: AST\NewObjectExpression, nestingLevel: int}> */
112 private array $deferredNewObjectExpressions = [];
113
114 /**
115 * The lexer.
116 */
117 private readonly Lexer $lexer;
118
119 /**
120 * The parser result.
121 */
122 private readonly ParserResult $parserResult;
123
124 /**
125 * The EntityManager.
126 */
127 private readonly EntityManagerInterface $em;
128
129 /**
130 * Map of declared query components in the parsed query.
131 *
132 * @psalm-var array<string, QueryComponent>
133 */
134 private array $queryComponents = [];
135
136 /**
137 * Keeps the nesting level of defined ResultVariables.
138 */
139 private int $nestingLevel = 0;
140
141 /**
142 * Any additional custom tree walkers that modify the AST.
143 *
144 * @psalm-var list<class-string<TreeWalker>>
145 */
146 private array $customTreeWalkers = [];
147
148 /**
149 * The custom last tree walker, if any, that is responsible for producing the output.
150 *
151 * @var class-string<SqlWalker>|null
152 */
153 private $customOutputWalker;
154
155 /** @psalm-var array<string, AST\SelectExpression> */
156 private array $identVariableExpressions = [];
157
158 /**
159 * Creates a new query parser object.
160 *
161 * @param Query $query The Query to parse.
162 */
163 public function __construct(private readonly Query $query)
164 {
165 $this->em = $query->getEntityManager();
166 $this->lexer = new Lexer((string) $query->getDQL());
167 $this->parserResult = new ParserResult();
168 }
169
170 /**
171 * Sets a custom tree walker that produces output.
172 * This tree walker will be run last over the AST, after any other walkers.
173 *
174 * @psalm-param class-string<SqlWalker> $className
175 */
176 public function setCustomOutputTreeWalker(string $className): void
177 {
178 $this->customOutputWalker = $className;
179 }
180
181 /**
182 * Adds a custom tree walker for modifying the AST.
183 *
184 * @psalm-param class-string<TreeWalker> $className
185 */
186 public function addCustomTreeWalker(string $className): void
187 {
188 $this->customTreeWalkers[] = $className;
189 }
190
191 /**
192 * Gets the lexer used by the parser.
193 */
194 public function getLexer(): Lexer
195 {
196 return $this->lexer;
197 }
198
199 /**
200 * Gets the ParserResult that is being filled with information during parsing.
201 */
202 public function getParserResult(): ParserResult
203 {
204 return $this->parserResult;
205 }
206
207 /**
208 * Gets the EntityManager used by the parser.
209 */
210 public function getEntityManager(): EntityManagerInterface
211 {
212 return $this->em;
213 }
214
215 /**
216 * Parses and builds AST for the given Query.
217 */
218 public function getAST(): AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement
219 {
220 // Parse & build AST
221 $AST = $this->QueryLanguage();
222
223 // Process any deferred validations of some nodes in the AST.
224 // This also allows post-processing of the AST for modification purposes.
225 $this->processDeferredIdentificationVariables();
226
227 if ($this->deferredPathExpressions) {
228 $this->processDeferredPathExpressions();
229 }
230
231 if ($this->deferredResultVariables) {
232 $this->processDeferredResultVariables();
233 }
234
235 if ($this->deferredNewObjectExpressions) {
236 $this->processDeferredNewObjectExpressions($AST);
237 }
238
239 $this->processRootEntityAliasSelected();
240
241 // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
242 $this->fixIdentificationVariableOrder($AST);
243
244 return $AST;
245 }
246
247 /**
248 * Attempts to match the given token with the current lookahead token.
249 *
250 * If they match, updates the lookahead token; otherwise raises a syntax
251 * error.
252 *
253 * @throws QueryException If the tokens don't match.
254 */
255 public function match(TokenType $token): void
256 {
257 $lookaheadType = $this->lexer->lookahead->type ?? null;
258
259 // Short-circuit on first condition, usually types match
260 if ($lookaheadType === $token) {
261 $this->lexer->moveNext();
262
263 return;
264 }
265
266 // If parameter is not identifier (1-99) must be exact match
267 if ($token->value < TokenType::T_IDENTIFIER->value) {
268 $this->syntaxError($this->lexer->getLiteral($token));
269 }
270
271 // If parameter is keyword (200+) must be exact match
272 if ($token->value > TokenType::T_IDENTIFIER->value) {
273 $this->syntaxError($this->lexer->getLiteral($token));
274 }
275
276 // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
277 if ($token->value === TokenType::T_IDENTIFIER->value && $lookaheadType->value < TokenType::T_IDENTIFIER->value) {
278 $this->syntaxError($this->lexer->getLiteral($token));
279 }
280
281 $this->lexer->moveNext();
282 }
283
284 /**
285 * Frees this parser, enabling it to be reused.
286 *
287 * @param bool $deep Whether to clean peek and reset errors.
288 * @param int $position Position to reset.
289 */
290 public function free(bool $deep = false, int $position = 0): void
291 {
292 // WARNING! Use this method with care. It resets the scanner!
293 $this->lexer->resetPosition($position);
294
295 // Deep = true cleans peek and also any previously defined errors
296 if ($deep) {
297 $this->lexer->resetPeek();
298 }
299
300 $this->lexer->token = null;
301 $this->lexer->lookahead = null;
302 }
303
304 /**
305 * Parses a query string.
306 */
307 public function parse(): ParserResult
308 {
309 $AST = $this->getAST();
310
311 $customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
312 if ($customWalkers !== false) {
313 $this->customTreeWalkers = $customWalkers;
314 }
315
316 $customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER);
317 if ($customOutputWalker !== false) {
318 $this->customOutputWalker = $customOutputWalker;
319 }
320
321 // Run any custom tree walkers over the AST
322 if ($this->customTreeWalkers) {
323 $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
324
325 foreach ($this->customTreeWalkers as $walker) {
326 $treeWalkerChain->addTreeWalker($walker);
327 }
328
329 match (true) {
330 $AST instanceof AST\UpdateStatement => $treeWalkerChain->walkUpdateStatement($AST),
331 $AST instanceof AST\DeleteStatement => $treeWalkerChain->walkDeleteStatement($AST),
332 $AST instanceof AST\SelectStatement => $treeWalkerChain->walkSelectStatement($AST),
333 };
334
335 $this->queryComponents = $treeWalkerChain->getQueryComponents();
336 }
337
338 $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
339 $outputWalker = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
340
341 // Assign an SQL executor to the parser result
342 $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
343
344 return $this->parserResult;
345 }
346
347 /**
348 * Fixes order of identification variables.
349 *
350 * They have to appear in the select clause in the same order as the
351 * declarations (from ... x join ... y join ... z ...) appear in the query
352 * as the hydration process relies on that order for proper operation.
353 */
354 private function fixIdentificationVariableOrder(AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST): void
355 {
356 if (count($this->identVariableExpressions) <= 1) {
357 return;
358 }
359
360 assert($AST instanceof AST\SelectStatement);
361
362 foreach ($this->queryComponents as $dqlAlias => $qComp) {
363 if (! isset($this->identVariableExpressions[$dqlAlias])) {
364 continue;
365 }
366
367 $expr = $this->identVariableExpressions[$dqlAlias];
368 $key = array_search($expr, $AST->selectClause->selectExpressions, true);
369
370 unset($AST->selectClause->selectExpressions[$key]);
371
372 $AST->selectClause->selectExpressions[] = $expr;
373 }
374 }
375
376 /**
377 * Generates a new syntax error.
378 *
379 * @param string $expected Expected string.
380 * @param DqlToken|null $token Got token.
381 *
382 * @throws QueryException
383 */
384 public function syntaxError(string $expected = '', Token|null $token = null): never
385 {
386 if ($token === null) {
387 $token = $this->lexer->lookahead;
388 }
389
390 $tokenPos = $token->position ?? '-1';
391
392 $message = sprintf('line 0, col %d: Error: ', $tokenPos);
393 $message .= $expected !== '' ? sprintf('Expected %s, got ', $expected) : 'Unexpected ';
394 $message .= $this->lexer->lookahead === null ? 'end of string.' : sprintf("'%s'", $token->value);
395
396 throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL() ?? ''));
397 }
398
399 /**
400 * Generates a new semantical error.
401 *
402 * @param string $message Optional message.
403 * @psalm-param DqlToken|null $token
404 *
405 * @throws QueryException
406 */
407 public function semanticalError(string $message = '', Token|null $token = null): never
408 {
409 if ($token === null) {
410 $token = $this->lexer->lookahead ?? new Token('fake token', 42, 0);
411 }
412
413 // Minimum exposed chars ahead of token
414 $distance = 12;
415
416 // Find a position of a final word to display in error string
417 $dql = $this->query->getDQL();
418 $length = strlen($dql);
419 $pos = $token->position + $distance;
420 $pos = strpos($dql, ' ', $length > $pos ? $pos : $length);
421 $length = $pos !== false ? $pos - $token->position : $distance;
422
423 $tokenPos = $token->position > 0 ? $token->position : '-1';
424 $tokenStr = substr($dql, $token->position, $length);
425
426 // Building informative message
427 $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
428
429 throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL()));
430 }
431
432 /**
433 * Peeks beyond the matched closing parenthesis and returns the first token after that one.
434 *
435 * @param bool $resetPeek Reset peek after finding the closing parenthesis.
436 *
437 * @psalm-return DqlToken|null
438 */
439 private function peekBeyondClosingParenthesis(bool $resetPeek = true): Token|null
440 {
441 $token = $this->lexer->peek();
442 $numUnmatched = 1;
443
444 while ($numUnmatched > 0 && $token !== null) {
445 switch ($token->type) {
446 case TokenType::T_OPEN_PARENTHESIS:
447 ++$numUnmatched;
448 break;
449
450 case TokenType::T_CLOSE_PARENTHESIS:
451 --$numUnmatched;
452 break;
453
454 default:
455 // Do nothing
456 }
457
458 $token = $this->lexer->peek();
459 }
460
461 if ($resetPeek) {
462 $this->lexer->resetPeek();
463 }
464
465 return $token;
466 }
467
468 /**
469 * Checks if the given token indicates a mathematical operator.
470 *
471 * @psalm-param DqlToken|null $token
472 */
473 private function isMathOperator(Token|null $token): bool
474 {
475 return $token !== null && in_array($token->type, [TokenType::T_PLUS, TokenType::T_MINUS, TokenType::T_DIVIDE, TokenType::T_MULTIPLY], true);
476 }
477
478 /**
479 * Checks if the next-next (after lookahead) token starts a function.
480 *
481 * @return bool TRUE if the next-next tokens start a function, FALSE otherwise.
482 */
483 private function isFunction(): bool
484 {
485 assert($this->lexer->lookahead !== null);
486 $lookaheadType = $this->lexer->lookahead->type;
487 $peek = $this->lexer->peek();
488
489 $this->lexer->resetPeek();
490
491 return $lookaheadType->value >= TokenType::T_IDENTIFIER->value && $peek !== null && $peek->type === TokenType::T_OPEN_PARENTHESIS;
492 }
493
494 /**
495 * Checks whether the given token type indicates an aggregate function.
496 *
497 * @return bool TRUE if the token type is an aggregate function, FALSE otherwise.
498 */
499 private function isAggregateFunction(TokenType $tokenType): bool
500 {
501 return in_array(
502 $tokenType,
503 [TokenType::T_AVG, TokenType::T_MIN, TokenType::T_MAX, TokenType::T_SUM, TokenType::T_COUNT],
504 true,
505 );
506 }
507
508 /**
509 * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME.
510 */
511 private function isNextAllAnySome(): bool
512 {
513 assert($this->lexer->lookahead !== null);
514
515 return in_array(
516 $this->lexer->lookahead->type,
517 [TokenType::T_ALL, TokenType::T_ANY, TokenType::T_SOME],
518 true,
519 );
520 }
521
522 /**
523 * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
524 * It must exist in query components list.
525 */
526 private function processDeferredIdentificationVariables(): void
527 {
528 foreach ($this->deferredIdentificationVariables as $deferredItem) {
529 $identVariable = $deferredItem['expression'];
530
531 // Check if IdentificationVariable exists in queryComponents
532 if (! isset($this->queryComponents[$identVariable])) {
533 $this->semanticalError(
534 sprintf("'%s' is not defined.", $identVariable),
535 $deferredItem['token'],
536 );
537 }
538
539 $qComp = $this->queryComponents[$identVariable];
540
541 // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
542 if (! isset($qComp['metadata'])) {
543 $this->semanticalError(
544 sprintf("'%s' does not point to a Class.", $identVariable),
545 $deferredItem['token'],
546 );
547 }
548
549 // Validate if identification variable nesting level is lower or equal than the current one
550 if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
551 $this->semanticalError(
552 sprintf("'%s' is used outside the scope of its declaration.", $identVariable),
553 $deferredItem['token'],
554 );
555 }
556 }
557 }
558
559 /**
560 * Validates that the given <tt>NewObjectExpression</tt>.
561 */
562 private function processDeferredNewObjectExpressions(AST\SelectStatement $AST): void
563 {
564 foreach ($this->deferredNewObjectExpressions as $deferredItem) {
565 $expression = $deferredItem['expression'];
566 $token = $deferredItem['token'];
567 $className = $expression->className;
568 $args = $expression->args;
569 $fromClassName = $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null;
570
571 // If the namespace is not given then assumes the first FROM entity namespace
572 if (! str_contains($className, '\\') && ! class_exists($className) && is_string($fromClassName) && str_contains($fromClassName, '\\')) {
573 $namespace = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
574 $fqcn = $namespace . '\\' . $className;
575
576 if (class_exists($fqcn)) {
577 $expression->className = $fqcn;
578 $className = $fqcn;
579 }
580 }
581
582 if (! class_exists($className)) {
583 $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
584 }
585
586 $class = new ReflectionClass($className);
587
588 if (! $class->isInstantiable()) {
589 $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
590 }
591
592 if ($class->getConstructor() === null) {
593 $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
594 }
595
596 if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
597 $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
598 }
599 }
600 }
601
602 /**
603 * Validates that the given <tt>ResultVariable</tt> is semantically correct.
604 * It must exist in query components list.
605 */
606 private function processDeferredResultVariables(): void
607 {
608 foreach ($this->deferredResultVariables as $deferredItem) {
609 $resultVariable = $deferredItem['expression'];
610
611 // Check if ResultVariable exists in queryComponents
612 if (! isset($this->queryComponents[$resultVariable])) {
613 $this->semanticalError(
614 sprintf("'%s' is not defined.", $resultVariable),
615 $deferredItem['token'],
616 );
617 }
618
619 $qComp = $this->queryComponents[$resultVariable];
620
621 // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
622 if (! isset($qComp['resultVariable'])) {
623 $this->semanticalError(
624 sprintf("'%s' does not point to a ResultVariable.", $resultVariable),
625 $deferredItem['token'],
626 );
627 }
628
629 // Validate if identification variable nesting level is lower or equal than the current one
630 if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
631 $this->semanticalError(
632 sprintf("'%s' is used outside the scope of its declaration.", $resultVariable),
633 $deferredItem['token'],
634 );
635 }
636 }
637 }
638
639 /**
640 * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
641 *
642 * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
643 * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
644 * StateFieldPathExpression ::= IdentificationVariable "." StateField
645 * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
646 * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
647 */
648 private function processDeferredPathExpressions(): void
649 {
650 foreach ($this->deferredPathExpressions as $deferredItem) {
651 $pathExpression = $deferredItem['expression'];
652
653 $class = $this->getMetadataForDqlAlias($pathExpression->identificationVariable);
654
655 $field = $pathExpression->field;
656 if ($field === null) {
657 $field = $pathExpression->field = $class->identifier[0];
658 }
659
660 // Check if field or association exists
661 if (! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
662 $this->semanticalError(
663 'Class ' . $class->name . ' has no field or association named ' . $field,
664 $deferredItem['token'],
665 );
666 }
667
668 $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
669
670 if (isset($class->associationMappings[$field])) {
671 $assoc = $class->associationMappings[$field];
672
673 $fieldType = $assoc->isToOne()
674 ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
675 : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
676 }
677
678 // Validate if PathExpression is one of the expected types
679 $expectedType = $pathExpression->expectedType;
680
681 if (! ($expectedType & $fieldType)) {
682 // We need to recognize which was expected type(s)
683 $expectedStringTypes = [];
684
685 // Validate state field type
686 if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
687 $expectedStringTypes[] = 'StateFieldPathExpression';
688 }
689
690 // Validate single valued association (*-to-one)
691 if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
692 $expectedStringTypes[] = 'SingleValuedAssociationField';
693 }
694
695 // Validate single valued association (*-to-many)
696 if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
697 $expectedStringTypes[] = 'CollectionValuedAssociationField';
698 }
699
700 // Build the error message
701 $semanticalError = 'Invalid PathExpression. ';
702 $semanticalError .= count($expectedStringTypes) === 1
703 ? 'Must be a ' . $expectedStringTypes[0] . '.'
704 : implode(' or ', $expectedStringTypes) . ' expected.';
705
706 $this->semanticalError($semanticalError, $deferredItem['token']);
707 }
708
709 // We need to force the type in PathExpression
710 $pathExpression->type = $fieldType;
711 }
712 }
713
714 private function processRootEntityAliasSelected(): void
715 {
716 if (! count($this->identVariableExpressions)) {
717 return;
718 }
719
720 foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
721 if (isset($this->queryComponents[$dqlAlias]) && ! isset($this->queryComponents[$dqlAlias]['parent'])) {
722 return;
723 }
724 }
725
726 $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
727 }
728
729 /**
730 * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
731 */
732 public function QueryLanguage(): AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement
733 {
734 $statement = null;
735
736 $this->lexer->moveNext();
737
738 switch ($this->lexer->lookahead->type ?? null) {
739 case TokenType::T_SELECT:
740 $statement = $this->SelectStatement();
741 break;
742
743 case TokenType::T_UPDATE:
744 $statement = $this->UpdateStatement();
745 break;
746
747 case TokenType::T_DELETE:
748 $statement = $this->DeleteStatement();
749 break;
750
751 default:
752 $this->syntaxError('SELECT, UPDATE or DELETE');
753 break;
754 }
755
756 // Check for end of string
757 if ($this->lexer->lookahead !== null) {
758 $this->syntaxError('end of string');
759 }
760
761 return $statement;
762 }
763
764 /**
765 * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
766 */
767 public function SelectStatement(): AST\SelectStatement
768 {
769 $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
770
771 $selectStatement->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
772 $selectStatement->groupByClause = $this->lexer->isNextToken(TokenType::T_GROUP) ? $this->GroupByClause() : null;
773 $selectStatement->havingClause = $this->lexer->isNextToken(TokenType::T_HAVING) ? $this->HavingClause() : null;
774 $selectStatement->orderByClause = $this->lexer->isNextToken(TokenType::T_ORDER) ? $this->OrderByClause() : null;
775
776 return $selectStatement;
777 }
778
779 /**
780 * UpdateStatement ::= UpdateClause [WhereClause]
781 */
782 public function UpdateStatement(): AST\UpdateStatement
783 {
784 $updateStatement = new AST\UpdateStatement($this->UpdateClause());
785
786 $updateStatement->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
787
788 return $updateStatement;
789 }
790
791 /**
792 * DeleteStatement ::= DeleteClause [WhereClause]
793 */
794 public function DeleteStatement(): AST\DeleteStatement
795 {
796 $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
797
798 $deleteStatement->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
799
800 return $deleteStatement;
801 }
802
803 /**
804 * IdentificationVariable ::= identifier
805 */
806 public function IdentificationVariable(): string
807 {
808 $this->match(TokenType::T_IDENTIFIER);
809
810 assert($this->lexer->token !== null);
811 $identVariable = $this->lexer->token->value;
812
813 $this->deferredIdentificationVariables[] = [
814 'expression' => $identVariable,
815 'nestingLevel' => $this->nestingLevel,
816 'token' => $this->lexer->token,
817 ];
818
819 return $identVariable;
820 }
821
822 /**
823 * AliasIdentificationVariable = identifier
824 */
825 public function AliasIdentificationVariable(): string
826 {
827 $this->match(TokenType::T_IDENTIFIER);
828
829 assert($this->lexer->token !== null);
830 $aliasIdentVariable = $this->lexer->token->value;
831 $exists = isset($this->queryComponents[$aliasIdentVariable]);
832
833 if ($exists) {
834 $this->semanticalError(
835 sprintf("'%s' is already defined.", $aliasIdentVariable),
836 $this->lexer->token,
837 );
838 }
839
840 return $aliasIdentVariable;
841 }
842
843 /**
844 * AbstractSchemaName ::= fully_qualified_name | identifier
845 */
846 public function AbstractSchemaName(): string
847 {
848 if ($this->lexer->isNextToken(TokenType::T_FULLY_QUALIFIED_NAME)) {
849 $this->match(TokenType::T_FULLY_QUALIFIED_NAME);
850 assert($this->lexer->token !== null);
851
852 return $this->lexer->token->value;
853 }
854
855 $this->match(TokenType::T_IDENTIFIER);
856 assert($this->lexer->token !== null);
857
858 return $this->lexer->token->value;
859 }
860
861 /**
862 * Validates an AbstractSchemaName, making sure the class exists.
863 *
864 * @param string $schemaName The name to validate.
865 *
866 * @throws QueryException if the name does not exist.
867 */
868 private function validateAbstractSchemaName(string $schemaName): void
869 {
870 assert($this->lexer->token !== null);
871 if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) {
872 $this->semanticalError(
873 sprintf("Class '%s' is not defined.", $schemaName),
874 $this->lexer->token,
875 );
876 }
877 }
878
879 /**
880 * AliasResultVariable ::= identifier
881 */
882 public function AliasResultVariable(): string
883 {
884 $this->match(TokenType::T_IDENTIFIER);
885
886 assert($this->lexer->token !== null);
887 $resultVariable = $this->lexer->token->value;
888 $exists = isset($this->queryComponents[$resultVariable]);
889
890 if ($exists) {
891 $this->semanticalError(
892 sprintf("'%s' is already defined.", $resultVariable),
893 $this->lexer->token,
894 );
895 }
896
897 return $resultVariable;
898 }
899
900 /**
901 * ResultVariable ::= identifier
902 */
903 public function ResultVariable(): string
904 {
905 $this->match(TokenType::T_IDENTIFIER);
906
907 assert($this->lexer->token !== null);
908 $resultVariable = $this->lexer->token->value;
909
910 // Defer ResultVariable validation
911 $this->deferredResultVariables[] = [
912 'expression' => $resultVariable,
913 'nestingLevel' => $this->nestingLevel,
914 'token' => $this->lexer->token,
915 ];
916
917 return $resultVariable;
918 }
919
920 /**
921 * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
922 */
923 public function JoinAssociationPathExpression(): AST\JoinAssociationPathExpression
924 {
925 $identVariable = $this->IdentificationVariable();
926
927 if (! isset($this->queryComponents[$identVariable])) {
928 $this->semanticalError(
929 'Identification Variable ' . $identVariable . ' used in join path expression but was not defined before.',
930 );
931 }
932
933 $this->match(TokenType::T_DOT);
934 $this->match(TokenType::T_IDENTIFIER);
935
936 assert($this->lexer->token !== null);
937 $field = $this->lexer->token->value;
938
939 // Validate association field
940 $class = $this->getMetadataForDqlAlias($identVariable);
941
942 if (! $class->hasAssociation($field)) {
943 $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
944 }
945
946 return new AST\JoinAssociationPathExpression($identVariable, $field);
947 }
948
949 /**
950 * Parses an arbitrary path expression and defers semantical validation
951 * based on expected types.
952 *
953 * PathExpression ::= IdentificationVariable {"." identifier}*
954 *
955 * @psalm-param int-mask-of<AST\PathExpression::TYPE_*> $expectedTypes
956 */
957 public function PathExpression(int $expectedTypes): AST\PathExpression
958 {
959 $identVariable = $this->IdentificationVariable();
960 $field = null;
961
962 assert($this->lexer->token !== null);
963 if ($this->lexer->isNextToken(TokenType::T_DOT)) {
964 $this->match(TokenType::T_DOT);
965 $this->match(TokenType::T_IDENTIFIER);
966
967 $field = $this->lexer->token->value;
968
969 while ($this->lexer->isNextToken(TokenType::T_DOT)) {
970 $this->match(TokenType::T_DOT);
971 $this->match(TokenType::T_IDENTIFIER);
972 $field .= '.' . $this->lexer->token->value;
973 }
974 }
975
976 // Creating AST node
977 $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
978
979 // Defer PathExpression validation if requested to be deferred
980 $this->deferredPathExpressions[] = [
981 'expression' => $pathExpr,
982 'nestingLevel' => $this->nestingLevel,
983 'token' => $this->lexer->token,
984 ];
985
986 return $pathExpr;
987 }
988
989 /**
990 * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
991 */
992 public function AssociationPathExpression(): AST\PathExpression
993 {
994 return $this->PathExpression(
995 AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
996 AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION,
997 );
998 }
999
1000 /**
1001 * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
1002 */
1003 public function SingleValuedPathExpression(): AST\PathExpression
1004 {
1005 return $this->PathExpression(
1006 AST\PathExpression::TYPE_STATE_FIELD |
1007 AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
1008 );
1009 }
1010
1011 /**
1012 * StateFieldPathExpression ::= IdentificationVariable "." StateField
1013 */
1014 public function StateFieldPathExpression(): AST\PathExpression
1015 {
1016 return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
1017 }
1018
1019 /**
1020 * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
1021 */
1022 public function SingleValuedAssociationPathExpression(): AST\PathExpression
1023 {
1024 return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
1025 }
1026
1027 /**
1028 * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
1029 */
1030 public function CollectionValuedPathExpression(): AST\PathExpression
1031 {
1032 return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
1033 }
1034
1035 /**
1036 * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
1037 */
1038 public function SelectClause(): AST\SelectClause
1039 {
1040 $isDistinct = false;
1041 $this->match(TokenType::T_SELECT);
1042
1043 // Check for DISTINCT
1044 if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) {
1045 $this->match(TokenType::T_DISTINCT);
1046
1047 $isDistinct = true;
1048 }
1049
1050 // Process SelectExpressions (1..N)
1051 $selectExpressions = [];
1052 $selectExpressions[] = $this->SelectExpression();
1053
1054 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
1055 $this->match(TokenType::T_COMMA);
1056
1057 $selectExpressions[] = $this->SelectExpression();
1058 }
1059
1060 return new AST\SelectClause($selectExpressions, $isDistinct);
1061 }
1062
1063 /**
1064 * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1065 */
1066 public function SimpleSelectClause(): AST\SimpleSelectClause
1067 {
1068 $isDistinct = false;
1069 $this->match(TokenType::T_SELECT);
1070
1071 if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) {
1072 $this->match(TokenType::T_DISTINCT);
1073
1074 $isDistinct = true;
1075 }
1076
1077 return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1078 }
1079
1080 /**
1081 * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1082 */
1083 public function UpdateClause(): AST\UpdateClause
1084 {
1085 $this->match(TokenType::T_UPDATE);
1086 assert($this->lexer->lookahead !== null);
1087
1088 $token = $this->lexer->lookahead;
1089 $abstractSchemaName = $this->AbstractSchemaName();
1090
1091 $this->validateAbstractSchemaName($abstractSchemaName);
1092
1093 if ($this->lexer->isNextToken(TokenType::T_AS)) {
1094 $this->match(TokenType::T_AS);
1095 }
1096
1097 $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1098
1099 $class = $this->em->getClassMetadata($abstractSchemaName);
1100
1101 // Building queryComponent
1102 $queryComponent = [
1103 'metadata' => $class,
1104 'parent' => null,
1105 'relation' => null,
1106 'map' => null,
1107 'nestingLevel' => $this->nestingLevel,
1108 'token' => $token,
1109 ];
1110
1111 $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1112
1113 $this->match(TokenType::T_SET);
1114
1115 $updateItems = [];
1116 $updateItems[] = $this->UpdateItem();
1117
1118 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
1119 $this->match(TokenType::T_COMMA);
1120
1121 $updateItems[] = $this->UpdateItem();
1122 }
1123
1124 $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
1125 $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1126
1127 return $updateClause;
1128 }
1129
1130 /**
1131 * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1132 */
1133 public function DeleteClause(): AST\DeleteClause
1134 {
1135 $this->match(TokenType::T_DELETE);
1136
1137 if ($this->lexer->isNextToken(TokenType::T_FROM)) {
1138 $this->match(TokenType::T_FROM);
1139 }
1140
1141 assert($this->lexer->lookahead !== null);
1142 $token = $this->lexer->lookahead;
1143 $abstractSchemaName = $this->AbstractSchemaName();
1144
1145 $this->validateAbstractSchemaName($abstractSchemaName);
1146
1147 $deleteClause = new AST\DeleteClause($abstractSchemaName);
1148
1149 if ($this->lexer->isNextToken(TokenType::T_AS)) {
1150 $this->match(TokenType::T_AS);
1151 }
1152
1153 $aliasIdentificationVariable = $this->lexer->isNextToken(TokenType::T_IDENTIFIER)
1154 ? $this->AliasIdentificationVariable()
1155 : 'alias_should_have_been_set';
1156
1157 $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1158 $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1159
1160 // Building queryComponent
1161 $queryComponent = [
1162 'metadata' => $class,
1163 'parent' => null,
1164 'relation' => null,
1165 'map' => null,
1166 'nestingLevel' => $this->nestingLevel,
1167 'token' => $token,
1168 ];
1169
1170 $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1171
1172 return $deleteClause;
1173 }
1174
1175 /**
1176 * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1177 */
1178 public function FromClause(): AST\FromClause
1179 {
1180 $this->match(TokenType::T_FROM);
1181
1182 $identificationVariableDeclarations = [];
1183 $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1184
1185 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
1186 $this->match(TokenType::T_COMMA);
1187
1188 $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1189 }
1190
1191 return new AST\FromClause($identificationVariableDeclarations);
1192 }
1193
1194 /**
1195 * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1196 */
1197 public function SubselectFromClause(): AST\SubselectFromClause
1198 {
1199 $this->match(TokenType::T_FROM);
1200
1201 $identificationVariables = [];
1202 $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1203
1204 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
1205 $this->match(TokenType::T_COMMA);
1206
1207 $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1208 }
1209
1210 return new AST\SubselectFromClause($identificationVariables);
1211 }
1212
1213 /**
1214 * WhereClause ::= "WHERE" ConditionalExpression
1215 */
1216 public function WhereClause(): AST\WhereClause
1217 {
1218 $this->match(TokenType::T_WHERE);
1219
1220 return new AST\WhereClause($this->ConditionalExpression());
1221 }
1222
1223 /**
1224 * HavingClause ::= "HAVING" ConditionalExpression
1225 */
1226 public function HavingClause(): AST\HavingClause
1227 {
1228 $this->match(TokenType::T_HAVING);
1229
1230 return new AST\HavingClause($this->ConditionalExpression());
1231 }
1232
1233 /**
1234 * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1235 */
1236 public function GroupByClause(): AST\GroupByClause
1237 {
1238 $this->match(TokenType::T_GROUP);
1239 $this->match(TokenType::T_BY);
1240
1241 $groupByItems = [$this->GroupByItem()];
1242
1243 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
1244 $this->match(TokenType::T_COMMA);
1245
1246 $groupByItems[] = $this->GroupByItem();
1247 }
1248
1249 return new AST\GroupByClause($groupByItems);
1250 }
1251
1252 /**
1253 * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1254 */
1255 public function OrderByClause(): AST\OrderByClause
1256 {
1257 $this->match(TokenType::T_ORDER);
1258 $this->match(TokenType::T_BY);
1259
1260 $orderByItems = [];
1261 $orderByItems[] = $this->OrderByItem();
1262
1263 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
1264 $this->match(TokenType::T_COMMA);
1265
1266 $orderByItems[] = $this->OrderByItem();
1267 }
1268
1269 return new AST\OrderByClause($orderByItems);
1270 }
1271
1272 /**
1273 * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1274 */
1275 public function Subselect(): AST\Subselect
1276 {
1277 // Increase query nesting level
1278 $this->nestingLevel++;
1279
1280 $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1281
1282 $subselect->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
1283 $subselect->groupByClause = $this->lexer->isNextToken(TokenType::T_GROUP) ? $this->GroupByClause() : null;
1284 $subselect->havingClause = $this->lexer->isNextToken(TokenType::T_HAVING) ? $this->HavingClause() : null;
1285 $subselect->orderByClause = $this->lexer->isNextToken(TokenType::T_ORDER) ? $this->OrderByClause() : null;
1286
1287 // Decrease query nesting level
1288 $this->nestingLevel--;
1289
1290 return $subselect;
1291 }
1292
1293 /**
1294 * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1295 */
1296 public function UpdateItem(): AST\UpdateItem
1297 {
1298 $pathExpr = $this->SingleValuedPathExpression();
1299
1300 $this->match(TokenType::T_EQUALS);
1301
1302 return new AST\UpdateItem($pathExpr, $this->NewValue());
1303 }
1304
1305 /**
1306 * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1307 */
1308 public function GroupByItem(): string|AST\PathExpression
1309 {
1310 // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1311 $glimpse = $this->lexer->glimpse();
1312
1313 if ($glimpse !== null && $glimpse->type === TokenType::T_DOT) {
1314 return $this->SingleValuedPathExpression();
1315 }
1316
1317 assert($this->lexer->lookahead !== null);
1318 // Still need to decide between IdentificationVariable or ResultVariable
1319 $lookaheadValue = $this->lexer->lookahead->value;
1320
1321 if (! isset($this->queryComponents[$lookaheadValue])) {
1322 $this->semanticalError('Cannot group by undefined identification or result variable.');
1323 }
1324
1325 return isset($this->queryComponents[$lookaheadValue]['metadata'])
1326 ? $this->IdentificationVariable()
1327 : $this->ResultVariable();
1328 }
1329
1330 /**
1331 * OrderByItem ::= (
1332 * SimpleArithmeticExpression | SingleValuedPathExpression | CaseExpression |
1333 * ScalarExpression | ResultVariable | FunctionDeclaration
1334 * ) ["ASC" | "DESC"]
1335 */
1336 public function OrderByItem(): AST\OrderByItem
1337 {
1338 $this->lexer->peek(); // lookahead => '.'
1339 $this->lexer->peek(); // lookahead => token after '.'
1340
1341 $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1342
1343 $this->lexer->resetPeek();
1344
1345 $glimpse = $this->lexer->glimpse();
1346
1347 assert($this->lexer->lookahead !== null);
1348 $expr = match (true) {
1349 $this->isMathOperator($peek) => $this->SimpleArithmeticExpression(),
1350 $glimpse !== null && $glimpse->type === TokenType::T_DOT => $this->SingleValuedPathExpression(),
1351 $this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis()) => $this->ScalarExpression(),
1352 $this->lexer->lookahead->type === TokenType::T_CASE => $this->CaseExpression(),
1353 $this->isFunction() => $this->FunctionDeclaration(),
1354 default => $this->ResultVariable(),
1355 };
1356
1357 $type = 'ASC';
1358 $item = new AST\OrderByItem($expr);
1359
1360 switch (true) {
1361 case $this->lexer->isNextToken(TokenType::T_DESC):
1362 $this->match(TokenType::T_DESC);
1363 $type = 'DESC';
1364 break;
1365
1366 case $this->lexer->isNextToken(TokenType::T_ASC):
1367 $this->match(TokenType::T_ASC);
1368 break;
1369
1370 default:
1371 // Do nothing
1372 }
1373
1374 $item->type = $type;
1375
1376 return $item;
1377 }
1378
1379 /**
1380 * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1381 * EnumPrimary | SimpleEntityExpression | "NULL"
1382 *
1383 * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1384 * grammar that needs to be supported:
1385 *
1386 * NewValue ::= SimpleArithmeticExpression | "NULL"
1387 *
1388 * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1389 */
1390 public function NewValue(): AST\ArithmeticExpression|AST\InputParameter|null
1391 {
1392 if ($this->lexer->isNextToken(TokenType::T_NULL)) {
1393 $this->match(TokenType::T_NULL);
1394
1395 return null;
1396 }
1397
1398 if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
1399 $this->match(TokenType::T_INPUT_PARAMETER);
1400 assert($this->lexer->token !== null);
1401
1402 return new AST\InputParameter($this->lexer->token->value);
1403 }
1404
1405 return $this->ArithmeticExpression();
1406 }
1407
1408 /**
1409 * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1410 */
1411 public function IdentificationVariableDeclaration(): AST\IdentificationVariableDeclaration
1412 {
1413 $joins = [];
1414 $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1415 $indexBy = $this->lexer->isNextToken(TokenType::T_INDEX)
1416 ? $this->IndexBy()
1417 : null;
1418
1419 $rangeVariableDeclaration->isRoot = true;
1420
1421 while (
1422 $this->lexer->isNextToken(TokenType::T_LEFT) ||
1423 $this->lexer->isNextToken(TokenType::T_INNER) ||
1424 $this->lexer->isNextToken(TokenType::T_JOIN)
1425 ) {
1426 $joins[] = $this->Join();
1427 }
1428
1429 return new AST\IdentificationVariableDeclaration(
1430 $rangeVariableDeclaration,
1431 $indexBy,
1432 $joins,
1433 );
1434 }
1435
1436 /**
1437 * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
1438 *
1439 * {Internal note: WARNING: Solution is harder than a bare implementation.
1440 * Desired EBNF support:
1441 *
1442 * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1443 *
1444 * It demands that entire SQL generation to become programmatical. This is
1445 * needed because association based subselect requires "WHERE" conditional
1446 * expressions to be injected, but there is no scope to do that. Only scope
1447 * accessible is "FROM", prohibiting an easy implementation without larger
1448 * changes.}
1449 */
1450 public function SubselectIdentificationVariableDeclaration(): AST\IdentificationVariableDeclaration
1451 {
1452 /*
1453 NOT YET IMPLEMENTED!
1454
1455 $glimpse = $this->lexer->glimpse();
1456
1457 if ($glimpse->type == TokenType::T_DOT) {
1458 $associationPathExpression = $this->AssociationPathExpression();
1459
1460 if ($this->lexer->isNextToken(TokenType::T_AS)) {
1461 $this->match(TokenType::T_AS);
1462 }
1463
1464 $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1465 $identificationVariable = $associationPathExpression->identificationVariable;
1466 $field = $associationPathExpression->associationField;
1467
1468 $class = $this->queryComponents[$identificationVariable]['metadata'];
1469 $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1470
1471 // Building queryComponent
1472 $joinQueryComponent = array(
1473 'metadata' => $targetClass,
1474 'parent' => $identificationVariable,
1475 'relation' => $class->getAssociationMapping($field),
1476 'map' => null,
1477 'nestingLevel' => $this->nestingLevel,
1478 'token' => $this->lexer->lookahead
1479 );
1480
1481 $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1482
1483 return new AST\SubselectIdentificationVariableDeclaration(
1484 $associationPathExpression, $aliasIdentificationVariable
1485 );
1486 }
1487 */
1488
1489 return $this->IdentificationVariableDeclaration();
1490 }
1491
1492 /**
1493 * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1494 * (JoinAssociationDeclaration | RangeVariableDeclaration)
1495 * ["WITH" ConditionalExpression]
1496 */
1497 public function Join(): AST\Join
1498 {
1499 // Check Join type
1500 $joinType = AST\Join::JOIN_TYPE_INNER;
1501
1502 switch (true) {
1503 case $this->lexer->isNextToken(TokenType::T_LEFT):
1504 $this->match(TokenType::T_LEFT);
1505
1506 $joinType = AST\Join::JOIN_TYPE_LEFT;
1507
1508 // Possible LEFT OUTER join
1509 if ($this->lexer->isNextToken(TokenType::T_OUTER)) {
1510 $this->match(TokenType::T_OUTER);
1511
1512 $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1513 }
1514
1515 break;
1516
1517 case $this->lexer->isNextToken(TokenType::T_INNER):
1518 $this->match(TokenType::T_INNER);
1519 break;
1520
1521 default:
1522 // Do nothing
1523 }
1524
1525 $this->match(TokenType::T_JOIN);
1526
1527 $next = $this->lexer->glimpse();
1528 assert($next !== null);
1529 $joinDeclaration = $next->type === TokenType::T_DOT ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1530 $adhocConditions = $this->lexer->isNextToken(TokenType::T_WITH);
1531 $join = new AST\Join($joinType, $joinDeclaration);
1532
1533 // Describe non-root join declaration
1534 if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1535 $joinDeclaration->isRoot = false;
1536 }
1537
1538 // Check for ad-hoc Join conditions
1539 if ($adhocConditions) {
1540 $this->match(TokenType::T_WITH);
1541
1542 $join->conditionalExpression = $this->ConditionalExpression();
1543 }
1544
1545 return $join;
1546 }
1547
1548 /**
1549 * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1550 *
1551 * @throws QueryException
1552 */
1553 public function RangeVariableDeclaration(): AST\RangeVariableDeclaration
1554 {
1555 if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()->type === TokenType::T_SELECT) {
1556 $this->semanticalError('Subquery is not supported here', $this->lexer->token);
1557 }
1558
1559 $abstractSchemaName = $this->AbstractSchemaName();
1560
1561 $this->validateAbstractSchemaName($abstractSchemaName);
1562
1563 if ($this->lexer->isNextToken(TokenType::T_AS)) {
1564 $this->match(TokenType::T_AS);
1565 }
1566
1567 assert($this->lexer->lookahead !== null);
1568 $token = $this->lexer->lookahead;
1569 $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1570 $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
1571
1572 // Building queryComponent
1573 $queryComponent = [
1574 'metadata' => $classMetadata,
1575 'parent' => null,
1576 'relation' => null,
1577 'map' => null,
1578 'nestingLevel' => $this->nestingLevel,
1579 'token' => $token,
1580 ];
1581
1582 $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1583
1584 return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1585 }
1586
1587 /**
1588 * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
1589 */
1590 public function JoinAssociationDeclaration(): AST\JoinAssociationDeclaration
1591 {
1592 $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1593
1594 if ($this->lexer->isNextToken(TokenType::T_AS)) {
1595 $this->match(TokenType::T_AS);
1596 }
1597
1598 assert($this->lexer->lookahead !== null);
1599
1600 $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1601 $indexBy = $this->lexer->isNextToken(TokenType::T_INDEX) ? $this->IndexBy() : null;
1602
1603 $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1604 $field = $joinAssociationPathExpression->associationField;
1605
1606 $class = $this->getMetadataForDqlAlias($identificationVariable);
1607 $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]->targetEntity);
1608
1609 // Building queryComponent
1610 $joinQueryComponent = [
1611 'metadata' => $targetClass,
1612 'parent' => $joinAssociationPathExpression->identificationVariable,
1613 'relation' => $class->getAssociationMapping($field),
1614 'map' => null,
1615 'nestingLevel' => $this->nestingLevel,
1616 'token' => $this->lexer->lookahead,
1617 ];
1618
1619 $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1620
1621 return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy);
1622 }
1623
1624 /**
1625 * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1626 */
1627 public function NewObjectExpression(): AST\NewObjectExpression
1628 {
1629 $args = [];
1630 $this->match(TokenType::T_NEW);
1631
1632 $className = $this->AbstractSchemaName(); // note that this is not yet validated
1633 $token = $this->lexer->token;
1634
1635 $this->match(TokenType::T_OPEN_PARENTHESIS);
1636
1637 $args[] = $this->NewObjectArg();
1638
1639 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
1640 $this->match(TokenType::T_COMMA);
1641
1642 $args[] = $this->NewObjectArg();
1643 }
1644
1645 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1646
1647 $expression = new AST\NewObjectExpression($className, $args);
1648
1649 // Defer NewObjectExpression validation
1650 $this->deferredNewObjectExpressions[] = [
1651 'token' => $token,
1652 'expression' => $expression,
1653 'nestingLevel' => $this->nestingLevel,
1654 ];
1655
1656 return $expression;
1657 }
1658
1659 /**
1660 * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1661 */
1662 public function NewObjectArg(): mixed
1663 {
1664 assert($this->lexer->lookahead !== null);
1665 $token = $this->lexer->lookahead;
1666 $peek = $this->lexer->glimpse();
1667
1668 assert($peek !== null);
1669 if ($token->type === TokenType::T_OPEN_PARENTHESIS && $peek->type === TokenType::T_SELECT) {
1670 $this->match(TokenType::T_OPEN_PARENTHESIS);
1671 $expression = $this->Subselect();
1672 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1673
1674 return $expression;
1675 }
1676
1677 return $this->ScalarExpression();
1678 }
1679
1680 /**
1681 * IndexBy ::= "INDEX" "BY" SingleValuedPathExpression
1682 */
1683 public function IndexBy(): AST\IndexBy
1684 {
1685 $this->match(TokenType::T_INDEX);
1686 $this->match(TokenType::T_BY);
1687 $pathExpr = $this->SingleValuedPathExpression();
1688
1689 // Add the INDEX BY info to the query component
1690 $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1691
1692 return new AST\IndexBy($pathExpr);
1693 }
1694
1695 /**
1696 * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1697 * StateFieldPathExpression | BooleanPrimary | CaseExpression |
1698 * InstanceOfExpression
1699 *
1700 * @return mixed One of the possible expressions or subexpressions.
1701 */
1702 public function ScalarExpression(): mixed
1703 {
1704 assert($this->lexer->token !== null);
1705 assert($this->lexer->lookahead !== null);
1706 $lookahead = $this->lexer->lookahead->type;
1707 $peek = $this->lexer->glimpse();
1708
1709 switch (true) {
1710 case $lookahead === TokenType::T_INTEGER:
1711 case $lookahead === TokenType::T_FLOAT:
1712 // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) or ( - 1 ) or ( + 1 )
1713 case $lookahead === TokenType::T_MINUS:
1714 case $lookahead === TokenType::T_PLUS:
1715 return $this->SimpleArithmeticExpression();
1716
1717 case $lookahead === TokenType::T_STRING:
1718 return $this->StringPrimary();
1719
1720 case $lookahead === TokenType::T_TRUE:
1721 case $lookahead === TokenType::T_FALSE:
1722 $this->match($lookahead);
1723
1724 return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token->value);
1725
1726 case $lookahead === TokenType::T_INPUT_PARAMETER:
1727 return match (true) {
1728 $this->isMathOperator($peek) => $this->SimpleArithmeticExpression(),
1729 default => $this->InputParameter(),
1730 };
1731
1732 case $lookahead === TokenType::T_CASE:
1733 case $lookahead === TokenType::T_COALESCE:
1734 case $lookahead === TokenType::T_NULLIF:
1735 // Since NULLIF and COALESCE can be identified as a function,
1736 // we need to check these before checking for FunctionDeclaration
1737 return $this->CaseExpression();
1738
1739 case $lookahead === TokenType::T_OPEN_PARENTHESIS:
1740 return $this->SimpleArithmeticExpression();
1741
1742 // this check must be done before checking for a filed path expression
1743 case $this->isFunction():
1744 $this->lexer->peek();
1745
1746 return match (true) {
1747 $this->isMathOperator($this->peekBeyondClosingParenthesis()) => $this->SimpleArithmeticExpression(),
1748 default => $this->FunctionDeclaration(),
1749 };
1750
1751 // it is no function, so it must be a field path
1752 case $lookahead === TokenType::T_IDENTIFIER:
1753 $this->lexer->peek(); // lookahead => '.'
1754 $this->lexer->peek(); // lookahead => token after '.'
1755 $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1756 $this->lexer->resetPeek();
1757
1758 if ($this->isMathOperator($peek)) {
1759 return $this->SimpleArithmeticExpression();
1760 }
1761
1762 return $this->StateFieldPathExpression();
1763
1764 default:
1765 $this->syntaxError();
1766 }
1767 }
1768
1769 /**
1770 * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
1771 * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
1772 * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
1773 * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
1774 * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
1775 * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
1776 * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
1777 * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
1778 *
1779 * @return mixed One of the possible expressions or subexpressions.
1780 */
1781 public function CaseExpression(): mixed
1782 {
1783 assert($this->lexer->lookahead !== null);
1784 $lookahead = $this->lexer->lookahead->type;
1785
1786 switch ($lookahead) {
1787 case TokenType::T_NULLIF:
1788 return $this->NullIfExpression();
1789
1790 case TokenType::T_COALESCE:
1791 return $this->CoalesceExpression();
1792
1793 case TokenType::T_CASE:
1794 $this->lexer->resetPeek();
1795 $peek = $this->lexer->peek();
1796
1797 assert($peek !== null);
1798 if ($peek->type === TokenType::T_WHEN) {
1799 return $this->GeneralCaseExpression();
1800 }
1801
1802 return $this->SimpleCaseExpression();
1803
1804 default:
1805 // Do nothing
1806 break;
1807 }
1808
1809 $this->syntaxError();
1810 }
1811
1812 /**
1813 * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
1814 */
1815 public function CoalesceExpression(): AST\CoalesceExpression
1816 {
1817 $this->match(TokenType::T_COALESCE);
1818 $this->match(TokenType::T_OPEN_PARENTHESIS);
1819
1820 // Process ScalarExpressions (1..N)
1821 $scalarExpressions = [];
1822 $scalarExpressions[] = $this->ScalarExpression();
1823
1824 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
1825 $this->match(TokenType::T_COMMA);
1826
1827 $scalarExpressions[] = $this->ScalarExpression();
1828 }
1829
1830 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1831
1832 return new AST\CoalesceExpression($scalarExpressions);
1833 }
1834
1835 /**
1836 * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
1837 */
1838 public function NullIfExpression(): AST\NullIfExpression
1839 {
1840 $this->match(TokenType::T_NULLIF);
1841 $this->match(TokenType::T_OPEN_PARENTHESIS);
1842
1843 $firstExpression = $this->ScalarExpression();
1844 $this->match(TokenType::T_COMMA);
1845 $secondExpression = $this->ScalarExpression();
1846
1847 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1848
1849 return new AST\NullIfExpression($firstExpression, $secondExpression);
1850 }
1851
1852 /**
1853 * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
1854 */
1855 public function GeneralCaseExpression(): AST\GeneralCaseExpression
1856 {
1857 $this->match(TokenType::T_CASE);
1858
1859 // Process WhenClause (1..N)
1860 $whenClauses = [];
1861
1862 do {
1863 $whenClauses[] = $this->WhenClause();
1864 } while ($this->lexer->isNextToken(TokenType::T_WHEN));
1865
1866 $this->match(TokenType::T_ELSE);
1867 $scalarExpression = $this->ScalarExpression();
1868 $this->match(TokenType::T_END);
1869
1870 return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
1871 }
1872
1873 /**
1874 * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
1875 * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
1876 */
1877 public function SimpleCaseExpression(): AST\SimpleCaseExpression
1878 {
1879 $this->match(TokenType::T_CASE);
1880 $caseOperand = $this->StateFieldPathExpression();
1881
1882 // Process SimpleWhenClause (1..N)
1883 $simpleWhenClauses = [];
1884
1885 do {
1886 $simpleWhenClauses[] = $this->SimpleWhenClause();
1887 } while ($this->lexer->isNextToken(TokenType::T_WHEN));
1888
1889 $this->match(TokenType::T_ELSE);
1890 $scalarExpression = $this->ScalarExpression();
1891 $this->match(TokenType::T_END);
1892
1893 return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
1894 }
1895
1896 /**
1897 * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
1898 */
1899 public function WhenClause(): AST\WhenClause
1900 {
1901 $this->match(TokenType::T_WHEN);
1902 $conditionalExpression = $this->ConditionalExpression();
1903 $this->match(TokenType::T_THEN);
1904
1905 return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
1906 }
1907
1908 /**
1909 * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
1910 */
1911 public function SimpleWhenClause(): AST\SimpleWhenClause
1912 {
1913 $this->match(TokenType::T_WHEN);
1914 $conditionalExpression = $this->ScalarExpression();
1915 $this->match(TokenType::T_THEN);
1916
1917 return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
1918 }
1919
1920 /**
1921 * SelectExpression ::= (
1922 * IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
1923 * "(" Subselect ")" | CaseExpression | NewObjectExpression
1924 * ) [["AS"] ["HIDDEN"] AliasResultVariable]
1925 */
1926 public function SelectExpression(): AST\SelectExpression
1927 {
1928 assert($this->lexer->lookahead !== null);
1929 $expression = null;
1930 $identVariable = null;
1931 $peek = $this->lexer->glimpse();
1932 $lookaheadType = $this->lexer->lookahead->type;
1933 assert($peek !== null);
1934
1935 switch (true) {
1936 // ScalarExpression (u.name)
1937 case $lookaheadType === TokenType::T_IDENTIFIER && $peek->type === TokenType::T_DOT:
1938 $expression = $this->ScalarExpression();
1939 break;
1940
1941 // IdentificationVariable (u)
1942 case $lookaheadType === TokenType::T_IDENTIFIER && $peek->type !== TokenType::T_OPEN_PARENTHESIS:
1943 $expression = $identVariable = $this->IdentificationVariable();
1944 break;
1945
1946 // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
1947 case $lookaheadType === TokenType::T_CASE:
1948 case $lookaheadType === TokenType::T_COALESCE:
1949 case $lookaheadType === TokenType::T_NULLIF:
1950 $expression = $this->CaseExpression();
1951 break;
1952
1953 // DQL Function (SUM(u.value) or SUM(u.value) + 1)
1954 case $this->isFunction():
1955 $this->lexer->peek(); // "("
1956
1957 $expression = match (true) {
1958 $this->isMathOperator($this->peekBeyondClosingParenthesis()) => $this->ScalarExpression(),
1959 default => $this->FunctionDeclaration(),
1960 };
1961
1962 break;
1963
1964 // Subselect
1965 case $lookaheadType === TokenType::T_OPEN_PARENTHESIS && $peek->type === TokenType::T_SELECT:
1966 $this->match(TokenType::T_OPEN_PARENTHESIS);
1967 $expression = $this->Subselect();
1968 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1969 break;
1970
1971 // Shortcut: ScalarExpression => SimpleArithmeticExpression
1972 case $lookaheadType === TokenType::T_OPEN_PARENTHESIS:
1973 case $lookaheadType === TokenType::T_INTEGER:
1974 case $lookaheadType === TokenType::T_STRING:
1975 case $lookaheadType === TokenType::T_FLOAT:
1976 // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
1977 case $lookaheadType === TokenType::T_MINUS:
1978 case $lookaheadType === TokenType::T_PLUS:
1979 $expression = $this->SimpleArithmeticExpression();
1980 break;
1981
1982 // NewObjectExpression (New ClassName(id, name))
1983 case $lookaheadType === TokenType::T_NEW:
1984 $expression = $this->NewObjectExpression();
1985 break;
1986
1987 default:
1988 $this->syntaxError(
1989 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | "(" Subselect ")" | CaseExpression',
1990 $this->lexer->lookahead,
1991 );
1992 }
1993
1994 // [["AS"] ["HIDDEN"] AliasResultVariable]
1995 $mustHaveAliasResultVariable = false;
1996
1997 if ($this->lexer->isNextToken(TokenType::T_AS)) {
1998 $this->match(TokenType::T_AS);
1999
2000 $mustHaveAliasResultVariable = true;
2001 }
2002
2003 $hiddenAliasResultVariable = false;
2004
2005 if ($this->lexer->isNextToken(TokenType::T_HIDDEN)) {
2006 $this->match(TokenType::T_HIDDEN);
2007
2008 $hiddenAliasResultVariable = true;
2009 }
2010
2011 $aliasResultVariable = null;
2012
2013 if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(TokenType::T_IDENTIFIER)) {
2014 assert($expression instanceof AST\Node || is_string($expression));
2015 $token = $this->lexer->lookahead;
2016 $aliasResultVariable = $this->AliasResultVariable();
2017
2018 // Include AliasResultVariable in query components.
2019 $this->queryComponents[$aliasResultVariable] = [
2020 'resultVariable' => $expression,
2021 'nestingLevel' => $this->nestingLevel,
2022 'token' => $token,
2023 ];
2024 }
2025
2026 // AST
2027
2028 $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2029
2030 if ($identVariable) {
2031 $this->identVariableExpressions[$identVariable] = $expr;
2032 }
2033
2034 return $expr;
2035 }
2036
2037 /**
2038 * SimpleSelectExpression ::= (
2039 * StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2040 * AggregateExpression | "(" Subselect ")" | ScalarExpression
2041 * ) [["AS"] AliasResultVariable]
2042 */
2043 public function SimpleSelectExpression(): AST\SimpleSelectExpression
2044 {
2045 assert($this->lexer->lookahead !== null);
2046 $peek = $this->lexer->glimpse();
2047 assert($peek !== null);
2048
2049 switch ($this->lexer->lookahead->type) {
2050 case TokenType::T_IDENTIFIER:
2051 switch (true) {
2052 case $peek->type === TokenType::T_DOT:
2053 $expression = $this->StateFieldPathExpression();
2054
2055 return new AST\SimpleSelectExpression($expression);
2056
2057 case $peek->type !== TokenType::T_OPEN_PARENTHESIS:
2058 $expression = $this->IdentificationVariable();
2059
2060 return new AST\SimpleSelectExpression($expression);
2061
2062 case $this->isFunction():
2063 // SUM(u.id) + COUNT(u.id)
2064 if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) {
2065 return new AST\SimpleSelectExpression($this->ScalarExpression());
2066 }
2067
2068 // COUNT(u.id)
2069 if ($this->isAggregateFunction($this->lexer->lookahead->type)) {
2070 return new AST\SimpleSelectExpression($this->AggregateExpression());
2071 }
2072
2073 // IDENTITY(u)
2074 return new AST\SimpleSelectExpression($this->FunctionDeclaration());
2075
2076 default:
2077 // Do nothing
2078 }
2079
2080 break;
2081
2082 case TokenType::T_OPEN_PARENTHESIS:
2083 if ($peek->type !== TokenType::T_SELECT) {
2084 // Shortcut: ScalarExpression => SimpleArithmeticExpression
2085 $expression = $this->SimpleArithmeticExpression();
2086
2087 return new AST\SimpleSelectExpression($expression);
2088 }
2089
2090 // Subselect
2091 $this->match(TokenType::T_OPEN_PARENTHESIS);
2092 $expression = $this->Subselect();
2093 $this->match(TokenType::T_CLOSE_PARENTHESIS);
2094
2095 return new AST\SimpleSelectExpression($expression);
2096
2097 default:
2098 // Do nothing
2099 }
2100
2101 $this->lexer->peek();
2102
2103 $expression = $this->ScalarExpression();
2104 $expr = new AST\SimpleSelectExpression($expression);
2105
2106 if ($this->lexer->isNextToken(TokenType::T_AS)) {
2107 $this->match(TokenType::T_AS);
2108 }
2109
2110 if ($this->lexer->isNextToken(TokenType::T_IDENTIFIER)) {
2111 $token = $this->lexer->lookahead;
2112 $resultVariable = $this->AliasResultVariable();
2113 $expr->fieldIdentificationVariable = $resultVariable;
2114
2115 // Include AliasResultVariable in query components.
2116 $this->queryComponents[$resultVariable] = [
2117 'resultvariable' => $expr,
2118 'nestingLevel' => $this->nestingLevel,
2119 'token' => $token,
2120 ];
2121 }
2122
2123 return $expr;
2124 }
2125
2126 /**
2127 * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2128 */
2129 public function ConditionalExpression(): AST\ConditionalExpression|AST\ConditionalFactor|AST\ConditionalPrimary|AST\ConditionalTerm
2130 {
2131 $conditionalTerms = [];
2132 $conditionalTerms[] = $this->ConditionalTerm();
2133
2134 while ($this->lexer->isNextToken(TokenType::T_OR)) {
2135 $this->match(TokenType::T_OR);
2136
2137 $conditionalTerms[] = $this->ConditionalTerm();
2138 }
2139
2140 // Phase 1 AST optimization: Prevent AST\ConditionalExpression
2141 // if only one AST\ConditionalTerm is defined
2142 if (count($conditionalTerms) === 1) {
2143 return $conditionalTerms[0];
2144 }
2145
2146 return new AST\ConditionalExpression($conditionalTerms);
2147 }
2148
2149 /**
2150 * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
2151 */
2152 public function ConditionalTerm(): AST\ConditionalFactor|AST\ConditionalPrimary|AST\ConditionalTerm
2153 {
2154 $conditionalFactors = [];
2155 $conditionalFactors[] = $this->ConditionalFactor();
2156
2157 while ($this->lexer->isNextToken(TokenType::T_AND)) {
2158 $this->match(TokenType::T_AND);
2159
2160 $conditionalFactors[] = $this->ConditionalFactor();
2161 }
2162
2163 // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2164 // if only one AST\ConditionalFactor is defined
2165 if (count($conditionalFactors) === 1) {
2166 return $conditionalFactors[0];
2167 }
2168
2169 return new AST\ConditionalTerm($conditionalFactors);
2170 }
2171
2172 /**
2173 * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2174 */
2175 public function ConditionalFactor(): AST\ConditionalFactor|AST\ConditionalPrimary
2176 {
2177 $not = false;
2178
2179 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
2180 $this->match(TokenType::T_NOT);
2181
2182 $not = true;
2183 }
2184
2185 $conditionalPrimary = $this->ConditionalPrimary();
2186
2187 // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2188 // if only one AST\ConditionalPrimary is defined
2189 if (! $not) {
2190 return $conditionalPrimary;
2191 }
2192
2193 return new AST\ConditionalFactor($conditionalPrimary, $not);
2194 }
2195
2196 /**
2197 * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
2198 */
2199 public function ConditionalPrimary(): AST\ConditionalPrimary
2200 {
2201 $condPrimary = new AST\ConditionalPrimary();
2202
2203 if (! $this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) {
2204 $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2205
2206 return $condPrimary;
2207 }
2208
2209 // Peek beyond the matching closing parenthesis ')'
2210 $peek = $this->peekBeyondClosingParenthesis();
2211
2212 if (
2213 $peek !== null && (
2214 in_array($peek->value, ['=', '<', '<=', '<>', '>', '>=', '!='], true) ||
2215 in_array($peek->type, [TokenType::T_NOT, TokenType::T_BETWEEN, TokenType::T_LIKE, TokenType::T_IN, TokenType::T_IS, TokenType::T_EXISTS], true) ||
2216 $this->isMathOperator($peek)
2217 )
2218 ) {
2219 $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2220
2221 return $condPrimary;
2222 }
2223
2224 $this->match(TokenType::T_OPEN_PARENTHESIS);
2225 $condPrimary->conditionalExpression = $this->ConditionalExpression();
2226 $this->match(TokenType::T_CLOSE_PARENTHESIS);
2227
2228 return $condPrimary;
2229 }
2230
2231 /**
2232 * SimpleConditionalExpression ::=
2233 * ComparisonExpression | BetweenExpression | LikeExpression |
2234 * InExpression | NullComparisonExpression | ExistsExpression |
2235 * EmptyCollectionComparisonExpression | CollectionMemberExpression |
2236 * InstanceOfExpression
2237 */
2238 public function SimpleConditionalExpression(): AST\ExistsExpression|AST\BetweenExpression|AST\LikeExpression|AST\InListExpression|AST\InSubselectExpression|AST\InstanceOfExpression|AST\CollectionMemberExpression|AST\NullComparisonExpression|AST\EmptyCollectionComparisonExpression|AST\ComparisonExpression
2239 {
2240 assert($this->lexer->lookahead !== null);
2241 if ($this->lexer->isNextToken(TokenType::T_EXISTS)) {
2242 return $this->ExistsExpression();
2243 }
2244
2245 $token = $this->lexer->lookahead;
2246 $peek = $this->lexer->glimpse();
2247 $lookahead = $token;
2248
2249 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
2250 $token = $this->lexer->glimpse();
2251 }
2252
2253 assert($token !== null);
2254 assert($peek !== null);
2255 if ($token->type === TokenType::T_IDENTIFIER || $token->type === TokenType::T_INPUT_PARAMETER || $this->isFunction()) {
2256 // Peek beyond the matching closing parenthesis.
2257 $beyond = $this->lexer->peek();
2258
2259 switch ($peek->value) {
2260 case '(':
2261 // Peeks beyond the matched closing parenthesis.
2262 $token = $this->peekBeyondClosingParenthesis(false);
2263 assert($token !== null);
2264
2265 if ($token->type === TokenType::T_NOT) {
2266 $token = $this->lexer->peek();
2267 assert($token !== null);
2268 }
2269
2270 if ($token->type === TokenType::T_IS) {
2271 $lookahead = $this->lexer->peek();
2272 }
2273
2274 break;
2275
2276 default:
2277 // Peek beyond the PathExpression or InputParameter.
2278 $token = $beyond;
2279
2280 while ($token->value === '.') {
2281 $this->lexer->peek();
2282
2283 $token = $this->lexer->peek();
2284 assert($token !== null);
2285 }
2286
2287 // Also peek beyond a NOT if there is one.
2288 assert($token !== null);
2289 if ($token->type === TokenType::T_NOT) {
2290 $token = $this->lexer->peek();
2291 assert($token !== null);
2292 }
2293
2294 // We need to go even further in case of IS (differentiate between NULL and EMPTY)
2295 $lookahead = $this->lexer->peek();
2296 }
2297
2298 assert($lookahead !== null);
2299 // Also peek beyond a NOT if there is one.
2300 if ($lookahead->type === TokenType::T_NOT) {
2301 $lookahead = $this->lexer->peek();
2302 }
2303
2304 $this->lexer->resetPeek();
2305 }
2306
2307 if ($token->type === TokenType::T_BETWEEN) {
2308 return $this->BetweenExpression();
2309 }
2310
2311 if ($token->type === TokenType::T_LIKE) {
2312 return $this->LikeExpression();
2313 }
2314
2315 if ($token->type === TokenType::T_IN) {
2316 return $this->InExpression();
2317 }
2318
2319 if ($token->type === TokenType::T_INSTANCE) {
2320 return $this->InstanceOfExpression();
2321 }
2322
2323 if ($token->type === TokenType::T_MEMBER) {
2324 return $this->CollectionMemberExpression();
2325 }
2326
2327 assert($lookahead !== null);
2328 if ($token->type === TokenType::T_IS && $lookahead->type === TokenType::T_NULL) {
2329 return $this->NullComparisonExpression();
2330 }
2331
2332 if ($token->type === TokenType::T_IS && $lookahead->type === TokenType::T_EMPTY) {
2333 return $this->EmptyCollectionComparisonExpression();
2334 }
2335
2336 return $this->ComparisonExpression();
2337 }
2338
2339 /**
2340 * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2341 */
2342 public function EmptyCollectionComparisonExpression(): AST\EmptyCollectionComparisonExpression
2343 {
2344 $pathExpression = $this->CollectionValuedPathExpression();
2345 $this->match(TokenType::T_IS);
2346
2347 $not = false;
2348 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
2349 $this->match(TokenType::T_NOT);
2350 $not = true;
2351 }
2352
2353 $this->match(TokenType::T_EMPTY);
2354
2355 return new AST\EmptyCollectionComparisonExpression(
2356 $pathExpression,
2357 $not,
2358 );
2359 }
2360
2361 /**
2362 * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2363 *
2364 * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2365 * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2366 */
2367 public function CollectionMemberExpression(): AST\CollectionMemberExpression
2368 {
2369 $not = false;
2370 $entityExpr = $this->EntityExpression();
2371
2372 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
2373 $this->match(TokenType::T_NOT);
2374
2375 $not = true;
2376 }
2377
2378 $this->match(TokenType::T_MEMBER);
2379
2380 if ($this->lexer->isNextToken(TokenType::T_OF)) {
2381 $this->match(TokenType::T_OF);
2382 }
2383
2384 return new AST\CollectionMemberExpression(
2385 $entityExpr,
2386 $this->CollectionValuedPathExpression(),
2387 $not,
2388 );
2389 }
2390
2391 /**
2392 * Literal ::= string | char | integer | float | boolean
2393 */
2394 public function Literal(): AST\Literal
2395 {
2396 assert($this->lexer->lookahead !== null);
2397 assert($this->lexer->token !== null);
2398 switch ($this->lexer->lookahead->type) {
2399 case TokenType::T_STRING:
2400 $this->match(TokenType::T_STRING);
2401
2402 return new AST\Literal(AST\Literal::STRING, $this->lexer->token->value);
2403
2404 case TokenType::T_INTEGER:
2405 case TokenType::T_FLOAT:
2406 $this->match(
2407 $this->lexer->isNextToken(TokenType::T_INTEGER) ? TokenType::T_INTEGER : TokenType::T_FLOAT,
2408 );
2409
2410 return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token->value);
2411
2412 case TokenType::T_TRUE:
2413 case TokenType::T_FALSE:
2414 $this->match(
2415 $this->lexer->isNextToken(TokenType::T_TRUE) ? TokenType::T_TRUE : TokenType::T_FALSE,
2416 );
2417
2418 return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token->value);
2419
2420 default:
2421 $this->syntaxError('Literal');
2422 }
2423 }
2424
2425 /**
2426 * InParameter ::= ArithmeticExpression | InputParameter
2427 */
2428 public function InParameter(): AST\InputParameter|AST\ArithmeticExpression
2429 {
2430 assert($this->lexer->lookahead !== null);
2431 if ($this->lexer->lookahead->type === TokenType::T_INPUT_PARAMETER) {
2432 return $this->InputParameter();
2433 }
2434
2435 return $this->ArithmeticExpression();
2436 }
2437
2438 /**
2439 * InputParameter ::= PositionalParameter | NamedParameter
2440 */
2441 public function InputParameter(): AST\InputParameter
2442 {
2443 $this->match(TokenType::T_INPUT_PARAMETER);
2444 assert($this->lexer->token !== null);
2445
2446 return new AST\InputParameter($this->lexer->token->value);
2447 }
2448
2449 /**
2450 * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2451 */
2452 public function ArithmeticExpression(): AST\ArithmeticExpression
2453 {
2454 $expr = new AST\ArithmeticExpression();
2455
2456 if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) {
2457 $peek = $this->lexer->glimpse();
2458 assert($peek !== null);
2459
2460 if ($peek->type === TokenType::T_SELECT) {
2461 $this->match(TokenType::T_OPEN_PARENTHESIS);
2462 $expr->subselect = $this->Subselect();
2463 $this->match(TokenType::T_CLOSE_PARENTHESIS);
2464
2465 return $expr;
2466 }
2467 }
2468
2469 $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2470
2471 return $expr;
2472 }
2473
2474 /**
2475 * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2476 */
2477 public function SimpleArithmeticExpression(): AST\Node|string
2478 {
2479 $terms = [];
2480 $terms[] = $this->ArithmeticTerm();
2481
2482 while (($isPlus = $this->lexer->isNextToken(TokenType::T_PLUS)) || $this->lexer->isNextToken(TokenType::T_MINUS)) {
2483 $this->match($isPlus ? TokenType::T_PLUS : TokenType::T_MINUS);
2484
2485 assert($this->lexer->token !== null);
2486 $terms[] = $this->lexer->token->value;
2487 $terms[] = $this->ArithmeticTerm();
2488 }
2489
2490 // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2491 // if only one AST\ArithmeticTerm is defined
2492 if (count($terms) === 1) {
2493 return $terms[0];
2494 }
2495
2496 return new AST\SimpleArithmeticExpression($terms);
2497 }
2498
2499 /**
2500 * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2501 */
2502 public function ArithmeticTerm(): AST\Node|string
2503 {
2504 $factors = [];
2505 $factors[] = $this->ArithmeticFactor();
2506
2507 while (($isMult = $this->lexer->isNextToken(TokenType::T_MULTIPLY)) || $this->lexer->isNextToken(TokenType::T_DIVIDE)) {
2508 $this->match($isMult ? TokenType::T_MULTIPLY : TokenType::T_DIVIDE);
2509
2510 assert($this->lexer->token !== null);
2511 $factors[] = $this->lexer->token->value;
2512 $factors[] = $this->ArithmeticFactor();
2513 }
2514
2515 // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2516 // if only one AST\ArithmeticFactor is defined
2517 if (count($factors) === 1) {
2518 return $factors[0];
2519 }
2520
2521 return new AST\ArithmeticTerm($factors);
2522 }
2523
2524 /**
2525 * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2526 */
2527 public function ArithmeticFactor(): AST\Node|string|AST\ArithmeticFactor
2528 {
2529 $sign = null;
2530
2531 $isPlus = $this->lexer->isNextToken(TokenType::T_PLUS);
2532 if ($isPlus || $this->lexer->isNextToken(TokenType::T_MINUS)) {
2533 $this->match($isPlus ? TokenType::T_PLUS : TokenType::T_MINUS);
2534 $sign = $isPlus;
2535 }
2536
2537 $primary = $this->ArithmeticPrimary();
2538
2539 // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2540 // if only one AST\ArithmeticPrimary is defined
2541 if ($sign === null) {
2542 return $primary;
2543 }
2544
2545 return new AST\ArithmeticFactor($primary, $sign);
2546 }
2547
2548 /**
2549 * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
2550 * | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2551 * | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2552 * | InputParameter | CaseExpression
2553 */
2554 public function ArithmeticPrimary(): AST\Node|string
2555 {
2556 if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) {
2557 $this->match(TokenType::T_OPEN_PARENTHESIS);
2558
2559 $expr = $this->SimpleArithmeticExpression();
2560
2561 $this->match(TokenType::T_CLOSE_PARENTHESIS);
2562
2563 return new AST\ParenthesisExpression($expr);
2564 }
2565
2566 if ($this->lexer->lookahead === null) {
2567 $this->syntaxError('ArithmeticPrimary');
2568 }
2569
2570 switch ($this->lexer->lookahead->type) {
2571 case TokenType::T_COALESCE:
2572 case TokenType::T_NULLIF:
2573 case TokenType::T_CASE:
2574 return $this->CaseExpression();
2575
2576 case TokenType::T_IDENTIFIER:
2577 $peek = $this->lexer->glimpse();
2578
2579 if ($peek !== null && $peek->value === '(') {
2580 return $this->FunctionDeclaration();
2581 }
2582
2583 if ($peek !== null && $peek->value === '.') {
2584 return $this->SingleValuedPathExpression();
2585 }
2586
2587 if (isset($this->queryComponents[$this->lexer->lookahead->value]['resultVariable'])) {
2588 return $this->ResultVariable();
2589 }
2590
2591 return $this->StateFieldPathExpression();
2592
2593 case TokenType::T_INPUT_PARAMETER:
2594 return $this->InputParameter();
2595
2596 default:
2597 $peek = $this->lexer->glimpse();
2598
2599 if ($peek !== null && $peek->value === '(') {
2600 return $this->FunctionDeclaration();
2601 }
2602
2603 return $this->Literal();
2604 }
2605 }
2606
2607 /**
2608 * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2609 */
2610 public function StringExpression(): AST\Subselect|AST\Node|string
2611 {
2612 $peek = $this->lexer->glimpse();
2613 assert($peek !== null);
2614
2615 // Subselect
2616 if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS) && $peek->type === TokenType::T_SELECT) {
2617 $this->match(TokenType::T_OPEN_PARENTHESIS);
2618 $expr = $this->Subselect();
2619 $this->match(TokenType::T_CLOSE_PARENTHESIS);
2620
2621 return $expr;
2622 }
2623
2624 assert($this->lexer->lookahead !== null);
2625 // ResultVariable (string)
2626 if (
2627 $this->lexer->isNextToken(TokenType::T_IDENTIFIER) &&
2628 isset($this->queryComponents[$this->lexer->lookahead->value]['resultVariable'])
2629 ) {
2630 return $this->ResultVariable();
2631 }
2632
2633 return $this->StringPrimary();
2634 }
2635
2636 /**
2637 * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2638 */
2639 public function StringPrimary(): AST\Node
2640 {
2641 assert($this->lexer->lookahead !== null);
2642 $lookaheadType = $this->lexer->lookahead->type;
2643
2644 switch ($lookaheadType) {
2645 case TokenType::T_IDENTIFIER:
2646 $peek = $this->lexer->glimpse();
2647 assert($peek !== null);
2648
2649 if ($peek->value === '.') {
2650 return $this->StateFieldPathExpression();
2651 }
2652
2653 if ($peek->value === '(') {
2654 // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2655 return $this->FunctionDeclaration();
2656 }
2657
2658 $this->syntaxError("'.' or '('");
2659 break;
2660
2661 case TokenType::T_STRING:
2662 $this->match(TokenType::T_STRING);
2663 assert($this->lexer->token !== null);
2664
2665 return new AST\Literal(AST\Literal::STRING, $this->lexer->token->value);
2666
2667 case TokenType::T_INPUT_PARAMETER:
2668 return $this->InputParameter();
2669
2670 case TokenType::T_CASE:
2671 case TokenType::T_COALESCE:
2672 case TokenType::T_NULLIF:
2673 return $this->CaseExpression();
2674
2675 default:
2676 assert($lookaheadType !== null);
2677 if ($this->isAggregateFunction($lookaheadType)) {
2678 return $this->AggregateExpression();
2679 }
2680 }
2681
2682 $this->syntaxError(
2683 'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression',
2684 );
2685 }
2686
2687 /**
2688 * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2689 */
2690 public function EntityExpression(): AST\InputParameter|AST\PathExpression
2691 {
2692 $glimpse = $this->lexer->glimpse();
2693 assert($glimpse !== null);
2694
2695 if ($this->lexer->isNextToken(TokenType::T_IDENTIFIER) && $glimpse->value === '.') {
2696 return $this->SingleValuedAssociationPathExpression();
2697 }
2698
2699 return $this->SimpleEntityExpression();
2700 }
2701
2702 /**
2703 * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2704 */
2705 public function SimpleEntityExpression(): AST\InputParameter|AST\PathExpression
2706 {
2707 if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
2708 return $this->InputParameter();
2709 }
2710
2711 return $this->StateFieldPathExpression();
2712 }
2713
2714 /**
2715 * AggregateExpression ::=
2716 * ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
2717 */
2718 public function AggregateExpression(): AST\AggregateExpression
2719 {
2720 assert($this->lexer->lookahead !== null);
2721 $lookaheadType = $this->lexer->lookahead->type;
2722 $isDistinct = false;
2723
2724 if (! in_array($lookaheadType, [TokenType::T_COUNT, TokenType::T_AVG, TokenType::T_MAX, TokenType::T_MIN, TokenType::T_SUM], true)) {
2725 $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2726 }
2727
2728 $this->match($lookaheadType);
2729 assert($this->lexer->token !== null);
2730 $functionName = $this->lexer->token->value;
2731 $this->match(TokenType::T_OPEN_PARENTHESIS);
2732
2733 if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) {
2734 $this->match(TokenType::T_DISTINCT);
2735 $isDistinct = true;
2736 }
2737
2738 $pathExp = $this->SimpleArithmeticExpression();
2739
2740 $this->match(TokenType::T_CLOSE_PARENTHESIS);
2741
2742 return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
2743 }
2744
2745 /**
2746 * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2747 */
2748 public function QuantifiedExpression(): AST\QuantifiedExpression
2749 {
2750 assert($this->lexer->lookahead !== null);
2751 $lookaheadType = $this->lexer->lookahead->type;
2752 $value = $this->lexer->lookahead->value;
2753
2754 if (! in_array($lookaheadType, [TokenType::T_ALL, TokenType::T_ANY, TokenType::T_SOME], true)) {
2755 $this->syntaxError('ALL, ANY or SOME');
2756 }
2757
2758 $this->match($lookaheadType);
2759 $this->match(TokenType::T_OPEN_PARENTHESIS);
2760
2761 $qExpr = new AST\QuantifiedExpression($this->Subselect());
2762 $qExpr->type = $value;
2763
2764 $this->match(TokenType::T_CLOSE_PARENTHESIS);
2765
2766 return $qExpr;
2767 }
2768
2769 /**
2770 * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
2771 */
2772 public function BetweenExpression(): AST\BetweenExpression
2773 {
2774 $not = false;
2775 $arithExpr1 = $this->ArithmeticExpression();
2776
2777 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
2778 $this->match(TokenType::T_NOT);
2779 $not = true;
2780 }
2781
2782 $this->match(TokenType::T_BETWEEN);
2783 $arithExpr2 = $this->ArithmeticExpression();
2784 $this->match(TokenType::T_AND);
2785 $arithExpr3 = $this->ArithmeticExpression();
2786
2787 return new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3, $not);
2788 }
2789
2790 /**
2791 * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
2792 */
2793 public function ComparisonExpression(): AST\ComparisonExpression
2794 {
2795 $this->lexer->glimpse();
2796
2797 $leftExpr = $this->ArithmeticExpression();
2798 $operator = $this->ComparisonOperator();
2799 $rightExpr = $this->isNextAllAnySome()
2800 ? $this->QuantifiedExpression()
2801 : $this->ArithmeticExpression();
2802
2803 return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
2804 }
2805
2806 /**
2807 * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
2808 */
2809 public function InExpression(): AST\InListExpression|AST\InSubselectExpression
2810 {
2811 $expression = $this->ArithmeticExpression();
2812
2813 $not = false;
2814 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
2815 $this->match(TokenType::T_NOT);
2816 $not = true;
2817 }
2818
2819 $this->match(TokenType::T_IN);
2820 $this->match(TokenType::T_OPEN_PARENTHESIS);
2821
2822 if ($this->lexer->isNextToken(TokenType::T_SELECT)) {
2823 $inExpression = new AST\InSubselectExpression(
2824 $expression,
2825 $this->Subselect(),
2826 $not,
2827 );
2828 } else {
2829 $literals = [$this->InParameter()];
2830
2831 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
2832 $this->match(TokenType::T_COMMA);
2833 $literals[] = $this->InParameter();
2834 }
2835
2836 $inExpression = new AST\InListExpression(
2837 $expression,
2838 $literals,
2839 $not,
2840 );
2841 }
2842
2843 $this->match(TokenType::T_CLOSE_PARENTHESIS);
2844
2845 return $inExpression;
2846 }
2847
2848 /**
2849 * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
2850 */
2851 public function InstanceOfExpression(): AST\InstanceOfExpression
2852 {
2853 $identificationVariable = $this->IdentificationVariable();
2854
2855 $not = false;
2856 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
2857 $this->match(TokenType::T_NOT);
2858 $not = true;
2859 }
2860
2861 $this->match(TokenType::T_INSTANCE);
2862 $this->match(TokenType::T_OF);
2863
2864 $exprValues = $this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)
2865 ? $this->InstanceOfParameterList()
2866 : [$this->InstanceOfParameter()];
2867
2868 return new AST\InstanceOfExpression(
2869 $identificationVariable,
2870 $exprValues,
2871 $not,
2872 );
2873 }
2874
2875 /** @return non-empty-list<AST\InputParameter|string> */
2876 public function InstanceOfParameterList(): array
2877 {
2878 $this->match(TokenType::T_OPEN_PARENTHESIS);
2879
2880 $exprValues = [$this->InstanceOfParameter()];
2881
2882 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
2883 $this->match(TokenType::T_COMMA);
2884
2885 $exprValues[] = $this->InstanceOfParameter();
2886 }
2887
2888 $this->match(TokenType::T_CLOSE_PARENTHESIS);
2889
2890 return $exprValues;
2891 }
2892
2893 /**
2894 * InstanceOfParameter ::= AbstractSchemaName | InputParameter
2895 */
2896 public function InstanceOfParameter(): AST\InputParameter|string
2897 {
2898 if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
2899 $this->match(TokenType::T_INPUT_PARAMETER);
2900 assert($this->lexer->token !== null);
2901
2902 return new AST\InputParameter($this->lexer->token->value);
2903 }
2904
2905 $abstractSchemaName = $this->AbstractSchemaName();
2906
2907 $this->validateAbstractSchemaName($abstractSchemaName);
2908
2909 return $abstractSchemaName;
2910 }
2911
2912 /**
2913 * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
2914 */
2915 public function LikeExpression(): AST\LikeExpression
2916 {
2917 $stringExpr = $this->StringExpression();
2918 $not = false;
2919
2920 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
2921 $this->match(TokenType::T_NOT);
2922 $not = true;
2923 }
2924
2925 $this->match(TokenType::T_LIKE);
2926
2927 if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
2928 $this->match(TokenType::T_INPUT_PARAMETER);
2929 assert($this->lexer->token !== null);
2930 $stringPattern = new AST\InputParameter($this->lexer->token->value);
2931 } else {
2932 $stringPattern = $this->StringPrimary();
2933 }
2934
2935 $escapeChar = null;
2936
2937 if ($this->lexer->lookahead !== null && $this->lexer->lookahead->type === TokenType::T_ESCAPE) {
2938 $this->match(TokenType::T_ESCAPE);
2939 $this->match(TokenType::T_STRING);
2940 assert($this->lexer->token !== null);
2941
2942 $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token->value);
2943 }
2944
2945 return new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar, $not);
2946 }
2947
2948 /**
2949 * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
2950 */
2951 public function NullComparisonExpression(): AST\NullComparisonExpression
2952 {
2953 switch (true) {
2954 case $this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER):
2955 $this->match(TokenType::T_INPUT_PARAMETER);
2956 assert($this->lexer->token !== null);
2957
2958 $expr = new AST\InputParameter($this->lexer->token->value);
2959 break;
2960
2961 case $this->lexer->isNextToken(TokenType::T_NULLIF):
2962 $expr = $this->NullIfExpression();
2963 break;
2964
2965 case $this->lexer->isNextToken(TokenType::T_COALESCE):
2966 $expr = $this->CoalesceExpression();
2967 break;
2968
2969 case $this->isFunction():
2970 $expr = $this->FunctionDeclaration();
2971 break;
2972
2973 default:
2974 // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
2975 $glimpse = $this->lexer->glimpse();
2976 assert($glimpse !== null);
2977
2978 if ($glimpse->type === TokenType::T_DOT) {
2979 $expr = $this->SingleValuedPathExpression();
2980
2981 // Leave switch statement
2982 break;
2983 }
2984
2985 assert($this->lexer->lookahead !== null);
2986 $lookaheadValue = $this->lexer->lookahead->value;
2987
2988 // Validate existing component
2989 if (! isset($this->queryComponents[$lookaheadValue])) {
2990 $this->semanticalError('Cannot add having condition on undefined result variable.');
2991 }
2992
2993 // Validate SingleValuedPathExpression (ie.: "product")
2994 if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
2995 $expr = $this->SingleValuedPathExpression();
2996 break;
2997 }
2998
2999 // Validating ResultVariable
3000 if (! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3001 $this->semanticalError('Cannot add having condition on a non result variable.');
3002 }
3003
3004 $expr = $this->ResultVariable();
3005 break;
3006 }
3007
3008 $this->match(TokenType::T_IS);
3009
3010 $not = false;
3011 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
3012 $this->match(TokenType::T_NOT);
3013
3014 $not = true;
3015 }
3016
3017 $this->match(TokenType::T_NULL);
3018
3019 return new AST\NullComparisonExpression($expr, $not);
3020 }
3021
3022 /**
3023 * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
3024 */
3025 public function ExistsExpression(): AST\ExistsExpression
3026 {
3027 $not = false;
3028
3029 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
3030 $this->match(TokenType::T_NOT);
3031 $not = true;
3032 }
3033
3034 $this->match(TokenType::T_EXISTS);
3035 $this->match(TokenType::T_OPEN_PARENTHESIS);
3036
3037 $subselect = $this->Subselect();
3038
3039 $this->match(TokenType::T_CLOSE_PARENTHESIS);
3040
3041 return new AST\ExistsExpression($subselect, $not);
3042 }
3043
3044 /**
3045 * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
3046 */
3047 public function ComparisonOperator(): string
3048 {
3049 assert($this->lexer->lookahead !== null);
3050 switch ($this->lexer->lookahead->value) {
3051 case '=':
3052 $this->match(TokenType::T_EQUALS);
3053
3054 return '=';
3055
3056 case '<':
3057 $this->match(TokenType::T_LOWER_THAN);
3058 $operator = '<';
3059
3060 if ($this->lexer->isNextToken(TokenType::T_EQUALS)) {
3061 $this->match(TokenType::T_EQUALS);
3062 $operator .= '=';
3063 } elseif ($this->lexer->isNextToken(TokenType::T_GREATER_THAN)) {
3064 $this->match(TokenType::T_GREATER_THAN);
3065 $operator .= '>';
3066 }
3067
3068 return $operator;
3069
3070 case '>':
3071 $this->match(TokenType::T_GREATER_THAN);
3072 $operator = '>';
3073
3074 if ($this->lexer->isNextToken(TokenType::T_EQUALS)) {
3075 $this->match(TokenType::T_EQUALS);
3076 $operator .= '=';
3077 }
3078
3079 return $operator;
3080
3081 case '!':
3082 $this->match(TokenType::T_NEGATE);
3083 $this->match(TokenType::T_EQUALS);
3084
3085 return '<>';
3086
3087 default:
3088 $this->syntaxError('=, <, <=, <>, >, >=, !=');
3089 }
3090 }
3091
3092 /**
3093 * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
3094 */
3095 public function FunctionDeclaration(): Functions\FunctionNode
3096 {
3097 assert($this->lexer->lookahead !== null);
3098 $token = $this->lexer->lookahead;
3099 $funcName = strtolower($token->value);
3100
3101 $customFunctionDeclaration = $this->CustomFunctionDeclaration();
3102
3103 // Check for custom functions functions first!
3104 switch (true) {
3105 case $customFunctionDeclaration !== null:
3106 return $customFunctionDeclaration;
3107
3108 case isset(self::$stringFunctions[$funcName]):
3109 return $this->FunctionsReturningStrings();
3110
3111 case isset(self::$numericFunctions[$funcName]):
3112 return $this->FunctionsReturningNumerics();
3113
3114 case isset(self::$datetimeFunctions[$funcName]):
3115 return $this->FunctionsReturningDatetime();
3116
3117 default:
3118 $this->syntaxError('known function', $token);
3119 }
3120 }
3121
3122 /**
3123 * Helper function for FunctionDeclaration grammar rule.
3124 */
3125 private function CustomFunctionDeclaration(): Functions\FunctionNode|null
3126 {
3127 assert($this->lexer->lookahead !== null);
3128 $token = $this->lexer->lookahead;
3129 $funcName = strtolower($token->value);
3130
3131 // Check for custom functions afterwards
3132 $config = $this->em->getConfiguration();
3133
3134 return match (true) {
3135 $config->getCustomStringFunction($funcName) !== null => $this->CustomFunctionsReturningStrings(),
3136 $config->getCustomNumericFunction($funcName) !== null => $this->CustomFunctionsReturningNumerics(),
3137 $config->getCustomDatetimeFunction($funcName) !== null => $this->CustomFunctionsReturningDatetime(),
3138 default => null,
3139 };
3140 }
3141
3142 /**
3143 * FunctionsReturningNumerics ::=
3144 * "LENGTH" "(" StringPrimary ")" |
3145 * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
3146 * "ABS" "(" SimpleArithmeticExpression ")" |
3147 * "SQRT" "(" SimpleArithmeticExpression ")" |
3148 * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3149 * "SIZE" "(" CollectionValuedPathExpression ")" |
3150 * "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3151 * "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3152 * "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
3153 */
3154 public function FunctionsReturningNumerics(): AST\Functions\FunctionNode
3155 {
3156 assert($this->lexer->lookahead !== null);
3157 $funcNameLower = strtolower($this->lexer->lookahead->value);
3158 $funcClass = self::$numericFunctions[$funcNameLower];
3159
3160 $function = new $funcClass($funcNameLower);
3161 $function->parse($this);
3162
3163 return $function;
3164 }
3165
3166 public function CustomFunctionsReturningNumerics(): AST\Functions\FunctionNode
3167 {
3168 assert($this->lexer->lookahead !== null);
3169 // getCustomNumericFunction is case-insensitive
3170 $functionName = strtolower($this->lexer->lookahead->value);
3171 $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
3172
3173 assert($functionClass !== null);
3174
3175 $function = is_string($functionClass)
3176 ? new $functionClass($functionName)
3177 : $functionClass($functionName);
3178
3179 $function->parse($this);
3180
3181 return $function;
3182 }
3183
3184 /**
3185 * FunctionsReturningDateTime ::=
3186 * "CURRENT_DATE" |
3187 * "CURRENT_TIME" |
3188 * "CURRENT_TIMESTAMP" |
3189 * "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
3190 * "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
3191 */
3192 public function FunctionsReturningDatetime(): AST\Functions\FunctionNode
3193 {
3194 assert($this->lexer->lookahead !== null);
3195 $funcNameLower = strtolower($this->lexer->lookahead->value);
3196 $funcClass = self::$datetimeFunctions[$funcNameLower];
3197
3198 $function = new $funcClass($funcNameLower);
3199 $function->parse($this);
3200
3201 return $function;
3202 }
3203
3204 public function CustomFunctionsReturningDatetime(): AST\Functions\FunctionNode
3205 {
3206 assert($this->lexer->lookahead !== null);
3207 // getCustomDatetimeFunction is case-insensitive
3208 $functionName = $this->lexer->lookahead->value;
3209 $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
3210
3211 assert($functionClass !== null);
3212
3213 $function = is_string($functionClass)
3214 ? new $functionClass($functionName)
3215 : $functionClass($functionName);
3216
3217 $function->parse($this);
3218
3219 return $function;
3220 }
3221
3222 /**
3223 * FunctionsReturningStrings ::=
3224 * "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" |
3225 * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3226 * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
3227 * "LOWER" "(" StringPrimary ")" |
3228 * "UPPER" "(" StringPrimary ")" |
3229 * "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
3230 */
3231 public function FunctionsReturningStrings(): AST\Functions\FunctionNode
3232 {
3233 assert($this->lexer->lookahead !== null);
3234 $funcNameLower = strtolower($this->lexer->lookahead->value);
3235 $funcClass = self::$stringFunctions[$funcNameLower];
3236
3237 $function = new $funcClass($funcNameLower);
3238 $function->parse($this);
3239
3240 return $function;
3241 }
3242
3243 public function CustomFunctionsReturningStrings(): Functions\FunctionNode
3244 {
3245 assert($this->lexer->lookahead !== null);
3246 // getCustomStringFunction is case-insensitive
3247 $functionName = $this->lexer->lookahead->value;
3248 $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
3249
3250 assert($functionClass !== null);
3251
3252 $function = is_string($functionClass)
3253 ? new $functionClass($functionName)
3254 : $functionClass($functionName);
3255
3256 $function->parse($this);
3257
3258 return $function;
3259 }
3260
3261 private function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata
3262 {
3263 if (! isset($this->queryComponents[$dqlAlias]['metadata'])) {
3264 throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias));
3265 }
3266
3267 return $this->queryComponents[$dqlAlias]['metadata'];
3268 }
3269}
diff --git a/vendor/doctrine/orm/src/Query/ParserResult.php b/vendor/doctrine/orm/src/Query/ParserResult.php
new file mode 100644
index 0000000..8b5ee1f
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/ParserResult.php
@@ -0,0 +1,118 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
8use LogicException;
9
10use function sprintf;
11
12/**
13 * Encapsulates the resulting components from a DQL query parsing process that
14 * can be serialized.
15 *
16 * @link http://www.doctrine-project.org
17 */
18class ParserResult
19{
20 /**
21 * The SQL executor used for executing the SQL.
22 */
23 private AbstractSqlExecutor|null $sqlExecutor = null;
24
25 /**
26 * The ResultSetMapping that describes how to map the SQL result set.
27 */
28 private ResultSetMapping $resultSetMapping;
29
30 /**
31 * The mappings of DQL parameter names/positions to SQL parameter positions.
32 *
33 * @psalm-var array<string|int, list<int>>
34 */
35 private array $parameterMappings = [];
36
37 /**
38 * Initializes a new instance of the <tt>ParserResult</tt> class.
39 * The new instance is initialized with an empty <tt>ResultSetMapping</tt>.
40 */
41 public function __construct()
42 {
43 $this->resultSetMapping = new ResultSetMapping();
44 }
45
46 /**
47 * Gets the ResultSetMapping for the parsed query.
48 *
49 * @return ResultSetMapping The result set mapping of the parsed query
50 */
51 public function getResultSetMapping(): ResultSetMapping
52 {
53 return $this->resultSetMapping;
54 }
55
56 /**
57 * Sets the ResultSetMapping of the parsed query.
58 */
59 public function setResultSetMapping(ResultSetMapping $rsm): void
60 {
61 $this->resultSetMapping = $rsm;
62 }
63
64 /**
65 * Sets the SQL executor that should be used for this ParserResult.
66 */
67 public function setSqlExecutor(AbstractSqlExecutor $executor): void
68 {
69 $this->sqlExecutor = $executor;
70 }
71
72 /**
73 * Gets the SQL executor used by this ParserResult.
74 */
75 public function getSqlExecutor(): AbstractSqlExecutor
76 {
77 if ($this->sqlExecutor === null) {
78 throw new LogicException(sprintf(
79 'Executor not set yet. Call %s::setSqlExecutor() first.',
80 self::class,
81 ));
82 }
83
84 return $this->sqlExecutor;
85 }
86
87 /**
88 * Adds a DQL to SQL parameter mapping. One DQL parameter name/position can map to
89 * several SQL parameter positions.
90 */
91 public function addParameterMapping(string|int $dqlPosition, int $sqlPosition): void
92 {
93 $this->parameterMappings[$dqlPosition][] = $sqlPosition;
94 }
95
96 /**
97 * Gets all DQL to SQL parameter mappings.
98 *
99 * @psalm-return array<int|string, list<int>> The parameter mappings.
100 */
101 public function getParameterMappings(): array
102 {
103 return $this->parameterMappings;
104 }
105
106 /**
107 * Gets the SQL parameter positions for a DQL parameter name/position.
108 *
109 * @param string|int $dqlPosition The name or position of the DQL parameter.
110 *
111 * @return int[] The positions of the corresponding SQL parameters.
112 * @psalm-return list<int>
113 */
114 public function getSqlParameterPositions(string|int $dqlPosition): array
115 {
116 return $this->parameterMappings[$dqlPosition];
117 }
118}
diff --git a/vendor/doctrine/orm/src/Query/Printer.php b/vendor/doctrine/orm/src/Query/Printer.php
new file mode 100644
index 0000000..db1f159
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/Printer.php
@@ -0,0 +1,64 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use function str_repeat;
8
9/**
10 * A parse tree printer for Doctrine Query Language parser.
11 *
12 * @link http://www.phpdoctrine.org
13 */
14class Printer
15{
16 /** Current indentation level */
17 protected int $indent = 0;
18
19 /**
20 * Constructs a new parse tree printer.
21 *
22 * @param bool $silent Parse tree will not be printed if true.
23 */
24 public function __construct(protected bool $silent = false)
25 {
26 }
27
28 /**
29 * Prints an opening parenthesis followed by production name and increases
30 * indentation level by one.
31 *
32 * This method is called before executing a production.
33 *
34 * @param string $name Production name.
35 */
36 public function startProduction(string $name): void
37 {
38 $this->println('(' . $name);
39 $this->indent++;
40 }
41
42 /**
43 * Decreases indentation level by one and prints a closing parenthesis.
44 *
45 * This method is called after executing a production.
46 */
47 public function endProduction(): void
48 {
49 $this->indent--;
50 $this->println(')');
51 }
52
53 /**
54 * Prints text indented with spaces depending on current indentation level.
55 *
56 * @param string $str The text.
57 */
58 public function println(string $str): void
59 {
60 if (! $this->silent) {
61 echo str_repeat(' ', $this->indent), $str, "\n";
62 }
63 }
64}
diff --git a/vendor/doctrine/orm/src/Query/QueryException.php b/vendor/doctrine/orm/src/Query/QueryException.php
new file mode 100644
index 0000000..ae945b1
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/QueryException.php
@@ -0,0 +1,155 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use Doctrine\ORM\Exception\ORMException;
8use Doctrine\ORM\Mapping\AssociationMapping;
9use Doctrine\ORM\Query\AST\PathExpression;
10use Exception;
11use Stringable;
12use Throwable;
13
14class QueryException extends Exception implements ORMException
15{
16 public static function dqlError(string $dql): self
17 {
18 return new self($dql);
19 }
20
21 public static function syntaxError(string $message, Throwable|null $previous = null): self
22 {
23 return new self('[Syntax Error] ' . $message, 0, $previous);
24 }
25
26 public static function semanticalError(string $message, Throwable|null $previous = null): self
27 {
28 return new self('[Semantical Error] ' . $message, 0, $previous);
29 }
30
31 public static function invalidLockMode(): self
32 {
33 return new self('Invalid lock mode hint provided.');
34 }
35
36 public static function invalidParameterType(string $expected, string $received): self
37 {
38 return new self('Invalid parameter type, ' . $received . ' given, but ' . $expected . ' expected.');
39 }
40
41 public static function invalidParameterPosition(string $pos): self
42 {
43 return new self('Invalid parameter position: ' . $pos);
44 }
45
46 public static function tooManyParameters(int $expected, int $received): self
47 {
48 return new self('Too many parameters: the query defines ' . $expected . ' parameters and you bound ' . $received);
49 }
50
51 public static function tooFewParameters(int $expected, int $received): self
52 {
53 return new self('Too few parameters: the query defines ' . $expected . ' parameters but you only bound ' . $received);
54 }
55
56 public static function invalidParameterFormat(string $value): self
57 {
58 return new self('Invalid parameter format, ' . $value . ' given, but :<name> or ?<num> expected.');
59 }
60
61 public static function unknownParameter(string $key): self
62 {
63 return new self('Invalid parameter: token ' . $key . ' is not defined in the query.');
64 }
65
66 public static function parameterTypeMismatch(): self
67 {
68 return new self('DQL Query parameter and type numbers mismatch, but have to be exactly equal.');
69 }
70
71 public static function invalidPathExpression(PathExpression $pathExpr): self
72 {
73 return new self(
74 "Invalid PathExpression '" . $pathExpr->identificationVariable . '.' . $pathExpr->field . "'.",
75 );
76 }
77
78 public static function invalidLiteral(string|Stringable $literal): self
79 {
80 return new self("Invalid literal '" . $literal . "'");
81 }
82
83 public static function iterateWithFetchJoinCollectionNotAllowed(AssociationMapping $assoc): self
84 {
85 return new self(
86 'Invalid query operation: Not allowed to iterate over fetch join collections ' .
87 'in class ' . $assoc->sourceEntity . ' association ' . $assoc->fieldName,
88 );
89 }
90
91 /**
92 * @param string[] $assoc
93 * @psalm-param array<string, string> $assoc
94 */
95 public static function overwritingJoinConditionsNotYetSupported(array $assoc): self
96 {
97 return new self(
98 'Unsupported query operation: It is not yet possible to overwrite the join ' .
99 'conditions in class ' . $assoc['sourceEntityName'] . ' association ' . $assoc['fieldName'] . '. ' .
100 'Use WITH to append additional join conditions to the association.',
101 );
102 }
103
104 public static function associationPathInverseSideNotSupported(PathExpression $pathExpr): self
105 {
106 return new self(
107 'A single-valued association path expression to an inverse side is not supported in DQL queries. ' .
108 'Instead of "' . $pathExpr->identificationVariable . '.' . $pathExpr->field . '" use an explicit join.',
109 );
110 }
111
112 public static function iterateWithFetchJoinNotAllowed(AssociationMapping $assoc): self
113 {
114 return new self(
115 'Iterate with fetch join in class ' . $assoc->sourceEntity .
116 ' using association ' . $assoc->fieldName . ' not allowed.',
117 );
118 }
119
120 public static function eagerFetchJoinWithNotAllowed(string $sourceEntity, string $fieldName): self
121 {
122 return new self(
123 'Associations with fetch-mode=EAGER may not be using WITH conditions in
124 "' . $sourceEntity . '#' . $fieldName . '".',
125 );
126 }
127
128 public static function iterateWithMixedResultNotAllowed(): self
129 {
130 return new self('Iterating a query with mixed results (using scalars) is not supported.');
131 }
132
133 public static function associationPathCompositeKeyNotSupported(): self
134 {
135 return new self(
136 'A single-valued association path expression to an entity with a composite primary ' .
137 'key is not supported. Explicitly name the components of the composite primary key ' .
138 'in the query.',
139 );
140 }
141
142 public static function instanceOfUnrelatedClass(string $className, string $rootClass): self
143 {
144 return new self("Cannot check if a child of '" . $rootClass . "' is instanceof '" . $className . "', " .
145 'inheritance hierarchy does not exists between these two classes.');
146 }
147
148 public static function invalidQueryComponent(string $dqlAlias): self
149 {
150 return new self(
151 "Invalid query component given for DQL alias '" . $dqlAlias . "', " .
152 "requires 'metadata', 'parent', 'relation', 'map', 'nestingLevel' and 'token' keys.",
153 );
154 }
155}
diff --git a/vendor/doctrine/orm/src/Query/QueryExpressionVisitor.php b/vendor/doctrine/orm/src/Query/QueryExpressionVisitor.php
new file mode 100644
index 0000000..3e0ec65
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/QueryExpressionVisitor.php
@@ -0,0 +1,180 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use Doctrine\Common\Collections\ArrayCollection;
8use Doctrine\Common\Collections\Expr\Comparison;
9use Doctrine\Common\Collections\Expr\CompositeExpression;
10use Doctrine\Common\Collections\Expr\ExpressionVisitor;
11use Doctrine\Common\Collections\Expr\Value;
12use RuntimeException;
13
14use function count;
15use function str_replace;
16use function str_starts_with;
17
18/**
19 * Converts Collection expressions to Query expressions.
20 */
21class QueryExpressionVisitor extends ExpressionVisitor
22{
23 private const OPERATOR_MAP = [
24 Comparison::GT => Expr\Comparison::GT,
25 Comparison::GTE => Expr\Comparison::GTE,
26 Comparison::LT => Expr\Comparison::LT,
27 Comparison::LTE => Expr\Comparison::LTE,
28 ];
29
30 private readonly Expr $expr;
31
32 /** @var list<mixed> */
33 private array $parameters = [];
34
35 /** @param mixed[] $queryAliases */
36 public function __construct(
37 private readonly array $queryAliases,
38 ) {
39 $this->expr = new Expr();
40 }
41
42 /**
43 * Gets bound parameters.
44 * Filled after {@link dispach()}.
45 *
46 * @return ArrayCollection<int, mixed>
47 */
48 public function getParameters(): ArrayCollection
49 {
50 return new ArrayCollection($this->parameters);
51 }
52
53 public function clearParameters(): void
54 {
55 $this->parameters = [];
56 }
57
58 /**
59 * Converts Criteria expression to Query one based on static map.
60 */
61 private static function convertComparisonOperator(string $criteriaOperator): string|null
62 {
63 return self::OPERATOR_MAP[$criteriaOperator] ?? null;
64 }
65
66 public function walkCompositeExpression(CompositeExpression $expr): mixed
67 {
68 $expressionList = [];
69
70 foreach ($expr->getExpressionList() as $child) {
71 $expressionList[] = $this->dispatch($child);
72 }
73
74 return match ($expr->getType()) {
75 CompositeExpression::TYPE_AND => new Expr\Andx($expressionList),
76 CompositeExpression::TYPE_OR => new Expr\Orx($expressionList),
77 CompositeExpression::TYPE_NOT => $this->expr->not($expressionList[0]),
78 default => throw new RuntimeException('Unknown composite ' . $expr->getType()),
79 };
80 }
81
82 public function walkComparison(Comparison $comparison): mixed
83 {
84 if (! isset($this->queryAliases[0])) {
85 throw new QueryException('No aliases are set before invoking walkComparison().');
86 }
87
88 $field = $this->queryAliases[0] . '.' . $comparison->getField();
89
90 foreach ($this->queryAliases as $alias) {
91 if (str_starts_with($comparison->getField() . '.', $alias . '.')) {
92 $field = $comparison->getField();
93 break;
94 }
95 }
96
97 $parameterName = str_replace('.', '_', $comparison->getField());
98
99 foreach ($this->parameters as $parameter) {
100 if ($parameter->getName() === $parameterName) {
101 $parameterName .= '_' . count($this->parameters);
102 break;
103 }
104 }
105
106 $parameter = new Parameter($parameterName, $this->walkValue($comparison->getValue()));
107 $placeholder = ':' . $parameterName;
108
109 switch ($comparison->getOperator()) {
110 case Comparison::IN:
111 $this->parameters[] = $parameter;
112
113 return $this->expr->in($field, $placeholder);
114
115 case Comparison::NIN:
116 $this->parameters[] = $parameter;
117
118 return $this->expr->notIn($field, $placeholder);
119
120 case Comparison::EQ:
121 case Comparison::IS:
122 if ($this->walkValue($comparison->getValue()) === null) {
123 return $this->expr->isNull($field);
124 }
125
126 $this->parameters[] = $parameter;
127
128 return $this->expr->eq($field, $placeholder);
129
130 case Comparison::NEQ:
131 if ($this->walkValue($comparison->getValue()) === null) {
132 return $this->expr->isNotNull($field);
133 }
134
135 $this->parameters[] = $parameter;
136
137 return $this->expr->neq($field, $placeholder);
138
139 case Comparison::CONTAINS:
140 $parameter->setValue('%' . $parameter->getValue() . '%', $parameter->getType());
141 $this->parameters[] = $parameter;
142
143 return $this->expr->like($field, $placeholder);
144
145 case Comparison::MEMBER_OF:
146 return $this->expr->isMemberOf($comparison->getField(), $comparison->getValue()->getValue());
147
148 case Comparison::STARTS_WITH:
149 $parameter->setValue($parameter->getValue() . '%', $parameter->getType());
150 $this->parameters[] = $parameter;
151
152 return $this->expr->like($field, $placeholder);
153
154 case Comparison::ENDS_WITH:
155 $parameter->setValue('%' . $parameter->getValue(), $parameter->getType());
156 $this->parameters[] = $parameter;
157
158 return $this->expr->like($field, $placeholder);
159
160 default:
161 $operator = self::convertComparisonOperator($comparison->getOperator());
162 if ($operator) {
163 $this->parameters[] = $parameter;
164
165 return new Expr\Comparison(
166 $field,
167 $operator,
168 $placeholder,
169 );
170 }
171
172 throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator());
173 }
174 }
175
176 public function walkValue(Value $value): mixed
177 {
178 return $value->getValue();
179 }
180}
diff --git a/vendor/doctrine/orm/src/Query/ResultSetMapping.php b/vendor/doctrine/orm/src/Query/ResultSetMapping.php
new file mode 100644
index 0000000..612474d
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/ResultSetMapping.php
@@ -0,0 +1,547 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use function count;
8
9/**
10 * A ResultSetMapping describes how a result set of an SQL query maps to a Doctrine result.
11 *
12 * IMPORTANT NOTE:
13 * The properties of this class are only public for fast internal READ access and to (drastically)
14 * reduce the size of serialized instances for more effective caching due to better (un-)serialization
15 * performance.
16 *
17 * <b>Users should use the public methods.</b>
18 *
19 * @todo Think about whether the number of lookup maps can be reduced.
20 */
21class ResultSetMapping
22{
23 /**
24 * Whether the result is mixed (contains scalar values together with field values).
25 *
26 * @ignore
27 */
28 public bool $isMixed = false;
29
30 /**
31 * Whether the result is a select statement.
32 *
33 * @ignore
34 */
35 public bool $isSelect = true;
36
37 /**
38 * Maps alias names to class names.
39 *
40 * @ignore
41 * @psalm-var array<string, class-string>
42 */
43 public array $aliasMap = [];
44
45 /**
46 * Maps alias names to related association field names.
47 *
48 * @ignore
49 * @psalm-var array<string, string>
50 */
51 public array $relationMap = [];
52
53 /**
54 * Maps alias names to parent alias names.
55 *
56 * @ignore
57 * @psalm-var array<string, string>
58 */
59 public array $parentAliasMap = [];
60
61 /**
62 * Maps column names in the result set to field names for each class.
63 *
64 * @ignore
65 * @psalm-var array<string, string>
66 */
67 public array $fieldMappings = [];
68
69 /**
70 * Maps column names in the result set to the alias/field name to use in the mapped result.
71 *
72 * @ignore
73 * @psalm-var array<string, string|int>
74 */
75 public array $scalarMappings = [];
76
77 /**
78 * Maps scalar columns to enums
79 *
80 * @ignore
81 * @psalm-var array<string, string>
82 */
83 public $enumMappings = [];
84
85 /**
86 * Maps column names in the result set to the alias/field type to use in the mapped result.
87 *
88 * @ignore
89 * @psalm-var array<string, string>
90 */
91 public array $typeMappings = [];
92
93 /**
94 * Maps entities in the result set to the alias name to use in the mapped result.
95 *
96 * @ignore
97 * @psalm-var array<string, string|null>
98 */
99 public array $entityMappings = [];
100
101 /**
102 * Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names.
103 *
104 * @ignore
105 * @psalm-var array<string, string>
106 */
107 public array $metaMappings = [];
108
109 /**
110 * Maps column names in the result set to the alias they belong to.
111 *
112 * @ignore
113 * @psalm-var array<string, string>
114 */
115 public array $columnOwnerMap = [];
116
117 /**
118 * List of columns in the result set that are used as discriminator columns.
119 *
120 * @ignore
121 * @psalm-var array<string, string>
122 */
123 public array $discriminatorColumns = [];
124
125 /**
126 * Maps alias names to field names that should be used for indexing.
127 *
128 * @ignore
129 * @psalm-var array<string, string>
130 */
131 public array $indexByMap = [];
132
133 /**
134 * Map from column names to class names that declare the field the column is mapped to.
135 *
136 * @ignore
137 * @psalm-var array<string, class-string>
138 */
139 public array $declaringClasses = [];
140
141 /**
142 * This is necessary to hydrate derivate foreign keys correctly.
143 *
144 * @psalm-var array<string, array<string, bool>>
145 */
146 public array $isIdentifierColumn = [];
147
148 /**
149 * Maps column names in the result set to field names for each new object expression.
150 *
151 * @psalm-var array<string, array<string, mixed>>
152 */
153 public array $newObjectMappings = [];
154
155 /**
156 * Maps metadata parameter names to the metadata attribute.
157 *
158 * @psalm-var array<int|string, string>
159 */
160 public array $metadataParameterMapping = [];
161
162 /**
163 * Contains query parameter names to be resolved as discriminator values
164 *
165 * @psalm-var array<string, string>
166 */
167 public array $discriminatorParameters = [];
168
169 /**
170 * Adds an entity result to this ResultSetMapping.
171 *
172 * @param string $class The class name of the entity.
173 * @param string $alias The alias for the class. The alias must be unique among all entity
174 * results or joined entity results within this ResultSetMapping.
175 * @param string|null $resultAlias The result alias with which the entity result should be
176 * placed in the result structure.
177 * @psalm-param class-string $class
178 *
179 * @return $this
180 *
181 * @todo Rename: addRootEntity
182 */
183 public function addEntityResult(string $class, string $alias, string|null $resultAlias = null): static
184 {
185 $this->aliasMap[$alias] = $class;
186 $this->entityMappings[$alias] = $resultAlias;
187
188 if ($resultAlias !== null) {
189 $this->isMixed = true;
190 }
191
192 return $this;
193 }
194
195 /**
196 * Sets a discriminator column for an entity result or joined entity result.
197 * The discriminator column will be used to determine the concrete class name to
198 * instantiate.
199 *
200 * @param string $alias The alias of the entity result or joined entity result the discriminator
201 * column should be used for.
202 * @param string $discrColumn The name of the discriminator column in the SQL result set.
203 *
204 * @return $this
205 *
206 * @todo Rename: addDiscriminatorColumn
207 */
208 public function setDiscriminatorColumn(string $alias, string $discrColumn): static
209 {
210 $this->discriminatorColumns[$alias] = $discrColumn;
211 $this->columnOwnerMap[$discrColumn] = $alias;
212
213 return $this;
214 }
215
216 /**
217 * Sets a field to use for indexing an entity result or joined entity result.
218 *
219 * @param string $alias The alias of an entity result or joined entity result.
220 * @param string $fieldName The name of the field to use for indexing.
221 *
222 * @return $this
223 */
224 public function addIndexBy(string $alias, string $fieldName): static
225 {
226 $found = false;
227
228 foreach ([...$this->metaMappings, ...$this->fieldMappings] as $columnName => $columnFieldName) {
229 if (! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) {
230 continue;
231 }
232
233 $this->addIndexByColumn($alias, $columnName);
234 $found = true;
235
236 break;
237 }
238
239 /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals
240 if ( ! $found) {
241 $message = sprintf(
242 'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.',
243 $alias,
244 $fieldName
245 );
246
247 throw new \LogicException($message);
248 }
249 */
250
251 return $this;
252 }
253
254 /**
255 * Sets to index by a scalar result column name.
256 *
257 * @return $this
258 */
259 public function addIndexByScalar(string $resultColumnName): static
260 {
261 $this->indexByMap['scalars'] = $resultColumnName;
262
263 return $this;
264 }
265
266 /**
267 * Sets a column to use for indexing an entity or joined entity result by the given alias name.
268 *
269 * @return $this
270 */
271 public function addIndexByColumn(string $alias, string $resultColumnName): static
272 {
273 $this->indexByMap[$alias] = $resultColumnName;
274
275 return $this;
276 }
277
278 /**
279 * Checks whether an entity result or joined entity result with a given alias has
280 * a field set for indexing.
281 *
282 * @todo Rename: isIndexed($alias)
283 */
284 public function hasIndexBy(string $alias): bool
285 {
286 return isset($this->indexByMap[$alias]);
287 }
288
289 /**
290 * Checks whether the column with the given name is mapped as a field result
291 * as part of an entity result or joined entity result.
292 *
293 * @param string $columnName The name of the column in the SQL result set.
294 *
295 * @todo Rename: isField
296 */
297 public function isFieldResult(string $columnName): bool
298 {
299 return isset($this->fieldMappings[$columnName]);
300 }
301
302 /**
303 * Adds a field to the result that belongs to an entity or joined entity.
304 *
305 * @param string $alias The alias of the root entity or joined entity to which the field belongs.
306 * @param string $columnName The name of the column in the SQL result set.
307 * @param string $fieldName The name of the field on the declaring class.
308 * @param string|null $declaringClass The name of the class that declares/owns the specified field.
309 * When $alias refers to a superclass in a mapped hierarchy but
310 * the field $fieldName is defined on a subclass, specify that here.
311 * If not specified, the field is assumed to belong to the class
312 * designated by $alias.
313 * @psalm-param class-string|null $declaringClass
314 *
315 * @return $this
316 *
317 * @todo Rename: addField
318 */
319 public function addFieldResult(string $alias, string $columnName, string $fieldName, string|null $declaringClass = null): static
320 {
321 // column name (in result set) => field name
322 $this->fieldMappings[$columnName] = $fieldName;
323 // column name => alias of owner
324 $this->columnOwnerMap[$columnName] = $alias;
325 // field name => class name of declaring class
326 $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias];
327
328 if (! $this->isMixed && $this->scalarMappings) {
329 $this->isMixed = true;
330 }
331
332 return $this;
333 }
334
335 /**
336 * Adds a joined entity result.
337 *
338 * @param string $class The class name of the joined entity.
339 * @param string $alias The unique alias to use for the joined entity.
340 * @param string $parentAlias The alias of the entity result that is the parent of this joined result.
341 * @param string $relation The association field that connects the parent entity result
342 * with the joined entity result.
343 * @psalm-param class-string $class
344 *
345 * @return $this
346 *
347 * @todo Rename: addJoinedEntity
348 */
349 public function addJoinedEntityResult(string $class, string $alias, string $parentAlias, string $relation): static
350 {
351 $this->aliasMap[$alias] = $class;
352 $this->parentAliasMap[$alias] = $parentAlias;
353 $this->relationMap[$alias] = $relation;
354
355 return $this;
356 }
357
358 /**
359 * Adds a scalar result mapping.
360 *
361 * @param string $columnName The name of the column in the SQL result set.
362 * @param string|int $alias The result alias with which the scalar result should be placed in the result structure.
363 * @param string $type The column type
364 *
365 * @return $this
366 *
367 * @todo Rename: addScalar
368 */
369 public function addScalarResult(string $columnName, string|int $alias, string $type = 'string'): static
370 {
371 $this->scalarMappings[$columnName] = $alias;
372 $this->typeMappings[$columnName] = $type;
373
374 if (! $this->isMixed && $this->fieldMappings) {
375 $this->isMixed = true;
376 }
377
378 return $this;
379 }
380
381 /**
382 * Adds a scalar result mapping.
383 *
384 * @param string $columnName The name of the column in the SQL result set.
385 * @param string $enumType The enum type
386 *
387 * @return $this
388 */
389 public function addEnumResult(string $columnName, string $enumType): static
390 {
391 $this->enumMappings[$columnName] = $enumType;
392
393 return $this;
394 }
395
396 /**
397 * Adds a metadata parameter mappings.
398 */
399 public function addMetadataParameterMapping(string|int $parameter, string $attribute): void
400 {
401 $this->metadataParameterMapping[$parameter] = $attribute;
402 }
403
404 /**
405 * Checks whether a column with a given name is mapped as a scalar result.
406 *
407 * @todo Rename: isScalar
408 */
409 public function isScalarResult(string $columnName): bool
410 {
411 return isset($this->scalarMappings[$columnName]);
412 }
413
414 /**
415 * Gets the name of the class of an entity result or joined entity result,
416 * identified by the given unique alias.
417 *
418 * @psalm-return class-string
419 */
420 public function getClassName(string $alias): string
421 {
422 return $this->aliasMap[$alias];
423 }
424
425 /**
426 * Gets the field alias for a column that is mapped as a scalar value.
427 *
428 * @param string $columnName The name of the column in the SQL result set.
429 */
430 public function getScalarAlias(string $columnName): string|int
431 {
432 return $this->scalarMappings[$columnName];
433 }
434
435 /**
436 * Gets the name of the class that owns a field mapping for the specified column.
437 *
438 * @psalm-return class-string
439 */
440 public function getDeclaringClass(string $columnName): string
441 {
442 return $this->declaringClasses[$columnName];
443 }
444
445 public function getRelation(string $alias): string
446 {
447 return $this->relationMap[$alias];
448 }
449
450 public function isRelation(string $alias): bool
451 {
452 return isset($this->relationMap[$alias]);
453 }
454
455 /**
456 * Gets the alias of the class that owns a field mapping for the specified column.
457 */
458 public function getEntityAlias(string $columnName): string
459 {
460 return $this->columnOwnerMap[$columnName];
461 }
462
463 /**
464 * Gets the parent alias of the given alias.
465 */
466 public function getParentAlias(string $alias): string
467 {
468 return $this->parentAliasMap[$alias];
469 }
470
471 /**
472 * Checks whether the given alias has a parent alias.
473 */
474 public function hasParentAlias(string $alias): bool
475 {
476 return isset($this->parentAliasMap[$alias]);
477 }
478
479 /**
480 * Gets the field name for a column name.
481 */
482 public function getFieldName(string $columnName): string
483 {
484 return $this->fieldMappings[$columnName];
485 }
486
487 /** @psalm-return array<string, class-string> */
488 public function getAliasMap(): array
489 {
490 return $this->aliasMap;
491 }
492
493 /**
494 * Gets the number of different entities that appear in the mapped result.
495 *
496 * @psalm-return 0|positive-int
497 */
498 public function getEntityResultCount(): int
499 {
500 return count($this->aliasMap);
501 }
502
503 /**
504 * Checks whether this ResultSetMapping defines a mixed result.
505 *
506 * Mixed results can only occur in object and array (graph) hydration. In such a
507 * case a mixed result means that scalar values are mixed with objects/array in
508 * the result.
509 */
510 public function isMixedResult(): bool
511 {
512 return $this->isMixed;
513 }
514
515 /**
516 * Adds a meta column (foreign key or discriminator column) to the result set.
517 *
518 * @param string $alias The result alias with which the meta result should be placed in the result structure.
519 * @param string $columnName The name of the column in the SQL result set.
520 * @param string $fieldName The name of the field on the declaring class.
521 * @param string|null $type The column type
522 *
523 * @return $this
524 *
525 * @todo Make all methods of this class require all parameters and not infer anything
526 */
527 public function addMetaResult(
528 string $alias,
529 string $columnName,
530 string $fieldName,
531 bool $isIdentifierColumn = false,
532 string|null $type = null,
533 ): static {
534 $this->metaMappings[$columnName] = $fieldName;
535 $this->columnOwnerMap[$columnName] = $alias;
536
537 if ($isIdentifierColumn) {
538 $this->isIdentifierColumn[$alias][$columnName] = true;
539 }
540
541 if ($type) {
542 $this->typeMappings[$columnName] = $type;
543 }
544
545 return $this;
546 }
547}
diff --git a/vendor/doctrine/orm/src/Query/ResultSetMappingBuilder.php b/vendor/doctrine/orm/src/Query/ResultSetMappingBuilder.php
new file mode 100644
index 0000000..f28f3a9
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/ResultSetMappingBuilder.php
@@ -0,0 +1,281 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use Doctrine\DBAL\Types\Type;
8use Doctrine\ORM\EntityManagerInterface;
9use Doctrine\ORM\Internal\SQLResultCasing;
10use Doctrine\ORM\Mapping\ClassMetadata;
11use Doctrine\ORM\Utility\PersisterHelper;
12use InvalidArgumentException;
13use Stringable;
14
15use function in_array;
16use function sprintf;
17
18/**
19 * A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields.
20 */
21class ResultSetMappingBuilder extends ResultSetMapping implements Stringable
22{
23 use SQLResultCasing;
24
25 /**
26 * Picking this rename mode will register entity columns as is,
27 * as they are in the database. This can cause clashes when multiple
28 * entities are fetched that have columns with the same name.
29 */
30 public const COLUMN_RENAMING_NONE = 1;
31
32 /**
33 * Picking custom renaming allows the user to define the renaming
34 * of specific columns with a rename array that contains column names as
35 * keys and result alias as values.
36 */
37 public const COLUMN_RENAMING_CUSTOM = 2;
38
39 /**
40 * Incremental renaming uses a result set mapping internal counter to add a
41 * number to each column result, leading to uniqueness. This only works if
42 * you use {@see generateSelectClause()} to generate the SELECT clause for
43 * you.
44 */
45 public const COLUMN_RENAMING_INCREMENT = 3;
46
47 private int $sqlCounter = 0;
48
49 /** @psalm-param self::COLUMN_RENAMING_* $defaultRenameMode */
50 public function __construct(
51 private readonly EntityManagerInterface $em,
52 private readonly int $defaultRenameMode = self::COLUMN_RENAMING_NONE,
53 ) {
54 }
55
56 /**
57 * Adds a root entity and all of its fields to the result set.
58 *
59 * @param string $class The class name of the root entity.
60 * @param string $alias The unique alias to use for the root entity.
61 * @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName).
62 * @psalm-param class-string $class
63 * @psalm-param array<string, string> $renamedColumns
64 * @psalm-param self::COLUMN_RENAMING_*|null $renameMode
65 */
66 public function addRootEntityFromClassMetadata(
67 string $class,
68 string $alias,
69 array $renamedColumns = [],
70 int|null $renameMode = null,
71 ): void {
72 $renameMode = $renameMode ?: $this->defaultRenameMode;
73 $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns);
74
75 $this->addEntityResult($class, $alias);
76 $this->addAllClassFields($class, $alias, $columnAliasMap);
77 }
78
79 /**
80 * Adds a joined entity and all of its fields to the result set.
81 *
82 * @param string $class The class name of the joined entity.
83 * @param string $alias The unique alias to use for the joined entity.
84 * @param string $parentAlias The alias of the entity result that is the parent of this joined result.
85 * @param string $relation The association field that connects the parent entity result
86 * with the joined entity result.
87 * @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName).
88 * @psalm-param class-string $class
89 * @psalm-param array<string, string> $renamedColumns
90 * @psalm-param self::COLUMN_RENAMING_*|null $renameMode
91 */
92 public function addJoinedEntityFromClassMetadata(
93 string $class,
94 string $alias,
95 string $parentAlias,
96 string $relation,
97 array $renamedColumns = [],
98 int|null $renameMode = null,
99 ): void {
100 $renameMode = $renameMode ?: $this->defaultRenameMode;
101 $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns);
102
103 $this->addJoinedEntityResult($class, $alias, $parentAlias, $relation);
104 $this->addAllClassFields($class, $alias, $columnAliasMap);
105 }
106
107 /**
108 * Adds all fields of the given class to the result set mapping (columns and meta fields).
109 *
110 * @param string[] $columnAliasMap
111 * @psalm-param array<string, string> $columnAliasMap
112 *
113 * @throws InvalidArgumentException
114 */
115 protected function addAllClassFields(string $class, string $alias, array $columnAliasMap = []): void
116 {
117 $classMetadata = $this->em->getClassMetadata($class);
118 $platform = $this->em->getConnection()->getDatabasePlatform();
119
120 if (! $this->isInheritanceSupported($classMetadata)) {
121 throw new InvalidArgumentException('ResultSetMapping builder does not currently support your inheritance scheme.');
122 }
123
124 foreach ($classMetadata->getColumnNames() as $columnName) {
125 $propertyName = $classMetadata->getFieldName($columnName);
126 $columnAlias = $this->getSQLResultCasing($platform, $columnAliasMap[$columnName]);
127
128 if (isset($this->fieldMappings[$columnAlias])) {
129 throw new InvalidArgumentException(sprintf(
130 "The column '%s' conflicts with another column in the mapper.",
131 $columnName,
132 ));
133 }
134
135 $this->addFieldResult($alias, $columnAlias, $propertyName);
136
137 $enumType = $classMetadata->getFieldMapping($propertyName)->enumType ?? null;
138 if (! empty($enumType)) {
139 $this->addEnumResult($columnAlias, $enumType);
140 }
141 }
142
143 foreach ($classMetadata->associationMappings as $associationMapping) {
144 if ($associationMapping->isToOneOwningSide()) {
145 $targetClass = $this->em->getClassMetadata($associationMapping->targetEntity);
146 $isIdentifier = isset($associationMapping->id) && $associationMapping->id === true;
147
148 foreach ($associationMapping->joinColumns as $joinColumn) {
149 $columnName = $joinColumn->name;
150 $columnAlias = $this->getSQLResultCasing($platform, $columnAliasMap[$columnName]);
151 $columnType = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em);
152
153 if (isset($this->metaMappings[$columnAlias])) {
154 throw new InvalidArgumentException(sprintf(
155 "The column '%s' conflicts with another column in the mapper.",
156 $columnAlias,
157 ));
158 }
159
160 $this->addMetaResult($alias, $columnAlias, $columnName, $isIdentifier, $columnType);
161 }
162 }
163 }
164 }
165
166 private function isInheritanceSupported(ClassMetadata $classMetadata): bool
167 {
168 if (
169 $classMetadata->isInheritanceTypeSingleTable()
170 && in_array($classMetadata->name, $classMetadata->discriminatorMap, true)
171 ) {
172 return true;
173 }
174
175 return ! ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined());
176 }
177
178 /**
179 * Gets column alias for a given column.
180 *
181 * @psalm-param array<string, string> $customRenameColumns
182 *
183 * @psalm-assert self::COLUMN_RENAMING_* $mode
184 */
185 private function getColumnAlias(string $columnName, int $mode, array $customRenameColumns): string
186 {
187 return match ($mode) {
188 self::COLUMN_RENAMING_INCREMENT => $columnName . $this->sqlCounter++,
189 self::COLUMN_RENAMING_CUSTOM => $customRenameColumns[$columnName] ?? $columnName,
190 self::COLUMN_RENAMING_NONE => $columnName,
191 default => throw new InvalidArgumentException(sprintf('%d is not a valid value for $mode', $mode)),
192 };
193 }
194
195 /**
196 * Retrieves a class columns and join columns aliases that are used in the SELECT clause.
197 *
198 * This depends on the renaming mode selected by the user.
199 *
200 * @psalm-param class-string $className
201 * @psalm-param self::COLUMN_RENAMING_* $mode
202 * @psalm-param array<string, string> $customRenameColumns
203 *
204 * @return string[]
205 * @psalm-return array<array-key, string>
206 */
207 private function getColumnAliasMap(
208 string $className,
209 int $mode,
210 array $customRenameColumns,
211 ): array {
212 if ($customRenameColumns) { // for BC with 2.2-2.3 API
213 $mode = self::COLUMN_RENAMING_CUSTOM;
214 }
215
216 $columnAlias = [];
217 $class = $this->em->getClassMetadata($className);
218
219 foreach ($class->getColumnNames() as $columnName) {
220 $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns);
221 }
222
223 foreach ($class->associationMappings as $associationMapping) {
224 if ($associationMapping->isToOneOwningSide()) {
225 foreach ($associationMapping->joinColumns as $joinColumn) {
226 $columnName = $joinColumn->name;
227 $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns);
228 }
229 }
230 }
231
232 return $columnAlias;
233 }
234
235 /**
236 * Generates the Select clause from this ResultSetMappingBuilder.
237 *
238 * Works only for all the entity results. The select parts for scalar
239 * expressions have to be written manually.
240 *
241 * @param string[] $tableAliases
242 * @psalm-param array<string, string> $tableAliases
243 */
244 public function generateSelectClause(array $tableAliases = []): string
245 {
246 $sql = '';
247
248 foreach ($this->columnOwnerMap as $columnName => $dqlAlias) {
249 $tableAlias = $tableAliases[$dqlAlias] ?? $dqlAlias;
250
251 if ($sql !== '') {
252 $sql .= ', ';
253 }
254
255 if (isset($this->fieldMappings[$columnName])) {
256 $class = $this->em->getClassMetadata($this->declaringClasses[$columnName]);
257 $fieldName = $this->fieldMappings[$columnName];
258 $classFieldMapping = $class->fieldMappings[$fieldName];
259 $columnSql = $tableAlias . '.' . $classFieldMapping->columnName;
260
261 $type = Type::getType($classFieldMapping->type);
262 $columnSql = $type->convertToPHPValueSQL($columnSql, $this->em->getConnection()->getDatabasePlatform());
263
264 $sql .= $columnSql;
265 } elseif (isset($this->metaMappings[$columnName])) {
266 $sql .= $tableAlias . '.' . $this->metaMappings[$columnName];
267 } elseif (isset($this->discriminatorColumns[$dqlAlias])) {
268 $sql .= $tableAlias . '.' . $this->discriminatorColumns[$dqlAlias];
269 }
270
271 $sql .= ' AS ' . $columnName;
272 }
273
274 return $sql;
275 }
276
277 public function __toString(): string
278 {
279 return $this->generateSelectClause([]);
280 }
281}
diff --git a/vendor/doctrine/orm/src/Query/SqlWalker.php b/vendor/doctrine/orm/src/Query/SqlWalker.php
new file mode 100644
index 0000000..c6f98c1
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/SqlWalker.php
@@ -0,0 +1,2264 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use BadMethodCallException;
8use Doctrine\DBAL\Connection;
9use Doctrine\DBAL\LockMode;
10use Doctrine\DBAL\Platforms\AbstractPlatform;
11use Doctrine\DBAL\Types\Type;
12use Doctrine\ORM\EntityManagerInterface;
13use Doctrine\ORM\Mapping\ClassMetadata;
14use Doctrine\ORM\Mapping\QuoteStrategy;
15use Doctrine\ORM\OptimisticLockException;
16use Doctrine\ORM\Query;
17use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
18use Doctrine\ORM\Utility\LockSqlHelper;
19use Doctrine\ORM\Utility\PersisterHelper;
20use InvalidArgumentException;
21use LogicException;
22
23use function array_diff;
24use function array_filter;
25use function array_keys;
26use function array_map;
27use function array_merge;
28use function assert;
29use function count;
30use function implode;
31use function is_array;
32use function is_float;
33use function is_int;
34use function is_numeric;
35use function is_string;
36use function preg_match;
37use function reset;
38use function sprintf;
39use function strtolower;
40use function strtoupper;
41use function trim;
42
43/**
44 * The SqlWalker walks over a DQL AST and constructs the corresponding SQL.
45 *
46 * @psalm-import-type QueryComponent from Parser
47 * @psalm-consistent-constructor
48 */
49class SqlWalker
50{
51 use LockSqlHelper;
52
53 public const HINT_DISTINCT = 'doctrine.distinct';
54
55 private readonly ResultSetMapping $rsm;
56
57 /**
58 * Counter for generating unique column aliases.
59 */
60 private int $aliasCounter = 0;
61
62 /**
63 * Counter for generating unique table aliases.
64 */
65 private int $tableAliasCounter = 0;
66
67 /**
68 * Counter for generating unique scalar result.
69 */
70 private int $scalarResultCounter = 1;
71
72 /**
73 * Counter for generating unique parameter indexes.
74 */
75 private int $sqlParamIndex = 0;
76
77 /**
78 * Counter for generating indexes.
79 */
80 private int $newObjectCounter = 0;
81
82 private readonly EntityManagerInterface $em;
83 private readonly Connection $conn;
84
85 /** @var mixed[] */
86 private array $tableAliasMap = [];
87
88 /**
89 * Map from result variable names to their SQL column alias names.
90 *
91 * @psalm-var array<string|int, string|list<string>>
92 */
93 private array $scalarResultAliasMap = [];
94
95 /**
96 * Map from Table-Alias + Column-Name to OrderBy-Direction.
97 *
98 * @var array<string, string>
99 */
100 private array $orderedColumnsMap = [];
101
102 /**
103 * Map from DQL-Alias + Field-Name to SQL Column Alias.
104 *
105 * @var array<string, array<string, string>>
106 */
107 private array $scalarFields = [];
108
109 /**
110 * A list of classes that appear in non-scalar SelectExpressions.
111 *
112 * @psalm-var array<string, array{class: ClassMetadata, dqlAlias: string, resultAlias: string|null}>
113 */
114 private array $selectedClasses = [];
115
116 /**
117 * The DQL alias of the root class of the currently traversed query.
118 *
119 * @psalm-var list<string>
120 */
121 private array $rootAliases = [];
122
123 /**
124 * Flag that indicates whether to generate SQL table aliases in the SQL.
125 * These should only be generated for SELECT queries, not for UPDATE/DELETE.
126 */
127 private bool $useSqlTableAliases = true;
128
129 /**
130 * The database platform abstraction.
131 */
132 private readonly AbstractPlatform $platform;
133
134 /**
135 * The quote strategy.
136 */
137 private readonly QuoteStrategy $quoteStrategy;
138
139 /** @psalm-param array<string, QueryComponent> $queryComponents The query components (symbol table). */
140 public function __construct(
141 private readonly Query $query,
142 private readonly ParserResult $parserResult,
143 private array $queryComponents,
144 ) {
145 $this->rsm = $parserResult->getResultSetMapping();
146 $this->em = $query->getEntityManager();
147 $this->conn = $this->em->getConnection();
148 $this->platform = $this->conn->getDatabasePlatform();
149 $this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy();
150 }
151
152 /**
153 * Gets the Query instance used by the walker.
154 */
155 public function getQuery(): Query
156 {
157 return $this->query;
158 }
159
160 /**
161 * Gets the Connection used by the walker.
162 */
163 public function getConnection(): Connection
164 {
165 return $this->conn;
166 }
167
168 /**
169 * Gets the EntityManager used by the walker.
170 */
171 public function getEntityManager(): EntityManagerInterface
172 {
173 return $this->em;
174 }
175
176 /**
177 * Gets the information about a single query component.
178 *
179 * @param string $dqlAlias The DQL alias.
180 *
181 * @return mixed[]
182 * @psalm-return QueryComponent
183 */
184 public function getQueryComponent(string $dqlAlias): array
185 {
186 return $this->queryComponents[$dqlAlias];
187 }
188
189 public function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata
190 {
191 return $this->queryComponents[$dqlAlias]['metadata']
192 ?? throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias));
193 }
194
195 /**
196 * Returns internal queryComponents array.
197 *
198 * @return array<string, QueryComponent>
199 */
200 public function getQueryComponents(): array
201 {
202 return $this->queryComponents;
203 }
204
205 /**
206 * Sets or overrides a query component for a given dql alias.
207 *
208 * @psalm-param QueryComponent $queryComponent
209 */
210 public function setQueryComponent(string $dqlAlias, array $queryComponent): void
211 {
212 $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'];
213
214 if (array_diff($requiredKeys, array_keys($queryComponent))) {
215 throw QueryException::invalidQueryComponent($dqlAlias);
216 }
217
218 $this->queryComponents[$dqlAlias] = $queryComponent;
219 }
220
221 /**
222 * Gets an executor that can be used to execute the result of this walker.
223 */
224 public function getExecutor(AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement $statement): Exec\AbstractSqlExecutor
225 {
226 return match (true) {
227 $statement instanceof AST\SelectStatement
228 => new Exec\SingleSelectExecutor($statement, $this),
229 $statement instanceof AST\UpdateStatement
230 => $this->em->getClassMetadata($statement->updateClause->abstractSchemaName)->isInheritanceTypeJoined()
231 ? new Exec\MultiTableUpdateExecutor($statement, $this)
232 : new Exec\SingleTableDeleteUpdateExecutor($statement, $this),
233 $statement instanceof AST\DeleteStatement
234 => $this->em->getClassMetadata($statement->deleteClause->abstractSchemaName)->isInheritanceTypeJoined()
235 ? new Exec\MultiTableDeleteExecutor($statement, $this)
236 : new Exec\SingleTableDeleteUpdateExecutor($statement, $this),
237 };
238 }
239
240 /**
241 * Generates a unique, short SQL table alias.
242 */
243 public function getSQLTableAlias(string $tableName, string $dqlAlias = ''): string
244 {
245 $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : '';
246
247 if (! isset($this->tableAliasMap[$tableName])) {
248 $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't')
249 . $this->tableAliasCounter++ . '_';
250 }
251
252 return $this->tableAliasMap[$tableName];
253 }
254
255 /**
256 * Forces the SqlWalker to use a specific alias for a table name, rather than
257 * generating an alias on its own.
258 */
259 public function setSQLTableAlias(string $tableName, string $alias, string $dqlAlias = ''): string
260 {
261 $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : '';
262
263 $this->tableAliasMap[$tableName] = $alias;
264
265 return $alias;
266 }
267
268 /**
269 * Gets an SQL column alias for a column name.
270 */
271 public function getSQLColumnAlias(string $columnName): string
272 {
273 return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform);
274 }
275
276 /**
277 * Generates the SQL JOINs that are necessary for Class Table Inheritance
278 * for the given class.
279 */
280 private function generateClassTableInheritanceJoins(
281 ClassMetadata $class,
282 string $dqlAlias,
283 ): string {
284 $sql = '';
285
286 $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
287
288 // INNER JOIN parent class tables
289 foreach ($class->parentClasses as $parentClassName) {
290 $parentClass = $this->em->getClassMetadata($parentClassName);
291 $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
292
293 // If this is a joined association we must use left joins to preserve the correct result.
294 $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
295 $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
296
297 $sqlParts = [];
298
299 foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
300 $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
301 }
302
303 // Add filters on the root class
304 $sqlParts[] = $this->generateFilterConditionSQL($parentClass, $tableAlias);
305
306 $sql .= implode(' AND ', array_filter($sqlParts));
307 }
308
309 // LEFT JOIN child class tables
310 foreach ($class->subClasses as $subClassName) {
311 $subClass = $this->em->getClassMetadata($subClassName);
312 $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
313
314 $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
315
316 $sqlParts = [];
317
318 foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) {
319 $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
320 }
321
322 $sql .= implode(' AND ', $sqlParts);
323 }
324
325 return $sql;
326 }
327
328 private function generateOrderedCollectionOrderByItems(): string
329 {
330 $orderedColumns = [];
331
332 foreach ($this->selectedClasses as $selectedClass) {
333 $dqlAlias = $selectedClass['dqlAlias'];
334 $qComp = $this->queryComponents[$dqlAlias];
335
336 if (! isset($qComp['relation']->orderBy)) {
337 continue;
338 }
339
340 assert(isset($qComp['metadata']));
341 $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
342
343 foreach ($qComp['relation']->orderBy as $fieldName => $orientation) {
344 $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform);
345 $tableName = $qComp['metadata']->isInheritanceTypeJoined()
346 ? $persister->getOwningTable($fieldName)
347 : $qComp['metadata']->getTableName();
348
349 $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
350
351 // OrderByClause should replace an ordered relation. see - DDC-2475
352 if (isset($this->orderedColumnsMap[$orderedColumn])) {
353 continue;
354 }
355
356 $this->orderedColumnsMap[$orderedColumn] = $orientation;
357 $orderedColumns[] = $orderedColumn . ' ' . $orientation;
358 }
359 }
360
361 return implode(', ', $orderedColumns);
362 }
363
364 /**
365 * Generates a discriminator column SQL condition for the class with the given DQL alias.
366 *
367 * @psalm-param list<string> $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
368 */
369 private function generateDiscriminatorColumnConditionSQL(array $dqlAliases): string
370 {
371 $sqlParts = [];
372
373 foreach ($dqlAliases as $dqlAlias) {
374 $class = $this->getMetadataForDqlAlias($dqlAlias);
375
376 if (! $class->isInheritanceTypeSingleTable()) {
377 continue;
378 }
379
380 $sqlTableAlias = $this->useSqlTableAliases
381 ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
382 : '';
383
384 $conn = $this->em->getConnection();
385 $values = [];
386
387 if ($class->discriminatorValue !== null) { // discriminators can be 0
388 $values[] = $class->getDiscriminatorColumn()->type === 'integer' && is_int($class->discriminatorValue)
389 ? $class->discriminatorValue
390 : $conn->quote((string) $class->discriminatorValue);
391 }
392
393 foreach ($class->subClasses as $subclassName) {
394 $subclassMetadata = $this->em->getClassMetadata($subclassName);
395
396 // Abstract entity classes show up in the list of subClasses, but may be omitted
397 // from the discriminator map. In that case, they have a null discriminator value.
398 if ($subclassMetadata->discriminatorValue === null) {
399 continue;
400 }
401
402 $values[] = $subclassMetadata->getDiscriminatorColumn()->type === 'integer' && is_int($subclassMetadata->discriminatorValue)
403 ? $subclassMetadata->discriminatorValue
404 : $conn->quote((string) $subclassMetadata->discriminatorValue);
405 }
406
407 if ($values !== []) {
408 $sqlParts[] = $sqlTableAlias . $class->getDiscriminatorColumn()->name . ' IN (' . implode(', ', $values) . ')';
409 } else {
410 $sqlParts[] = '1=0'; // impossible condition
411 }
412 }
413
414 $sql = implode(' AND ', $sqlParts);
415
416 return count($sqlParts) > 1 ? '(' . $sql . ')' : $sql;
417 }
418
419 /**
420 * Generates the filter SQL for a given entity and table alias.
421 */
422 private function generateFilterConditionSQL(
423 ClassMetadata $targetEntity,
424 string $targetTableAlias,
425 ): string {
426 if (! $this->em->hasFilters()) {
427 return '';
428 }
429
430 switch ($targetEntity->inheritanceType) {
431 case ClassMetadata::INHERITANCE_TYPE_NONE:
432 break;
433 case ClassMetadata::INHERITANCE_TYPE_JOINED:
434 // The classes in the inheritance will be added to the query one by one,
435 // but only the root node is getting filtered
436 if ($targetEntity->name !== $targetEntity->rootEntityName) {
437 return '';
438 }
439
440 break;
441 case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE:
442 // With STI the table will only be queried once, make sure that the filters
443 // are added to the root entity
444 $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName);
445 break;
446 default:
447 //@todo: throw exception?
448 return '';
449 }
450
451 $filterClauses = [];
452 foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
453 $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias);
454 if ($filterExpr !== '') {
455 $filterClauses[] = '(' . $filterExpr . ')';
456 }
457 }
458
459 return implode(' AND ', $filterClauses);
460 }
461
462 /**
463 * Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
464 */
465 public function walkSelectStatement(AST\SelectStatement $selectStatement): string
466 {
467 $limit = $this->query->getMaxResults();
468 $offset = $this->query->getFirstResult();
469 $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE) ?: LockMode::NONE;
470 $sql = $this->walkSelectClause($selectStatement->selectClause)
471 . $this->walkFromClause($selectStatement->fromClause)
472 . $this->walkWhereClause($selectStatement->whereClause);
473
474 if ($selectStatement->groupByClause) {
475 $sql .= $this->walkGroupByClause($selectStatement->groupByClause);
476 }
477
478 if ($selectStatement->havingClause) {
479 $sql .= $this->walkHavingClause($selectStatement->havingClause);
480 }
481
482 if ($selectStatement->orderByClause) {
483 $sql .= $this->walkOrderByClause($selectStatement->orderByClause);
484 }
485
486 $orderBySql = $this->generateOrderedCollectionOrderByItems();
487 if (! $selectStatement->orderByClause && $orderBySql) {
488 $sql .= ' ORDER BY ' . $orderBySql;
489 }
490
491 $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);
492
493 if ($lockMode === LockMode::NONE) {
494 return $sql;
495 }
496
497 if ($lockMode === LockMode::PESSIMISTIC_READ) {
498 return $sql . ' ' . $this->getReadLockSQL($this->platform);
499 }
500
501 if ($lockMode === LockMode::PESSIMISTIC_WRITE) {
502 return $sql . ' ' . $this->getWriteLockSQL($this->platform);
503 }
504
505 if ($lockMode !== LockMode::OPTIMISTIC) {
506 throw QueryException::invalidLockMode();
507 }
508
509 foreach ($this->selectedClasses as $selectedClass) {
510 if (! $selectedClass['class']->isVersioned) {
511 throw OptimisticLockException::lockFailed($selectedClass['class']->name);
512 }
513 }
514
515 return $sql;
516 }
517
518 /**
519 * Walks down a UpdateStatement AST node, thereby generating the appropriate SQL.
520 */
521 public function walkUpdateStatement(AST\UpdateStatement $updateStatement): string
522 {
523 $this->useSqlTableAliases = false;
524 $this->rsm->isSelect = false;
525
526 return $this->walkUpdateClause($updateStatement->updateClause)
527 . $this->walkWhereClause($updateStatement->whereClause);
528 }
529
530 /**
531 * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL.
532 */
533 public function walkDeleteStatement(AST\DeleteStatement $deleteStatement): string
534 {
535 $this->useSqlTableAliases = false;
536 $this->rsm->isSelect = false;
537
538 return $this->walkDeleteClause($deleteStatement->deleteClause)
539 . $this->walkWhereClause($deleteStatement->whereClause);
540 }
541
542 /**
543 * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
544 * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
545 */
546 public function walkEntityIdentificationVariable(string $identVariable): string
547 {
548 $class = $this->getMetadataForDqlAlias($identVariable);
549 $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
550 $sqlParts = [];
551
552 foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
553 $sqlParts[] = $tableAlias . '.' . $columnName;
554 }
555
556 return implode(', ', $sqlParts);
557 }
558
559 /**
560 * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
561 */
562 public function walkIdentificationVariable(string $identificationVariable, string|null $fieldName = null): string
563 {
564 $class = $this->getMetadataForDqlAlias($identificationVariable);
565
566 if (
567 $fieldName !== null && $class->isInheritanceTypeJoined() &&
568 isset($class->fieldMappings[$fieldName]->inherited)
569 ) {
570 $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]->inherited);
571 }
572
573 return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
574 }
575
576 /**
577 * Walks down a PathExpression AST node, thereby generating the appropriate SQL.
578 */
579 public function walkPathExpression(AST\PathExpression $pathExpr): string
580 {
581 $sql = '';
582 assert($pathExpr->field !== null);
583
584 switch ($pathExpr->type) {
585 case AST\PathExpression::TYPE_STATE_FIELD:
586 $fieldName = $pathExpr->field;
587 $dqlAlias = $pathExpr->identificationVariable;
588 $class = $this->getMetadataForDqlAlias($dqlAlias);
589
590 if ($this->useSqlTableAliases) {
591 $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
592 }
593
594 $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
595 break;
596
597 case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
598 // 1- the owning side:
599 // Just use the foreign key, i.e. u.group_id
600 $fieldName = $pathExpr->field;
601 $dqlAlias = $pathExpr->identificationVariable;
602 $class = $this->getMetadataForDqlAlias($dqlAlias);
603
604 if (isset($class->associationMappings[$fieldName]->inherited)) {
605 $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]->inherited);
606 }
607
608 $assoc = $class->associationMappings[$fieldName];
609
610 if (! $assoc->isOwningSide()) {
611 throw QueryException::associationPathInverseSideNotSupported($pathExpr);
612 }
613
614 assert($assoc->isToOneOwningSide());
615
616 // COMPOSITE KEYS NOT (YET?) SUPPORTED
617 if (count($assoc->sourceToTargetKeyColumns) > 1) {
618 throw QueryException::associationPathCompositeKeyNotSupported();
619 }
620
621 if ($this->useSqlTableAliases) {
622 $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
623 }
624
625 $sql .= reset($assoc->targetToSourceKeyColumns);
626 break;
627
628 default:
629 throw QueryException::invalidPathExpression($pathExpr);
630 }
631
632 return $sql;
633 }
634
635 /**
636 * Walks down a SelectClause AST node, thereby generating the appropriate SQL.
637 */
638 public function walkSelectClause(AST\SelectClause $selectClause): string
639 {
640 $sql = 'SELECT ' . ($selectClause->isDistinct ? 'DISTINCT ' : '');
641 $sqlSelectExpressions = array_filter(array_map($this->walkSelectExpression(...), $selectClause->selectExpressions));
642
643 if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true && $selectClause->isDistinct) {
644 $this->query->setHint(self::HINT_DISTINCT, true);
645 }
646
647 $addMetaColumns = $this->query->getHydrationMode() === Query::HYDRATE_OBJECT
648 || $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
649
650 foreach ($this->selectedClasses as $selectedClass) {
651 $class = $selectedClass['class'];
652 $dqlAlias = $selectedClass['dqlAlias'];
653 $resultAlias = $selectedClass['resultAlias'];
654
655 // Register as entity or joined entity result
656 if (! isset($this->queryComponents[$dqlAlias]['relation'])) {
657 $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
658 } else {
659 assert(isset($this->queryComponents[$dqlAlias]['parent']));
660
661 $this->rsm->addJoinedEntityResult(
662 $class->name,
663 $dqlAlias,
664 $this->queryComponents[$dqlAlias]['parent'],
665 $this->queryComponents[$dqlAlias]['relation']->fieldName,
666 );
667 }
668
669 if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
670 // Add discriminator columns to SQL
671 $rootClass = $this->em->getClassMetadata($class->rootEntityName);
672 $tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
673 $discrColumn = $rootClass->getDiscriminatorColumn();
674 $columnAlias = $this->getSQLColumnAlias($discrColumn->name);
675
676 $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn->name . ' AS ' . $columnAlias;
677
678 $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
679 $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn->fieldName, false, $discrColumn->type);
680 if (! empty($discrColumn->enumType)) {
681 $this->rsm->addEnumResult($columnAlias, $discrColumn->enumType);
682 }
683 }
684
685 // Add foreign key columns to SQL, if necessary
686 if (! $addMetaColumns && ! $class->containsForeignIdentifier) {
687 continue;
688 }
689
690 // Add foreign key columns of class and also parent classes
691 foreach ($class->associationMappings as $assoc) {
692 if (
693 ! $assoc->isToOneOwningSide()
694 || ( ! $addMetaColumns && ! isset($assoc->id))
695 ) {
696 continue;
697 }
698
699 $targetClass = $this->em->getClassMetadata($assoc->targetEntity);
700 $isIdentifier = (isset($assoc->id) && $assoc->id === true);
701 $owningClass = isset($assoc->inherited) ? $this->em->getClassMetadata($assoc->inherited) : $class;
702 $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
703
704 foreach ($assoc->joinColumns as $joinColumn) {
705 $columnName = $joinColumn->name;
706 $columnAlias = $this->getSQLColumnAlias($columnName);
707 $columnType = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em);
708
709 $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
710 $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
711
712 $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
713 }
714 }
715
716 // Add foreign key columns to SQL, if necessary
717 if (! $addMetaColumns) {
718 continue;
719 }
720
721 // Add foreign key columns of subclasses
722 foreach ($class->subClasses as $subClassName) {
723 $subClass = $this->em->getClassMetadata($subClassName);
724 $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
725
726 foreach ($subClass->associationMappings as $assoc) {
727 // Skip if association is inherited
728 if (isset($assoc->inherited)) {
729 continue;
730 }
731
732 if ($assoc->isToOneOwningSide()) {
733 $targetClass = $this->em->getClassMetadata($assoc->targetEntity);
734
735 foreach ($assoc->joinColumns as $joinColumn) {
736 $columnName = $joinColumn->name;
737 $columnAlias = $this->getSQLColumnAlias($columnName);
738 $columnType = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em);
739
740 $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform);
741 $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
742
743 $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
744 }
745 }
746 }
747 }
748 }
749
750 return $sql . implode(', ', $sqlSelectExpressions);
751 }
752
753 /**
754 * Walks down a FromClause AST node, thereby generating the appropriate SQL.
755 */
756 public function walkFromClause(AST\FromClause $fromClause): string
757 {
758 $identificationVarDecls = $fromClause->identificationVariableDeclarations;
759 $sqlParts = [];
760
761 foreach ($identificationVarDecls as $identificationVariableDecl) {
762 $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
763 }
764
765 return ' FROM ' . implode(', ', $sqlParts);
766 }
767
768 /**
769 * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL.
770 */
771 public function walkIdentificationVariableDeclaration(AST\IdentificationVariableDeclaration $identificationVariableDecl): string
772 {
773 $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
774
775 if ($identificationVariableDecl->indexBy) {
776 $this->walkIndexBy($identificationVariableDecl->indexBy);
777 }
778
779 foreach ($identificationVariableDecl->joins as $join) {
780 $sql .= $this->walkJoin($join);
781 }
782
783 return $sql;
784 }
785
786 /**
787 * Walks down a IndexBy AST node.
788 */
789 public function walkIndexBy(AST\IndexBy $indexBy): void
790 {
791 $pathExpression = $indexBy->singleValuedPathExpression;
792 $alias = $pathExpression->identificationVariable;
793 assert($pathExpression->field !== null);
794
795 switch ($pathExpression->type) {
796 case AST\PathExpression::TYPE_STATE_FIELD:
797 $field = $pathExpression->field;
798 break;
799
800 case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
801 // Just use the foreign key, i.e. u.group_id
802 $fieldName = $pathExpression->field;
803 $class = $this->getMetadataForDqlAlias($alias);
804
805 if (isset($class->associationMappings[$fieldName]->inherited)) {
806 $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]->inherited);
807 }
808
809 $association = $class->associationMappings[$fieldName];
810
811 if (! $association->isOwningSide()) {
812 throw QueryException::associationPathInverseSideNotSupported($pathExpression);
813 }
814
815 assert($association->isToOneOwningSide());
816
817 if (count($association->sourceToTargetKeyColumns) > 1) {
818 throw QueryException::associationPathCompositeKeyNotSupported();
819 }
820
821 $field = reset($association->targetToSourceKeyColumns);
822 break;
823
824 default:
825 throw QueryException::invalidPathExpression($pathExpression);
826 }
827
828 if (isset($this->scalarFields[$alias][$field])) {
829 $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
830
831 return;
832 }
833
834 $this->rsm->addIndexBy($alias, $field);
835 }
836
837 /**
838 * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
839 */
840 public function walkRangeVariableDeclaration(AST\RangeVariableDeclaration $rangeVariableDeclaration): string
841 {
842 return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
843 }
844
845 /**
846 * Generate appropriate SQL for RangeVariableDeclaration AST node
847 */
848 private function generateRangeVariableDeclarationSQL(
849 AST\RangeVariableDeclaration $rangeVariableDeclaration,
850 bool $buildNestedJoins,
851 ): string {
852 $class = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
853 $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
854
855 if ($rangeVariableDeclaration->isRoot) {
856 $this->rootAliases[] = $dqlAlias;
857 }
858
859 $sql = $this->platform->appendLockHint(
860 $this->quoteStrategy->getTableName($class, $this->platform) . ' ' .
861 $this->getSQLTableAlias($class->getTableName(), $dqlAlias),
862 $this->query->getHint(Query::HINT_LOCK_MODE) ?: LockMode::NONE,
863 );
864
865 if (! $class->isInheritanceTypeJoined()) {
866 return $sql;
867 }
868
869 $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins($class, $dqlAlias);
870
871 if (! $buildNestedJoins) {
872 return $sql . $classTableInheritanceJoins;
873 }
874
875 return $classTableInheritanceJoins === '' ? $sql : '(' . $sql . $classTableInheritanceJoins . ')';
876 }
877
878 /**
879 * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
880 *
881 * @psalm-param AST\Join::JOIN_TYPE_* $joinType
882 *
883 * @throws QueryException
884 */
885 public function walkJoinAssociationDeclaration(
886 AST\JoinAssociationDeclaration $joinAssociationDeclaration,
887 int $joinType = AST\Join::JOIN_TYPE_INNER,
888 AST\ConditionalExpression|AST\Phase2OptimizableConditional|null $condExpr = null,
889 ): string {
890 $sql = '';
891
892 $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
893 $joinedDqlAlias = $joinAssociationDeclaration->aliasIdentificationVariable;
894 $indexBy = $joinAssociationDeclaration->indexBy;
895
896 $relation = $this->queryComponents[$joinedDqlAlias]['relation'] ?? null;
897 assert($relation !== null);
898 $targetClass = $this->em->getClassMetadata($relation->targetEntity);
899 $sourceClass = $this->em->getClassMetadata($relation->sourceEntity);
900 $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
901
902 $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
903 $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
904
905 // Ensure we got the owning side, since it has all mapping info
906 $assoc = $this->em->getMetadataFactory()->getOwningSide($relation);
907
908 if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true && (! $this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
909 if ($relation->isToMany()) {
910 throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
911 }
912 }
913
914 $fetchMode = $this->query->getHint('fetchMode')[$assoc->sourceEntity][$assoc->fieldName] ?? $relation->fetch;
915
916 if ($fetchMode === ClassMetadata::FETCH_EAGER && $condExpr !== null) {
917 throw QueryException::eagerFetchJoinWithNotAllowed($assoc->sourceEntity, $assoc->fieldName);
918 }
919
920 // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
921 // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
922 // The owning side is necessary at this point because only it contains the JoinColumn information.
923 switch (true) {
924 case $assoc->isToOne():
925 assert($assoc->isToOneOwningSide());
926 $conditions = [];
927
928 foreach ($assoc->joinColumns as $joinColumn) {
929 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
930 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
931
932 if ($relation->isOwningSide()) {
933 $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
934
935 continue;
936 }
937
938 $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
939 }
940
941 // Apply remaining inheritance restrictions
942 $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
943
944 if ($discrSql) {
945 $conditions[] = $discrSql;
946 }
947
948 // Apply the filters
949 $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
950
951 if ($filterExpr) {
952 $conditions[] = $filterExpr;
953 }
954
955 $targetTableJoin = [
956 'table' => $targetTableName . ' ' . $targetTableAlias,
957 'condition' => implode(' AND ', $conditions),
958 ];
959 break;
960
961 case $assoc->isManyToMany():
962 // Join relation table
963 $joinTable = $assoc->joinTable;
964 $joinTableAlias = $this->getSQLTableAlias($joinTable->name, $joinedDqlAlias);
965 $joinTableName = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform);
966
967 $conditions = [];
968 $relationColumns = $relation->isOwningSide()
969 ? $assoc->joinTable->joinColumns
970 : $assoc->joinTable->inverseJoinColumns;
971
972 foreach ($relationColumns as $joinColumn) {
973 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
974 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
975
976 $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
977 }
978
979 $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
980
981 // Join target table
982 $sql .= $joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER ? ' LEFT JOIN ' : ' INNER JOIN ';
983
984 $conditions = [];
985 $relationColumns = $relation->isOwningSide()
986 ? $assoc->joinTable->inverseJoinColumns
987 : $assoc->joinTable->joinColumns;
988
989 foreach ($relationColumns as $joinColumn) {
990 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
991 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
992
993 $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
994 }
995
996 // Apply remaining inheritance restrictions
997 $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
998
999 if ($discrSql) {
1000 $conditions[] = $discrSql;
1001 }
1002
1003 // Apply the filters
1004 $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1005
1006 if ($filterExpr) {
1007 $conditions[] = $filterExpr;
1008 }
1009
1010 $targetTableJoin = [
1011 'table' => $targetTableName . ' ' . $targetTableAlias,
1012 'condition' => implode(' AND ', $conditions),
1013 ];
1014 break;
1015
1016 default:
1017 throw new BadMethodCallException('Type of association must be one of *_TO_ONE or MANY_TO_MANY');
1018 }
1019
1020 // Handle WITH clause
1021 $withCondition = $condExpr === null ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1022
1023 if ($targetClass->isInheritanceTypeJoined()) {
1024 $ctiJoins = $this->generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
1025 // If we have WITH condition, we need to build nested joins for target class table and cti joins
1026 if ($withCondition && $ctiJoins) {
1027 $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1028 } else {
1029 $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1030 }
1031 } else {
1032 $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1033 }
1034
1035 if ($withCondition) {
1036 $sql .= ' AND ' . $withCondition;
1037 }
1038
1039 // Apply the indexes
1040 if ($indexBy) {
1041 // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
1042 $this->walkIndexBy($indexBy);
1043 } elseif ($relation->isIndexed()) {
1044 $this->rsm->addIndexBy($joinedDqlAlias, $relation->indexBy());
1045 }
1046
1047 return $sql;
1048 }
1049
1050 /**
1051 * Walks down a FunctionNode AST node, thereby generating the appropriate SQL.
1052 */
1053 public function walkFunction(AST\Functions\FunctionNode $function): string
1054 {
1055 return $function->getSql($this);
1056 }
1057
1058 /**
1059 * Walks down an OrderByClause AST node, thereby generating the appropriate SQL.
1060 */
1061 public function walkOrderByClause(AST\OrderByClause $orderByClause): string
1062 {
1063 $orderByItems = array_map($this->walkOrderByItem(...), $orderByClause->orderByItems);
1064
1065 $collectionOrderByItems = $this->generateOrderedCollectionOrderByItems();
1066 if ($collectionOrderByItems !== '') {
1067 $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1068 }
1069
1070 return ' ORDER BY ' . implode(', ', $orderByItems);
1071 }
1072
1073 /**
1074 * Walks down an OrderByItem AST node, thereby generating the appropriate SQL.
1075 */
1076 public function walkOrderByItem(AST\OrderByItem $orderByItem): string
1077 {
1078 $type = strtoupper($orderByItem->type);
1079 $expr = $orderByItem->expression;
1080 $sql = $expr instanceof AST\Node
1081 ? $expr->dispatch($this)
1082 : $this->walkResultVariable($this->queryComponents[$expr]['token']->value);
1083
1084 $this->orderedColumnsMap[$sql] = $type;
1085
1086 if ($expr instanceof AST\Subselect) {
1087 return '(' . $sql . ') ' . $type;
1088 }
1089
1090 return $sql . ' ' . $type;
1091 }
1092
1093 /**
1094 * Walks down a HavingClause AST node, thereby generating the appropriate SQL.
1095 */
1096 public function walkHavingClause(AST\HavingClause $havingClause): string
1097 {
1098 return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1099 }
1100
1101 /**
1102 * Walks down a Join AST node and creates the corresponding SQL.
1103 */
1104 public function walkJoin(AST\Join $join): string
1105 {
1106 $joinType = $join->joinType;
1107 $joinDeclaration = $join->joinAssociationDeclaration;
1108
1109 $sql = $joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER
1110 ? ' LEFT JOIN '
1111 : ' INNER JOIN ';
1112
1113 switch (true) {
1114 case $joinDeclaration instanceof AST\RangeVariableDeclaration:
1115 $class = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1116 $dqlAlias = $joinDeclaration->aliasIdentificationVariable;
1117 $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1118 $conditions = [];
1119
1120 if ($join->conditionalExpression) {
1121 $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1122 }
1123
1124 $isUnconditionalJoin = $conditions === [];
1125 $condExprConjunction = $class->isInheritanceTypeJoined() && $joinType !== AST\Join::JOIN_TYPE_LEFT && $joinType !== AST\Join::JOIN_TYPE_LEFTOUTER && $isUnconditionalJoin
1126 ? ' AND '
1127 : ' ON ';
1128
1129 $sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, ! $isUnconditionalJoin);
1130
1131 // Apply remaining inheritance restrictions
1132 $discrSql = $this->generateDiscriminatorColumnConditionSQL([$dqlAlias]);
1133
1134 if ($discrSql) {
1135 $conditions[] = $discrSql;
1136 }
1137
1138 // Apply the filters
1139 $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1140
1141 if ($filterExpr) {
1142 $conditions[] = $filterExpr;
1143 }
1144
1145 if ($conditions) {
1146 $sql .= $condExprConjunction . implode(' AND ', $conditions);
1147 }
1148
1149 break;
1150
1151 case $joinDeclaration instanceof AST\JoinAssociationDeclaration:
1152 $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1153 break;
1154 }
1155
1156 return $sql;
1157 }
1158
1159 /**
1160 * Walks down a CoalesceExpression AST node and generates the corresponding SQL.
1161 */
1162 public function walkCoalesceExpression(AST\CoalesceExpression $coalesceExpression): string
1163 {
1164 $sql = 'COALESCE(';
1165
1166 $scalarExpressions = [];
1167
1168 foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
1169 $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
1170 }
1171
1172 return $sql . implode(', ', $scalarExpressions) . ')';
1173 }
1174
1175 /**
1176 * Walks down a NullIfExpression AST node and generates the corresponding SQL.
1177 */
1178 public function walkNullIfExpression(AST\NullIfExpression $nullIfExpression): string
1179 {
1180 $firstExpression = is_string($nullIfExpression->firstExpression)
1181 ? $this->conn->quote($nullIfExpression->firstExpression)
1182 : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
1183
1184 $secondExpression = is_string($nullIfExpression->secondExpression)
1185 ? $this->conn->quote($nullIfExpression->secondExpression)
1186 : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
1187
1188 return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
1189 }
1190
1191 /**
1192 * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
1193 */
1194 public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression): string
1195 {
1196 $sql = 'CASE';
1197
1198 foreach ($generalCaseExpression->whenClauses as $whenClause) {
1199 $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1200 $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1201 }
1202
1203 $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1204
1205 return $sql;
1206 }
1207
1208 /**
1209 * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL.
1210 */
1211 public function walkSimpleCaseExpression(AST\SimpleCaseExpression $simpleCaseExpression): string
1212 {
1213 $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
1214
1215 foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
1216 $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
1217 $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
1218 }
1219
1220 $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
1221
1222 return $sql;
1223 }
1224
1225 /**
1226 * Walks down a SelectExpression AST node and generates the corresponding SQL.
1227 */
1228 public function walkSelectExpression(AST\SelectExpression $selectExpression): string
1229 {
1230 $sql = '';
1231 $expr = $selectExpression->expression;
1232 $hidden = $selectExpression->hiddenAliasResultVariable;
1233
1234 switch (true) {
1235 case $expr instanceof AST\PathExpression:
1236 if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1237 throw QueryException::invalidPathExpression($expr);
1238 }
1239
1240 assert($expr->field !== null);
1241 $fieldName = $expr->field;
1242 $dqlAlias = $expr->identificationVariable;
1243 $class = $this->getMetadataForDqlAlias($dqlAlias);
1244
1245 $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1246 $tableName = $class->isInheritanceTypeJoined()
1247 ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
1248 : $class->getTableName();
1249
1250 $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1251 $fieldMapping = $class->fieldMappings[$fieldName];
1252 $columnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1253 $columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName);
1254 $col = $sqlTableAlias . '.' . $columnName;
1255
1256 $type = Type::getType($fieldMapping->type);
1257 $col = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform());
1258
1259 $sql .= $col . ' AS ' . $columnAlias;
1260
1261 $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1262
1263 if (! $hidden) {
1264 $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping->type);
1265 $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1266
1267 if (! empty($fieldMapping->enumType)) {
1268 $this->rsm->addEnumResult($columnAlias, $fieldMapping->enumType);
1269 }
1270 }
1271
1272 break;
1273
1274 case $expr instanceof AST\AggregateExpression:
1275 case $expr instanceof AST\Functions\FunctionNode:
1276 case $expr instanceof AST\SimpleArithmeticExpression:
1277 case $expr instanceof AST\ArithmeticTerm:
1278 case $expr instanceof AST\ArithmeticFactor:
1279 case $expr instanceof AST\ParenthesisExpression:
1280 case $expr instanceof AST\Literal:
1281 case $expr instanceof AST\NullIfExpression:
1282 case $expr instanceof AST\CoalesceExpression:
1283 case $expr instanceof AST\GeneralCaseExpression:
1284 case $expr instanceof AST\SimpleCaseExpression:
1285 $columnAlias = $this->getSQLColumnAlias('sclr');
1286 $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1287
1288 $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1289
1290 $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1291
1292 if ($hidden) {
1293 break;
1294 }
1295
1296 if (! $expr instanceof Query\AST\TypedExpression) {
1297 // Conceptually we could resolve field type here by traverse through AST to retrieve field type,
1298 // but this is not a feasible solution; assume 'string'.
1299 $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1300
1301 break;
1302 }
1303
1304 $this->rsm->addScalarResult($columnAlias, $resultAlias, Type::getTypeRegistry()->lookupName($expr->getReturnType()));
1305
1306 break;
1307
1308 case $expr instanceof AST\Subselect:
1309 $columnAlias = $this->getSQLColumnAlias('sclr');
1310 $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1311
1312 $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1313
1314 $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1315
1316 if (! $hidden) {
1317 // We cannot resolve field type here; assume 'string'.
1318 $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1319 }
1320
1321 break;
1322
1323 case $expr instanceof AST\NewObjectExpression:
1324 $sql .= $this->walkNewObject($expr, $selectExpression->fieldIdentificationVariable);
1325 break;
1326
1327 default:
1328 $dqlAlias = $expr;
1329 $class = $this->getMetadataForDqlAlias($dqlAlias);
1330 $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1331
1332 if (! isset($this->selectedClasses[$dqlAlias])) {
1333 $this->selectedClasses[$dqlAlias] = [
1334 'class' => $class,
1335 'dqlAlias' => $dqlAlias,
1336 'resultAlias' => $resultAlias,
1337 ];
1338 }
1339
1340 $sqlParts = [];
1341
1342 // Select all fields from the queried class
1343 foreach ($class->fieldMappings as $fieldName => $mapping) {
1344 $tableName = isset($mapping->inherited)
1345 ? $this->em->getClassMetadata($mapping->inherited)->getTableName()
1346 : $class->getTableName();
1347
1348 $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1349 $columnAlias = $this->getSQLColumnAlias($mapping->columnName);
1350 $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1351
1352 $col = $sqlTableAlias . '.' . $quotedColumnName;
1353
1354 $type = Type::getType($mapping->type);
1355 $col = $type->convertToPHPValueSQL($col, $this->platform);
1356
1357 $sqlParts[] = $col . ' AS ' . $columnAlias;
1358
1359 $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1360
1361 $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
1362
1363 if (! empty($mapping->enumType)) {
1364 $this->rsm->addEnumResult($columnAlias, $mapping->enumType);
1365 }
1366 }
1367
1368 // Add any additional fields of subclasses (excluding inherited fields)
1369 // 1) on Single Table Inheritance: always, since its marginal overhead
1370 // 2) on Class Table Inheritance
1371 foreach ($class->subClasses as $subClassName) {
1372 $subClass = $this->em->getClassMetadata($subClassName);
1373 $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
1374
1375 foreach ($subClass->fieldMappings as $fieldName => $mapping) {
1376 if (isset($mapping->inherited)) {
1377 continue;
1378 }
1379
1380 $columnAlias = $this->getSQLColumnAlias($mapping->columnName);
1381 $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform);
1382
1383 $col = $sqlTableAlias . '.' . $quotedColumnName;
1384
1385 $type = Type::getType($mapping->type);
1386 $col = $type->convertToPHPValueSQL($col, $this->platform);
1387
1388 $sqlParts[] = $col . ' AS ' . $columnAlias;
1389
1390 $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1391
1392 $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1393 }
1394 }
1395
1396 $sql .= implode(', ', $sqlParts);
1397 }
1398
1399 return $sql;
1400 }
1401
1402 public function walkQuantifiedExpression(AST\QuantifiedExpression $qExpr): string
1403 {
1404 return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
1405 }
1406
1407 /**
1408 * Walks down a Subselect AST node, thereby generating the appropriate SQL.
1409 */
1410 public function walkSubselect(AST\Subselect $subselect): string
1411 {
1412 $useAliasesBefore = $this->useSqlTableAliases;
1413 $rootAliasesBefore = $this->rootAliases;
1414
1415 $this->rootAliases = []; // reset the rootAliases for the subselect
1416 $this->useSqlTableAliases = true;
1417
1418 $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1419 $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1420 $sql .= $this->walkWhereClause($subselect->whereClause);
1421
1422 $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1423 $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1424 $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1425
1426 $this->rootAliases = $rootAliasesBefore; // put the main aliases back
1427 $this->useSqlTableAliases = $useAliasesBefore;
1428
1429 return $sql;
1430 }
1431
1432 /**
1433 * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL.
1434 */
1435 public function walkSubselectFromClause(AST\SubselectFromClause $subselectFromClause): string
1436 {
1437 $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1438 $sqlParts = [];
1439
1440 foreach ($identificationVarDecls as $subselectIdVarDecl) {
1441 $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
1442 }
1443
1444 return ' FROM ' . implode(', ', $sqlParts);
1445 }
1446
1447 /**
1448 * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL.
1449 */
1450 public function walkSimpleSelectClause(AST\SimpleSelectClause $simpleSelectClause): string
1451 {
1452 return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1453 . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
1454 }
1455
1456 public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression): string
1457 {
1458 return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this));
1459 }
1460
1461 public function walkNewObject(AST\NewObjectExpression $newObjectExpression, string|null $newObjectResultAlias = null): string
1462 {
1463 $sqlSelectExpressions = [];
1464 $objIndex = $newObjectResultAlias ?: $this->newObjectCounter++;
1465
1466 foreach ($newObjectExpression->args as $argIndex => $e) {
1467 $resultAlias = $this->scalarResultCounter++;
1468 $columnAlias = $this->getSQLColumnAlias('sclr');
1469 $fieldType = 'string';
1470
1471 switch (true) {
1472 case $e instanceof AST\NewObjectExpression:
1473 $sqlSelectExpressions[] = $e->dispatch($this);
1474 break;
1475
1476 case $e instanceof AST\Subselect:
1477 $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
1478 break;
1479
1480 case $e instanceof AST\PathExpression:
1481 assert($e->field !== null);
1482 $dqlAlias = $e->identificationVariable;
1483 $class = $this->getMetadataForDqlAlias($dqlAlias);
1484 $fieldName = $e->field;
1485 $fieldMapping = $class->fieldMappings[$fieldName];
1486 $fieldType = $fieldMapping->type;
1487 $col = trim($e->dispatch($this));
1488
1489 $type = Type::getType($fieldType);
1490 $col = $type->convertToPHPValueSQL($col, $this->platform);
1491
1492 $sqlSelectExpressions[] = $col . ' AS ' . $columnAlias;
1493
1494 if (! empty($fieldMapping->enumType)) {
1495 $this->rsm->addEnumResult($columnAlias, $fieldMapping->enumType);
1496 }
1497
1498 break;
1499
1500 case $e instanceof AST\Literal:
1501 switch ($e->type) {
1502 case AST\Literal::BOOLEAN:
1503 $fieldType = 'boolean';
1504 break;
1505
1506 case AST\Literal::NUMERIC:
1507 $fieldType = is_float($e->value) ? 'float' : 'integer';
1508 break;
1509 }
1510
1511 $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1512 break;
1513
1514 default:
1515 $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1516 break;
1517 }
1518
1519 $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1520 $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1521
1522 $this->rsm->newObjectMappings[$columnAlias] = [
1523 'className' => $newObjectExpression->className,
1524 'objIndex' => $objIndex,
1525 'argIndex' => $argIndex,
1526 ];
1527 }
1528
1529 return implode(', ', $sqlSelectExpressions);
1530 }
1531
1532 /**
1533 * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL.
1534 */
1535 public function walkSimpleSelectExpression(AST\SimpleSelectExpression $simpleSelectExpression): string
1536 {
1537 $expr = $simpleSelectExpression->expression;
1538 $sql = ' ';
1539
1540 switch (true) {
1541 case $expr instanceof AST\PathExpression:
1542 $sql .= $this->walkPathExpression($expr);
1543 break;
1544
1545 case $expr instanceof AST\Subselect:
1546 $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1547
1548 $columnAlias = 'sclr' . $this->aliasCounter++;
1549 $this->scalarResultAliasMap[$alias] = $columnAlias;
1550
1551 $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1552 break;
1553
1554 case $expr instanceof AST\Functions\FunctionNode:
1555 case $expr instanceof AST\SimpleArithmeticExpression:
1556 case $expr instanceof AST\ArithmeticTerm:
1557 case $expr instanceof AST\ArithmeticFactor:
1558 case $expr instanceof AST\Literal:
1559 case $expr instanceof AST\NullIfExpression:
1560 case $expr instanceof AST\CoalesceExpression:
1561 case $expr instanceof AST\GeneralCaseExpression:
1562 case $expr instanceof AST\SimpleCaseExpression:
1563 $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1564
1565 $columnAlias = $this->getSQLColumnAlias('sclr');
1566 $this->scalarResultAliasMap[$alias] = $columnAlias;
1567
1568 $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1569 break;
1570
1571 case $expr instanceof AST\ParenthesisExpression:
1572 $sql .= $this->walkParenthesisExpression($expr);
1573 break;
1574
1575 default: // IdentificationVariable
1576 $sql .= $this->walkEntityIdentificationVariable($expr);
1577 break;
1578 }
1579
1580 return $sql;
1581 }
1582
1583 /**
1584 * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL.
1585 */
1586 public function walkAggregateExpression(AST\AggregateExpression $aggExpression): string
1587 {
1588 return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1589 . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
1590 }
1591
1592 /**
1593 * Walks down a GroupByClause AST node, thereby generating the appropriate SQL.
1594 */
1595 public function walkGroupByClause(AST\GroupByClause $groupByClause): string
1596 {
1597 $sqlParts = [];
1598
1599 foreach ($groupByClause->groupByItems as $groupByItem) {
1600 $sqlParts[] = $this->walkGroupByItem($groupByItem);
1601 }
1602
1603 return ' GROUP BY ' . implode(', ', $sqlParts);
1604 }
1605
1606 /**
1607 * Walks down a GroupByItem AST node, thereby generating the appropriate SQL.
1608 */
1609 public function walkGroupByItem(AST\PathExpression|string $groupByItem): string
1610 {
1611 // StateFieldPathExpression
1612 if (! is_string($groupByItem)) {
1613 return $this->walkPathExpression($groupByItem);
1614 }
1615
1616 // ResultVariable
1617 if (isset($this->queryComponents[$groupByItem]['resultVariable'])) {
1618 $resultVariable = $this->queryComponents[$groupByItem]['resultVariable'];
1619
1620 if ($resultVariable instanceof AST\PathExpression) {
1621 return $this->walkPathExpression($resultVariable);
1622 }
1623
1624 if ($resultVariable instanceof AST\Node && isset($resultVariable->pathExpression)) {
1625 return $this->walkPathExpression($resultVariable->pathExpression);
1626 }
1627
1628 return $this->walkResultVariable($groupByItem);
1629 }
1630
1631 // IdentificationVariable
1632 $sqlParts = [];
1633
1634 foreach ($this->getMetadataForDqlAlias($groupByItem)->fieldNames as $field) {
1635 $item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
1636 $item->type = AST\PathExpression::TYPE_STATE_FIELD;
1637
1638 $sqlParts[] = $this->walkPathExpression($item);
1639 }
1640
1641 foreach ($this->getMetadataForDqlAlias($groupByItem)->associationMappings as $mapping) {
1642 if ($mapping->isToOneOwningSide()) {
1643 $item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping->fieldName);
1644 $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
1645
1646 $sqlParts[] = $this->walkPathExpression($item);
1647 }
1648 }
1649
1650 return implode(', ', $sqlParts);
1651 }
1652
1653 /**
1654 * Walks down a DeleteClause AST node, thereby generating the appropriate SQL.
1655 */
1656 public function walkDeleteClause(AST\DeleteClause $deleteClause): string
1657 {
1658 $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1659 $tableName = $class->getTableName();
1660 $sql = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform);
1661
1662 $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1663 $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1664
1665 return $sql;
1666 }
1667
1668 /**
1669 * Walks down an UpdateClause AST node, thereby generating the appropriate SQL.
1670 */
1671 public function walkUpdateClause(AST\UpdateClause $updateClause): string
1672 {
1673 $class = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1674 $tableName = $class->getTableName();
1675 $sql = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform);
1676
1677 $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1678 $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1679
1680 return $sql . ' SET ' . implode(', ', array_map($this->walkUpdateItem(...), $updateClause->updateItems));
1681 }
1682
1683 /**
1684 * Walks down an UpdateItem AST node, thereby generating the appropriate SQL.
1685 */
1686 public function walkUpdateItem(AST\UpdateItem $updateItem): string
1687 {
1688 $useTableAliasesBefore = $this->useSqlTableAliases;
1689 $this->useSqlTableAliases = false;
1690
1691 $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1692 $newValue = $updateItem->newValue;
1693
1694 $sql .= match (true) {
1695 $newValue instanceof AST\Node => $newValue->dispatch($this),
1696 $newValue === null => 'NULL',
1697 };
1698
1699 $this->useSqlTableAliases = $useTableAliasesBefore;
1700
1701 return $sql;
1702 }
1703
1704 /**
1705 * Walks down a WhereClause AST node, thereby generating the appropriate SQL.
1706 *
1707 * WhereClause or not, the appropriate discriminator sql is added.
1708 */
1709 public function walkWhereClause(AST\WhereClause|null $whereClause): string
1710 {
1711 $condSql = $whereClause !== null ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1712 $discrSql = $this->generateDiscriminatorColumnConditionSQL($this->rootAliases);
1713
1714 if ($this->em->hasFilters()) {
1715 $filterClauses = [];
1716 foreach ($this->rootAliases as $dqlAlias) {
1717 $class = $this->getMetadataForDqlAlias($dqlAlias);
1718 $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1719
1720 $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1721 if ($filterExpr) {
1722 $filterClauses[] = $filterExpr;
1723 }
1724 }
1725
1726 if (count($filterClauses)) {
1727 if ($condSql) {
1728 $condSql = '(' . $condSql . ') AND ';
1729 }
1730
1731 $condSql .= implode(' AND ', $filterClauses);
1732 }
1733 }
1734
1735 if ($condSql) {
1736 return ' WHERE ' . (! $discrSql ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1737 }
1738
1739 if ($discrSql) {
1740 return ' WHERE ' . $discrSql;
1741 }
1742
1743 return '';
1744 }
1745
1746 /**
1747 * Walk down a ConditionalExpression AST node, thereby generating the appropriate SQL.
1748 */
1749 public function walkConditionalExpression(
1750 AST\ConditionalExpression|AST\Phase2OptimizableConditional $condExpr,
1751 ): string {
1752 // Phase 2 AST optimization: Skip processing of ConditionalExpression
1753 // if only one ConditionalTerm is defined
1754 if (! ($condExpr instanceof AST\ConditionalExpression)) {
1755 return $this->walkConditionalTerm($condExpr);
1756 }
1757
1758 return implode(' OR ', array_map($this->walkConditionalTerm(...), $condExpr->conditionalTerms));
1759 }
1760
1761 /**
1762 * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL.
1763 */
1764 public function walkConditionalTerm(
1765 AST\ConditionalTerm|AST\ConditionalPrimary|AST\ConditionalFactor $condTerm,
1766 ): string {
1767 // Phase 2 AST optimization: Skip processing of ConditionalTerm
1768 // if only one ConditionalFactor is defined
1769 if (! ($condTerm instanceof AST\ConditionalTerm)) {
1770 return $this->walkConditionalFactor($condTerm);
1771 }
1772
1773 return implode(' AND ', array_map($this->walkConditionalFactor(...), $condTerm->conditionalFactors));
1774 }
1775
1776 /**
1777 * Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL.
1778 */
1779 public function walkConditionalFactor(
1780 AST\ConditionalFactor|AST\ConditionalPrimary $factor,
1781 ): string {
1782 // Phase 2 AST optimization: Skip processing of ConditionalFactor
1783 // if only one ConditionalPrimary is defined
1784 return ! ($factor instanceof AST\ConditionalFactor)
1785 ? $this->walkConditionalPrimary($factor)
1786 : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1787 }
1788
1789 /**
1790 * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL.
1791 */
1792 public function walkConditionalPrimary(AST\ConditionalPrimary $primary): string
1793 {
1794 if ($primary->isSimpleConditionalExpression()) {
1795 return $primary->simpleConditionalExpression->dispatch($this);
1796 }
1797
1798 if ($primary->isConditionalExpression()) {
1799 $condExpr = $primary->conditionalExpression;
1800
1801 return '(' . $this->walkConditionalExpression($condExpr) . ')';
1802 }
1803
1804 throw new LogicException('Unexpected state of ConditionalPrimary node.');
1805 }
1806
1807 /**
1808 * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL.
1809 */
1810 public function walkExistsExpression(AST\ExistsExpression $existsExpr): string
1811 {
1812 $sql = $existsExpr->not ? 'NOT ' : '';
1813
1814 $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
1815
1816 return $sql;
1817 }
1818
1819 /**
1820 * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL.
1821 */
1822 public function walkCollectionMemberExpression(AST\CollectionMemberExpression $collMemberExpr): string
1823 {
1824 $sql = $collMemberExpr->not ? 'NOT ' : '';
1825 $sql .= 'EXISTS (SELECT 1 FROM ';
1826
1827 $entityExpr = $collMemberExpr->entityExpression;
1828 $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1829 assert($collPathExpr->field !== null);
1830
1831 $fieldName = $collPathExpr->field;
1832 $dqlAlias = $collPathExpr->identificationVariable;
1833
1834 $class = $this->getMetadataForDqlAlias($dqlAlias);
1835
1836 switch (true) {
1837 // InputParameter
1838 case $entityExpr instanceof AST\InputParameter:
1839 $dqlParamKey = $entityExpr->name;
1840 $entitySql = '?';
1841 break;
1842
1843 // SingleValuedAssociationPathExpression | IdentificationVariable
1844 case $entityExpr instanceof AST\PathExpression:
1845 $entitySql = $this->walkPathExpression($entityExpr);
1846 break;
1847
1848 default:
1849 throw new BadMethodCallException('Not implemented');
1850 }
1851
1852 $assoc = $class->associationMappings[$fieldName];
1853
1854 if ($assoc->isOneToMany()) {
1855 $targetClass = $this->em->getClassMetadata($assoc->targetEntity);
1856 $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1857 $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1858
1859 $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE ';
1860
1861 $owningAssoc = $targetClass->associationMappings[$assoc->mappedBy];
1862 assert($owningAssoc->isManyToOne());
1863 $sqlParts = [];
1864
1865 foreach ($owningAssoc->targetToSourceKeyColumns as $targetColumn => $sourceColumn) {
1866 $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform);
1867
1868 $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
1869 }
1870
1871 foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1872 if (isset($dqlParamKey)) {
1873 $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1874 }
1875
1876 $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql;
1877 }
1878
1879 $sql .= implode(' AND ', $sqlParts);
1880 } else { // many-to-many
1881 $targetClass = $this->em->getClassMetadata($assoc->targetEntity);
1882
1883 $owningAssoc = $this->em->getMetadataFactory()->getOwningSide($assoc);
1884 assert($owningAssoc->isManyToManyOwningSide());
1885 $joinTable = $owningAssoc->joinTable;
1886
1887 // SQL table aliases
1888 $joinTableAlias = $this->getSQLTableAlias($joinTable->name);
1889 $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1890
1891 $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias . ' WHERE ';
1892
1893 $joinColumns = $assoc->isOwningSide() ? $joinTable->joinColumns : $joinTable->inverseJoinColumns;
1894 $sqlParts = [];
1895
1896 foreach ($joinColumns as $joinColumn) {
1897 $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn->referencedColumnName], $class, $this->platform);
1898
1899 $sqlParts[] = $joinTableAlias . '.' . $joinColumn->name . ' = ' . $sourceTableAlias . '.' . $targetColumn;
1900 }
1901
1902 $joinColumns = $assoc->isOwningSide() ? $joinTable->inverseJoinColumns : $joinTable->joinColumns;
1903
1904 foreach ($joinColumns as $joinColumn) {
1905 if (isset($dqlParamKey)) {
1906 $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1907 }
1908
1909 $sqlParts[] = $joinTableAlias . '.' . $joinColumn->name . ' IN (' . $entitySql . ')';
1910 }
1911
1912 $sql .= implode(' AND ', $sqlParts);
1913 }
1914
1915 return $sql . ')';
1916 }
1917
1918 /**
1919 * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL.
1920 */
1921 public function walkEmptyCollectionComparisonExpression(AST\EmptyCollectionComparisonExpression $emptyCollCompExpr): string
1922 {
1923 $sizeFunc = new AST\Functions\SizeFunction('size');
1924 $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
1925
1926 return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
1927 }
1928
1929 /**
1930 * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL.
1931 */
1932 public function walkNullComparisonExpression(AST\NullComparisonExpression $nullCompExpr): string
1933 {
1934 $expression = $nullCompExpr->expression;
1935 $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
1936
1937 // Handle ResultVariable
1938 if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) {
1939 return $this->walkResultVariable($expression) . $comparison;
1940 }
1941
1942 // Handle InputParameter mapping inclusion to ParserResult
1943 if ($expression instanceof AST\InputParameter) {
1944 return $this->walkInputParameter($expression) . $comparison;
1945 }
1946
1947 assert(! is_string($expression));
1948
1949 return $expression->dispatch($this) . $comparison;
1950 }
1951
1952 /**
1953 * Walks down an InExpression AST node, thereby generating the appropriate SQL.
1954 */
1955 public function walkInListExpression(AST\InListExpression $inExpr): string
1956 {
1957 return $this->walkArithmeticExpression($inExpr->expression)
1958 . ($inExpr->not ? ' NOT' : '') . ' IN ('
1959 . implode(', ', array_map($this->walkInParameter(...), $inExpr->literals))
1960 . ')';
1961 }
1962
1963 /**
1964 * Walks down an InExpression AST node, thereby generating the appropriate SQL.
1965 */
1966 public function walkInSubselectExpression(AST\InSubselectExpression $inExpr): string
1967 {
1968 return $this->walkArithmeticExpression($inExpr->expression)
1969 . ($inExpr->not ? ' NOT' : '') . ' IN ('
1970 . $this->walkSubselect($inExpr->subselect)
1971 . ')';
1972 }
1973
1974 /**
1975 * Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL.
1976 *
1977 * @throws QueryException
1978 */
1979 public function walkInstanceOfExpression(AST\InstanceOfExpression $instanceOfExpr): string
1980 {
1981 $sql = '';
1982
1983 $dqlAlias = $instanceOfExpr->identificationVariable;
1984 $discrClass = $class = $this->getMetadataForDqlAlias($dqlAlias);
1985
1986 if ($class->discriminatorColumn) {
1987 $discrClass = $this->em->getClassMetadata($class->rootEntityName);
1988 }
1989
1990 if ($this->useSqlTableAliases) {
1991 $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.';
1992 }
1993
1994 $sql .= $class->getDiscriminatorColumn()->name . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
1995 $sql .= $this->getChildDiscriminatorsFromClassMetadata($discrClass, $instanceOfExpr);
1996
1997 return $sql;
1998 }
1999
2000 public function walkInParameter(mixed $inParam): string
2001 {
2002 return $inParam instanceof AST\InputParameter
2003 ? $this->walkInputParameter($inParam)
2004 : $this->walkArithmeticExpression($inParam);
2005 }
2006
2007 /**
2008 * Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
2009 */
2010 public function walkLiteral(AST\Literal $literal): string
2011 {
2012 return match ($literal->type) {
2013 AST\Literal::STRING => $this->conn->quote($literal->value),
2014 AST\Literal::BOOLEAN => (string) $this->conn->getDatabasePlatform()->convertBooleans(strtolower($literal->value) === 'true'),
2015 AST\Literal::NUMERIC => (string) $literal->value,
2016 default => throw QueryException::invalidLiteral($literal),
2017 };
2018 }
2019
2020 /**
2021 * Walks down a BetweenExpression AST node, thereby generating the appropriate SQL.
2022 */
2023 public function walkBetweenExpression(AST\BetweenExpression $betweenExpr): string
2024 {
2025 $sql = $this->walkArithmeticExpression($betweenExpr->expression);
2026
2027 if ($betweenExpr->not) {
2028 $sql .= ' NOT';
2029 }
2030
2031 $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
2032 . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2033
2034 return $sql;
2035 }
2036
2037 /**
2038 * Walks down a LikeExpression AST node, thereby generating the appropriate SQL.
2039 */
2040 public function walkLikeExpression(AST\LikeExpression $likeExpr): string
2041 {
2042 $stringExpr = $likeExpr->stringExpression;
2043 if (is_string($stringExpr)) {
2044 if (! isset($this->queryComponents[$stringExpr]['resultVariable'])) {
2045 throw new LogicException(sprintf('No result variable found for string expression "%s".', $stringExpr));
2046 }
2047
2048 $leftExpr = $this->walkResultVariable($stringExpr);
2049 } else {
2050 $leftExpr = $stringExpr->dispatch($this);
2051 }
2052
2053 $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2054
2055 if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2056 $sql .= $this->walkInputParameter($likeExpr->stringPattern);
2057 } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
2058 $sql .= $this->walkFunction($likeExpr->stringPattern);
2059 } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
2060 $sql .= $this->walkPathExpression($likeExpr->stringPattern);
2061 } else {
2062 $sql .= $this->walkLiteral($likeExpr->stringPattern);
2063 }
2064
2065 if ($likeExpr->escapeChar) {
2066 $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2067 }
2068
2069 return $sql;
2070 }
2071
2072 /**
2073 * Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL.
2074 */
2075 public function walkStateFieldPathExpression(AST\PathExpression $stateFieldPathExpression): string
2076 {
2077 return $this->walkPathExpression($stateFieldPathExpression);
2078 }
2079
2080 /**
2081 * Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL.
2082 */
2083 public function walkComparisonExpression(AST\ComparisonExpression $compExpr): string
2084 {
2085 $leftExpr = $compExpr->leftExpression;
2086 $rightExpr = $compExpr->rightExpression;
2087 $sql = '';
2088
2089 $sql .= $leftExpr instanceof AST\Node
2090 ? $leftExpr->dispatch($this)
2091 : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2092
2093 $sql .= ' ' . $compExpr->operator . ' ';
2094
2095 $sql .= $rightExpr instanceof AST\Node
2096 ? $rightExpr->dispatch($this)
2097 : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2098
2099 return $sql;
2100 }
2101
2102 /**
2103 * Walks down an InputParameter AST node, thereby generating the appropriate SQL.
2104 */
2105 public function walkInputParameter(AST\InputParameter $inputParam): string
2106 {
2107 $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2108
2109 $parameter = $this->query->getParameter($inputParam->name);
2110
2111 if ($parameter) {
2112 $type = $parameter->getType();
2113 if (is_string($type) && Type::hasType($type)) {
2114 return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2115 }
2116 }
2117
2118 return '?';
2119 }
2120
2121 /**
2122 * Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL.
2123 */
2124 public function walkArithmeticExpression(AST\ArithmeticExpression $arithmeticExpr): string
2125 {
2126 return $arithmeticExpr->isSimpleArithmeticExpression()
2127 ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression)
2128 : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')';
2129 }
2130
2131 /**
2132 * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL.
2133 */
2134 public function walkSimpleArithmeticExpression(AST\Node|string $simpleArithmeticExpr): string
2135 {
2136 if (! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2137 return $this->walkArithmeticTerm($simpleArithmeticExpr);
2138 }
2139
2140 return implode(' ', array_map($this->walkArithmeticTerm(...), $simpleArithmeticExpr->arithmeticTerms));
2141 }
2142
2143 /**
2144 * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL.
2145 */
2146 public function walkArithmeticTerm(AST\Node|string $term): string
2147 {
2148 if (is_string($term)) {
2149 return isset($this->queryComponents[$term])
2150 ? $this->walkResultVariable($this->queryComponents[$term]['token']->value)
2151 : $term;
2152 }
2153
2154 // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2155 // if only one ArithmeticFactor is defined
2156 if (! ($term instanceof AST\ArithmeticTerm)) {
2157 return $this->walkArithmeticFactor($term);
2158 }
2159
2160 return implode(' ', array_map($this->walkArithmeticFactor(...), $term->arithmeticFactors));
2161 }
2162
2163 /**
2164 * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL.
2165 */
2166 public function walkArithmeticFactor(AST\Node|string $factor): string
2167 {
2168 if (is_string($factor)) {
2169 return isset($this->queryComponents[$factor])
2170 ? $this->walkResultVariable($this->queryComponents[$factor]['token']->value)
2171 : $factor;
2172 }
2173
2174 // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2175 // if only one ArithmeticPrimary is defined
2176 if (! ($factor instanceof AST\ArithmeticFactor)) {
2177 return $this->walkArithmeticPrimary($factor);
2178 }
2179
2180 $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2181
2182 return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2183 }
2184
2185 /**
2186 * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2187 */
2188 public function walkArithmeticPrimary(AST\Node|string $primary): string
2189 {
2190 if ($primary instanceof AST\SimpleArithmeticExpression) {
2191 return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2192 }
2193
2194 if ($primary instanceof AST\Node) {
2195 return $primary->dispatch($this);
2196 }
2197
2198 return $this->walkEntityIdentificationVariable($primary);
2199 }
2200
2201 /**
2202 * Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL.
2203 */
2204 public function walkStringPrimary(AST\Node|string $stringPrimary): string
2205 {
2206 return is_string($stringPrimary)
2207 ? $this->conn->quote($stringPrimary)
2208 : $stringPrimary->dispatch($this);
2209 }
2210
2211 /**
2212 * Walks down a ResultVariable that represents an AST node, thereby generating the appropriate SQL.
2213 */
2214 public function walkResultVariable(string $resultVariable): string
2215 {
2216 if (! isset($this->scalarResultAliasMap[$resultVariable])) {
2217 throw new InvalidArgumentException(sprintf('Unknown result variable: %s', $resultVariable));
2218 }
2219
2220 $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2221
2222 if (is_array($resultAlias)) {
2223 return implode(', ', $resultAlias);
2224 }
2225
2226 return $resultAlias;
2227 }
2228
2229 /**
2230 * @return string The list in parentheses of valid child discriminators from the given class
2231 *
2232 * @throws QueryException
2233 */
2234 private function getChildDiscriminatorsFromClassMetadata(
2235 ClassMetadata $rootClass,
2236 AST\InstanceOfExpression $instanceOfExpr,
2237 ): string {
2238 $sqlParameterList = [];
2239 $discriminators = [];
2240 foreach ($instanceOfExpr->value as $parameter) {
2241 if ($parameter instanceof AST\InputParameter) {
2242 $this->rsm->discriminatorParameters[$parameter->name] = $parameter->name;
2243 $sqlParameterList[] = $this->walkInParameter($parameter);
2244 continue;
2245 }
2246
2247 $metadata = $this->em->getClassMetadata($parameter);
2248
2249 if ($metadata->getName() !== $rootClass->name && ! $metadata->getReflectionClass()->isSubclassOf($rootClass->name)) {
2250 throw QueryException::instanceOfUnrelatedClass($parameter, $rootClass->name);
2251 }
2252
2253 $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($metadata, $this->em);
2254 }
2255
2256 foreach (array_keys($discriminators) as $discriminatorValue) {
2257 $sqlParameterList[] = $rootClass->getDiscriminatorColumn()->type === 'integer' && is_int($discriminatorValue)
2258 ? $discriminatorValue
2259 : $this->conn->quote((string) $discriminatorValue);
2260 }
2261
2262 return '(' . implode(', ', $sqlParameterList) . ')';
2263 }
2264}
diff --git a/vendor/doctrine/orm/src/Query/TokenType.php b/vendor/doctrine/orm/src/Query/TokenType.php
new file mode 100644
index 0000000..e745e4a
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/TokenType.php
@@ -0,0 +1,91 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7enum TokenType: int
8{
9 // All tokens that are not valid identifiers must be < 100
10 case T_NONE = 1;
11 case T_INTEGER = 2;
12 case T_STRING = 3;
13 case T_INPUT_PARAMETER = 4;
14 case T_FLOAT = 5;
15 case T_CLOSE_PARENTHESIS = 6;
16 case T_OPEN_PARENTHESIS = 7;
17 case T_COMMA = 8;
18 case T_DIVIDE = 9;
19 case T_DOT = 10;
20 case T_EQUALS = 11;
21 case T_GREATER_THAN = 12;
22 case T_LOWER_THAN = 13;
23 case T_MINUS = 14;
24 case T_MULTIPLY = 15;
25 case T_NEGATE = 16;
26 case T_PLUS = 17;
27 case T_OPEN_CURLY_BRACE = 18;
28 case T_CLOSE_CURLY_BRACE = 19;
29
30 // All tokens that are identifiers or keywords that could be considered as identifiers should be >= 100
31 case T_FULLY_QUALIFIED_NAME = 101;
32 case T_IDENTIFIER = 102;
33
34 // All keyword tokens should be >= 200
35 case T_ALL = 200;
36 case T_AND = 201;
37 case T_ANY = 202;
38 case T_AS = 203;
39 case T_ASC = 204;
40 case T_AVG = 205;
41 case T_BETWEEN = 206;
42 case T_BOTH = 207;
43 case T_BY = 208;
44 case T_CASE = 209;
45 case T_COALESCE = 210;
46 case T_COUNT = 211;
47 case T_DELETE = 212;
48 case T_DESC = 213;
49 case T_DISTINCT = 214;
50 case T_ELSE = 215;
51 case T_EMPTY = 216;
52 case T_END = 217;
53 case T_ESCAPE = 218;
54 case T_EXISTS = 219;
55 case T_FALSE = 220;
56 case T_FROM = 221;
57 case T_GROUP = 222;
58 case T_HAVING = 223;
59 case T_HIDDEN = 224;
60 case T_IN = 225;
61 case T_INDEX = 226;
62 case T_INNER = 227;
63 case T_INSTANCE = 228;
64 case T_IS = 229;
65 case T_JOIN = 230;
66 case T_LEADING = 231;
67 case T_LEFT = 232;
68 case T_LIKE = 233;
69 case T_MAX = 234;
70 case T_MEMBER = 235;
71 case T_MIN = 236;
72 case T_NEW = 237;
73 case T_NOT = 238;
74 case T_NULL = 239;
75 case T_NULLIF = 240;
76 case T_OF = 241;
77 case T_OR = 242;
78 case T_ORDER = 243;
79 case T_OUTER = 244;
80 case T_SELECT = 246;
81 case T_SET = 247;
82 case T_SOME = 248;
83 case T_SUM = 249;
84 case T_THEN = 250;
85 case T_TRAILING = 251;
86 case T_TRUE = 252;
87 case T_UPDATE = 253;
88 case T_WHEN = 254;
89 case T_WHERE = 255;
90 case T_WITH = 256;
91}
diff --git a/vendor/doctrine/orm/src/Query/TreeWalker.php b/vendor/doctrine/orm/src/Query/TreeWalker.php
new file mode 100644
index 0000000..6c21577
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/TreeWalker.php
@@ -0,0 +1,44 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use Doctrine\ORM\AbstractQuery;
8
9/**
10 * Interface for walkers of DQL ASTs (abstract syntax trees).
11 *
12 * @psalm-import-type QueryComponent from Parser
13 */
14interface TreeWalker
15{
16 /**
17 * Initializes TreeWalker with important information about the ASTs to be walked.
18 *
19 * @psalm-param array<string, QueryComponent> $queryComponents The query components (symbol table).
20 */
21 public function __construct(AbstractQuery $query, ParserResult $parserResult, array $queryComponents);
22
23 /**
24 * Returns internal queryComponents array.
25 *
26 * @psalm-return array<string, QueryComponent>
27 */
28 public function getQueryComponents(): array;
29
30 /**
31 * Walks down a SelectStatement AST node.
32 */
33 public function walkSelectStatement(AST\SelectStatement $selectStatement): void;
34
35 /**
36 * Walks down an UpdateStatement AST node.
37 */
38 public function walkUpdateStatement(AST\UpdateStatement $updateStatement): void;
39
40 /**
41 * Walks down a DeleteStatement AST node.
42 */
43 public function walkDeleteStatement(AST\DeleteStatement $deleteStatement): void;
44}
diff --git a/vendor/doctrine/orm/src/Query/TreeWalkerAdapter.php b/vendor/doctrine/orm/src/Query/TreeWalkerAdapter.php
new file mode 100644
index 0000000..a7948db
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/TreeWalkerAdapter.php
@@ -0,0 +1,90 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use Doctrine\ORM\AbstractQuery;
8use Doctrine\ORM\Mapping\ClassMetadata;
9use LogicException;
10
11use function array_diff;
12use function array_keys;
13use function sprintf;
14
15/**
16 * An adapter implementation of the TreeWalker interface. The methods in this class
17 * are empty. This class exists as convenience for creating tree walkers.
18 *
19 * @psalm-import-type QueryComponent from Parser
20 */
21abstract class TreeWalkerAdapter implements TreeWalker
22{
23 /**
24 * {@inheritDoc}
25 */
26 public function __construct(
27 private readonly AbstractQuery $query,
28 private readonly ParserResult $parserResult,
29 private array $queryComponents,
30 ) {
31 }
32
33 /**
34 * {@inheritDoc}
35 */
36 public function getQueryComponents(): array
37 {
38 return $this->queryComponents;
39 }
40
41 public function walkSelectStatement(AST\SelectStatement $selectStatement): void
42 {
43 }
44
45 public function walkUpdateStatement(AST\UpdateStatement $updateStatement): void
46 {
47 }
48
49 public function walkDeleteStatement(AST\DeleteStatement $deleteStatement): void
50 {
51 }
52
53 /**
54 * Sets or overrides a query component for a given dql alias.
55 *
56 * @psalm-param QueryComponent $queryComponent
57 */
58 protected function setQueryComponent(string $dqlAlias, array $queryComponent): void
59 {
60 $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'];
61
62 if (array_diff($requiredKeys, array_keys($queryComponent))) {
63 throw QueryException::invalidQueryComponent($dqlAlias);
64 }
65
66 $this->queryComponents[$dqlAlias] = $queryComponent;
67 }
68
69 /**
70 * Retrieves the Query Instance responsible for the current walkers execution.
71 */
72 protected function _getQuery(): AbstractQuery
73 {
74 return $this->query;
75 }
76
77 /**
78 * Retrieves the ParserResult.
79 */
80 protected function _getParserResult(): ParserResult
81 {
82 return $this->parserResult;
83 }
84
85 protected function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata
86 {
87 return $this->queryComponents[$dqlAlias]['metadata']
88 ?? throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias));
89 }
90}
diff --git a/vendor/doctrine/orm/src/Query/TreeWalkerChain.php b/vendor/doctrine/orm/src/Query/TreeWalkerChain.php
new file mode 100644
index 0000000..7bb3051
--- /dev/null
+++ b/vendor/doctrine/orm/src/Query/TreeWalkerChain.php
@@ -0,0 +1,88 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Query;
6
7use Doctrine\ORM\AbstractQuery;
8use Generator;
9
10/**
11 * Represents a chain of tree walkers that modify an AST and finally emit output.
12 * Only the last walker in the chain can emit output. Any previous walkers can modify
13 * the AST to influence the final output produced by the last walker.
14 *
15 * @psalm-import-type QueryComponent from Parser
16 */
17class TreeWalkerChain implements TreeWalker
18{
19 /**
20 * The tree walkers.
21 *
22 * @var string[]
23 * @psalm-var list<class-string<TreeWalker>>
24 */
25 private array $walkers = [];
26
27 /**
28 * {@inheritDoc}
29 */
30 public function __construct(
31 private readonly AbstractQuery $query,
32 private readonly ParserResult $parserResult,
33 private array $queryComponents,
34 ) {
35 }
36
37 /**
38 * Returns the internal queryComponents array.
39 *
40 * {@inheritDoc}
41 */
42 public function getQueryComponents(): array
43 {
44 return $this->queryComponents;
45 }
46
47 /**
48 * Adds a tree walker to the chain.
49 *
50 * @param string $walkerClass The class of the walker to instantiate.
51 * @psalm-param class-string<TreeWalker> $walkerClass
52 */
53 public function addTreeWalker(string $walkerClass): void
54 {
55 $this->walkers[] = $walkerClass;
56 }
57
58 public function walkSelectStatement(AST\SelectStatement $selectStatement): void
59 {
60 foreach ($this->getWalkers() as $walker) {
61 $walker->walkSelectStatement($selectStatement);
62
63 $this->queryComponents = $walker->getQueryComponents();
64 }
65 }
66
67 public function walkUpdateStatement(AST\UpdateStatement $updateStatement): void
68 {
69 foreach ($this->getWalkers() as $walker) {
70 $walker->walkUpdateStatement($updateStatement);
71 }
72 }
73
74 public function walkDeleteStatement(AST\DeleteStatement $deleteStatement): void
75 {
76 foreach ($this->getWalkers() as $walker) {
77 $walker->walkDeleteStatement($deleteStatement);
78 }
79 }
80
81 /** @psalm-return Generator<int, TreeWalker> */
82 private function getWalkers(): Generator
83 {
84 foreach ($this->walkers as $walkerClass) {
85 yield new $walkerClass($this->query, $this->parserResult, $this->queryComponents);
86 }
87 }
88}