Skip to content

Commit 9c2e49a

Browse files
authored
Merge pull request doctrine#10970 from goetas/distinct-limit
2 parents 8ef5c80 + 55699a9 commit 9c2e49a

File tree

4 files changed

+38
-3
lines changed

4 files changed

+38
-3
lines changed

docs/en/tutorials/pagination.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,18 @@ the future.
4343
.. note::
4444

4545
``fetchJoinCollection`` argument set to ``true`` might affect results if you use aggregations in your query.
46+
47+
By using the ``Paginator::HINT_ENABLE_DISTINCT`` you can instruct doctrine that the query to be executed
48+
will not produce "duplicate" rows (only to-one relations are joined), thus the SQL limit will work as expected.
49+
In this way the `DISTINCT` keyword will be omitted and can bring important performance improvements.
50+
51+
.. code-block:: php
52+
53+
<?php
54+
use Doctrine\ORM\Tools\Pagination\Paginator;
55+
56+
$dql = "SELECT u, p FROM User u JOIN u.mainPicture p";
57+
$query = $entityManager->createQuery($dql)
58+
->setHint(Paginator::HINT_ENABLE_DISTINCT, false)
59+
->setFirstResult(0)
60+
->setMaxResults(100);

lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,14 @@ public function walkSelectStatement(SelectStatement $AST)
5050
throw new RuntimeException('Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator.');
5151
}
5252

53-
$this->_getQuery()->setHint(
53+
$query = $this->_getQuery();
54+
55+
$query->setHint(
5456
self::IDENTIFIER_TYPE,
5557
Type::getType($rootClass->fieldMappings[$identifier]['type'])
5658
);
5759

58-
$this->_getQuery()->setHint(self::FORCE_DBAL_TYPE_CONVERSION, true);
60+
$query->setHint(self::FORCE_DBAL_TYPE_CONVERSION, true);
5961

6062
$pathExpression = new PathExpression(
6163
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
@@ -66,7 +68,7 @@ public function walkSelectStatement(SelectStatement $AST)
6668
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
6769

6870
$AST->selectClause->selectExpressions = [new SelectExpression($pathExpression, '_dctrn_id')];
69-
$AST->selectClause->isDistinct = true;
71+
$AST->selectClause->isDistinct = ($query->getHints()[Paginator::HINT_ENABLE_DISTINCT] ?? true) === true;
7072

7173
if (! isset($AST->orderByClause)) {
7274
return;

lib/Doctrine/ORM/Tools/Pagination/Paginator.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class Paginator implements Countable, IteratorAggregate
3434
{
3535
use SQLResultCasing;
3636

37+
public const HINT_ENABLE_DISTINCT = 'paginator.distinct.enable';
38+
3739
/** @var Query */
3840
private $query;
3941

tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryWalkerTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Doctrine\ORM\Query;
88
use Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker;
9+
use Doctrine\ORM\Tools\Pagination\Paginator;
910

1011
/** @group DDC-1613 */
1112
class LimitSubqueryWalkerTest extends PaginationTestCase
@@ -24,6 +25,21 @@ public function testLimitSubquery(): void
2425
);
2526
}
2627

28+
public function testHintCanDisableDistinct(): void
29+
{
30+
$dql = 'SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p JOIN p.category c JOIN p.author a';
31+
$query = $this->entityManager->createQuery($dql);
32+
$limitQuery = clone $query;
33+
34+
$limitQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [LimitSubqueryWalker::class]);
35+
$limitQuery->setHint(Paginator::HINT_ENABLE_DISTINCT, false);
36+
37+
self::assertEquals(
38+
'SELECT m0_.id AS id_0 FROM MyBlogPost m0_ INNER JOIN Category c1_ ON m0_.category_id = c1_.id INNER JOIN Author a2_ ON m0_.author_id = a2_.id',
39+
$limitQuery->getSQL()
40+
);
41+
}
42+
2743
public function testLimitSubqueryWithSort(): void
2844
{
2945
$dql = 'SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p JOIN p.category c JOIN p.author a ORDER BY p.title';

0 commit comments

Comments
 (0)