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

[4.0] Query builder has no way to reset query parts once set #6186

Closed
mbabker opened this issue Oct 12, 2023 · 17 comments
Closed

[4.0] Query builder has no way to reset query parts once set #6186

mbabker opened this issue Oct 12, 2023 · 17 comments

Comments

@mbabker
Copy link
Contributor

mbabker commented Oct 12, 2023

Seeing #6179 land, which deprecates getQueryPart() and getQueryParts() in the upcoming 3.8 release, alerted me to the PR it is based on (#3836) which removed those methods and their reset counterparts (resetQueryPart() and resetQueryParts()) in 4.0. As a side effect of the changes from #3836, it is now no longer possible to remove something from the query builder once it's added in.

Example Use Case

In Pagerfanta, there is an adapter for a DBAL query builder which performs a SELECT query against a single table. In this adapter, the Closure which modifies the query builder to change the query to a SELECT COUNT()... style query uses the query builder's resetQueryPart() method to clear the ORDER BY part from the query. This behavior was introduced as a bug fix to resolve an issue with an incorrect query being created on certain platforms.

@greg0ire
Copy link
Member

Cc @BenMorel @morozov

@derrabus
Copy link
Member

Would introducing a method resetOrderBy() (possibly more fir other query parts) solve this issue?

@morozov
Copy link
Member

morozov commented Oct 13, 2023

It should and we can do that. But at the same time, it feels like resetting a part of the query is a workaround for poor design on the application side. If the application doesn't need the query results to be ordered, why does it specify the order to begin with?

Instead of the current logic as I understand it:

$builder->orderBy($order);

if (weNeedCount()) {
    $builder->resetOrderBy();
}

It should behave like this:

if (! weNeedCount()) {
    $builder->orderBy($order);
}

@derrabus
Copy link
Member

If the application doesn't need the query results to be ordered, why does it specify the order to begin with?

My understanding of the issue is that we're talking about a component that applies pagination to an arbitrary query represented as a QueryBuilder object. That paginator needs to know, how many rows the unlimited query would yield, so it clones the QB, resets the "order by" part and replaces the selected columns with COUNT(*).

It is totally reasonable that the original QB contains the "order by" part and I find it okay to make that part resetable.

@mbabker
Copy link
Contributor Author

mbabker commented Oct 13, 2023

If the application doesn't need the query results to be ordered, why does it specify the order to begin with?

On the application side, I agree wholeheartedly.

My understanding of the issue is that we're talking about a component that applies pagination to an arbitrary query represented as a QueryBuilder object. That paginator needs to know, how many rows the unlimited query would yield, so it clones the QB, resets the "order by" part and replaces the selected columns with COUNT(*).

Exactly this. In the Symfony ecosystem, there are Pagerfanta and KnpPaginatorBundle (the KNP package also provides a DBAL integration that uses a similar workflow and is hit by this same deprecation) which provide pagination components for applications to use, both of which have some sort of abstraction layer which lets them integrate with a number of libraries. In that abstraction layer, both have a mechanism for taking a SELECT query for a paginated data set and converting that to a SELECT COUNT(*) style query to get the total item count, most commonly used to render a pagination component on a site's frontend.

@morozov
Copy link
Member

morozov commented Oct 13, 2023

I don't mind the missing API being implemented, please feel free to file a pull request.

@derrabus
Copy link
Member

Up for a PR, @mbabker? If yes, please target 3.8.x.

@BenMorel
Copy link
Contributor

I don't mind either. I can work on a PR, unless @mbabker wants to do it!

@mbabker
Copy link
Contributor Author

mbabker commented Oct 13, 2023

@BenMorel If you beat me to it, then go for it. Otherwise I can get to it at some point over the weekend (got other query issues to sort out for work today 😅 ).

@BenMorel
Copy link
Contributor

@derrabus I'm not sure if targeting 3.8.x is a good idea:

  • we can indeed add resetFrom() resetJoin() etc. methods in 3.8.x (but porting them to 4.0.x will not be straightforward)
  • we could add getFrom(), getJoin() etc. in 3.8.x, but their return type would be very different from the return type they'll have in 4.0.x, as 3.8.x lacks value objects such as From and Join

Regarding the second point, I don't think implementing these methods will help consumers of the QueryBuilder, as they will have to change their code anyway if they use these methods in 3.8.x then upgrade to 4.0.x. This could possibly make the upgrade even more painful than not having these methods in the first place.

I would suggest to just implement them in 4.0.x, and document the upgrade path from (get|reset)QueryPart() to (get|reset)(From|Join|OrderBy|...)() in UPGRADE.md.

@BenMorel
Copy link
Contributor

Another solution would be to backport the From / Join / ... value objects from 4.0.x to 3.8.x, and provide a forward-compatible API in getFrom() / getJoin() / etc.

Thoughts, @morozov?

@derrabus
Copy link
Member

we can indeed add resetFrom() resetJoin() etc. methods in 3.8.x

So let's do that.

(but porting them to 4.0.x will not be straightforward)

Can you elaborate?

we could add getFrom(), getJoin()

I don't think that we need those.

they will have to change their code anyway if they use these methods in 3.8.x then upgrade to 4.0.x.

This does not apply to the proposed reset*() methods, does it?

@derrabus derrabus linked a pull request Oct 14, 2023 that will close this issue
@derrabus derrabus removed a link to a pull request Oct 14, 2023
@BenMorel
Copy link
Contributor

(but porting them to 4.0.x will not be straightforward)

Can you elaborate?

I mean, merging into 4.0.x will require changing the implementation. Not that big of a deal.

we could add getFrom(), getJoin()

I don't think that we need those.

Don't paginators need to introspect the QueryBuilder in order to perform their job?

they will have to change their code anyway if they use these methods in 3.8.x then upgrade to 4.0.x.

This does not apply to the proposed reset*() methods, does it?

No, only to getters.

@derrabus
Copy link
Member

Okay, we have a resetOrderBy() method now. Should we add more resetters for the sake of consistency or shall we leave it as it is?

@derrabus derrabus removed this from the 3.8.0 milestone Oct 14, 2023
@BenMorel
Copy link
Contributor

BenMorel commented Oct 14, 2023

Should we add more resetters for the sake of consistency

Yes, I think they should have been added in the same PR!

@derrabus
Copy link
Member

Following up on @mbabker's work, I've created #6193. I don't think that we need replacements for every part, tbh.

Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants