From 0e4b9528fcc2e0f28c0118682ca502a7c468a498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Wed, 20 Nov 2024 13:41:52 +0100 Subject: [PATCH 1/2] Fixed passing DateTime instances into `DoctrineDatabase::findBy()` methods --- .../Gateway/AbstractDoctrineDatabase.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/contracts/Gateway/AbstractDoctrineDatabase.php b/src/contracts/Gateway/AbstractDoctrineDatabase.php index 8a9a94d..7da0a54 100644 --- a/src/contracts/Gateway/AbstractDoctrineDatabase.php +++ b/src/contracts/Gateway/AbstractDoctrineDatabase.php @@ -11,6 +11,7 @@ use Doctrine\Common\Collections\Expr\Expression; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Query\QueryBuilder; +use Doctrine\DBAL\Types\ConversionException; use Ibexa\CorePersistence\Gateway\ExpressionVisitor; use InvalidArgumentException; @@ -382,7 +383,21 @@ private function buildCondition(QueryBuilder $qb, string $column, $value): strin return $qb->expr()->isNull($fullColumnName); } + $columnType = $metadata->getColumnType($column); + $platform = $this->connection->getDatabasePlatform(); + if (is_array($value)) { + $value = array_map( + static function ($value) use ($columnType, $platform) { + try { + return $columnType->convertToDatabaseValue($value, $platform); + } catch (ConversionException $e) { + return $value; + } + }, + $value, + ); + $parameter = $qb->createPositionalParameter( $value, $columnBinding + Connection::ARRAY_PARAM_OFFSET @@ -391,6 +406,11 @@ private function buildCondition(QueryBuilder $qb, string $column, $value): strin return $qb->expr()->in($fullColumnName, $parameter); } + try { + $value = $columnType->convertToDatabaseValue($value, $this->connection->getDatabasePlatform()); + } catch (ConversionException $e) { + } + $parameter = $qb->createPositionalParameter($value, $columnBinding); return $qb->expr()->eq($fullColumnName, $parameter); From 9f48398c801185bc36be26fbe1deaebd664605e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Wed, 20 Nov 2024 14:55:55 +0100 Subject: [PATCH 2/2] Added basic test --- .../Gateway/AbstractDoctrineDatabase.php | 8 +- src/contracts/Gateway/GatewayInterface.php | 6 +- .../Gateway/AbstractDoctrineDatabaseTest.php | 87 +++++++++++++++++++ 3 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 tests/lib/Gateway/AbstractDoctrineDatabaseTest.php diff --git a/src/contracts/Gateway/AbstractDoctrineDatabase.php b/src/contracts/Gateway/AbstractDoctrineDatabase.php index 7da0a54..088de3c 100644 --- a/src/contracts/Gateway/AbstractDoctrineDatabase.php +++ b/src/contracts/Gateway/AbstractDoctrineDatabase.php @@ -117,8 +117,6 @@ public function countAll(): int } /** - * @param \Doctrine\Common\Collections\Expr\Expression|array|null> $criteria - * * @throws \Doctrine\DBAL\Driver\Exception * @throws \Doctrine\DBAL\Exception * @throws \Ibexa\Contracts\CorePersistence\Exception\MappingException @@ -258,7 +256,7 @@ private function applyInheritance(QueryBuilder $qb): void } /** - * @param \Doctrine\Common\Collections\Expr\Expression|array|null> $criteria + * @param \Doctrine\Common\Collections\Expr\Expression|array|null> $criteria * * @return \Doctrine\DBAL\Query\Expression\CompositeExpression|string|null * @@ -326,7 +324,7 @@ private function buildConditionExpression(ExpressionVisitor $visitor, QueryBuild } /** - * @param scalar|array|null $value + * @param scalar|\DateTimeInterface|array|null $value * * @throws \Doctrine\DBAL\Exception * @throws \Ibexa\Contracts\CorePersistence\Exception\MappingException @@ -449,7 +447,7 @@ final protected function applyOrderBy(QueryBuilder $qb, ?array $orderBy = []): v } /** - * @param \Doctrine\Common\Collections\Expr\Expression|array|null> $criteria + * @param \Doctrine\Common\Collections\Expr\Expression|array|null> $criteria */ final protected function applyCriteria(QueryBuilder $qb, $criteria): void { diff --git a/src/contracts/Gateway/GatewayInterface.php b/src/contracts/Gateway/GatewayInterface.php index 248ccde..4e1cc17 100644 --- a/src/contracts/Gateway/GatewayInterface.php +++ b/src/contracts/Gateway/GatewayInterface.php @@ -23,7 +23,7 @@ public function getMetadata(): DoctrineSchemaMetadataInterface; public function countAll(): int; /** - * @param \Doctrine\Common\Collections\Expr\Expression|array<\Doctrine\Common\Collections\Expr\Expression|scalar|array> $criteria + * @param \Doctrine\Common\Collections\Expr\Expression|array<\Doctrine\Common\Collections\Expr\Expression|scalar|\DateTimeInterface|array> $criteria */ public function countBy($criteria): int; @@ -33,7 +33,7 @@ public function countBy($criteria): int; public function findAll(?int $limit = null, int $offset = 0): array; /** - * @param \Doctrine\Common\Collections\Expr\Expression|array<\Doctrine\Common\Collections\Expr\Expression|scalar|array|null> $criteria Map of column names to values that will be used as part of WHERE query + * @param \Doctrine\Common\Collections\Expr\Expression|array<\Doctrine\Common\Collections\Expr\Expression|scalar|\DateTimeInterface|array|null> $criteria Map of column names to values that will be used as part of WHERE query * @param array|null $orderBy Map of column names to "ASC" or "DESC", that will be used in SORT query * * @phpstan-param array|null $orderBy @@ -43,7 +43,7 @@ public function findAll(?int $limit = null, int $offset = 0): array; public function findBy($criteria, ?array $orderBy = null, ?int $limit = null, int $offset = 0): array; /** - * @param array|null> $criteria Map of column names to values that will be used as part of WHERE query + * @param array|null> $criteria Map of column names to values that will be used as part of WHERE query * @param array|null $orderBy Map of column names to "ASC" or "DESC", that will be used in SORT query * * @phpstan-param array|null $orderBy diff --git a/tests/lib/Gateway/AbstractDoctrineDatabaseTest.php b/tests/lib/Gateway/AbstractDoctrineDatabaseTest.php new file mode 100644 index 0000000..69ee98b --- /dev/null +++ b/tests/lib/Gateway/AbstractDoctrineDatabaseTest.php @@ -0,0 +1,87 @@ +createMock(Connection::class); + $connection->expects(self::once()) + ->method('createQueryBuilder') + ->willReturn(new QueryBuilder($connection)); + + $connection->expects(self::atLeastOnce()) + ->method('getDatabasePlatform') + ->willReturn(new PostgreSQL94Platform()); + + $connection->expects(self::atLeastOnce()) + ->method('getExpressionBuilder') + ->willReturn(new ExpressionBuilder($connection)); + + $result = $this->createMock(ResultStatement::class); + $result->expects(self::once()) + ->method('fetch') + ->willReturn(false); + + $connection->expects(self::once()) + ->method('executeQuery') + ->with( + self::identicalTo( + 'SELECT __foo_table__.id, __foo_table__.date_time_immutable_column FROM __foo_table__ __foo_table__ WHERE __foo_table__.date_time_immutable_column = ?', + ), + self::identicalTo([ + 1 => '2024-01-01 00:00:00', + ]), + ) + ->willReturn($result); + + $registry = $this->createMock(DoctrineSchemaMetadataRegistryInterface::class); + + $database = new class($connection, $registry) extends AbstractDoctrineDatabase { + protected function getTableName(): string + { + return '__foo_table__'; + } + + protected function buildMetadata(): DoctrineSchemaMetadataInterface + { + return new DoctrineSchemaMetadata( + $this->connection, + null, + $this->getTableName(), + [ + 'id' => Types::INTEGER, + 'date_time_immutable_column' => Types::DATETIME_IMMUTABLE, + ], + ['id'], + ); + } + }; + + $database->findBy([ + 'date_time_immutable_column' => new \DateTimeImmutable('2024-01-01 00:00:00'), + ]); + } +}