From ad9138826ed274f6ce9bb7e6b35774738fc2d6bc Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 25 Jun 2024 12:42:10 +0200 Subject: [PATCH] QueryResultTypeWalker: fix nullability checks over unknown type --- .../Doctrine/Query/QueryResultTypeWalker.php | 24 +++++++++---------- .../Query/QueryResultTypeWalkerTest.php | 16 +++++++++++++ .../data/QueryResult/CustomIntType.php | 17 +++++++++++++ .../data/QueryResult/Entities/One.php | 7 ++++++ 4 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 tests/Type/Doctrine/data/QueryResult/CustomIntType.php diff --git a/src/Type/Doctrine/Query/QueryResultTypeWalker.php b/src/Type/Doctrine/Query/QueryResultTypeWalker.php index 6ca0c2e1..5a2b33b8 100644 --- a/src/Type/Doctrine/Query/QueryResultTypeWalker.php +++ b/src/Type/Doctrine/Query/QueryResultTypeWalker.php @@ -376,7 +376,7 @@ public function walkFunction($function): string new FloatType() ); - if (TypeCombinator::containsNull($exprType)) { + if ($this->canBeNull($exprType)) { $type = TypeCombinator::addNull($type); } @@ -388,7 +388,7 @@ public function walkFunction($function): string $secondExprType = $this->unmarshalType($function->secondArithmetic->dispatch($this)); $type = IntegerRangeType::fromInterval(0, null); - if (TypeCombinator::containsNull($firstExprType) || TypeCombinator::containsNull($secondExprType)) { + if ($this->canBeNull($firstExprType) || $this->canBeNull($secondExprType)) { $type = TypeCombinator::addNull($type); } @@ -399,7 +399,7 @@ public function walkFunction($function): string foreach ($function->concatExpressions as $expr) { $type = $this->unmarshalType($expr->dispatch($this)); - $hasNull = $hasNull || TypeCombinator::containsNull($type); + $hasNull = $hasNull || $this->canBeNull($type); } $type = new StringType(); @@ -420,7 +420,7 @@ public function walkFunction($function): string $intervalExprType = $this->unmarshalType($function->intervalExpression->dispatch($this)); $type = new StringType(); - if (TypeCombinator::containsNull($dateExprType) || TypeCombinator::containsNull($intervalExprType)) { + if ($this->canBeNull($dateExprType) || $this->canBeNull($intervalExprType)) { $type = TypeCombinator::addNull($type); } @@ -434,7 +434,7 @@ public function walkFunction($function): string new IntegerType(), new FloatType() ); - if (TypeCombinator::containsNull($date1ExprType) || TypeCombinator::containsNull($date2ExprType)) { + if ($this->canBeNull($date1ExprType) || $this->canBeNull($date2ExprType)) { $type = TypeCombinator::addNull($type); } @@ -444,7 +444,7 @@ public function walkFunction($function): string $stringPrimaryType = $this->unmarshalType($function->stringPrimary->dispatch($this)); $type = IntegerRangeType::fromInterval(0, null); - if (TypeCombinator::containsNull($stringPrimaryType)) { + if ($this->canBeNull($stringPrimaryType)) { $type = TypeCombinator::addNull($type); } @@ -455,7 +455,7 @@ public function walkFunction($function): string $secondExprType = $this->unmarshalType($this->walkStringPrimary($function->secondStringPrimary)); $type = IntegerRangeType::fromInterval(0, null); - if (TypeCombinator::containsNull($firstExprType) || TypeCombinator::containsNull($secondExprType)) { + if ($this->canBeNull($firstExprType) || $this->canBeNull($secondExprType)) { $type = TypeCombinator::addNull($type); } @@ -467,7 +467,7 @@ public function walkFunction($function): string $stringPrimaryType = $this->unmarshalType($function->stringPrimary->dispatch($this)); $type = new StringType(); - if (TypeCombinator::containsNull($stringPrimaryType)) { + if ($this->canBeNull($stringPrimaryType)) { $type = TypeCombinator::addNull($type); } @@ -478,7 +478,7 @@ public function walkFunction($function): string $secondExprType = $this->unmarshalType($this->walkSimpleArithmeticExpression($function->secondSimpleArithmeticExpression)); $type = IntegerRangeType::fromInterval(0, null); - if (TypeCombinator::containsNull($firstExprType) || TypeCombinator::containsNull($secondExprType)) { + if ($this->canBeNull($firstExprType) || $this->canBeNull($secondExprType)) { $type = TypeCombinator::addNull($type); } @@ -493,7 +493,7 @@ public function walkFunction($function): string $exprType = $this->unmarshalType($this->walkSimpleArithmeticExpression($function->simpleArithmeticExpression)); $type = new FloatType(); - if (TypeCombinator::containsNull($exprType)) { + if ($this->canBeNull($exprType)) { $type = TypeCombinator::addNull($type); } @@ -510,7 +510,7 @@ public function walkFunction($function): string } $type = new StringType(); - if (TypeCombinator::containsNull($stringType) || TypeCombinator::containsNull($firstExprType) || TypeCombinator::containsNull($secondExprType)) { + if ($this->canBeNull($stringType) || $this->canBeNull($firstExprType) || $this->canBeNull($secondExprType)) { $type = TypeCombinator::addNull($type); } @@ -714,7 +714,7 @@ public function walkCoalesceExpression($coalesceExpression): string } $type = $this->unmarshalType($expression->dispatch($this)); - $allTypesContainNull = $allTypesContainNull && TypeCombinator::containsNull($type); + $allTypesContainNull = $allTypesContainNull && $this->canBeNull($type); // Some drivers manipulate the types, lets avoid false positives by generalizing constant types // e.g. sqlsrv: "COALESCE returns the data type of value with the highest precedence" diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php index d0fc0306..76611781 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php @@ -7,6 +7,7 @@ use DateTime; use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\DBAL\Types\Type as DbalType; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Tools\SchemaTool; @@ -44,6 +45,7 @@ use QueryResult\EntitiesEnum\IntEnum; use QueryResult\EntitiesEnum\StringEnum; use Throwable; +use Type\Doctrine\data\QueryResult\CustomIntType; use function array_merge; use function array_shift; use function assert; @@ -76,6 +78,10 @@ public static function setUpBeforeClass(): void $em = require __DIR__ . '/../data/QueryResult/entity-manager.php'; self::$em = $em; + if (!DbalType::hasType(CustomIntType::NAME)) { + DbalType::addType(CustomIntType::NAME, CustomIntType::class); + } + $schemaTool = new SchemaTool($em); $classes = $em->getMetadataFactory()->getAllMetadata(); $schemaTool->createSchema($classes); @@ -1241,6 +1247,16 @@ public function getTestData(): iterable ', ]; + yield 'abs function with mixed' => [ + $this->constantArray([ + [new ConstantIntegerType(1), TypeCombinator::addNull($this->unumericStringified())], + ]), + ' + SELECT ABS(o.mixedColumn) + FROM QueryResult\Entities\One o + ', + ]; + yield 'bit_and function' => [ $this->constantArray([ [new ConstantIntegerType(1), $this->uintStringified()], diff --git a/tests/Type/Doctrine/data/QueryResult/CustomIntType.php b/tests/Type/Doctrine/data/QueryResult/CustomIntType.php new file mode 100644 index 00000000..785bed33 --- /dev/null +++ b/tests/Type/Doctrine/data/QueryResult/CustomIntType.php @@ -0,0 +1,17 @@ +subOne = new SubOne();