diff options
| author | polo <ordipolo@gmx.fr> | 2024-08-13 23:45:21 +0200 |
|---|---|---|
| committer | polo <ordipolo@gmx.fr> | 2024-08-13 23:45:21 +0200 |
| commit | bf6655a534a6775d30cafa67bd801276bda1d98d (patch) | |
| tree | c6381e3f6c81c33eab72508f410b165ba05f7e9c /vendor/doctrine/orm/src/Query | |
| parent | 94d67a4b51f8e62e7d518cce26a526ae1ec48278 (diff) | |
| download | AppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.tar.gz AppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.tar.bz2 AppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.zip | |
VERSION 0.2 doctrine ORM et entités
Diffstat (limited to 'vendor/doctrine/orm/src/Query')
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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\QueryException; | ||
| 8 | |||
| 9 | use function get_debug_type; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Base exception class for AST exceptions. | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use 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 | */ | ||
| 19 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * ConditionalFactor ::= ["NOT"] ConditionalPrimary | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable] | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * DeleteStatement = DeleteClause [WhereClause] | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * "ABS" "(" SimpleArithmeticExpression ")" | ||
| 14 | * | ||
| 15 | * @link www.doctrine-project.org | ||
| 16 | */ | ||
| 17 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\AggregateExpression; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * "AVG" "(" ["DISTINCT"] StringPrimary ")" | ||
| 13 | */ | ||
| 14 | final 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | ||
| 14 | * | ||
| 15 | * @link www.doctrine-project.org | ||
| 16 | */ | ||
| 17 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | ||
| 14 | * | ||
| 15 | * @link www.doctrine-project.org | ||
| 16 | */ | ||
| 17 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary }* ")" | ||
| 14 | * | ||
| 15 | * @link www.doctrine-project.org | ||
| 16 | */ | ||
| 17 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Types\Type; | ||
| 8 | use Doctrine\DBAL\Types\Types; | ||
| 9 | use Doctrine\ORM\Query\AST\AggregateExpression; | ||
| 10 | use Doctrine\ORM\Query\AST\TypedExpression; | ||
| 11 | use Doctrine\ORM\Query\Parser; | ||
| 12 | use Doctrine\ORM\Query\SqlWalker; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * "COUNT" "(" ["DISTINCT"] StringPrimary ")" | ||
| 16 | */ | ||
| 17 | final 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\Parser; | ||
| 8 | use Doctrine\ORM\Query\SqlWalker; | ||
| 9 | use Doctrine\ORM\Query\TokenType; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * "CURRENT_DATE" | ||
| 13 | * | ||
| 14 | * @link www.doctrine-project.org | ||
| 15 | */ | ||
| 16 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\Parser; | ||
| 8 | use Doctrine\ORM\Query\SqlWalker; | ||
| 9 | use Doctrine\ORM\Query\TokenType; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * "CURRENT_TIME" | ||
| 13 | * | ||
| 14 | * @link www.doctrine-project.org | ||
| 15 | */ | ||
| 16 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\Parser; | ||
| 8 | use Doctrine\ORM\Query\SqlWalker; | ||
| 9 | use Doctrine\ORM\Query\TokenType; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * "CURRENT_TIMESTAMP" | ||
| 13 | * | ||
| 14 | * @link www.doctrine-project.org | ||
| 15 | */ | ||
| 16 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\ASTException; | ||
| 8 | use Doctrine\ORM\Query\AST\Node; | ||
| 9 | use Doctrine\ORM\Query\Parser; | ||
| 10 | use Doctrine\ORM\Query\QueryException; | ||
| 11 | use Doctrine\ORM\Query\SqlWalker; | ||
| 12 | use Doctrine\ORM\Query\TokenType; | ||
| 13 | |||
| 14 | use function strtolower; | ||
| 15 | |||
| 16 | /** | ||
| 17 | * "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" | ||
| 18 | * | ||
| 19 | * @link www.doctrine-project.org | ||
| 20 | */ | ||
| 21 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | ||
| 14 | * | ||
| 15 | * @link www.doctrine-project.org | ||
| 16 | */ | ||
| 17 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\ASTException; | ||
| 8 | use Doctrine\ORM\Query\QueryException; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | |||
| 11 | use function strtolower; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * "DATE_SUB(date1, interval, unit)" | ||
| 15 | * | ||
| 16 | * @link www.doctrine-project.org | ||
| 17 | */ | ||
| 18 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Abstract Function Node. | ||
| 13 | * | ||
| 14 | * @link www.doctrine-project.org | ||
| 15 | * | ||
| 16 | * @psalm-consistent-constructor | ||
| 17 | */ | ||
| 18 | abstract 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\PathExpression; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\QueryException; | ||
| 10 | use Doctrine\ORM\Query\SqlWalker; | ||
| 11 | use Doctrine\ORM\Query\TokenType; | ||
| 12 | |||
| 13 | use function assert; | ||
| 14 | use function reset; | ||
| 15 | use function sprintf; | ||
| 16 | |||
| 17 | /** | ||
| 18 | * "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")" | ||
| 19 | * | ||
| 20 | * @link www.doctrine-project.org | ||
| 21 | */ | ||
| 22 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Types\Type; | ||
| 8 | use Doctrine\DBAL\Types\Types; | ||
| 9 | use Doctrine\ORM\Query\AST\Node; | ||
| 10 | use Doctrine\ORM\Query\AST\TypedExpression; | ||
| 11 | use Doctrine\ORM\Query\Parser; | ||
| 12 | use Doctrine\ORM\Query\SqlWalker; | ||
| 13 | use Doctrine\ORM\Query\TokenType; | ||
| 14 | |||
| 15 | /** | ||
| 16 | * "LENGTH" "(" StringPrimary ")" | ||
| 17 | * | ||
| 18 | * @link www.doctrine-project.org | ||
| 19 | */ | ||
| 20 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | ||
| 14 | * | ||
| 15 | * @link www.doctrine-project.org | ||
| 16 | */ | ||
| 17 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | use function sprintf; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * "LOWER" "(" StringPrimary ")" | ||
| 16 | * | ||
| 17 | * @link www.doctrine-project.org | ||
| 18 | */ | ||
| 19 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\AggregateExpression; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * "MAX" "(" ["DISTINCT"] StringPrimary ")" | ||
| 13 | */ | ||
| 14 | final 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\AggregateExpression; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * "MIN" "(" ["DISTINCT"] StringPrimary ")" | ||
| 13 | */ | ||
| 14 | final 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | ||
| 14 | * | ||
| 15 | * @link www.doctrine-project.org | ||
| 16 | */ | ||
| 17 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\PathExpression; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | use function assert; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * "SIZE" "(" CollectionValuedPathExpression ")" | ||
| 16 | * | ||
| 17 | * @link www.doctrine-project.org | ||
| 18 | */ | ||
| 19 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | use function sprintf; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * "SQRT" "(" SimpleArithmeticExpression ")" | ||
| 16 | * | ||
| 17 | * @link www.doctrine-project.org | ||
| 18 | */ | ||
| 19 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | ||
| 14 | * | ||
| 15 | * @link www.doctrine-project.org | ||
| 16 | */ | ||
| 17 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\AggregateExpression; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * "SUM" "(" ["DISTINCT"] StringPrimary ")" | ||
| 13 | */ | ||
| 14 | final 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Platforms\TrimMode; | ||
| 8 | use Doctrine\ORM\Query\AST\Node; | ||
| 9 | use Doctrine\ORM\Query\Parser; | ||
| 10 | use Doctrine\ORM\Query\SqlWalker; | ||
| 11 | use Doctrine\ORM\Query\TokenType; | ||
| 12 | |||
| 13 | use function assert; | ||
| 14 | use function strcasecmp; | ||
| 15 | |||
| 16 | /** | ||
| 17 | * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | ||
| 18 | * | ||
| 19 | * @link www.doctrine-project.org | ||
| 20 | */ | ||
| 21 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST\Functions; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Node; | ||
| 8 | use Doctrine\ORM\Query\Parser; | ||
| 9 | use Doctrine\ORM\Query\SqlWalker; | ||
| 10 | use Doctrine\ORM\Query\TokenType; | ||
| 11 | |||
| 12 | use function sprintf; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * "UPPER" "(" StringPrimary ")" | ||
| 16 | * | ||
| 17 | * @link www.doctrine-project.org | ||
| 18 | */ | ||
| 19 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * IndexBy ::= "INDEX" "BY" SingleValuedPathExpression | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\QueryException; | ||
| 8 | use Doctrine\ORM\Query\SqlWalker; | ||
| 9 | |||
| 10 | use function is_numeric; | ||
| 11 | use function strlen; | ||
| 12 | use function substr; | ||
| 13 | |||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use 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 | */ | ||
| 15 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use 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 | */ | ||
| 15 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * JoinAssociationPathExpression ::= IdentificationVariable "." (SingleValuedAssociationField | CollectionValuedAssociationField) | ||
| 9 | * | ||
| 10 | * @link www.doctrine-project.org | ||
| 11 | */ | ||
| 12 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * JoinClassPathExpression ::= AbstractSchemaName ["AS"] AliasIdentificationVariable | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * JoinVariableDeclaration ::= Join [IndexBy] | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\AST\Functions\FunctionNode; | ||
| 8 | use Doctrine\ORM\Query\SqlWalker; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] | ||
| 12 | * | ||
| 13 | * @link www.doctrine-project.org | ||
| 14 | */ | ||
| 15 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")" | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | use Stringable; | ||
| 9 | |||
| 10 | use function get_debug_type; | ||
| 11 | use function get_object_vars; | ||
| 12 | use function is_array; | ||
| 13 | use function is_object; | ||
| 14 | use function str_repeat; | ||
| 15 | use function var_export; | ||
| 16 | |||
| 17 | use const PHP_EOL; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Abstract class of an AST node. | ||
| 21 | * | ||
| 22 | * @link www.doctrine-project.org | ||
| 23 | */ | ||
| 24 | abstract 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | use function strtoupper; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] | ||
| 13 | * | ||
| 14 | * @link www.doctrine-project.org | ||
| 15 | */ | ||
| 16 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * ParenthesisExpression ::= "(" ArithmeticPrimary ")" | ||
| 11 | */ | ||
| 12 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use 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 | */ | ||
| 18 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace 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 | */ | ||
| 15 | interface 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | use function strtoupper; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" | ||
| 13 | * | ||
| 14 | * @link www.doctrine-project.org | ||
| 15 | */ | ||
| 16 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * SelectClause = "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression | | ||
| 11 | * (AggregateExpression | "(" Subselect ")") [["AS"] ["HIDDEN"] FieldAliasIdentificationVariable] | ||
| 12 | * | ||
| 13 | * @link www.doctrine-project.org | ||
| 14 | */ | ||
| 15 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * SelectStatement = SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | ||
| 11 | * | (AggregateExpression [["AS"] FieldAliasIdentificationVariable]) | ||
| 12 | * | ||
| 13 | * @link www.doctrine-project.org | ||
| 14 | */ | ||
| 15 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * SubselectIdentificationVariableDeclaration ::= AssociationPathExpression ["AS"] AliasIdentificationVariable | ||
| 9 | * | ||
| 10 | * @link www.doctrine-project.org | ||
| 11 | */ | ||
| 12 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Types\Type; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Provides an API for resolving the type of a Node | ||
| 11 | */ | ||
| 12 | interface 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}* | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use 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 | */ | ||
| 16 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * UpdateStatement = UpdateClause [WhereClause] | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\AST; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\SqlWalker; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * WhereClause ::= "WHERE" ConditionalExpression | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Exec; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\ArrayParameterType; | ||
| 8 | use Doctrine\DBAL\Cache\QueryCacheProfile; | ||
| 9 | use Doctrine\DBAL\Connection; | ||
| 10 | use Doctrine\DBAL\ParameterType; | ||
| 11 | use Doctrine\DBAL\Result; | ||
| 12 | use 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 | */ | ||
| 23 | abstract 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Exec; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Connection; | ||
| 8 | use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; | ||
| 9 | use Doctrine\DBAL\Types\Type; | ||
| 10 | use Doctrine\ORM\Query\AST; | ||
| 11 | use Doctrine\ORM\Query\AST\DeleteStatement; | ||
| 12 | use Doctrine\ORM\Query\SqlWalker; | ||
| 13 | use Doctrine\ORM\Utility\PersisterHelper; | ||
| 14 | use Throwable; | ||
| 15 | |||
| 16 | use function array_reverse; | ||
| 17 | use 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 | */ | ||
| 25 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Exec; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Connection; | ||
| 8 | use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; | ||
| 9 | use Doctrine\DBAL\Types\Type; | ||
| 10 | use Doctrine\ORM\Query\AST; | ||
| 11 | use Doctrine\ORM\Query\AST\UpdateStatement; | ||
| 12 | use Doctrine\ORM\Query\ParameterTypeInferer; | ||
| 13 | use Doctrine\ORM\Query\SqlWalker; | ||
| 14 | use Doctrine\ORM\Utility\PersisterHelper; | ||
| 15 | |||
| 16 | use function array_reverse; | ||
| 17 | use function array_slice; | ||
| 18 | use function implode; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Executes the SQL statements for bulk DQL UPDATE statements on classes in | ||
| 22 | * Class Table Inheritance (JOINED). | ||
| 23 | */ | ||
| 24 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Exec; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Connection; | ||
| 8 | use Doctrine\DBAL\Result; | ||
| 9 | use Doctrine\ORM\Query\AST\SelectStatement; | ||
| 10 | use 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 | */ | ||
| 17 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Exec; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Connection; | ||
| 8 | use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; | ||
| 9 | use Doctrine\ORM\Query\AST; | ||
| 10 | use 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 | */ | ||
| 20 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Internal\NoUnknownNamedArguments; | ||
| 8 | use Traversable; | ||
| 9 | |||
| 10 | use function implode; | ||
| 11 | use function is_bool; | ||
| 12 | use function is_float; | ||
| 13 | use function is_int; | ||
| 14 | use function is_iterable; | ||
| 15 | use function iterator_to_array; | ||
| 16 | use 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 | */ | ||
| 25 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Expression class for building DQL and parts. | ||
| 9 | * | ||
| 10 | * @link www.doctrine-project.org | ||
| 11 | */ | ||
| 12 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | use InvalidArgumentException; | ||
| 8 | use Stringable; | ||
| 9 | |||
| 10 | use function array_key_exists; | ||
| 11 | use function count; | ||
| 12 | use function get_debug_type; | ||
| 13 | use function implode; | ||
| 14 | use function in_array; | ||
| 15 | use function is_array; | ||
| 16 | use function is_string; | ||
| 17 | use function sprintf; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Abstract base Expr class for building DQL parts. | ||
| 21 | * | ||
| 22 | * @link www.doctrine-project.org | ||
| 23 | */ | ||
| 24 | abstract 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | use Stringable; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Expression class for DQL comparison expressions. | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | use Stringable; | ||
| 8 | |||
| 9 | use function implode; | ||
| 10 | use function is_object; | ||
| 11 | use function preg_match; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Expression class for building DQL and parts. | ||
| 15 | * | ||
| 16 | * @link www.doctrine-project.org | ||
| 17 | */ | ||
| 18 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | use Stringable; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Expression class for DQL from. | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | use Stringable; | ||
| 8 | |||
| 9 | use function implode; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Expression class for generating DQL functions. | ||
| 13 | * | ||
| 14 | * @link www.doctrine-project.org | ||
| 15 | */ | ||
| 16 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Expression class for building DQL Group By parts. | ||
| 9 | * | ||
| 10 | * @link www.doctrine-project.org | ||
| 11 | */ | ||
| 12 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | use Stringable; | ||
| 8 | |||
| 9 | use function strtoupper; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Expression class for DQL join. | ||
| 13 | * | ||
| 14 | * @link www.doctrine-project.org | ||
| 15 | */ | ||
| 16 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Expression class for generating DQL functions. | ||
| 9 | * | ||
| 10 | * @link www.doctrine-project.org | ||
| 11 | */ | ||
| 12 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | use Stringable; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Expression class for DQL math statements. | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | use Stringable; | ||
| 8 | |||
| 9 | use function count; | ||
| 10 | use function implode; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * Expression class for building DQL Order By parts. | ||
| 14 | * | ||
| 15 | * @link www.doctrine-project.org | ||
| 16 | */ | ||
| 17 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Expression class for building DQL OR clauses. | ||
| 9 | * | ||
| 10 | * @link www.doctrine-project.org | ||
| 11 | */ | ||
| 12 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Expr; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Expression class for building DQL select statements. | ||
| 9 | * | ||
| 10 | * @link www.doctrine-project.org | ||
| 11 | */ | ||
| 12 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Filter; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Exception\ORMException; | ||
| 8 | use LogicException; | ||
| 9 | |||
| 10 | use function sprintf; | ||
| 11 | |||
| 12 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query\Filter; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Connection; | ||
| 8 | use Doctrine\DBAL\Types\Types; | ||
| 9 | use Doctrine\ORM\EntityManagerInterface; | ||
| 10 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 11 | use Doctrine\ORM\Query\ParameterTypeInferer; | ||
| 12 | use InvalidArgumentException; | ||
| 13 | use Stringable; | ||
| 14 | |||
| 15 | use function array_map; | ||
| 16 | use function implode; | ||
| 17 | use function ksort; | ||
| 18 | use 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 | */ | ||
| 27 | abstract 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Configuration; | ||
| 8 | use Doctrine\ORM\EntityManagerInterface; | ||
| 9 | use Doctrine\ORM\Query\Filter\SQLFilter; | ||
| 10 | use InvalidArgumentException; | ||
| 11 | |||
| 12 | use function assert; | ||
| 13 | use function ksort; | ||
| 14 | |||
| 15 | /** | ||
| 16 | * Collection class for all the query filters. | ||
| 17 | */ | ||
| 18 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use Doctrine\Common\Lexer\AbstractLexer; | ||
| 8 | |||
| 9 | use function constant; | ||
| 10 | use function ctype_alpha; | ||
| 11 | use function defined; | ||
| 12 | use function is_numeric; | ||
| 13 | use function str_contains; | ||
| 14 | use function str_replace; | ||
| 15 | use function stripos; | ||
| 16 | use function strlen; | ||
| 17 | use function strtoupper; | ||
| 18 | use function substr; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Scans a DQL query for tokens. | ||
| 22 | * | ||
| 23 | * @extends AbstractLexer<TokenType, string> | ||
| 24 | */ | ||
| 25 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use function trim; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Defines a Query Parameter. | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use BackedEnum; | ||
| 8 | use DateInterval; | ||
| 9 | use DateTimeImmutable; | ||
| 10 | use DateTimeInterface; | ||
| 11 | use Doctrine\DBAL\ArrayParameterType; | ||
| 12 | use Doctrine\DBAL\ParameterType; | ||
| 13 | use Doctrine\DBAL\Types\Types; | ||
| 14 | |||
| 15 | use function current; | ||
| 16 | use function is_array; | ||
| 17 | use function is_bool; | ||
| 18 | use function is_int; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Provides an enclosed support for parameter inferring. | ||
| 22 | * | ||
| 23 | * @link www.doctrine-project.org | ||
| 24 | */ | ||
| 25 | final 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use Doctrine\Common\Lexer\Token; | ||
| 8 | use Doctrine\ORM\EntityManagerInterface; | ||
| 9 | use Doctrine\ORM\Mapping\AssociationMapping; | ||
| 10 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 11 | use Doctrine\ORM\Query; | ||
| 12 | use Doctrine\ORM\Query\AST\Functions; | ||
| 13 | use LogicException; | ||
| 14 | use ReflectionClass; | ||
| 15 | |||
| 16 | use function array_search; | ||
| 17 | use function assert; | ||
| 18 | use function class_exists; | ||
| 19 | use function count; | ||
| 20 | use function implode; | ||
| 21 | use function in_array; | ||
| 22 | use function interface_exists; | ||
| 23 | use function is_string; | ||
| 24 | use function sprintf; | ||
| 25 | use function str_contains; | ||
| 26 | use function strlen; | ||
| 27 | use function strpos; | ||
| 28 | use function strrpos; | ||
| 29 | use function strtolower; | ||
| 30 | use 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 | */ | ||
| 47 | final 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Query\Exec\AbstractSqlExecutor; | ||
| 8 | use LogicException; | ||
| 9 | |||
| 10 | use 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 | */ | ||
| 18 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use function str_repeat; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * A parse tree printer for Doctrine Query Language parser. | ||
| 11 | * | ||
| 12 | * @link http://www.phpdoctrine.org | ||
| 13 | */ | ||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Exception\ORMException; | ||
| 8 | use Doctrine\ORM\Mapping\AssociationMapping; | ||
| 9 | use Doctrine\ORM\Query\AST\PathExpression; | ||
| 10 | use Exception; | ||
| 11 | use Stringable; | ||
| 12 | use Throwable; | ||
| 13 | |||
| 14 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use Doctrine\Common\Collections\ArrayCollection; | ||
| 8 | use Doctrine\Common\Collections\Expr\Comparison; | ||
| 9 | use Doctrine\Common\Collections\Expr\CompositeExpression; | ||
| 10 | use Doctrine\Common\Collections\Expr\ExpressionVisitor; | ||
| 11 | use Doctrine\Common\Collections\Expr\Value; | ||
| 12 | use RuntimeException; | ||
| 13 | |||
| 14 | use function count; | ||
| 15 | use function str_replace; | ||
| 16 | use function str_starts_with; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Converts Collection expressions to Query expressions. | ||
| 20 | */ | ||
| 21 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use 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 | */ | ||
| 21 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Types\Type; | ||
| 8 | use Doctrine\ORM\EntityManagerInterface; | ||
| 9 | use Doctrine\ORM\Internal\SQLResultCasing; | ||
| 10 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 11 | use Doctrine\ORM\Utility\PersisterHelper; | ||
| 12 | use InvalidArgumentException; | ||
| 13 | use Stringable; | ||
| 14 | |||
| 15 | use function in_array; | ||
| 16 | use function sprintf; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields. | ||
| 20 | */ | ||
| 21 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use BadMethodCallException; | ||
| 8 | use Doctrine\DBAL\Connection; | ||
| 9 | use Doctrine\DBAL\LockMode; | ||
| 10 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
| 11 | use Doctrine\DBAL\Types\Type; | ||
| 12 | use Doctrine\ORM\EntityManagerInterface; | ||
| 13 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 14 | use Doctrine\ORM\Mapping\QuoteStrategy; | ||
| 15 | use Doctrine\ORM\OptimisticLockException; | ||
| 16 | use Doctrine\ORM\Query; | ||
| 17 | use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver; | ||
| 18 | use Doctrine\ORM\Utility\LockSqlHelper; | ||
| 19 | use Doctrine\ORM\Utility\PersisterHelper; | ||
| 20 | use InvalidArgumentException; | ||
| 21 | use LogicException; | ||
| 22 | |||
| 23 | use function array_diff; | ||
| 24 | use function array_filter; | ||
| 25 | use function array_keys; | ||
| 26 | use function array_map; | ||
| 27 | use function array_merge; | ||
| 28 | use function assert; | ||
| 29 | use function count; | ||
| 30 | use function implode; | ||
| 31 | use function is_array; | ||
| 32 | use function is_float; | ||
| 33 | use function is_int; | ||
| 34 | use function is_numeric; | ||
| 35 | use function is_string; | ||
| 36 | use function preg_match; | ||
| 37 | use function reset; | ||
| 38 | use function sprintf; | ||
| 39 | use function strtolower; | ||
| 40 | use function strtoupper; | ||
| 41 | use 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 | */ | ||
| 49 | class 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | enum 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use Doctrine\ORM\AbstractQuery; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Interface for walkers of DQL ASTs (abstract syntax trees). | ||
| 11 | * | ||
| 12 | * @psalm-import-type QueryComponent from Parser | ||
| 13 | */ | ||
| 14 | interface 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use Doctrine\ORM\AbstractQuery; | ||
| 8 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 9 | use LogicException; | ||
| 10 | |||
| 11 | use function array_diff; | ||
| 12 | use function array_keys; | ||
| 13 | use 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 | */ | ||
| 21 | abstract 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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Query; | ||
| 6 | |||
| 7 | use Doctrine\ORM\AbstractQuery; | ||
| 8 | use 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 | */ | ||
| 17 | class 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 | } | ||
