Skip to content

Commit 20ae0f2

Browse files
authored
Merge pull request #2611 from soyuka/micro-optimization-doctrine-distinct
Do not use DISTINCT where there are no joins
2 parents 8fb54cf + 1806c48 commit 20ae0f2

2 files changed

Lines changed: 57 additions & 14 deletions

File tree

src/Bridge/Doctrine/Orm/Extension/PaginationExtension.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
2424
use Doctrine\Common\Persistence\ManagerRegistry;
2525
use Doctrine\ORM\QueryBuilder;
26+
use Doctrine\ORM\Tools\Pagination\CountWalker;
2627
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrineOrmPaginator;
2728
use Symfony\Component\HttpFoundation\Request;
2829
use Symfony\Component\HttpFoundation\RequestStack;
@@ -143,7 +144,14 @@ public function supportsResult(string $resourceClass, string $operationName = nu
143144
*/
144145
public function getResult(QueryBuilder $queryBuilder, string $resourceClass = null, string $operationName = null, array $context = [])
145146
{
146-
$doctrineOrmPaginator = new DoctrineOrmPaginator($queryBuilder, $this->useFetchJoinCollection($queryBuilder, $resourceClass, $operationName));
147+
$query = $queryBuilder->getQuery();
148+
149+
// Only one alias, without joins, disable the DISTINCT on the COUNT
150+
if (1 === \count($queryBuilder->getAllAliases())) {
151+
$query->setHint(CountWalker::HINT_DISTINCT, false);
152+
}
153+
154+
$doctrineOrmPaginator = new DoctrineOrmPaginator($query, $this->useFetchJoinCollection($queryBuilder, $resourceClass, $operationName));
147155
$doctrineOrmPaginator->setUseOutputWalkers($this->useOutputWalkers($queryBuilder));
148156

149157
if (null === $this->requestStack) {

tests/Bridge/Doctrine/Orm/Extension/PaginationExtensionTest.php

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Doctrine\ORM\EntityManagerInterface;
2828
use Doctrine\ORM\Query;
2929
use Doctrine\ORM\QueryBuilder;
30+
use Doctrine\ORM\Tools\Pagination\CountWalker;
3031
use PHPUnit\Framework\TestCase;
3132
use Prophecy\Argument;
3233
use Symfony\Component\HttpFoundation\Request;
@@ -887,6 +888,35 @@ public function testGetResult()
887888
$this->assertInstanceOf(PaginatorInterface::class, $result);
888889
}
889890

891+
public function testGetResultWithoutDistinct()
892+
{
893+
$configuration = new Configuration();
894+
895+
$entityManagerProphecy = $this->prophesize(EntityManagerInterface::class);
896+
$entityManagerProphecy->getConfiguration()->willReturn($configuration)->shouldBeCalled();
897+
898+
$query = new Query($entityManagerProphecy->reveal());
899+
$query->setFirstResult(0);
900+
$query->setMaxResults(42);
901+
902+
$queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
903+
$queryBuilderProphecy->getRootEntities()->willReturn([])->shouldBeCalled();
904+
$queryBuilderProphecy->getAllAliases()->willReturn(['o'])->shouldBeCalled();
905+
$queryBuilderProphecy->getQuery()->willReturn($query)->shouldBeCalled();
906+
$queryBuilderProphecy->getDQLPart(Argument::that(function ($arg) {
907+
return \in_array($arg, ['having', 'orderBy', 'join'], true);
908+
}))->willReturn('')->shouldBeCalled();
909+
$queryBuilderProphecy->getMaxResults()->willReturn(42)->shouldBeCalled();
910+
$queryBuilder = $queryBuilderProphecy->reveal();
911+
912+
$result = $this->getPaginationExtensionResult(false, false, true, $queryBuilder);
913+
914+
$this->assertInstanceOf(PartialPaginatorInterface::class, $result);
915+
$this->assertInstanceOf(PaginatorInterface::class, $result);
916+
917+
$this->assertFalse($query->getHint(CountWalker::HINT_DISTINCT));
918+
}
919+
890920
/**
891921
* @group legacy
892922
* @expectedDeprecation Passing an instance of "Symfony\Component\HttpFoundation\RequestStack" as second argument of "ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\PaginationExtension" is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Pass an instance of "ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface" instead.
@@ -963,7 +993,7 @@ public function testLegacySimpleGetResult()
963993
$this->assertInstanceOf(PaginatorInterface::class, $result);
964994
}
965995

966-
private function getPaginationExtensionResult(bool $partial = false, bool $legacy = false, bool $fetchJoinCollection = true)
996+
private function getPaginationExtensionResult(bool $partial = false, bool $legacy = false, bool $fetchJoinCollection = true, QueryBuilder $queryBuilder = null)
967997
{
968998
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
969999
$resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal();
@@ -979,25 +1009,29 @@ private function getPaginationExtensionResult(bool $partial = false, bool $legac
9791009
$entityManagerProphecy = $this->prophesize(EntityManagerInterface::class);
9801010
$entityManagerProphecy->getConfiguration()->willReturn($configuration);
9811011

982-
$query = new Query($entityManagerProphecy->reveal());
983-
$query->setFirstResult(0);
984-
$query->setMaxResults(42);
985-
986-
$queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
987-
$queryBuilderProphecy->getRootEntities()->willReturn([]);
988-
$queryBuilderProphecy->getQuery()->willReturn($query);
989-
$queryBuilderProphecy->getDQLPart(Argument::that(function ($arg) {
990-
return \in_array($arg, ['having', 'orderBy', 'join'], true);
991-
}))->willReturn('');
992-
$queryBuilderProphecy->getMaxResults()->willReturn(42);
1012+
if (null === $queryBuilder) {
1013+
$query = new Query($entityManagerProphecy->reveal());
1014+
$query->setFirstResult(0);
1015+
$query->setMaxResults(42);
1016+
1017+
$queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
1018+
$queryBuilderProphecy->getRootEntities()->willReturn([])->shouldBeCalled();
1019+
$queryBuilderProphecy->getAllAliases()->willReturn([])->shouldBeCalled();
1020+
$queryBuilderProphecy->getQuery()->willReturn($query)->shouldBeCalled();
1021+
$queryBuilderProphecy->getDQLPart(Argument::that(function ($arg) {
1022+
return \in_array($arg, ['having', 'orderBy', 'join'], true);
1023+
}))->willReturn('')->shouldBeCalled();
1024+
$queryBuilderProphecy->getMaxResults()->willReturn(42)->shouldBeCalled();
1025+
$queryBuilder = $queryBuilderProphecy->reveal();
1026+
}
9931027

9941028
$paginationExtension = new PaginationExtension(
9951029
$this->prophesize(ManagerRegistry::class)->reveal(),
9961030
$resourceMetadataFactory,
9971031
$pagination
9981032
);
9991033

1000-
$args = [$queryBuilderProphecy->reveal(), null, null, ['filters' => ['partial' => $partial]]];
1034+
$args = [$queryBuilder, null, null, ['filters' => ['partial' => $partial]]];
10011035

10021036
if (!$legacy) {
10031037
$args[1] = 'Foo';
@@ -1029,6 +1063,7 @@ private function getLegacyPaginationExtensionResult(bool $partial = false, bool
10291063

10301064
$queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
10311065
$queryBuilderProphecy->getRootEntities()->willReturn([])->shouldBeCalled();
1066+
$queryBuilderProphecy->getAllAliases()->willReturn([])->shouldBeCalled();
10321067
$queryBuilderProphecy->getQuery()->willReturn($query)->shouldBeCalled();
10331068
$queryBuilderProphecy->getDQLPart(Argument::that(function ($arg) {
10341069
return \in_array($arg, ['having', 'orderBy', 'join'], true);

0 commit comments

Comments
 (0)