Skip to content

Commit 277a66e

Browse files
committed
HSEARCH-3319 WIP: DRAFT: IDEA: TEST: Type-safe field references
1 parent b14da95 commit 277a66e

File tree

9 files changed

+412
-4
lines changed

9 files changed

+412
-4
lines changed

documentation/src/test/java/org/hibernate/search/documentation/search/converter/ProjectionConverterIT.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.hibernate.search.documentation.testsupport.DocumentationSetupHelper;
2424
import org.hibernate.search.engine.backend.types.Projectable;
2525
import org.hibernate.search.engine.search.common.ValueConvert;
26+
import org.hibernate.search.engine.search.reference.FieldReference;
2627
import org.hibernate.search.mapper.orm.Search;
2728
import org.hibernate.search.mapper.orm.session.SearchSession;
2829
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
@@ -34,7 +35,8 @@
3435

3536
class ProjectionConverterIT {
3637
@RegisterExtension
37-
public DocumentationSetupHelper setupHelper = DocumentationSetupHelper.withSingleBackend( BackendConfigurations.simple() );
38+
public DocumentationSetupHelper setupHelper = DocumentationSetupHelper.withSingleBackend(
39+
BackendConfigurations.simple() );
3840

3941
private EntityManagerFactory entityManagerFactory;
4042

@@ -80,6 +82,73 @@ void projectionConverterDisabled() {
8082
} );
8183
}
8284

85+
@Test
86+
void projectionConverterDisabledFR() {
87+
with( entityManagerFactory ).runInTransaction( entityManager -> {
88+
SearchSession searchSession = Search.session( entityManager );
89+
// "status", String.class, ValueConvert.NO
90+
91+
92+
FieldReference.FieldAttributeReference<OrderStatus, String> reference = new FieldReference.FieldAttributeReference<>() {
93+
private final String absolutePath = "status";
94+
95+
@Override
96+
public String absolutePath() {
97+
return absolutePath;
98+
}
99+
100+
@Override
101+
public Class<OrderStatus> type() {
102+
return OrderStatus.class;
103+
}
104+
105+
@Override
106+
public FieldReference<String> noConverter() {
107+
return new FieldReference<>() {
108+
@Override
109+
public String absolutePath() {
110+
return absolutePath;
111+
}
112+
113+
@Override
114+
public Class<String> type() {
115+
return String.class;
116+
}
117+
118+
@Override
119+
public ValueConvert valueConvert() {
120+
return ValueConvert.NO;
121+
}
122+
};
123+
}
124+
125+
@Override
126+
public FieldReference<String> asString() {
127+
throw new UnsupportedOperationException( "PARSE is not supported for projections" );
128+
}
129+
};
130+
131+
List<String> result = searchSession.search( Order.class )
132+
.select( f -> f.field( reference.noConverter() ) )
133+
.where( f -> f.matchAll() )
134+
.fetchHits( 20 );
135+
136+
assertThat( result )
137+
.containsExactlyInAnyOrder(
138+
Stream.of( OrderStatus.values() ).map( Enum::name ).toArray( String[]::new )
139+
);
140+
141+
List<OrderStatus> result2 = searchSession.search( Order.class )
142+
.select( f -> f.field( reference ) )
143+
.where( f -> f.matchAll() )
144+
.fetchHits( 20 );
145+
146+
assertThat( result2 )
147+
.containsExactlyInAnyOrder( OrderStatus.values() );
148+
} );
149+
150+
}
151+
83152
private void initData() {
84153
with( entityManagerFactory ).runInTransaction( entityManager -> {
85154
Order order1 = new Order( 1 );

documentation/src/test/java/org/hibernate/search/documentation/search/predicate/PredicateDslIT.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.hibernate.search.engine.search.common.RewriteMethod;
2626
import org.hibernate.search.engine.search.predicate.dsl.RegexpQueryFlag;
2727
import org.hibernate.search.engine.search.predicate.dsl.SimpleQueryFlag;
28+
import org.hibernate.search.engine.search.reference.FieldReference;
2829
import org.hibernate.search.engine.spatial.DistanceUnit;
2930
import org.hibernate.search.engine.spatial.GeoBoundingBox;
3031
import org.hibernate.search.engine.spatial.GeoPoint;
@@ -467,6 +468,39 @@ void match() {
467468
} );
468469
}
469470

471+
@Test
472+
void matchFieldReference() {
473+
withinSearchSession( searchSession -> {
474+
List<Book> hits = searchSession.search( Book.class )
475+
.where( f -> f.match().field( new FieldReference.FieldAttributeReference<String, String>() {
476+
@Override
477+
public String absolutePath() {
478+
return "title";
479+
}
480+
481+
@Override
482+
public Class<String> type() {
483+
return String.class;
484+
}
485+
486+
@Override
487+
public FieldReference<String> noConverter() {
488+
return null;
489+
}
490+
491+
@Override
492+
public FieldReference<String> asString() {
493+
return null;
494+
}
495+
} )
496+
.matching( "robot" ) )
497+
.fetchHits( 20 );
498+
assertThat( hits )
499+
.extracting( Book::getId )
500+
.containsExactlyInAnyOrder( BOOK1_ID, BOOK3_ID );
501+
} );
502+
}
503+
470504
@Test
471505
void match_analysis() {
472506
withinSearchSession( searchSession -> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Hibernate Search, full-text search for your domain model
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.search.engine.search.predicate.dsl;
8+
9+
import org.hibernate.search.engine.search.reference.FieldReference;
10+
11+
/**
12+
* The step in a "match" predicate definition where the value to match can be set
13+
* (see the superinterface {@link MatchPredicateMatchingStep}),
14+
* or optional parameters for the last targeted field(s) can be set,
15+
* or more target fields can be added.
16+
*
17+
* @param <S> The "self" type (the actual exposed type of this step).
18+
* @param <N> The type of the next step.
19+
*/
20+
public interface MatchPredicateFRFieldMoreStep<T,
21+
S extends MatchPredicateFRFieldMoreStep<T, ?, N>,
22+
N extends MatchPredicateOptionsStep<?>>
23+
extends MatchPredicateFRMatchingStep<T, N>, MultiFieldPredicateFieldBoostStep<S> {
24+
25+
/**
26+
* Target the given field in the match predicate,
27+
* as an alternative to the already-targeted fields.
28+
* <p>
29+
* See {@link MatchPredicateFieldStep#field(String)} for more information about targeting fields.
30+
*
31+
* @param fieldReference The <a href="SearchPredicateFactory.html#field-paths">path</a> to the index field
32+
* to apply the predicate on.
33+
* @return The next step.
34+
*
35+
* @see MatchPredicateFieldStep#field(String)
36+
*/
37+
default S field(FieldReference<T> fieldReference) {
38+
return fields( fieldReference );
39+
}
40+
41+
/**
42+
* Target the given fields in the match predicate,
43+
* as an alternative to the already-targeted fields.
44+
* <p>
45+
* See {@link MatchPredicateFieldStep#fields(String...)} for more information about targeting fields.
46+
*
47+
* @param fieldReference The <a href="SearchPredicateFactory.html#field-paths">paths</a> to the index fields
48+
* to apply the predicate on.
49+
* @return The next step.
50+
*
51+
* @see MatchPredicateFieldStep#fields(String...)
52+
*/
53+
S fields(FieldReference<T>... fieldReference);
54+
55+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Hibernate Search, full-text search for your domain model
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.search.engine.search.predicate.dsl;
8+
9+
import org.hibernate.search.engine.search.common.ValueConvert;
10+
11+
/**
12+
* The step in a "match" predicate definition where the value to match can be set.
13+
*
14+
* @param <N> The type of the next step.
15+
*/
16+
public interface MatchPredicateFRMatchingStep<T, N extends MatchPredicateOptionsStep<?>> {
17+
18+
/**
19+
* Require at least one of the targeted fields to match the given value.
20+
* <p>
21+
* This method will apply DSL converters to {@code value} before Hibernate Search attempts to interpret it as a field value.
22+
* See {@link ValueConvert#YES}.
23+
*
24+
* @param value The value to match.
25+
* The signature of this method defines this parameter as an {@link Object},
26+
* but a specific type is expected depending on the targeted field.
27+
* See {@link ValueConvert#YES} for more information.
28+
* @return The next step.
29+
*/
30+
N matching(T value);
31+
32+
}

engine/src/main/java/org/hibernate/search/engine/search/predicate/dsl/MatchPredicateFieldStep.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
package org.hibernate.search.engine.search.predicate.dsl;
88

99

10+
import org.hibernate.search.engine.search.reference.FieldReference;
11+
1012
/**
1113
* The initial step in a "match" predicate definition, where the target field can be set.
1214
*/
@@ -45,4 +47,10 @@ default N field(String fieldPath) {
4547
* @see #field(String)
4648
*/
4749
N fields(String... fieldPaths);
50+
51+
default <T> MatchPredicateFRFieldMoreStep<T, ?, ?> field(FieldReference<T> field) {
52+
return fields( field );
53+
}
54+
55+
<T> MatchPredicateFRFieldMoreStep<T, ?, ?> fields(FieldReference<T>... fields);
4856
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Hibernate Search, full-text search for your domain model
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.search.engine.search.predicate.dsl.impl;
8+
9+
import java.lang.invoke.MethodHandles;
10+
import java.util.ArrayList;
11+
import java.util.Arrays;
12+
import java.util.LinkedHashMap;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.function.Consumer;
16+
17+
import org.hibernate.search.engine.logging.impl.Log;
18+
import org.hibernate.search.engine.search.common.ValueConvert;
19+
import org.hibernate.search.engine.search.common.spi.SearchIndexScope;
20+
import org.hibernate.search.engine.search.predicate.SearchPredicate;
21+
import org.hibernate.search.engine.search.predicate.dsl.MatchPredicateFRFieldMoreStep;
22+
import org.hibernate.search.engine.search.predicate.dsl.MatchPredicateFieldMoreStep;
23+
import org.hibernate.search.engine.search.predicate.dsl.MatchPredicateOptionsStep;
24+
import org.hibernate.search.engine.search.predicate.dsl.spi.SearchPredicateDslContext;
25+
import org.hibernate.search.engine.search.predicate.spi.MatchPredicateBuilder;
26+
import org.hibernate.search.engine.search.predicate.spi.PredicateTypeKeys;
27+
import org.hibernate.search.engine.search.reference.FieldReference;
28+
import org.hibernate.search.util.common.impl.Contracts;
29+
import org.hibernate.search.util.common.logging.impl.LoggerFactory;
30+
31+
class MatchPredicateFRFieldMoreStepImpl<T>
32+
implements MatchPredicateFRFieldMoreStep<T, MatchPredicateFRFieldMoreStepImpl<T>, MatchPredicateOptionsStep<?>>,
33+
AbstractBooleanMultiFieldPredicateCommonState.FieldSetState {
34+
35+
private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() );
36+
37+
private final CommonState<T> commonState;
38+
39+
private final Map<FieldReference<T>, MatchPredicateBuilder> predicateBuilders = new LinkedHashMap<>();
40+
41+
private Float fieldSetBoost;
42+
43+
MatchPredicateFRFieldMoreStepImpl(CommonState<T> commonState, List<FieldReference<T>> fieldReferences) {
44+
this.commonState = commonState;
45+
this.commonState.add( this );
46+
SearchIndexScope<?> scope = commonState.scope();
47+
for ( FieldReference<T> fieldReference : fieldReferences ) {
48+
predicateBuilders.put(
49+
fieldReference, scope.fieldQueryElement( fieldReference.absolutePath(), PredicateTypeKeys.MATCH ) );
50+
}
51+
}
52+
53+
@Override
54+
public MatchPredicateFRFieldMoreStepImpl<T> fields(FieldReference<T>... fieldPaths) {
55+
return new MatchPredicateFRFieldMoreStepImpl<>( commonState, Arrays.asList( fieldPaths ) );
56+
}
57+
58+
@Override
59+
public MatchPredicateFRFieldMoreStepImpl<T> boost(float boost) {
60+
this.fieldSetBoost = boost;
61+
return this;
62+
}
63+
64+
@Override
65+
public MatchPredicateOptionsStep<?> matching(T value) {
66+
return commonState.matching( value );
67+
}
68+
69+
@Override
70+
public void contributePredicates(Consumer<SearchPredicate> collector) {
71+
for ( MatchPredicateBuilder predicateBuilder : predicateBuilders.values() ) {
72+
// Perform last-minute changes, since it's the last call that will be made on this field set state
73+
commonState.applyBoostAndConstantScore( fieldSetBoost, predicateBuilder );
74+
75+
collector.accept( predicateBuilder.build() );
76+
}
77+
}
78+
79+
static class CommonState<T> extends AbstractBooleanMultiFieldPredicateCommonState<CommonState<T>, MatchPredicateFRFieldMoreStepImpl<T>>
80+
implements MatchPredicateOptionsStep<CommonState<T>> {
81+
82+
CommonState(SearchPredicateDslContext<?> dslContext) {
83+
super( dslContext );
84+
}
85+
86+
MatchPredicateOptionsStep<?> matching(Object value) {
87+
Contracts.assertNotNull( value, "value" );
88+
89+
for ( MatchPredicateFRFieldMoreStepImpl<T> fieldSetState : getFieldSetStates() ) {
90+
for ( Map.Entry<FieldReference<T>, MatchPredicateBuilder> entry : fieldSetState.predicateBuilders.entrySet() ) {
91+
entry.getValue().value( value, entry.getKey().valueConvert() );
92+
}
93+
}
94+
return this;
95+
}
96+
97+
@Override
98+
public CommonState<T> fuzzy(int maxEditDistance, int exactPrefixLength) {
99+
if ( maxEditDistance < 0 || 2 < maxEditDistance ) {
100+
throw log.invalidFuzzyMaximumEditDistance( maxEditDistance );
101+
}
102+
if ( exactPrefixLength < 0 ) {
103+
throw log.invalidExactPrefixLength( exactPrefixLength );
104+
}
105+
106+
for ( MatchPredicateFRFieldMoreStepImpl<T> fieldSetState : getFieldSetStates() ) {
107+
for ( MatchPredicateBuilder predicateBuilder : fieldSetState.predicateBuilders.values() ) {
108+
predicateBuilder.fuzzy( maxEditDistance, exactPrefixLength );
109+
}
110+
}
111+
return this;
112+
}
113+
114+
@Override
115+
public CommonState<T> analyzer(String analyzerName) {
116+
for ( MatchPredicateFRFieldMoreStepImpl<T> fieldSetState : getFieldSetStates() ) {
117+
for ( MatchPredicateBuilder predicateBuilder : fieldSetState.predicateBuilders.values() ) {
118+
predicateBuilder.analyzer( analyzerName );
119+
}
120+
}
121+
return this;
122+
}
123+
124+
@Override
125+
public CommonState<T> skipAnalysis() {
126+
for ( MatchPredicateFRFieldMoreStepImpl<T> fieldSetState : getFieldSetStates() ) {
127+
for ( MatchPredicateBuilder predicateBuilder : fieldSetState.predicateBuilders.values() ) {
128+
predicateBuilder.skipAnalysis();
129+
}
130+
}
131+
return this;
132+
}
133+
134+
@Override
135+
protected CommonState<T> thisAsS() {
136+
return this;
137+
}
138+
}
139+
140+
}

0 commit comments

Comments
 (0)