Skip to content

Commit

Permalink
HSEARCH-3319 WIP: DRAFT: IDEA: TEST: Type-safe field references
Browse files Browse the repository at this point in the history
  • Loading branch information
marko-bekhta committed Apr 9, 2024
1 parent b14da95 commit 277a66e
Show file tree
Hide file tree
Showing 9 changed files with 412 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.hibernate.search.documentation.testsupport.DocumentationSetupHelper;
import org.hibernate.search.engine.backend.types.Projectable;
import org.hibernate.search.engine.search.common.ValueConvert;
import org.hibernate.search.engine.search.reference.FieldReference;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
Expand All @@ -34,7 +35,8 @@

class ProjectionConverterIT {
@RegisterExtension
public DocumentationSetupHelper setupHelper = DocumentationSetupHelper.withSingleBackend( BackendConfigurations.simple() );
public DocumentationSetupHelper setupHelper = DocumentationSetupHelper.withSingleBackend(
BackendConfigurations.simple() );

private EntityManagerFactory entityManagerFactory;

Expand Down Expand Up @@ -80,6 +82,73 @@ void projectionConverterDisabled() {
} );
}

@Test
void projectionConverterDisabledFR() {
with( entityManagerFactory ).runInTransaction( entityManager -> {
SearchSession searchSession = Search.session( entityManager );
// "status", String.class, ValueConvert.NO


FieldReference.FieldAttributeReference<OrderStatus, String> reference = new FieldReference.FieldAttributeReference<>() {
private final String absolutePath = "status";

@Override
public String absolutePath() {
return absolutePath;
}

@Override
public Class<OrderStatus> type() {
return OrderStatus.class;
}

@Override
public FieldReference<String> noConverter() {
return new FieldReference<>() {
@Override
public String absolutePath() {
return absolutePath;
}

@Override
public Class<String> type() {
return String.class;
}

@Override
public ValueConvert valueConvert() {
return ValueConvert.NO;
}
};
}

@Override
public FieldReference<String> asString() {
throw new UnsupportedOperationException( "PARSE is not supported for projections" );
}
};

List<String> result = searchSession.search( Order.class )
.select( f -> f.field( reference.noConverter() ) )
.where( f -> f.matchAll() )
.fetchHits( 20 );

assertThat( result )
.containsExactlyInAnyOrder(
Stream.of( OrderStatus.values() ).map( Enum::name ).toArray( String[]::new )
);

List<OrderStatus> result2 = searchSession.search( Order.class )
.select( f -> f.field( reference ) )
.where( f -> f.matchAll() )
.fetchHits( 20 );

assertThat( result2 )
.containsExactlyInAnyOrder( OrderStatus.values() );
} );

}

private void initData() {
with( entityManagerFactory ).runInTransaction( entityManager -> {
Order order1 = new Order( 1 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.hibernate.search.engine.search.common.RewriteMethod;
import org.hibernate.search.engine.search.predicate.dsl.RegexpQueryFlag;
import org.hibernate.search.engine.search.predicate.dsl.SimpleQueryFlag;
import org.hibernate.search.engine.search.reference.FieldReference;
import org.hibernate.search.engine.spatial.DistanceUnit;
import org.hibernate.search.engine.spatial.GeoBoundingBox;
import org.hibernate.search.engine.spatial.GeoPoint;
Expand Down Expand Up @@ -467,6 +468,39 @@ void match() {
} );
}

@Test
void matchFieldReference() {
withinSearchSession( searchSession -> {
List<Book> hits = searchSession.search( Book.class )
.where( f -> f.match().field( new FieldReference.FieldAttributeReference<String, String>() {
@Override
public String absolutePath() {
return "title";
}

@Override
public Class<String> type() {
return String.class;
}

@Override
public FieldReference<String> noConverter() {
return null;
}

@Override
public FieldReference<String> asString() {
return null;
}
} )
.matching( "robot" ) )
.fetchHits( 20 );
assertThat( hits )
.extracting( Book::getId )
.containsExactlyInAnyOrder( BOOK1_ID, BOOK3_ID );
} );
}

@Test
void match_analysis() {
withinSearchSession( searchSession -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.engine.search.predicate.dsl;

import org.hibernate.search.engine.search.reference.FieldReference;

/**
* The step in a "match" predicate definition where the value to match can be set
* (see the superinterface {@link MatchPredicateMatchingStep}),
* or optional parameters for the last targeted field(s) can be set,
* or more target fields can be added.
*
* @param <S> The "self" type (the actual exposed type of this step).
* @param <N> The type of the next step.
*/
public interface MatchPredicateFRFieldMoreStep<T,
S extends MatchPredicateFRFieldMoreStep<T, ?, N>,
N extends MatchPredicateOptionsStep<?>>
extends MatchPredicateFRMatchingStep<T, N>, MultiFieldPredicateFieldBoostStep<S> {

/**
* Target the given field in the match predicate,
* as an alternative to the already-targeted fields.
* <p>
* See {@link MatchPredicateFieldStep#field(String)} for more information about targeting fields.
*
* @param fieldReference The <a href="SearchPredicateFactory.html#field-paths">path</a> to the index field
* to apply the predicate on.
* @return The next step.
*
* @see MatchPredicateFieldStep#field(String)
*/
default S field(FieldReference<T> fieldReference) {
return fields( fieldReference );
}

/**
* Target the given fields in the match predicate,
* as an alternative to the already-targeted fields.
* <p>
* See {@link MatchPredicateFieldStep#fields(String...)} for more information about targeting fields.
*
* @param fieldReference The <a href="SearchPredicateFactory.html#field-paths">paths</a> to the index fields
* to apply the predicate on.
* @return The next step.
*
* @see MatchPredicateFieldStep#fields(String...)
*/
S fields(FieldReference<T>... fieldReference);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.engine.search.predicate.dsl;

import org.hibernate.search.engine.search.common.ValueConvert;

/**
* The step in a "match" predicate definition where the value to match can be set.
*
* @param <N> The type of the next step.
*/
public interface MatchPredicateFRMatchingStep<T, N extends MatchPredicateOptionsStep<?>> {

/**
* Require at least one of the targeted fields to match the given value.
* <p>
* This method will apply DSL converters to {@code value} before Hibernate Search attempts to interpret it as a field value.
* See {@link ValueConvert#YES}.
*
* @param value The value to match.
* The signature of this method defines this parameter as an {@link Object},
* but a specific type is expected depending on the targeted field.
* See {@link ValueConvert#YES} for more information.
* @return The next step.
*/
N matching(T value);

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
package org.hibernate.search.engine.search.predicate.dsl;


import org.hibernate.search.engine.search.reference.FieldReference;

/**
* The initial step in a "match" predicate definition, where the target field can be set.
*/
Expand Down Expand Up @@ -45,4 +47,10 @@ default N field(String fieldPath) {
* @see #field(String)
*/
N fields(String... fieldPaths);

default <T> MatchPredicateFRFieldMoreStep<T, ?, ?> field(FieldReference<T> field) {
return fields( field );
}

<T> MatchPredicateFRFieldMoreStep<T, ?, ?> fields(FieldReference<T>... fields);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.engine.search.predicate.dsl.impl;

import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import org.hibernate.search.engine.logging.impl.Log;
import org.hibernate.search.engine.search.common.ValueConvert;
import org.hibernate.search.engine.search.common.spi.SearchIndexScope;
import org.hibernate.search.engine.search.predicate.SearchPredicate;
import org.hibernate.search.engine.search.predicate.dsl.MatchPredicateFRFieldMoreStep;
import org.hibernate.search.engine.search.predicate.dsl.MatchPredicateFieldMoreStep;
import org.hibernate.search.engine.search.predicate.dsl.MatchPredicateOptionsStep;
import org.hibernate.search.engine.search.predicate.dsl.spi.SearchPredicateDslContext;
import org.hibernate.search.engine.search.predicate.spi.MatchPredicateBuilder;
import org.hibernate.search.engine.search.predicate.spi.PredicateTypeKeys;
import org.hibernate.search.engine.search.reference.FieldReference;
import org.hibernate.search.util.common.impl.Contracts;
import org.hibernate.search.util.common.logging.impl.LoggerFactory;

class MatchPredicateFRFieldMoreStepImpl<T>
implements MatchPredicateFRFieldMoreStep<T, MatchPredicateFRFieldMoreStepImpl<T>, MatchPredicateOptionsStep<?>>,
AbstractBooleanMultiFieldPredicateCommonState.FieldSetState {

private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() );

private final CommonState<T> commonState;

private final Map<FieldReference<T>, MatchPredicateBuilder> predicateBuilders = new LinkedHashMap<>();

private Float fieldSetBoost;

MatchPredicateFRFieldMoreStepImpl(CommonState<T> commonState, List<FieldReference<T>> fieldReferences) {
this.commonState = commonState;
this.commonState.add( this );
SearchIndexScope<?> scope = commonState.scope();
for ( FieldReference<T> fieldReference : fieldReferences ) {
predicateBuilders.put(
fieldReference, scope.fieldQueryElement( fieldReference.absolutePath(), PredicateTypeKeys.MATCH ) );
}
}

@Override
public MatchPredicateFRFieldMoreStepImpl<T> fields(FieldReference<T>... fieldPaths) {
return new MatchPredicateFRFieldMoreStepImpl<>( commonState, Arrays.asList( fieldPaths ) );
}

@Override
public MatchPredicateFRFieldMoreStepImpl<T> boost(float boost) {
this.fieldSetBoost = boost;
return this;
}

@Override
public MatchPredicateOptionsStep<?> matching(T value) {
return commonState.matching( value );
}

@Override
public void contributePredicates(Consumer<SearchPredicate> collector) {
for ( MatchPredicateBuilder predicateBuilder : predicateBuilders.values() ) {
// Perform last-minute changes, since it's the last call that will be made on this field set state
commonState.applyBoostAndConstantScore( fieldSetBoost, predicateBuilder );

collector.accept( predicateBuilder.build() );
}
}

static class CommonState<T> extends AbstractBooleanMultiFieldPredicateCommonState<CommonState<T>, MatchPredicateFRFieldMoreStepImpl<T>>
implements MatchPredicateOptionsStep<CommonState<T>> {

CommonState(SearchPredicateDslContext<?> dslContext) {
super( dslContext );
}

MatchPredicateOptionsStep<?> matching(Object value) {
Contracts.assertNotNull( value, "value" );

for ( MatchPredicateFRFieldMoreStepImpl<T> fieldSetState : getFieldSetStates() ) {
for ( Map.Entry<FieldReference<T>, MatchPredicateBuilder> entry : fieldSetState.predicateBuilders.entrySet() ) {
entry.getValue().value( value, entry.getKey().valueConvert() );
}
}
return this;
}

@Override
public CommonState<T> fuzzy(int maxEditDistance, int exactPrefixLength) {
if ( maxEditDistance < 0 || 2 < maxEditDistance ) {
throw log.invalidFuzzyMaximumEditDistance( maxEditDistance );
}
if ( exactPrefixLength < 0 ) {
throw log.invalidExactPrefixLength( exactPrefixLength );
}

for ( MatchPredicateFRFieldMoreStepImpl<T> fieldSetState : getFieldSetStates() ) {
for ( MatchPredicateBuilder predicateBuilder : fieldSetState.predicateBuilders.values() ) {
predicateBuilder.fuzzy( maxEditDistance, exactPrefixLength );
}
}
return this;
}

@Override
public CommonState<T> analyzer(String analyzerName) {
for ( MatchPredicateFRFieldMoreStepImpl<T> fieldSetState : getFieldSetStates() ) {
for ( MatchPredicateBuilder predicateBuilder : fieldSetState.predicateBuilders.values() ) {
predicateBuilder.analyzer( analyzerName );
}
}
return this;
}

@Override
public CommonState<T> skipAnalysis() {
for ( MatchPredicateFRFieldMoreStepImpl<T> fieldSetState : getFieldSetStates() ) {
for ( MatchPredicateBuilder predicateBuilder : fieldSetState.predicateBuilders.values() ) {
predicateBuilder.skipAnalysis();
}
}
return this;
}

@Override
protected CommonState<T> thisAsS() {
return this;
}
}

}
Loading

0 comments on commit 277a66e

Please sign in to comment.