Skip to content

Commit

Permalink
Clone query hints and parameters in LimitSubqueryOutputWalker constru…
Browse files Browse the repository at this point in the history
…ctor

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 #11741
  • Loading branch information
aprat84 committed Feb 6, 2025
1 parent 19912de commit 52b1cdb
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 6 deletions.
19 changes: 13 additions & 6 deletions src/Tools/Pagination/LimitSubqueryOutputWalker.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,24 @@ public function __construct($query, $parserResult, array $queryComponents)
$this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
$this->rsm = $parserResult->getResultSetMapping();

$query = clone $query;
$cloneQuery = clone $query;

$cloneQuery->setParameters(clone $query->getParameters());
$cloneQuery->setCacheable(false);

foreach ($query->getHints() as $name => $value) {
$cloneQuery->setHint($name, $value);
}

// Reset limit and offset
$this->firstResult = $query->getFirstResult();
$this->maxResults = $query->getMaxResults();
$query->setFirstResult(0)->setMaxResults(null);
$this->firstResult = $cloneQuery->getFirstResult();
$this->maxResults = $cloneQuery->getMaxResults();
$cloneQuery->setFirstResult(0)->setMaxResults(null);

$this->em = $query->getEntityManager();
$this->em = $cloneQuery->getEntityManager();
$this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy();

parent::__construct($query, $parserResult, $queryComponents);
parent::__construct($cloneQuery, $parserResult, $queryComponents);
}

/**
Expand Down
12 changes: 12 additions & 0 deletions tests/Tests/ORM/Functional/EagerFetchCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Doctrine\Tests\OrmFunctionalTestCase;

use function count;
use function iterator_to_array;

class EagerFetchCollectionTest extends OrmFunctionalTestCase
{
Expand Down Expand Up @@ -96,6 +98,16 @@ public function testSubselectFetchJoinWithAllowedWhenOverriddenNotEager(): void
$this->assertIsString($query->getSql());
}

public function testSubselectFetchJoinWithAllowedWhenOverriddenNotEagerPaginator(): void
{
$query = $this->_em->createQuery('SELECT o, c FROM ' . EagerFetchOwner::class . ' o JOIN o.children c WITH c.id = 1');
$query->setMaxResults(1);
$query->setFetchMode(EagerFetchChild::class, 'owner', ORM\ClassMetadata::FETCH_LAZY);

$paginator = new Paginator($query, true);
$this->assertIsArray(iterator_to_array($paginator));
}

public function testEagerFetchWithIterable(): void
{
$this->createOwnerWithChildren(2);
Expand Down
16 changes: 16 additions & 0 deletions tests/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ private function replaceDatabasePlatform(AbstractPlatform $platform): void
$this->entityManager->getConnection()->setDatabasePlatform($platform);
}

public function testSubqueryClonedCompletely(): void
{
$query = $this->createQuery('SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p');
$query->setParameter('dummy-param', 123);
$query->setHint('dummy-hint', 'dummy-value');
$query->setCacheable(true);

$walker = new LimitSubqueryOutputWalker($query, new Query\ParserResult(), []);

self::assertTrue($walker->getQuery()->hasHint('dummy-hint'));
self::assertSame('dummy-value', $walker->getQuery()->getHint('dummy-hint'));
self::assertInstanceOf(Query\Parameter::class, $param = $walker->getQuery()->getParameter('dummy-param'));
self::assertSame(123, $param->getValue());
self::assertFalse($walker->getQuery()->isCacheable());
}

public function testLimitSubquery(): void
{
$query = $this->createQuery('SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p JOIN p.category c JOIN p.author a');
Expand Down

0 comments on commit 52b1cdb

Please sign in to comment.