Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Paginator-internal query cacheable in the query cache #10444

Merged
merged 2 commits into from
Feb 8, 2023

Conversation

mpdude
Copy link
Contributor

@mpdude mpdude commented Jan 23, 2023

Make the Paginator-internal query (... WHERE ... IN (id, id2, id3...)) cacheable in the query cache again.

Background

When the Paginator creates the internal subquery that does the actual result limiting, it has to take DBAL type conversions for the identifier column of the paginated root entity into account (#7820, fixed in #7821).

In order to perform this type conversion, we need to know the DBAL type class for the root entity's #[Id], and we have to figure it out based on a given (arbitrary) DQL query. This requires DQL parsing and inspecting the AST, so #7821 placed the conversion code in the WhereInWalker where all the necessary information is available.

The problem is that type conversion has to happen every time the paginator is run, but the query that results from running WhereInWalker would be kept in the query cache. This was reported in #7837 and fixed by #7865, by making this particular query expire every time. The query must not be cached, since the necessary ID type conversion happens as a side-effect of running the WhereInWalker.

Current status

The Paginator internal query that uses WhereInWalker has its DQL re-parsed and transformed in every request.

Suggested solution

This PR moves the code that determines the DBAL type out of WhereInWalker into a dedicated SQL walker class, RootTypeWalker.

RootTypeWalker uses a hack  clever trick to report the type back: It sets the type as the resulting "SQL" string. The benefit is that RootTypeWalker results can be cached in the query cache themselves. Only the first time a given DQL query has to be paginated, we need to run this walker to find out the root entity's ID type. After that, the type will be returned from the query cache.

With the type information being provided, Paginator can take care of the necessary conversions by itself. This happens every time the Paginator is used.

The internal query that uses WhereInWalker can be cached again since it no longer has side effects.

@derrabus derrabus marked this pull request as draft January 23, 2023 08:23
@derrabus
Copy link
Member

(to be written)

Marking as draft for now. 🙂

@mpdude mpdude changed the title Make Paginator-internal query cacheable in the query cache [RFC] Make Paginator-internal query cacheable in the query cache Jan 23, 2023
@mpdude
Copy link
Contributor Author

mpdude commented Jan 23, 2023

Marking as draft for now. 🙂

@derrabus will you ignore PRs as long as they are drafts, and should I switch back to "ready for review" when I would like to get feedback (even if it is not 💯 ready yet)?

@derrabus
Copy link
Member

@derrabus will you ignore PRs as long as they are drafts

I can ignore non-draft PRs just as good as drafts, no worries. Don't hack my backlog. ✌🏻

@mpdude mpdude force-pushed the paginator-dql-cacheable branch 2 times, most recently from 648c576 to 54cf0ec Compare January 23, 2023 17:11
);
}

public function testWillReplaceBoundQueryIdentifiersWithConvertedTypesAsPerIdentifierMapping(): void
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer the WhereInWalker's responsibility

);
}

public function testWillReplaceBoundQueryIdentifiersWithConvertedTypesAsPerAssociatedEntityIdentifierMapping(): void
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer the WhereInWalker's responsibility

@mpdude mpdude force-pushed the paginator-dql-cacheable branch 3 times, most recently from 66a05c2 to 643a5cf Compare January 23, 2023 17:25
@mpdude mpdude marked this pull request as ready for review January 23, 2023 17:28
@mpdude
Copy link
Contributor Author

mpdude commented Jan 23, 2023

Ready for review. Please advise how to resolve the Psalm issue.

@mpdude mpdude changed the title [RFC] Make Paginator-internal query cacheable in the query cache Make Paginator-internal query cacheable in the query cache Jan 23, 2023
@greg0ire
Copy link
Member

Rebasing on ed56f42 should fix the issue.

@mpdude
Copy link
Contributor Author

mpdude commented Jan 23, 2023

Computer says no.

Error: lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php:0:0: UnusedBaselineEntry: Baseline for issue "MissingClosureReturnType" has 1 extra entry. (see https://psalm.dev/316)

Shall I update the baseline in/for this PR? That's probably gonna cause a merge conflict down the road.

@greg0ire
Copy link
Member

Shall I update the baseline in/for this PR? That's probably gonna cause a merge conflict down the road.

Oh right, didn't realised but yes, there is one less entry. I wouldn't worry about the merge conflicts.

Make the `Paginator`-internal query (`... WHERE ... IN (id, id2,
id3...)`) cacheable in the query cache again.

When the Paginator creates the internal subquery that does the actual
result limiting, it has to take DBAL type conversions for the identifier
column of the paginated root entity into account (doctrine#7820, fixed in
 doctrine#7821).

In order to perform this type conversion, we need to know the DBAL type
class for the root entity's `#[Id]`, and we have to figure it out based
on a given (arbitrary) DQL query. This requires DQL parsing and
inspecting the AST, so doctrine#7821 placed the conversion code in the
`WhereInWalker` where all the necessary information is available.

The problem is that type conversion has to happen every time the
paginator is run, but the query that results from running
`WhereInWalker` would be kept in the query cache. This was reported in
 doctrine#7837 and fixed by doctrine#7865, by making this particular query expire every
time. The query must not be cached, since the necessary ID type
conversion happens as a side-effect of running the `WhereInWalker`.

The Paginator internal query that uses `WhereInWalker` has its DQL
re-parsed and transformed in every request.

This PR moves the code that determines the DBAL type out of
`WhereInWalker` into a dedicated SQL walker class, `RootTypeWalker`.

`RootTypeWalker` uses a ~hack~  clever trick to report the type back: It
sets the type as the resulting "SQL" string. The benefit is that
`RootTypeWalker` results can be cached in the query cache themselves.
Only the first time a given DQL query has to be paginated, we need to
run this walker to find out the root entity's ID type. After that, the
type will be returned from the query cache.

With the type information being provided, `Paginator` can take care of
the necessary conversions by itself. This happens every time the
Paginator is used.

The internal query that uses `WhereInWalker` can be cached again since
it no longer has side effects.
greg0ire
greg0ire previously approved these changes Feb 5, 2023
@mpdude
Copy link
Contributor Author

mpdude commented Feb 5, 2023

@greg0ire Class is final now, please re-approve

@mpdude
Copy link
Contributor Author

mpdude commented Feb 7, 2023

🎉

@greg0ire greg0ire merged commit 022b945 into doctrine:2.14.x Feb 8, 2023
@greg0ire greg0ire added this to the 2.14.2 milestone Feb 8, 2023
@greg0ire
Copy link
Member

greg0ire commented Feb 8, 2023

Thanks @mpdude ! Right after clicking merged, I noticed the target branch. Not sure it should really have been 2.14.x but oh well…

@mpdude
Copy link
Contributor Author

mpdude commented Feb 8, 2023

I think that’s fine. I see no risk for breakage and the same argument as in #8797 (comment) applies

@mpdude mpdude deleted the paginator-dql-cacheable branch February 8, 2023 06:58
@mpdude
Copy link
Contributor Author

mpdude commented Feb 8, 2023

Maybe we can also bring #8797 to an end? It’s closely related, and I’ll resolve merge conflicts shortly.

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

Successfully merging this pull request may close these issues.

4 participants