Skip to content

Commit 4d1ebb9

Browse files
committed
HSEARCH-4806 Parameterized aggregations
1 parent a5980a4 commit 4d1ebb9

File tree

17 files changed

+342
-33
lines changed

17 files changed

+342
-33
lines changed

backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/aggregation/impl/ElasticsearchSearchAggregation.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@
66
*/
77
package org.hibernate.search.backend.elasticsearch.search.aggregation.impl;
88

9+
import static org.hibernate.search.util.common.impl.CollectionHelper.isSubset;
10+
import static org.hibernate.search.util.common.impl.CollectionHelper.notInTheOtherSet;
11+
12+
import java.lang.invoke.MethodHandles;
913
import java.util.Set;
1014

15+
import org.hibernate.search.backend.elasticsearch.logging.impl.Log;
16+
import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexScope;
1117
import org.hibernate.search.engine.search.aggregation.AggregationKey;
1218
import org.hibernate.search.engine.search.aggregation.SearchAggregation;
19+
import org.hibernate.search.util.common.logging.impl.LoggerFactory;
1320

1421
import com.google.gson.JsonObject;
1522

1623
public interface ElasticsearchSearchAggregation<A> extends SearchAggregation<A> {
24+
Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() );
1725

1826
/**
1927
* Contribute to the request, making sure that the requirements for this aggregation are met.
@@ -41,4 +49,19 @@ interface Extractor<T> {
4149
T extract(JsonObject aggregationResult, AggregationExtractContext context);
4250
}
4351

52+
static <A> ElasticsearchSearchAggregation<A> from(ElasticsearchSearchIndexScope<?> scope,
53+
SearchAggregation<A> aggregation) {
54+
if ( !( aggregation instanceof ElasticsearchSearchAggregation ) ) {
55+
throw log.cannotMixElasticsearchSearchQueryWithOtherAggregations( aggregation );
56+
}
57+
58+
ElasticsearchSearchAggregation<A> casted = (ElasticsearchSearchAggregation<A>) aggregation;
59+
if ( !isSubset( scope.hibernateSearchIndexNames(), casted.indexNames() ) ) {
60+
throw log.aggregationDefinedOnDifferentIndexes(
61+
aggregation, casted.indexNames(), scope.hibernateSearchIndexNames(),
62+
notInTheOtherSet( scope.hibernateSearchIndexNames(), casted.indexNames() )
63+
);
64+
}
65+
return casted;
66+
}
4467
}

backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/aggregation/impl/ElasticsearchSearchAggregationBuilderFactory.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexScope;
1010
import org.hibernate.search.engine.search.aggregation.spi.SearchAggregationBuilder;
1111
import org.hibernate.search.engine.search.aggregation.spi.SearchAggregationBuilderFactory;
12+
import org.hibernate.search.engine.search.aggregation.spi.WithParametersAggregationBuilder;
1213

1314
import com.google.gson.JsonObject;
1415

@@ -28,4 +29,9 @@ public SearchAggregationBuilder<JsonObject> fromJson(JsonObject jsonObject) {
2829
public SearchAggregationBuilder<JsonObject> fromJson(String jsonString) {
2930
return fromJson( scope.userFacingGson().fromJson( jsonString, JsonObject.class ) );
3031
}
32+
33+
@Override
34+
public <T> WithParametersAggregationBuilder<T> withParameters() {
35+
return new ElasticsearchWithParametersAggregation.Builder<>( scope );
36+
}
3137
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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.backend.elasticsearch.search.aggregation.impl;
8+
9+
import java.util.function.Function;
10+
11+
import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexScope;
12+
import org.hibernate.search.engine.search.aggregation.AggregationKey;
13+
import org.hibernate.search.engine.search.aggregation.SearchAggregation;
14+
import org.hibernate.search.engine.search.aggregation.dsl.AggregationFinalStep;
15+
import org.hibernate.search.engine.search.aggregation.spi.WithParametersAggregationBuilder;
16+
import org.hibernate.search.engine.search.common.NamedValues;
17+
18+
import com.google.gson.JsonObject;
19+
20+
public class ElasticsearchWithParametersAggregation<A> extends AbstractElasticsearchAggregation<A> {
21+
private final ElasticsearchSearchIndexScope<?> scope;
22+
private final Function<? super NamedValues, ? extends AggregationFinalStep<A>> aggregationCreator;
23+
24+
private ElasticsearchWithParametersAggregation(Builder<A> builder) {
25+
super( builder );
26+
scope = builder.scope;
27+
aggregationCreator = builder.aggregationCreator;
28+
}
29+
30+
public Extractor<A> request(AggregationRequestContext context, AggregationKey<?> key, JsonObject jsonAggregations) {
31+
SearchAggregation<A> aggregation =
32+
aggregationCreator.apply( context.getRootPredicateContext().queryParameters() ).toAggregation();
33+
return ElasticsearchSearchAggregation.from( scope, aggregation )
34+
.request( context, key, jsonAggregations );
35+
}
36+
37+
public static class Builder<T> extends AbstractBuilder<T>
38+
implements WithParametersAggregationBuilder<T> {
39+
40+
private Function<? super NamedValues, ? extends AggregationFinalStep<T>> aggregationCreator;
41+
42+
public Builder(ElasticsearchSearchIndexScope<?> scope) {
43+
super( scope );
44+
}
45+
46+
@Override
47+
public ElasticsearchWithParametersAggregation<T> build() {
48+
return new ElasticsearchWithParametersAggregation<>( this );
49+
}
50+
51+
@Override
52+
public void creator(Function<? super NamedValues, ? extends AggregationFinalStep<T>> aggregationCreator) {
53+
this.aggregationCreator = aggregationCreator;
54+
}
55+
}
56+
}

backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/query/impl/ElasticsearchSearchQueryBuilder.java

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
*/
77
package org.hibernate.search.backend.elasticsearch.search.query.impl;
88

9-
import static org.hibernate.search.util.common.impl.CollectionHelper.isSubset;
10-
import static org.hibernate.search.util.common.impl.CollectionHelper.notInTheOtherSet;
11-
129
import java.lang.invoke.MethodHandles;
1310
import java.util.ArrayList;
1411
import java.util.Collections;
@@ -129,17 +126,8 @@ public void sort(SearchSort sort) {
129126

130127
@Override
131128
public <A> void aggregation(AggregationKey<A> key, SearchAggregation<A> aggregation) {
132-
if ( !( aggregation instanceof ElasticsearchSearchAggregation ) ) {
133-
throw log.cannotMixElasticsearchSearchQueryWithOtherAggregations( aggregation );
134-
}
135-
136-
ElasticsearchSearchAggregation<A> casted = (ElasticsearchSearchAggregation<A>) aggregation;
137-
if ( !isSubset( scope.hibernateSearchIndexNames(), casted.indexNames() ) ) {
138-
throw log.aggregationDefinedOnDifferentIndexes(
139-
aggregation, casted.indexNames(), scope.hibernateSearchIndexNames(),
140-
notInTheOtherSet( scope.hibernateSearchIndexNames(), casted.indexNames() )
141-
);
142-
}
129+
ElasticsearchSearchAggregation<A> casted = ElasticsearchSearchAggregation.from(
130+
scope, aggregation );
143131

144132
if ( aggregations == null ) {
145133
aggregations = new LinkedHashMap<>();

backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/aggregation/impl/AggregationRequestContext.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,29 @@
88

99
import org.hibernate.search.backend.lucene.lowlevel.collector.impl.CollectorFactory;
1010
import org.hibernate.search.backend.lucene.search.extraction.impl.ExtractionRequirements;
11+
import org.hibernate.search.engine.search.common.NamedValues;
12+
import org.hibernate.search.engine.search.query.spi.QueryParameters;
1113

1214
import org.apache.lucene.search.Collector;
1315
import org.apache.lucene.search.CollectorManager;
1416

1517
public final class AggregationRequestContext {
1618

1719
private final ExtractionRequirements.Builder extractionRequirementsBuilder;
20+
private final QueryParameters parameters;
1821

19-
public AggregationRequestContext(ExtractionRequirements.Builder extractionRequirementsBuilder) {
22+
public AggregationRequestContext(ExtractionRequirements.Builder extractionRequirementsBuilder,
23+
QueryParameters parameters) {
2024
this.extractionRequirementsBuilder = extractionRequirementsBuilder;
25+
this.parameters = parameters;
2126
}
2227

2328
public <C extends Collector, T, CM extends CollectorManager<C, T>> void requireCollector(
2429
CollectorFactory<C, T, CM> collectorFactory) {
2530
extractionRequirementsBuilder.requireCollectorForAllMatchingDocs( collectorFactory );
2631
}
32+
33+
public NamedValues queryParameters() {
34+
return parameters;
35+
}
2736
}

backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/aggregation/impl/LuceneSearchAggregation.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@
66
*/
77
package org.hibernate.search.backend.lucene.search.aggregation.impl;
88

9+
import static org.hibernate.search.util.common.impl.CollectionHelper.isSubset;
10+
import static org.hibernate.search.util.common.impl.CollectionHelper.notInTheOtherSet;
11+
912
import java.io.IOException;
13+
import java.lang.invoke.MethodHandles;
1014
import java.util.Set;
1115

16+
import org.hibernate.search.backend.lucene.logging.impl.Log;
17+
import org.hibernate.search.backend.lucene.search.common.impl.LuceneSearchIndexScope;
1218
import org.hibernate.search.engine.search.aggregation.SearchAggregation;
19+
import org.hibernate.search.util.common.logging.impl.LoggerFactory;
1320

1421
public interface LuceneSearchAggregation<A> extends SearchAggregation<A> {
22+
Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() );
1523

1624
/**
1725
* Request the collection of per-document data that will be used in
@@ -37,4 +45,21 @@ interface Extractor<T> {
3745
T extract(AggregationExtractContext context) throws IOException;
3846
}
3947

48+
static <A> LuceneSearchAggregation<A> from(LuceneSearchIndexScope<?> scope,
49+
SearchAggregation<A> aggregation) {
50+
if ( !( aggregation instanceof LuceneSearchAggregation ) ) {
51+
throw log.cannotMixLuceneSearchQueryWithOtherAggregations( aggregation );
52+
}
53+
54+
LuceneSearchAggregation<A> casted = (LuceneSearchAggregation<A>) aggregation;
55+
if ( !isSubset( scope.hibernateSearchIndexNames(), casted.indexNames() ) ) {
56+
throw log.aggregationDefinedOnDifferentIndexes(
57+
aggregation, casted.indexNames(), scope.hibernateSearchIndexNames(),
58+
notInTheOtherSet( scope.hibernateSearchIndexNames(), casted.indexNames() )
59+
);
60+
}
61+
return casted;
62+
}
63+
64+
4065
}

backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/aggregation/impl/LuceneSearchAggregationBuilderFactory.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@
77
package org.hibernate.search.backend.lucene.search.aggregation.impl;
88

99
import org.hibernate.search.backend.lucene.search.common.impl.LuceneSearchIndexScope;
10+
import org.hibernate.search.backend.lucene.types.aggregation.impl.LuceneWithParametersAggregation;
1011
import org.hibernate.search.engine.search.aggregation.spi.SearchAggregationBuilderFactory;
12+
import org.hibernate.search.engine.search.aggregation.spi.WithParametersAggregationBuilder;
1113

12-
public class LuceneSearchAggregationBuilderFactory
13-
implements SearchAggregationBuilderFactory {
14+
public class LuceneSearchAggregationBuilderFactory implements SearchAggregationBuilderFactory {
15+
16+
private final LuceneSearchIndexScope<?> scope;
1417

15-
@SuppressWarnings("unused")
1618
public LuceneSearchAggregationBuilderFactory(LuceneSearchIndexScope<?> scope) {
19+
this.scope = scope;
1720
}
1821

22+
@Override
23+
public <T> WithParametersAggregationBuilder<T> withParameters() {
24+
return new LuceneWithParametersAggregation.Builder<>( scope );
25+
}
1926
}

backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/query/impl/LuceneSearchQueryBuilder.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
*/
77
package org.hibernate.search.backend.lucene.search.query.impl;
88

9-
import static org.hibernate.search.util.common.impl.CollectionHelper.isSubset;
10-
import static org.hibernate.search.util.common.impl.CollectionHelper.notInTheOtherSet;
11-
129
import java.lang.invoke.MethodHandles;
1310
import java.util.ArrayList;
1411
import java.util.Collections;
@@ -115,17 +112,7 @@ public void sort(SearchSort sort) {
115112

116113
@Override
117114
public <A> void aggregation(AggregationKey<A> key, SearchAggregation<A> aggregation) {
118-
if ( !( aggregation instanceof LuceneSearchAggregation ) ) {
119-
throw log.cannotMixLuceneSearchQueryWithOtherAggregations( aggregation );
120-
}
121-
122-
LuceneSearchAggregation<A> casted = (LuceneSearchAggregation<A>) aggregation;
123-
if ( !isSubset( scope.hibernateSearchIndexNames(), casted.indexNames() ) ) {
124-
throw log.aggregationDefinedOnDifferentIndexes(
125-
aggregation, casted.indexNames(), scope.hibernateSearchIndexNames(),
126-
notInTheOtherSet( scope.hibernateSearchIndexNames(), casted.indexNames() )
127-
);
128-
}
115+
LuceneSearchAggregation<A> casted = LuceneSearchAggregation.from( scope, aggregation );
129116

130117
if ( aggregations == null ) {
131118
aggregations = new LinkedHashMap<>();
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.backend.lucene.types.aggregation.impl;
8+
9+
import java.util.Set;
10+
import java.util.function.Function;
11+
12+
import org.hibernate.search.backend.lucene.search.aggregation.impl.AggregationRequestContext;
13+
import org.hibernate.search.backend.lucene.search.aggregation.impl.LuceneSearchAggregation;
14+
import org.hibernate.search.backend.lucene.search.common.impl.LuceneSearchIndexScope;
15+
import org.hibernate.search.engine.search.aggregation.SearchAggregation;
16+
import org.hibernate.search.engine.search.aggregation.dsl.AggregationFinalStep;
17+
import org.hibernate.search.engine.search.aggregation.spi.WithParametersAggregationBuilder;
18+
import org.hibernate.search.engine.search.common.NamedValues;
19+
20+
public class LuceneWithParametersAggregation<A> implements LuceneSearchAggregation<A> {
21+
private final LuceneSearchIndexScope<?> scope;
22+
private final Function<? super NamedValues, ? extends AggregationFinalStep<A>> aggregationCreator;
23+
24+
private LuceneWithParametersAggregation(Builder<A> builder) {
25+
scope = builder.scope;
26+
aggregationCreator = builder.aggregationCreator;
27+
}
28+
29+
@Override
30+
public Extractor<A> request(AggregationRequestContext context) {
31+
SearchAggregation<A> aggregation = aggregationCreator.apply( context.queryParameters() ).toAggregation();
32+
33+
return LuceneSearchAggregation.from( scope, aggregation ).request( context );
34+
}
35+
36+
@Override
37+
public Set<String> indexNames() {
38+
return scope.hibernateSearchIndexNames();
39+
}
40+
41+
public static class Builder<A> implements WithParametersAggregationBuilder<A> {
42+
43+
protected final LuceneSearchIndexScope<?> scope;
44+
private Function<? super NamedValues, ? extends AggregationFinalStep<A>> aggregationCreator;
45+
46+
public Builder(LuceneSearchIndexScope<?> scope) {
47+
this.scope = scope;
48+
}
49+
50+
@Override
51+
public LuceneSearchAggregation<A> build() {
52+
return new LuceneWithParametersAggregation<>( this );
53+
}
54+
55+
@Override
56+
public void creator(Function<? super NamedValues, ? extends AggregationFinalStep<A>> aggregationCreator) {
57+
this.aggregationCreator = aggregationCreator;
58+
}
59+
}
60+
}

engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/SearchAggregationFactory.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
*/
77
package org.hibernate.search.engine.search.aggregation.dsl;
88

9+
import java.util.function.Function;
10+
11+
import org.hibernate.search.engine.search.common.NamedValues;
912
import org.hibernate.search.util.common.SearchException;
1013
import org.hibernate.search.util.common.annotation.Incubating;
1114

@@ -60,6 +63,19 @@ public interface SearchAggregationFactory {
6063
*/
6164
TermsAggregationFieldStep<?> terms();
6265

66+
67+
/**
68+
* Delegating aggregation that creates the actual aggregation at query create time and provides access to query parameters.
69+
* <p>
70+
* Which aggregation exactly to create is defined by a function passed to the arguments of this aggregation.
71+
*
72+
* @param aggregationCreator The function creating an actual aggregation.
73+
* @return A final DSL step in a parameterized aggregation definition.
74+
*/
75+
@Incubating
76+
<T> AggregationFinalStep<T> withParameters(
77+
Function<? super NamedValues, ? extends AggregationFinalStep<T>> aggregationCreator);
78+
6379
/**
6480
* Extend the current factory with the given extension,
6581
* resulting in an extended factory offering different types of aggregations.

0 commit comments

Comments
 (0)