Skip to content

Commit 310fe1c

Browse files
committed
Clone query hints and parameters in LimitSubqueryOutputWalker constructor
This fixes a bug that arises when using Pagination and an entity relation is mapped with fetch-mode EAGER but setFetchMode LAZY (or anything that is not EAGER) has been used on the query. If the query use WITH condition, an exception is incorrectly raised (Associations with fetch-mode=EAGER may not be using WITH conditions). The class LimitSubqueryOutputWalker clones the query, but not its parameters and hints, so the generated subquery does not know that fetch-mode has been overridden. Fixes doctrine#11741
1 parent 19912de commit 310fe1c

File tree

3 files changed

+43
-6
lines changed

3 files changed

+43
-6
lines changed

src/Tools/Pagination/LimitSubqueryOutputWalker.php

+13-6
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,24 @@ public function __construct($query, $parserResult, array $queryComponents)
105105
$this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
106106
$this->rsm = $parserResult->getResultSetMapping();
107107

108-
$query = clone $query;
108+
$cloneQuery = clone $query;
109+
110+
$cloneQuery->setParameters(clone $query->getParameters());
111+
$cloneQuery->setCacheable(false);
112+
113+
foreach ($query->getHints() as $name => $value) {
114+
$cloneQuery->setHint($name, $value);
115+
}
109116

110117
// Reset limit and offset
111-
$this->firstResult = $query->getFirstResult();
112-
$this->maxResults = $query->getMaxResults();
113-
$query->setFirstResult(0)->setMaxResults(null);
118+
$this->firstResult = $cloneQuery->getFirstResult();
119+
$this->maxResults = $cloneQuery->getMaxResults();
120+
$cloneQuery->setFirstResult(0)->setMaxResults(null);
114121

115-
$this->em = $query->getEntityManager();
122+
$this->em = $cloneQuery->getEntityManager();
116123
$this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy();
117124

118-
parent::__construct($query, $parserResult, $queryComponents);
125+
parent::__construct($cloneQuery, $parserResult, $queryComponents);
119126
}
120127

121128
/**

tests/Tests/ORM/Functional/EagerFetchCollectionTest.php

+12
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
use Doctrine\Common\Collections\ArrayCollection;
88
use Doctrine\ORM\Mapping as ORM;
99
use Doctrine\ORM\Query\QueryException;
10+
use Doctrine\ORM\Tools\Pagination\Paginator;
1011
use Doctrine\Tests\OrmFunctionalTestCase;
1112

1213
use function count;
14+
use function iterator_to_array;
1315

1416
class EagerFetchCollectionTest extends OrmFunctionalTestCase
1517
{
@@ -96,6 +98,16 @@ public function testSubselectFetchJoinWithAllowedWhenOverriddenNotEager(): void
9698
$this->assertIsString($query->getSql());
9799
}
98100

101+
public function testSubselectFetchJoinWithAllowedWhenOverriddenNotEagerPaginator(): void
102+
{
103+
$query = $this->_em->createQuery('SELECT o, c FROM ' . EagerFetchOwner::class . ' o JOIN o.children c WITH c.id = 1');
104+
$query->setMaxResults(1);
105+
$query->setFetchMode(EagerFetchChild::class, 'owner', ORM\ClassMetadata::FETCH_LAZY);
106+
107+
$paginator = new Paginator($query, true);
108+
$this->assertIsArray(iterator_to_array($paginator));
109+
}
110+
99111
public function testEagerFetchWithIterable(): void
100112
{
101113
$this->createOwnerWithChildren(2);

tests/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php

+18
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,24 @@ private function replaceDatabasePlatform(AbstractPlatform $platform): void
4646
$this->entityManager->getConnection()->setDatabasePlatform($platform);
4747
}
4848

49+
public function testSubqueryClonedCompletely(): void
50+
{
51+
$query = $this->createQuery('SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p');
52+
$query->setParameter('dummy-param', 123);
53+
$query->setHint('dummy-hint', 'dummy-value');
54+
$query->setCacheable(true);
55+
56+
$walker = new LimitSubqueryOutputWalker($query, new Query\ParserResult(), []);
57+
58+
self::assertNotSame($query, $walker->getQuery());
59+
self::assertTrue($walker->getQuery()->hasHint('dummy-hint'));
60+
self::assertSame('dummy-value', $walker->getQuery()->getHint('dummy-hint'));
61+
self::assertNotSame($query->getParameters(), $walker->getQuery()->getParameters());
62+
self::assertInstanceOf(Query\Parameter::class, $param = $walker->getQuery()->getParameter('dummy-param'));
63+
self::assertSame(123, $param->getValue());
64+
self::assertFalse($walker->getQuery()->isCacheable());
65+
}
66+
4967
public function testLimitSubquery(): void
5068
{
5169
$query = $this->createQuery('SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p JOIN p.category c JOIN p.author a');

0 commit comments

Comments
 (0)