Skip to content

HSEARCH-**** Attempt to make the migration simpler #4618

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

marko-bekhta
Copy link
Member

[Please describe here what your change is about]


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.


@hibernate-github-bot
Copy link

hibernate-github-bot bot commented May 16, 2025

Thanks for your pull request!

This pull request does not follow the contribution rules. Could you have a look?

❌ All commit messages should start with a JIRA issue key matching pattern HSEARCH-\d+
    ↳ Offending commits: [e34f809, edd2090, fef0bdf, edeac21, a93ff9b, f98865b, 30297a8]

› This message was automatically generated.

* @see ProjectionDefinitionContext
*/
SearchProjection<? extends P> create(SearchProjectionFactory<?, ?, ?> factory, ProjectionDefinitionContext context);
SearchProjection<? extends P> create(TypedSearchProjectionFactory<?, ?, ?> factory, ProjectionDefinitionContext context);
Copy link
Member Author

Choose a reason for hiding this comment

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

if we do the SearchProjectionFactory<R, E> extends TypedSearchProjectionFactory<NonStaticMetamodelScope, R, E> then these definitions (similar for predicates will receive the typed version as at the time user creates the definitions we've got no idea what the scope will be... so with that users would need to update their definitions if they were creating local variables from these factories (in case of predicates) or simply update the create method signature in case of a projection..
cc @yrodiere 🙈

Copy link
Member

@yrodiere yrodiere May 19, 2025

Choose a reason for hiding this comment

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

It seems to me that in order for type-safe factories to be useful in these definitions, we'd need the implementor of the definition to be able to check/require a specific root type, no? E.g.:

public class BookAuthorNamesProjectionDef implements ProjectionDefinition<Book__, P> {
		@Override
		public SearchProjection<List<String>> create(SearchProjectionFactory<Book__, ?, ?> factory,
				ProjectionDefinitionContext context) {
			return factory.field( Book__.authors.name )
					.collector( ProjectionCollector.list() ) // <4>
					.toProjection();
		}
}

IMO, either this ^ can be made to work, or typed scopescan be ignored here.

Either way... this is incubating API. I'd say it's fine to do whatever is easier here, and create a follow-up issue?

Comment on lines 24 to 33
public interface TypedPredicateDefinition<SR> {

/**
* Creates a predicate.
* @param context The context, exposing in particular a {@link TypedSearchPredicateFactory}.
* @return The created {@link SearchPredicate}.
*/
SearchPredicate create(TypedPredicateDefinitionContext<SR> context);

}
Copy link
Member Author

@marko-bekhta marko-bekhta May 20, 2025

Choose a reason for hiding this comment

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

Hey @yrodiere

soooo here I've tried to explore what can be done about the predicate/projection definitions...
I didn't extend TypedPredicateDefinition with PredicateDefinition or vice-versa as that makes things more cumbersome .... since they expect the context (which has a factory either typed or not) we'd have to add the context type to the type arguments and that makes defining the typed one as:

new TypedPredicateDefinition<SomeScope, TypedPredicateDefinitionContext<SomeScope>> {
   SearchPredicate create(TypedPredicateDefinitionContext<SomeScope> context) {
   ...
   }
}

with this approach there's some ambiguity when creating these definitions as lambdas as it's unclear which context user wants in root.namedPredicate(name definition). Having a root.namedTypedPredicate() ... didn't look nice to me 😄.

and then in the actual impl... since this SR is only in the type definition and we never pass it anywhere... we can just do an "ugly-unchecked-cast".
I wonder if we should keep the scope type at runtime and make additional checks, or if it's not worth it, as the main point here was to have a compile-level check? WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

I think additional runtime checks would make sense, if possible.

I also think having the definition context type as a generic type parameter is a bit odd.

And... I think it's fine to leave this for later, too, as it's incubating API that you can break and break again :)

Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
63.5% Coverage on New Code (required ≥ 80%)
11.6% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

* @throws SearchException In case the current factory cannot be rescoped for the {@code scopeRootType}.
*/
@Incubating
<SR2> ExtendedSearchPredicateFactory<SR2, ?> withScopeRoot(Class<SR2> scopeRootType);
Copy link
Member Author

Choose a reason for hiding this comment

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

I added this method to rescope the factory in this extended interface so it wouldn't be right in front of the user all the time (I thought about creating another spi interface and moving it there ... but then we already have so many interfaces I wasn't sure if that would be a good solution..)

Comment on lines +39 to +54
public <SR2> ElasticsearchSearchPredicateFactory<SR2> withScopeRoot(Class<SR2> scopeRootType) {
if ( this.scopeRootType.equals( scopeRootType ) ) {
return (ElasticsearchSearchPredicateFactory<SR2>) this;
}
if (
// if we want the "untyped" version of the factory we can get it from any other factory
// e.g. we have one tied to a Book__ and we want to use some "raw" string paths in a named predicate.
scopeRootType.equals( NonStaticMetamodelScope.class )
// scope type is in the same hierarchy:
|| this.scopeRootType.isAssignableFrom( scopeRootType )
|| scopeRootType.isAssignableFrom( this.scopeRootType )
) {
return new ElasticsearchSearchPredicateFactoryImpl<>( scopeRootType, dslContext );
}
throw QueryLog.INSTANCE.incompatibleScopeRootType( scopeRootType, this.scopeRootType );
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Here's how "rescoping" works ^

Copy link
Member

Choose a reason for hiding this comment

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

I'm not entirely sure how safe downcasting would be, but... whatever, if it's incubating, let's try.

Comment on lines +39 to +54
public <SR2> ElasticsearchSearchPredicateFactory<SR2> withScopeRoot(Class<SR2> scopeRootType) {
if ( this.scopeRootType.equals( scopeRootType ) ) {
return (ElasticsearchSearchPredicateFactory<SR2>) this;
}
if (
// if we want the "untyped" version of the factory we can get it from any other factory
// e.g. we have one tied to a Book__ and we want to use some "raw" string paths in a named predicate.
scopeRootType.equals( NonStaticMetamodelScope.class )
// scope type is in the same hierarchy:
|| this.scopeRootType.isAssignableFrom( scopeRootType )
|| scopeRootType.isAssignableFrom( this.scopeRootType )
) {
return new ElasticsearchSearchPredicateFactoryImpl<>( scopeRootType, dslContext );
}
throw QueryLog.INSTANCE.incompatibleScopeRootType( scopeRootType, this.scopeRootType );
}
Copy link
Member

Choose a reason for hiding this comment

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

I'm not entirely sure how safe downcasting would be, but... whatever, if it's incubating, let's try.

Comment on lines 20 to +21
public interface ExtendedSearchPredicateFactory<SR, S extends ExtendedSearchPredicateFactory<SR, ?>>
extends SearchPredicateFactory<SR> {
extends TypedSearchPredicateFactory<SR> {
Copy link
Member

Choose a reason for hiding this comment

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

So IIUC, it's no longer possible to pass an ElasticsearchSearchPredicateFactory to a method expecting a SearchPredicateFactory? E.g. a method like the one in this example of the documentation?

private SearchPredicate matchFirstAndLastName(SearchPredicateFactory f,
        String firstName, String lastName) {
    return f.and(
            f.match().field( "firstName" ) 
                    .matching( firstName ),
            f.match().field( "lastName" )
                    .matching( lastName )
    )
            .toPredicate();
}

It's fine if this is a voluntary limitation, i.e. if it's a compromise you found between complexity and compatibility. But I thought you should be aware :)

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.

2 participants