-
Notifications
You must be signed in to change notification settings - Fork 3.6k
HHH-19364 - Introduce QuerySpecification #10034
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
Conversation
hibernate-core/src/main/java/org/hibernate/query/DynamicSelection.java
Outdated
Show resolved
Hide resolved
/** | ||
* Finalize the building and create the {@linkplain SelectionQuery} instance. | ||
*/ | ||
SelectionQuery<T> createQuery(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also need something here to replace:
KeyedResultList<R> getKeyedResultList(KeyedPage<R> page);
But just putting it on this interface doesn't work at all, because there's no way to bind arguments to query parameters.
So the solution has to be to split KeyedPage
up so that the keyDefinition
is no longer part of the KeyedPage
object. I already convinced them to make a similar change in the Data spec itself (before 1.0). So this is something I had been considering doing anyway. [The current definition of the KeyedPage
object still sorta reflects the previous design the Data group had before I got involved.]
So I guess it would mean adding this method:
DynamicSelection<T> setKeyDefinition(List<Order<? super T>> keyDefinition);
which sets up the order and the restrictions required. This implies changing KeyBasedPagination.paginate()
to set up parameters instead of just using the keys directly.
Now what would this look like from the user point of view? I guess it would be:
KeyedResultList<Person> firstPage =
session.createTailoredSelection("from Person where name like :name", Person.class)
.setOrder(Order.asc(Person_.ssn)) // not setKeyDefinition()
.createQuery()
.setParameter("name", name)
.setPage(Page.first(5)) // not setKeyedPage()
.getKeyedResultList();
KeyedResultList<Person> nextPage =
nextsession.createTailoredSelection("from Person where name like :name", Person.class)
.setKeyDefinition(Order.asc(Person_.ssn))
.createQuery()
.setParameter("name", name)
.setKeyedPage(firstPage.getNextPage())
.getKeyedResultList();
Note that setKeyedPage()
now binds the query parameters we created during the call to setKeyDefinition()
.
So, OK, fine, what is the blast radius here:
- Remove
keyDefinition
fromKeyedPage
- Add
setKeyDefinition()
to this new API, and change the implementation ofKeyBasedPagination.paginate()
to use parameter objects - Replace
SelectionQuery.getKeyedResultList(KeyedPage)
withsetKeyedPage(KeyedPage)
which does the parameter bindings andgetKeyedResultList()
which constructs the `KeyedResultList. - Update Hibernate Processor.
I think that is all more or less OK. The API is arguably somewhat less ergonomic, but it's also more consistent, and it goes in a direction I had already been playing with in my head.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could bind the keyset values through our JPA Criteria value()
API or simply bind the parameters automatically when invoking createQuery()
, so the ergonomics could stay the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could bind the keyset values through our JPA Criteria
value()
API or simply bind the parameters automatically when invokingcreateQuery()
, so the ergonomics could stay the same.
So Steve and I had talked about doing it something approximately like this:
KeyedResultList<Person> firstPage =
session.createTailoredSelection("from Person where name like :name", Person.class)
.createQuery(Page.first(5).keyedBy(Order.asc(Person_.ssn)))
.setParameter("name", name)
.getKeyedResultList();
KeyedResultList<Person> nextPage =
nextsession.createTailoredSelection("from Person where name like :name", Person.class)
.createQuery(firstPage.getNextPage())
.setParameter("name", name)
.getKeyedResultList();
That's still an option, certainly, but I guess I feel like it sits in a bit of an uncomfortable middle ground between the other two approaches, being neither one nor the other.
The big problem I have with that is it would not be legit to call .getKeyedResultList()
on a Query
which had not been created in a very specific way. Whereas with the approach I proposed above, you could essentially (perhaps with some restrictions) call getKeyedResultList()
on any query with an order.
Of course we could solve that "big problem" by introducing KeyedSelectionQuery
as a subtype of SelectionQuery
and perhaps that's worth considering.
I guess the question there really is: which direction do we want to take KeyedPage
?
- Do we want it to carry the key definition, or
- do we want to decouple it like in the Data spec?
I have not really been able to make up my mind about that one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, there's another option, which I had thought about in the context of the spec, but which didn't really make sense for repositories, but might indeed be the right thing here. The idea is to have KeyedPage
carry around the query parameters.
So the usage might look like this:
KeyedResultList<Person> firstPage =
session.createTailoredSelection("from Person where name like :name", Person.class)
.setOrder(Order.asc(Person_.ssn))
.createQuery()
.setParameter("name", name)
.setPage(Page.first(5))
.getKeyedResultList();
KeyedResultList<Person> nextPage =
firstPage.getNextPage()
.getKeyedResultList(nextsession);
And of course you would not even need to use setOrder()
, and could just write:
KeyedResultList<Person> firstPage =
session.createSelectionQuery("from Person where name like :name order by ssn", Person.class)
.setParameter("name", name)
.setPage(Page.first(5))
.getKeyedResultList();
KeyedResultList<Person> nextPage =
firstPage.getNextPage()
.getKeyedResultList(nextsession);
This way, we would not need to expose anything about key-based pagination on the new API, all we would need is SelectionQuery.getKeyedResultList()
.
I think this is a good way forward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is to have KeyedPage carry around the query parameters.
I don't think this will work for the very common use case of HTTP endpoints, where you have to serialize/deserialize this "cursor" somehow. From looking at the code, it feels like KeyedResultList
is something that is actively connected to a query, which would be very heavyweight to serialize and from a security PoV should not be deserialized.
Of course we could solve that "big problem" by introducing KeyedSelectionQuery as a subtype of SelectionQuery and perhaps that's worth considering.
A dedicated KeyedSelectionQuery
sounds more appropriate to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or how about:
interface DynamicSelection {
...
SelectionQuery createQuery();
KeyedResultList getKeyedResultList(KeyedPage keyedPage);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
though perhaps the
Limit
andPage
forms are overkill.
We don't have Limit
in Hibernate APIs. That's just setMaxResults()
for us.
And setPage()
is perfectly fine where it is right now. It's just a convenient way to call setMaxResults()
/setFirstResult()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but those comments are more about KeyedPage
/ KeyedResultList
.
What I absolutely do not want is to have this stuff on SelectionQuery
and have the expectation that it mutates the internal state of that SelectionQuery
. That internal state of SelectionQuery
ought to be immutable once created; and to me that includes the SQM tree.
However we achieve that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, yes, I know we cannot revert SelectionQuery
from being mutable in the short term because of the methods previously added there until we can remove those previously added methods.
But we can proceed with that expectation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright so at this point I'm pretty happy with this:
var firstPage =
session.createSelectionQuery("from Person where dob > :dob order by ssn", Person.class )
.setParameter( "dob", LocalDate.of(1970, 2, 1) )
.setPage( Page.first( 10 ) )
.getKeyedResultList();
firstPage.getResultList()
.forEach(p -> ... );
var secondPage =
session.createSelectionQuery( "from Person where dob > :dob order by ssn", Person.class )
.getKeyedResultList( firstPage.getNextPage() );
secondPage.getResultList()
.forEach(p -> ... );
which I actually already have implemented at a POC level.
30de57c
to
b984f7d
Compare
hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java
Fixed
Show fixed
Hide fixed
documentation/src/main/asciidoc/userguide/chapters/query/programmatic/QuerySpecification.adoc
Outdated
Show resolved
Hide resolved
...te-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java
Outdated
Show resolved
Hide resolved
hibernate-core/src/main/java/org/hibernate/query/programmatic/QuerySpecification.java
Show resolved
Hide resolved
hibernate-core/src/main/java/org/hibernate/query/programmatic/QuerySpecification.java
Outdated
Show resolved
Hide resolved
hibernate-core/src/main/java/org/hibernate/query/programmatic/QuerySpecification.java
Outdated
Show resolved
Hide resolved
hibernate-core/src/main/java/org/hibernate/query/programmatic/SelectionSpecification.java
Show resolved
Hide resolved
Thanks for your pull request! This pull request appears to follow the contribution rules. › This message was automatically generated. |
c2c44d3
to
5eb4447
Compare
/** | ||
* Finalize the building and create the {@linkplain SelectionQuery} instance. | ||
*/ | ||
MutationQuery createQuery(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MutationQuery createQuery(); | |
@Override | |
MutationQuery createQuery(); |
/** | ||
* Covariant override. | ||
*/ | ||
SelectionQuery<T> createQuery(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SelectionQuery<T> createQuery(); | |
@Override | |
SelectionQuery<T> createQuery(); |
5eb4447
to
c03c23e
Compare
Initial work on a proper solution to creating "query builders".
Initial commit focuses on HQL-based builders. Still need to add criteria-based, though maybe not for 7.0.
A few notes -
DynamicSelectionHqlImpl#createQuery
andDynamicMutationHqlImpl#createQuery
wrt copied code. Need to address that duplication.Class<T> mutationTarget
argument forQueryProducer#createDynamicMutation
- ran into compiler errors without it, but would be nice to remove it.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license
and can be relicensed under the terms of the LGPL v2.1 license in the future at the maintainers' discretion.
For more information on licensing, please check here.
https://hibernate.atlassian.net/browse/HHH-19364