Skip to content

Make Paginator use simpler queries when the paginated query allows it.#12518

Open
d-ph wants to merge 1 commit into
doctrine:3.7.xfrom
d-ph:3.7.x-paginator-auto-detect-lack-of-sql-joins
Open

Make Paginator use simpler queries when the paginated query allows it.#12518
d-ph wants to merge 1 commit into
doctrine:3.7.xfrom
d-ph:3.7.x-paginator-auto-detect-lack-of-sql-joins

Conversation

@d-ph

@d-ph d-ph commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

This PR is a follow-up from the following closed/abandoned PR: #11595. It now targets the 3.7.x git branch, and addresses all the comments from that past PR (apart from "please update the docs", which is explained further below).

To recap: this PR attempts to "action" the following ticket: #8278.

In particular: it implements all the use cases that stof mentioned.

I would like to get a "code sign-off" before I go and update the docs about the Paginator. In particular, I would like to get a "looks good" from stof, and greg0ire (or any other ORM maintainer).

Things that might need further discussion:

  1. The "auto-detection" code spans 3 static functions in the PaginatorQuery trait, and takes over 600 lines. It's about 70% of the PaginatorQuery source file. If preferred, I can extract that code to another class.

  2. In PaginatorQuery::searchEntityAliasesUsageInASTNode(), php reflection is used to "walk" AST expressions, in search for specific "entity aliases usage" (namely: whether ToMany JOIN aliases are used anywhere within those expressions). The function recursively lists all public properties of AST\Node classes, and seeks for instances of Query\AST\PathExpression. I believe that the only "contestable" fact here is that php reflection is used. I would however say that the code makes 0 assumptions about the reflected classes, making it quite "safe" to run in this context.

  3. I'd like to note that the "auto-detection" treats the result/count "output walkers" (i.e. the current default "code execution path") as the "fallback" if anything during the auto-detection procedure "looks strange". In particular, the auto-detection does NOT attempt to ascertain whether the output walkers can be run. I personally consider this alright.

  4. Stof, what follows from the point above, is that the code does NOT check whether the LimitSubqueryOutputWalker.php can be run (despite your listing (in one of your past comments) the conditions under which that output walker cannot be run). Again: I consider this acceptable but let me know if you have any further comments here.

  5. A new query hint was added: CountWalker::HINT_DROP_GROUP_BY_CLAUSE. It's needed to handle a few "GROUP BY" cases in the CountWalker. I "docblock'd" that hint as @internal because I believe that there are already too many query hints in the Paginator. But if it should be "opened to the public", then this can be discussed further.

  6. The auto-detection does not allow to run queries with GROUP BY by the CountWalker (beside a few exceptions).

  7. Less obvious fact: the CountWalker.php supports root entities with composite primary keys, but only if the COUNT(*) branch in its code is executed (as opposed to the COUNT(DISTINCT id) code branch).

  8. Less obvious fact: Paginator currently supports root entities with composite primary keys, only if the "fast result query" is run.

  9. I created the following directory: src/Tools/Pagination/ValueObject. Note that there already exists the following dir: tests/Tests/Models/ValueObjects (i.e. in plural). I generally don't use plurals in namespaces (or class names). If however naming consistency is desired, I can rename that directory to: src/Tools/Pagination/ValueObjects.

  10. Stof, just a quick note that I ensured that all the cases that you listed in the following past comment, are implemented / checked for:

Counting queries

  • all counting queries are incompatible with queries selecting multiple entities in the FROM (so the Paginator is actually incompatible with them).
  • the fast counting query (tree-walker one) is incompatible with entities using a composite primary key
  • the fast counting query is incompatible with queries using a HAVING clause
  • the fast counting query is compatible with toMany joins when using CountWalker::HINT_DISTINCT as it will count distinct values (compatibility with GROUP BY clause is probably partial in the case of toMany join as I think it breaks if one entity can end up in multiple groups due to including a field of the toMany relation in the grouping). This is the reason why the Paginator sets this hint by default.
  • the fast counting query is not compatible with toMany joins without CountWalker::HINT_DISTINCT (but this can go even faster for compatible queries)

Result fetching

  • the fast result fetching query (using a limit on the original query without changing it) is:
    • compatible with queries not using toMany joins
    • compatible with queries using toMany joins and GROUP BY when no fields from the grouping condition is dependant on the ToMany join (i.e. is part of the joined entity or of other entities joined again from that one)
    • incompatible with queries selecting data from toMany joins
    • compatible with queries doing toMany joins without selecting data from it (i.e. using it only for filtering in the WHERE clause or for aggregate functions) if the query does a SELECT DISTINCT
  • the tree-walker fetching query does not support entities using a relation as primary key
  • the tree walker fetching query does not support queries using a field from a toMany relation in its ORDER BY clause when using a limit (queries without a limit always use the fast result fetching query anyway)

NOTE: I DID NOT IMPLEMENT THE FOLLOWING, as mentioned in point 4.

  • walker-based fetching queries do not support entities using a composite primary key
  • walker-based fetching queries do not support queries selecting multiple entities in the FROM
  1. The code is heavily inline-docblocked. This is how I usually write code but I recognise that there are "other schools of thought" in this matter. I'm fine to drop some of the comments if needed, but please tell me which.

  2. In PaginatorAutoDetectionTest.php, there are test cases that assert an exception being thrown. There are also a few cases which produce a result that I personally did not expect (but those results are what the already released code already does). Please take note of those uses cases and let me know if they are "not acceptable".

Thank you.

cc @stof


Please note that phpstan is failing (at least on my local machine) for reasons that are not related to the code in this PR.

@d-ph

d-ph commented Jun 27, 2026

Copy link
Copy Markdown
Contributor Author

Looks like some tests are failing because some of the test DQL queries are not supported in some databases.

  1. SQLite supports all the queries.
  2. PostgreSQL doesn't seem to support some of the GROUP BY, ORDER BY and HAVING queries.
  3. MariaDB seems to support all the queries.
  4. Surprisingly, MySQL seems to not support some of the queries (the errors consistently say: this is incompatible with sql_mode=only_full_group_by)

Can I do the following please?

  1. For PostgreSQL, skip the DQL queries that it doesn't support.
  2. For MySQL: it seems that it's a matter of a single setting in the MySQL test server. So, either that setting is adjusted (project-wide), or I skip the failing DQL queries in MySQL. Please tell me which is preferred.

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant