-
Notifications
You must be signed in to change notification settings - Fork 243
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
HSEARCH-3403 + HSEARCH-3511 Polish the entry points to the Search DSL and make them more consistent #1909
Conversation
mapper/orm/src/main/java/org/hibernate/search/mapper/orm/hibernate/FullTextQuery.java
Outdated
Show resolved
Hide resolved
mapper/orm/src/main/java/org/hibernate/search/mapper/orm/hibernate/FullTextSession.java
Outdated
Show resolved
Hide resolved
mapper/orm/src/main/java/org/hibernate/search/mapper/orm/jpa/FullTextEntityManager.java
Outdated
Show resolved
Hide resolved
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.
I added some comments.
My main issue is about this result
/hit
thing. And it goes far beyond what I mentioned as you also have setFirstResult
and so on. Not sure if my proposal is a good idea but we need to think carefully about it.
...t/java/org/hibernate/search/integrationtest/mapper/orm/massindexing/BasicMassIndexingIT.java
Show resolved
Hide resolved
@@ -69,11 +72,26 @@ else if ( type.equals( SearchQuery.class ) ) { | |||
} | |||
} | |||
|
|||
@Override | |||
public TypedQuery<R> toJpaQuery() { |
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.
So we still inherit from AbstractProducedQuery
? Does it bring something to us?
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.
It mainly provides more resilience to API changes in ORM, which can be useful since the Query
contract is not meant to be implemented by third parties, thus they could add methods unexpectedly.
Also, it allows to more easily implement methods such as getSingleResult()
in a way that throws the correct exceptions.
Since we're splitting the interfaces, I could create a wrapper class, separate from this one, and instantiate it when toJpaQuery
is called? That would clean up the code of SearcheQueryImpl
quite a lot.
The main drawback is that we wouldn't notice when our own interfaces deviate from JPA/ORM interfaces in an incompatible way (same signature with different return type). But I suppose it's not such a big deal, since we're unlikely to go back to making our interfaces extend JPA/ORM interfaces.
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.
I split the implementations and moved the adaptor to a separate class.
* @return A context allowing to define the search query, | ||
* and ultimately {@link SearchQueryContext#toQuery() get the resulting query}. | ||
* @see SearchQueryResultDefinitionContext | ||
*/ | ||
default <T> SearchQueryResultDefinitionContext search(Class<T> type) { |
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.
Any reason why we do not have varargs versions for search()
and scope()
?
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: generics in varargs lead to call-site warnings ("heap pollution yada yada"). Usually the solution to avoid these warnings is to add @SafeVarargs
, but this can only be used on static or final methods, so not here in an interface.
For the JavaBean mapper, we could get rid of the generics, but not for the ORM mapper, where they are actually useful.
Our only other option would be to declare one method per number of parameters: one type, two types, etc. I'm really not thrilled by the idea.
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.
Hum. This is annoying :/.
What makes them useful for the ORM case?
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 generics, you mean? In short it allows for typed queries when requesting an entity result type. Without it, we wouldn't know which type to put in SearchQuery<T>
.
It's not useful in the JavaBean mapper because it cannot load entities from the database, since there isn't any database.
...avabean/src/main/java/org/hibernate/search/mapper/javabean/session/SearchSessionBuilder.java
Show resolved
Hide resolved
mapper/orm/src/main/java/org/hibernate/search/mapper/orm/session/SearchSession.java
Show resolved
Hide resolved
* @param fetchSize The fetch size. Must be positive or zero. | ||
* @return {@code this} for method chaining. | ||
* @see Query#setFetchSize(int) | ||
*/ |
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.
Does it make sense, now? Or is it used for loading the elements from the database?
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.
is it used for loading the elements from the database
Yes.
* For these reasons, it is recommended to only use this method | ||
* when integrating to an external library that expects JPA queries. | ||
* | ||
* @return A representation of this query as a JPA query. |
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.
I don't see how that makes sense. I mean you can't translate the query to a JPA one, which is more or less what people would expect with this method, wouldn't they?
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.
I'm not sure I'm following, we did agree it was useful to be able to use Hibernate Search queries through JPA/ORM interfaces, didn't we? I just wanted to move this away from our main interfaces because we know that it will always be just a hack and we cannot implement all the methods of TypedQuery
.
It's not a JPA query, it's a representation of a Hibernate Search query as a JPA query. The only purpose of this method is to allow using JPA interfaces to manipulate Hibernate Search queries, thereby integrating more easily into existing frameworks that are used to Hibernate ORM.
* @throws org.hibernate.HibernateException If something goes wrong while fetching results from the database. | ||
* @throws javax.persistence.PersistenceException If something goes wrong while fetching results from the database. | ||
*/ | ||
SearchResult<T> getResult(); |
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.
Hmmm. There's something weird with having getResult()
and getOptionalResult
/getSingleResult
not doing the same thing at all. At least the Optional
one could be seen as the same thing as getResult()
.
Maybe we should rename the others to getOptionalHit
, getSingleHit
, getHits
? That would be consistent with what you have inside SearchResult
?
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.
So it seems we're on the same page, but just a reminder for readers: what we call "result" in Hibernate Search is the whole thing: hits, total hit count, aggregations, facets, ... While a "hit" is only the part of the result that represents a document matching the query.
I agree the names you proposed are more correct. What I was trying to do here is to still more or less fit the JPA naming, so as not to be too confusing.
If we are to give up on that, then yes, I would pick different names. But I would go even further, because I want to make it extra clear that calling one of these methods does not just "get" the hits from a Java object that is already there, fully populated: it actually executes the query.
I would also get rid of the getSingleHit
variant, since returning an Optional provides the same feature, and more.
So ideally, if we forget about using the same naming as JPA, I would use:
SearchResult<T> fetch()
List<T> fetchHits()
(=>fetch().getHits()
)Optional<T> fetchSingleHit()
(=>fetch().getHits()
, but returns an Optional for 0 or 1 hit, throws an exception if more than 1 hit)long fetchCount()
(=>fetch().getHitCount()
, but optimized to not fetch any hit)- variants of
fetch()
andfetchHits
accepting two paramters to handle paging:fetch(long limit)
,fetch(long limit, long offset)
... OR at least renamesetFirstResult
tosetHitOffset
andsetMaxResults
tosetHitLimit
.
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.
TBH, I don't know.
I suppose people would expect an API similar to what they are used to with JPA but it's really 2 completely different things.
Another thing is that a lot of people are using layers on top of JPA so maybe they are not that used to the JPA API.
I like your fetch(limit, offset)
proposal.
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.
I added a few commits to use fetch(...)
as I proposed.
* @param firstResult The offset of the first result. Must be positive or zero. | ||
* @return {@code this} for method chaining. | ||
*/ | ||
@Deprecated |
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.
Should we remove them instead? My rationale is that people will have a tendency to use them considering you don't need the L
and they would have a warning.
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.
If I remove them, people trying to pass an int
(either a literal without a trailing L
, or a variable) will get a compilation error. There is no automatic boxing from int
to Long
, just from long
to Long
.
If I removed the int
variants and changed the parameter type from Long
to long
, then users would benefit from automatic casting from int
to long
. But then they couldn't reset the value by passing null
to setFirstResult
or setMaxResults
anymore. Which I suppose is fine, since they could pass 0
to setFirstResult
and Long.MAX_VALUE
to setMaxResults`?
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.
Well, what I mean is: either you support it and you don't have them deprecated or you don't.
But what you're saying in your first paragraph means they will end up with a deprecation warning.
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.
Ok, so I suppose "them" meant the @Deprecated
annotations, not the methods. Anyway... It's probably not relevant anymore if we switch to a fetch(limit, offset)
method.
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.
I removed the deprecation, we now have two variants of each fetch
method: one accepting longs and one accepting integers.
efa03cc
to
c00ff8b
Compare
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.
I think the API is better this way.
History will tell if people really want an API similar to JPA or not :).
…bernate ORM APIs Instead, allow to convert objects from our APIs to JPA/ORM APIs through explicit calls to toXXX() methods. There are two advantages to this: 1. Auto completion will be clearer: by default, only Search methods will be displayed, and we won't clutter the suggestions with the dozens of methods of the JPA/ORM APIs. 2. We will be able to explain clearly on the javadoc of the conversion methods that the converted objects only implement a subset of the JPA/ORM APIs. And we will have more legitimacy in explaining that, since those objects are not our primary APIs.
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.
I approve all the changes to the APIs. I think we've got a great step forward with them. I have just few comments, very few if compared with the change size.
.../java/org/hibernate/search/integrationtest/mapper/orm/hibernateormapis/ToHibernateOrmIT.java
Outdated
Show resolved
Hide resolved
.../src/test/java/org/hibernate/search/integrationtest/mapper/orm/hibernateormapis/ToJpaIT.java
Outdated
Show resolved
Hide resolved
integrationtest/showcase/library/src/test/resources/application-test.yaml
Outdated
Show resolved
Hide resolved
mapper/javabean/src/main/java/org/hibernate/search/mapper/javabean/session/SearchSession.java
Outdated
Show resolved
Hide resolved
...tegrationtest/showcase/library/repository/indexsearch/IndexSearchDocumentRepositoryImpl.java
Outdated
Show resolved
Hide resolved
...integrationtest/showcase/library/repository/indexsearch/IndexSearchPersonRepositoryImpl.java
Outdated
Show resolved
Hide resolved
...integrationtest/showcase/library/repository/indexsearch/IndexSearchPersonRepositoryImpl.java
Outdated
Show resolved
Hide resolved
...integrationtest/showcase/library/repository/indexsearch/IndexSearchPersonRepositoryImpl.java
Outdated
Show resolved
Hide resolved
...integrationtest/showcase/library/repository/indexsearch/IndexSearchPersonRepositoryImpl.java
Outdated
Show resolved
Hide resolved
...src/main/java/org/hibernate/search/mapper/orm/search/query/impl/HibernateOrmSearchQuery.java
Outdated
Show resolved
Hide resolved
…Search APIs and JPA/ORM APIs
…ith the Hibernate ORM version Since these interfaces are now independent from Hibernate ORM, there is no point in trying to keep them separate.
…iate packages Now that we don't have two versions of the APIs with the same names, we can use a package layout that is consistent with other Hibernate Search modules.
… the same name So that we don't reproduce the mistake of having two "FullTextQuery" types in different packages.
They were kept separate to avoid cyclic package dependencies, but now that we moved the classes to different packages it's no longer necessary.
…Impl ... and try to make it clearer that Search.getFullTextSession returns a lazily initializing proxy. The two types were kept separate to avoid cyclic package dependencies, but now that we moved the classes to different packages it's no longer necessary.
... so that it's clear that mappers need to declare their own type, wrapping the engine-provided query. This is the safest design: it allows to evolve SPIs independently from mapper APIs, avoiding naming conflicts in particular. If we want to define a common super-interface for the query type of all mappers, we can always do that later, but in the meantime let's be safe.
… IndexSearchResult ... to allow mappers to define interfaces with those names. JavaBeanSearchQuery, JavaBeanSearchResult, etc., are a mouthful. Let's make it simpler, since most (all?) users will only ever have one mapper in the classpath.
….class ) in the ORM mapper And introduce a separate .target() method next to search() for people who do not want to use lambdas.
….class ) in the JavaBean mapper For consistency.
Hopefully this will be clearer and will feel more natural. Additionally, this will allow (later) to add more operations that are not just related to search: delete queries, ... The idea being that "SearchScope" will mean "a scope in Hibernate Search", not "the scope of a search query".
Instead of using sometimes FullText* (session, query), sometimes Search* (main entry point Search.getFullTextSession, SearchPredicate, ...)
For consistency with the ORM mapper.
…pper For consistency with the ORM mapper.
…o toQuery() So that: 1. It's more consistent with the other DSLs (predicate, sort, projection, index field type, ...) 2. It "flows" more naturally: session.search( Book.class ).predicate( ... ).toQuery();
…RM methods easier to find
Returning a List is too limiting: sometimes we need to also return the total hit count, facets, aggregations, ... A dedicated SearchResult object will allow us to do so.
…/setFirstResult in the ORM mapper
…d to the ORM SearchQuery
…uery methods This is to avoid confusion between hit and result in SearchQuery method names. Using the JPA naming forced us to introduce such confusion.
…earchQuery interface
To make it extra clear that we ignore pagination settings.
…tead of limit/offset setters
…limit/offset setters
…wcase repositories consistent with our APIs
…cate results, on contrary to ORM methods (uniqueResult and getSingleResult)
…ategory to org.hibernate.search.query For consistency with the renaming of FullTextQuery to SearchQuery
…uery to a separate class
@fax4ever Thanks for the review. I applied all of your suggestions. Waiting for CI, then I'll merge. |
https://hibernate.atlassian.net//browse/HSEARCH-3403: Remove the inheritance from our Session/Query APIs to Hibernate ORM/JPA APIs.
https://hibernate.atlassian.net//browse/HSEARCH-3511: Make the entry points to the Search API more intuitive
Regarding HSEARCH-3403, note I ended up removing the inheritance in
FullTextSession
too, and exposing two methods:.toHibernateOrmSession()
/toJpaEntityManager()
, so that the use case mentioned in the ticket is still covered.Regarding HSEARCH-3511, I'd really like @gsmet to have a look, please, since you were the one to mention to me that the APIs were not that intuitive when trying to use them without having a look at the documentation. I think I addressed your main concern, but I went a bit beyond that, so it would be great if you could have a quick look at the resulting syntax.
In a nutshell:
Search
prefix in API types.toQuery
to build the query (consistent with predicates, sorts, etc.)..search()
, will work just fine with the lambda-based syntax. The more "advanced" one,.scope()
, will be necessary for the object-based syntax, and we may one day add more operations than just search there: mass indexer, delete queries, ...SearchQuery
interface no longer follow the same naming scheme as JPA/ORM: they are namedfetchXXX()
and offsets/limits are passed through parameters instead of setters. The reasons are:getResultList
can be confusing. Even if we decided to deal with it, finding appropriate names would be quite challenging...getXXX
gives the impression that you're just calling a getter.fetchTotalHitCount
)... but passing their value through setters gives the impression that every method inSearchQuery
is affected.You can find a few examples in the tests,
org.hibernate.search.integrationtest.mapper.orm.search.SearchQueryIT
in particular, but below are some simple ones.I changed this:
to this:
And I introduced a shortcut so that the lambda syntax (the one we will recommend for most use cases) feels more natural, so this:
became this: