From 66a05c26830dca751fac8491fc4b778f83de4e1d Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 23 Jan 2023 08:08:50 +0000 Subject: [PATCH] Make Paginator-internal query cacheable in the query cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the `Paginator`-internal query (`... WHERE ... IN (id, id2, id3...)`) cacheable in the query cache again. When the Paginator creates the internal subquery that does the actual result limiting, it has to take DBAL type conversions for the identifier column of the paginated root entity into account (#7820, fixed in #7821). In order to perform this type conversion, we need to know the DBAL type class for the root entity's identifier, and we have to derive it from a given (arbitrary) DQL query. This requires DQL parsing and inspecting the AST, so #7821 placed the conversion code in the `WhereInWalker` where all the necessary information is available. The problem is that type conversion has to happen every time the paginator is run, but the query that results from running `WhereInWalker` would be kept in the query cache. This was reported in #7837 and fixed by #7865, by making this particular query expire every time. The query must not be cached, since the necessary ID type conversion happens as a side-effect of running the `WhereInWalker`. The Paginator internal query that uses `WhereInWalker` has its DQL re-parsed and transformed in every request. In addition to the processing overhead, this may also waste opcache memory (#9917). This PR moves the code that determines the DBAL type out of `WhereInWalker` into a dedicated SQL walker class, `RootTypeWalker`. `RootTypeWalker` uses a ~hack~  clever trick to report the type back: It sets the type as the resulting "SQL" string. The benefit is that `RootTypeWalker` results can be cached in the query cache themselves. Only the first time a given DQL query has to be paginated, we need to run this walker to find out the root entity's ID type. After that, the type will be returned from the query cache. With the type information being provided, `Paginator` can take care of the necessary conversions by itself. This happens every time the Paginator is used. The internal query that uses `WhereInWalker` can be cached again since it no longer has side effects. --- .../ORM/Tools/Pagination/Paginator.php | 26 +- .../ORM/Tools/Pagination/RootTypeWalker.php | 48 +++ .../ORM/Tools/Pagination/WhereInWalker.php | 33 -- psalm-baseline.xml | 3 - psalm.xml | 5 + .../ORM/Functional/Ticket/GH7820Test.php | 30 ++ .../Tools/Pagination/RootTypeWalkerTest.php | 54 ++++ .../Tools/Pagination/WhereInWalkerTest.php | 283 +++--------------- 8 files changed, 205 insertions(+), 277 deletions(-) create mode 100644 lib/Doctrine/ORM/Tools/Pagination/RootTypeWalker.php create mode 100644 tests/Doctrine/Tests/ORM/Tools/Pagination/RootTypeWalkerTest.php diff --git a/lib/Doctrine/ORM/Tools/Pagination/Paginator.php b/lib/Doctrine/ORM/Tools/Pagination/Paginator.php index cdfa35d89b9..7d7ab4e5916 100644 --- a/lib/Doctrine/ORM/Tools/Pagination/Paginator.php +++ b/lib/Doctrine/ORM/Tools/Pagination/Paginator.php @@ -21,7 +21,9 @@ use function array_key_exists; use function array_map; use function array_sum; +use function assert; use function count; +use function is_string; /** * The paginator can handle various complex scenarios with DQL. @@ -160,9 +162,10 @@ public function getIterator() $this->appendTreeWalker($whereInQuery, WhereInWalker::class); $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids)); $whereInQuery->setFirstResult(0)->setMaxResults(null); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids); $whereInQuery->setCacheable($this->query->isCacheable()); - $whereInQuery->useQueryCache(false); + + $databaseIds = $this->convertWhereInIdentifiersToDatabaseValue($ids); + $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $databaseIds); $result = $whereInQuery->getResult($this->query->getHydrationMode()); } else { @@ -265,4 +268,23 @@ private function unbindUnusedQueryParams(Query $query): void $query->setParameters($parameters); } + + /** + * @param mixed[] $identifiers + * + * @return mixed[] + */ + private function convertWhereInIdentifiersToDatabaseValue(array $identifiers): array + { + $query = $this->cloneQuery($this->query); + $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, RootTypeWalker::class); + + $connection = $this->query->getEntityManager()->getConnection(); + $type = $query->getSQL(); + assert(is_string($type)); + + return array_map(static function ($id) use ($connection, $type) { + return $connection->convertToDatabaseValue($id, $type); + }, $identifiers); + } } diff --git a/lib/Doctrine/ORM/Tools/Pagination/RootTypeWalker.php b/lib/Doctrine/ORM/Tools/Pagination/RootTypeWalker.php new file mode 100644 index 00000000000..7662fff086c --- /dev/null +++ b/lib/Doctrine/ORM/Tools/Pagination/RootTypeWalker.php @@ -0,0 +1,48 @@ + root entity id type resolution can be cached in the query cache. + */ +class RootTypeWalker extends SqlWalker +{ + public function walkSelectStatement(AST\SelectStatement $AST): string + { + // Get the root entity and alias from the AST fromClause + $from = $AST->fromClause->identificationVariableDeclarations; + + if (count($from) > 1) { + throw new RuntimeException('Can only process queries that select only one FROM component'); + } + + $fromRoot = reset($from); + $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; + $rootClass = $this->getMetadataForDqlAlias($rootAlias); + $identifierFieldName = $rootClass->getSingleIdentifierFieldName(); + + return PersisterHelper::getTypeOfField( + $identifierFieldName, + $rootClass, + $this->getQuery() + ->getEntityManager() + )[0]; + } +} diff --git a/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php b/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php index 669bff9d7a2..fc694788abe 100644 --- a/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php +++ b/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php @@ -17,13 +17,9 @@ use Doctrine\ORM\Query\AST\SimpleArithmeticExpression; use Doctrine\ORM\Query\AST\WhereClause; use Doctrine\ORM\Query\TreeWalkerAdapter; -use Doctrine\ORM\Utility\PersisterHelper; use RuntimeException; -use function array_map; -use function assert; use function count; -use function is_array; use function reset; /** @@ -80,15 +76,6 @@ public function walkSelectStatement(SelectStatement $AST) $arithmeticExpression, [new InputParameter(':' . self::PAGINATOR_ID_ALIAS)] ); - - $this->convertWhereInIdentifiersToDatabaseValue( - PersisterHelper::getTypeOfField( - $identifierFieldName, - $rootClass, - $this->_getQuery() - ->getEntityManager() - )[0] - ); } else { $expression = new NullComparisonExpression($pathExpression); } @@ -130,24 +117,4 @@ public function walkSelectStatement(SelectStatement $AST) ); } } - - private function convertWhereInIdentifiersToDatabaseValue(string $type): void - { - $query = $this->_getQuery(); - $identifiersParameter = $query->getParameter(self::PAGINATOR_ID_ALIAS); - - assert($identifiersParameter !== null); - - $identifiers = $identifiersParameter->getValue(); - - assert(is_array($identifiers)); - - $connection = $this->_getQuery() - ->getEntityManager() - ->getConnection(); - - $query->setParameter(self::PAGINATOR_ID_ALIAS, array_map(static function ($id) use ($connection, $type) { - return $connection->convertToDatabaseValue($id, $type); - }, $identifiers)); - } } diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 740c2f7f8f8..4605bdec2b9 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -2809,9 +2809,6 @@ $AST->whereClause->conditionalExpression instanceof ConditionalFactor $AST->whereClause->conditionalExpression instanceof ConditionalPrimary - - static function ($id) use ($connection, $type) { - $AST->whereClause->conditionalExpression diff --git a/psalm.xml b/psalm.xml index e152d4ae529..25c16d07b7d 100644 --- a/psalm.xml +++ b/psalm.xml @@ -229,5 +229,10 @@ + + + + + diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7820Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7820Test.php index 6f7e3567293..1864a340195 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7820Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7820Test.php @@ -16,6 +16,7 @@ use PHPUnit\Framework\Assert; use Psr\Cache\CacheItemInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\CacheItem; use function array_map; use function is_string; @@ -112,6 +113,35 @@ public function save(CacheItemInterface $item): bool $this->fetchSongLinesWithPaginator(); } + public function testPaginatorQueriesWillBeCached(): void + { + $cache = new class extends ArrayAdapter { + /** @var bool */ + private $failOnCacheMiss = false; + + public function failOnCacheMiss(): void + { + $this->failOnCacheMiss = true; + } + + public function getItem($key): CacheItem + { + $item = parent::getItem($key); + Assert::assertTrue(! $this->failOnCacheMiss || $item->isHit(), 'cache was missed'); + + return $item; + } + }; + $this->_em->getConfiguration()->setQueryCache($cache); + + // Prime the cache + $this->fetchSongLinesWithPaginator(); + + $cache->failOnCacheMiss(); + + $this->fetchSongLinesWithPaginator(); + } + private function fetchSongLinesWithPaginator(): array { $query = $this->_em->getRepository(GH7820Line::class) diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/RootTypeWalkerTest.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/RootTypeWalkerTest.php new file mode 100644 index 00000000000..a1c2ec998fd --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/RootTypeWalkerTest.php @@ -0,0 +1,54 @@ +entityManager->createQuery($dqlQuery); + $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, RootTypeWalker::class); + + self::assertSame($expectedType, $query->getSQL()); + } + + public function exampleQueries(): Generator + { + yield 'Entity with #Id column of special type' => [ + 'SELECT e.id4 FROM ' . AuxiliaryEntity::class . ' e', + 'rot13', + ]; + + yield 'Entity where #Id is a to-one relation with special type identifier' => [ + 'SELECT e FROM ' . OwningManyToOneIdForeignKeyEntity::class . ' e', + 'rot13', + ]; + + yield 'Simple integer ID in a query with a JOIN' => [ + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g', + 'integer', + ]; + } +} diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php index 20477e8ee8b..75af9dd6612 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php @@ -4,274 +4,79 @@ namespace Doctrine\Tests\ORM\Tools\Pagination; -use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Query; +use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Tools\Pagination\WhereInWalker; -use Doctrine\Tests\DbalTypes\Rot13Type; -use Doctrine\Tests\Models\ValueConversionType\AuxiliaryEntity; -use Doctrine\Tests\Models\ValueConversionType\OwningManyToOneIdForeignKeyEntity; +use Generator; /** @group DDC-1613 */ class WhereInWalkerTest extends PaginationTestCase { - protected function setUp(): void - { - parent::setUp(); - - if (! Type::hasType('rot13')) { - Type::addType('rot13', Rot13Type::class); - } - } - - public function testWhereInQueryNoWhere(): void + /** + * @dataProvider exampleQueries + */ + public function testDqlQueryTransformation(string $dql, string $expectedSql): void { - $query = $this->entityManager->createQuery( - 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g' - ); - $whereInQuery = clone $query; - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + $query = $this->entityManager->createQuery($dql); + $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); + $query->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); - self::assertEquals( - 'SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE u0_.id IN (?)', - $whereInQuery->getSQL() - ); + $result = (new Parser($query))->parse(); - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); + self::assertEquals($expectedSql, $result->getSqlExecutor()->getSqlStatements()); + self::assertEquals([0], $result->getSqlParameterPositions(WhereInWalker::PAGINATOR_ID_ALIAS)); } - public function testCountQueryMixedResultsWithName(): void + public function exampleQueries(): Generator { - $query = $this->entityManager->createQuery( - 'SELECT a, sum(a.name) as foo FROM Doctrine\Tests\ORM\Tools\Pagination\Author a' - ); - $whereInQuery = clone $query; - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + yield 'no WHERE condition' => [ + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g', + 'SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE u0_.id IN (?)', + ]; - self::assertEquals( + yield 'mixed results with name' => [ + 'SELECT a, sum(a.name) as foo FROM Doctrine\Tests\ORM\Tools\Pagination\Author a', 'SELECT a0_.id AS id_0, a0_.name AS name_1, sum(a0_.name) AS sclr_2 FROM Author a0_ WHERE a0_.id IN (?)', - $whereInQuery->getSQL() - ); - - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - } + ]; - public function testWhereInQuerySingleWhere(): void - { - $query = $this->entityManager->createQuery( - 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1' - ); - $whereInQuery = clone $query; - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - - self::assertEquals( + yield 'single WHERE condition' => [ + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1', 'SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE 1 = 1 AND u0_.id IN (?)', - $whereInQuery->getSQL() - ); - - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - } + ]; - public function testWhereInQueryMultipleWhereWithAnd(): void - { - $query = $this->entityManager->createQuery( - 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1 AND 2 = 2' - ); - $whereInQuery = clone $query; - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - - self::assertEquals( + yield 'multiple WHERE conditions with AND' => [ + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1 AND 2 = 2', 'SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE 1 = 1 AND 2 = 2 AND u0_.id IN (?)', - $whereInQuery->getSQL() - ); - - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - } + ]; - public function testWhereInQueryMultipleWhereWithOr(): void - { - $query = $this->entityManager->createQuery( - 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1 OR 2 = 2' - ); - $whereInQuery = clone $query; - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - - self::assertEquals( + yield 'multiple WHERE conditions with OR' => [ + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1 OR 2 = 2', 'SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 OR 2 = 2) AND u0_.id IN (?)', - $whereInQuery->getSQL() - ); - - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - } - - public function testWhereInQueryMultipleWhereWithMixed1(): void - { - $query = $this->entityManager->createQuery( - 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE (1 = 1 OR 2 = 2) AND 3 = 3' - ); - $whereInQuery = clone $query; - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + ]; - self::assertEquals( + yield 'multiple WHERE conditions with mixed clauses 1' => [ + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE (1 = 1 OR 2 = 2) AND 3 = 3', 'SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 OR 2 = 2) AND 3 = 3 AND u0_.id IN (?)', - $whereInQuery->getSQL() - ); - - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - } - - public function testWhereInQueryMultipleWhereWithMixed2(): void - { - $query = $this->entityManager->createQuery( - 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1 AND 2 = 2 OR 3 = 3' - ); - $whereInQuery = clone $query; - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + ]; - self::assertEquals( + yield 'multiple WHERE conditions with mixed clauses 2' => [ + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1 AND 2 = 2 OR 3 = 3', 'SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 AND 2 = 2 OR 3 = 3) AND u0_.id IN (?)', - $whereInQuery->getSQL() - ); + ]; - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - } - - public function testWhereInQueryWhereNot(): void - { - $query = $this->entityManager->createQuery( - 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE NOT 1 = 2' - ); - $whereInQuery = clone $query; - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - - self::assertEquals( + yield 'WHERE NOT condition' => [ + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE NOT 1 = 2', 'SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (NOT 1 = 2) AND u0_.id IN (?)', - $whereInQuery->getSQL() - ); - - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - } - - /** - * Arbitrary Join - */ - public function testWhereInQueryWithArbitraryJoinNoWhere(): void - { - $whereInQuery = $this->entityManager->createQuery( - 'SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c WITH p.category = c' - ); - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + ]; - self::assertEquals( + yield 'arbitary join with no WHERE' => [ + 'SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c WITH p.category = c', 'SELECT b0_.id AS id_0, b0_.author_id AS author_id_1, b0_.category_id AS category_id_2 FROM BlogPost b0_ INNER JOIN Category c1_ ON (b0_.category_id = c1_.id) WHERE b0_.id IN (?)', - $whereInQuery->getSQL() - ); - - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - } - - public function testWhereInQueryWithArbitraryJoinSingleWhere(): void - { - $whereInQuery = $this->entityManager->createQuery( - 'SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c WITH p.category = c WHERE 1 = 1' - ); - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + ]; - self::assertEquals( + yield 'arbitary join with single WHERE' => [ + 'SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN Doctrine\Tests\ORM\Tools\Pagination\Category c WITH p.category = c WHERE 1 = 1', 'SELECT b0_.id AS id_0, b0_.author_id AS author_id_1, b0_.category_id AS category_id_2 FROM BlogPost b0_ INNER JOIN Category c1_ ON (b0_.category_id = c1_.id) WHERE 1 = 1 AND b0_.id IN (?)', - $whereInQuery->getSQL() - ); - - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - } - - public function testWillReplaceBoundQueryIdentifiersWithConvertedTypesAsPerIdentifierMapping(): void - { - $whereInQuery = $this->entityManager->createQuery( - 'SELECT e.id4 FROM ' . AuxiliaryEntity::class . ' e' - ); - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 3); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, ['foo', 'bar', 'baz']); - - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - ['sbb', 'one', 'onm'] - ); - } - - public function testWillReplaceBoundQueryIdentifiersWithConvertedTypesAsPerAssociatedEntityIdentifierMapping(): void - { - $whereInQuery = $this->entityManager->createQuery( - 'SELECT e FROM ' . OwningManyToOneIdForeignKeyEntity::class . ' e' - ); - $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]); - $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 3); - $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, ['foo', 'bar', 'baz']); - - $this->assertPaginatorWhereInParameterToBe( - $whereInQuery, - ['sbb', 'one', 'onm'] - ); - } - - /** @param mixed $parameter */ - private function assertPaginatorWhereInParameterToBe(Query $query, $parameter): void - { - $query->getSQL(); // forces walker to process the query - - $boundParameter = $query->getParameter(WhereInWalker::PAGINATOR_ID_ALIAS); - - self::assertNotNull($boundParameter); - self::assertSame($parameter, $boundParameter->getValue()); + ]; } }