Skip to content

Commit 7eec299

Browse files
committed
HSEARCH-5230 Support RAW model type with metric aggregations
1 parent 6000159 commit 7eec299

File tree

26 files changed

+807
-288
lines changed

26 files changed

+807
-288
lines changed

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

Lines changed: 120 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import org.hibernate.search.engine.backend.types.converter.spi.ProjectionConverter;
1818
import org.hibernate.search.engine.search.aggregation.spi.FieldMetricAggregationBuilder;
1919
import org.hibernate.search.engine.search.common.ValueModel;
20-
import org.hibernate.search.util.common.AssertionFailure;
2120

2221
import com.google.gson.JsonElement;
2322
import com.google.gson.JsonObject;
@@ -64,15 +63,13 @@ public static <F> ElasticsearchMetricFieldAggregation.Factory<F> avg(Elasticsear
6463
}
6564

6665
private final String absoluteFieldPath;
67-
private final ProjectionConverter<F, ? extends K> fromFieldValueConverter;
68-
private final ElasticsearchFieldCodec<F> codec;
66+
private final AggregationExtractorBuilder<K> metricFieldExtractorCreator;
6967
private final JsonAccessor<JsonObject> operation;
7068

7169
private ElasticsearchMetricFieldAggregation(Builder<F, K> builder) {
7270
super( builder );
7371
this.absoluteFieldPath = builder.field.absolutePath();
74-
this.fromFieldValueConverter = builder.fromFieldValueConverter;
75-
this.codec = builder.codec;
72+
this.metricFieldExtractorCreator = builder.metricFieldExtractorCreator;
7673
this.operation = builder.operation;
7774
}
7875

@@ -88,7 +85,7 @@ protected final JsonObject doRequest(AggregationRequestContext context) {
8885

8986
@Override
9087
protected Extractor<K> extractor(AggregationRequestContext context) {
91-
return new MetricFieldExtractor( nestedPathHierarchy, filter );
88+
return metricFieldExtractorCreator.extractor( filter );
9289
}
9390

9491
private static class Factory<F>
@@ -124,72 +121,155 @@ private TypeSelector(ElasticsearchFieldCodec<F> codec,
124121
this.operation = operation;
125122
}
126123

124+
@SuppressWarnings("unchecked")
127125
@Override
128126
public <T> Builder<F, T> type(Class<T> expectedType, ValueModel valueModel) {
129-
ProjectionConverter<F, ? extends T> projectionConverter = null;
130-
if ( useProjectionConverter( expectedType, valueModel ) ) {
131-
projectionConverter = field.type().projectionConverter( valueModel )
132-
.withConvertedType( expectedType, field );
133-
}
134-
return new Builder<>( codec, scope, field,
135-
projectionConverter,
136-
operation
137-
);
138-
}
127+
AggregationExtractorBuilder<T> metricFieldExtractorCreator;
139128

140-
private <T> boolean useProjectionConverter(Class<T> expectedType, ValueModel valueModel) {
141-
if ( !Double.class.isAssignableFrom( expectedType ) ) {
142-
if ( ValueModel.RAW.equals( valueModel ) ) {
143-
throw new AssertionFailure(
144-
"Raw projection converter is not supported with metric aggregations at the moment" );
129+
if ( ValueModel.RAW.equals( valueModel ) ) {
130+
if ( Double.class.isAssignableFrom( expectedType ) ) {
131+
metricFieldExtractorCreator = (AggregationExtractorBuilder<
132+
T>) new DoubleMetricFieldExtractor.Builder( field.nestedPathHierarchy() );
133+
}
134+
else {
135+
var projectionConverter = (ProjectionConverter<JsonElement, ? extends T>) field.type()
136+
.rawProjectionConverter().withConvertedType( expectedType, field );
137+
metricFieldExtractorCreator = (AggregationExtractorBuilder<T>) new RawMetricFieldExtractor.Builder<>(
138+
field.nestedPathHierarchy(),
139+
projectionConverter );
145140
}
146-
return true;
147141
}
148-
149-
// expectedType == Double.class
150-
if ( ValueModel.RAW.equals( valueModel ) ) {
151-
return false;
142+
else {
143+
var projectionConverter = field.type()
144+
.projectionConverter( valueModel ).withConvertedType( expectedType, field );
145+
metricFieldExtractorCreator =
146+
new MetricFieldExtractor.Builder<>( field.nestedPathHierarchy(), projectionConverter, codec );
152147
}
153-
return field.type().projectionConverter( valueModel ).valueType().isAssignableFrom( Double.class );
148+
149+
return new Builder<>( scope, field, metricFieldExtractorCreator, operation );
154150
}
155151
}
156152

157-
private class MetricFieldExtractor extends AbstractExtractor<K> {
158-
protected MetricFieldExtractor(List<String> nestedPathHierarchy, ElasticsearchSearchPredicate filter) {
153+
private static class MetricFieldExtractor<F, K> extends AbstractExtractor<K> {
154+
155+
private final ProjectionConverter<F, ? extends K> fromFieldValueConverter;
156+
private final ElasticsearchFieldCodec<F> codec;
157+
158+
protected MetricFieldExtractor(List<String> nestedPathHierarchy, ElasticsearchSearchPredicate filter,
159+
ProjectionConverter<F, ? extends K> fromFieldValueConverter, ElasticsearchFieldCodec<F> codec) {
159160
super( nestedPathHierarchy, filter );
161+
this.fromFieldValueConverter = fromFieldValueConverter;
162+
this.codec = codec;
160163
}
161164

162165
@Override
163-
@SuppressWarnings("unchecked")
164166
protected K doExtract(JsonObject aggregationResult, AggregationExtractContext context) {
165167
FromDocumentValueConvertContext convertContext = context.fromDocumentValueConvertContext();
166168
Optional<Double> value = VALUE_ACCESSOR.get( aggregationResult );
167169
JsonElement valueAsString = aggregationResult.get( "value_as_string" );
168170

169-
if ( fromFieldValueConverter == null ) {
170-
Double decode = value.orElse( null );
171-
return (K) decode;
172-
}
171+
173172
return fromFieldValueConverter.fromDocumentValue(
174173
codec.decodeAggregationValue( value, valueAsString ),
175174
convertContext
176175
);
177176
}
177+
178+
private static class Builder<F, K> extends AggregationExtractorBuilder<K> {
179+
private final ProjectionConverter<F, ? extends K> fromFieldValueConverter;
180+
private final ElasticsearchFieldCodec<F> codec;
181+
182+
private Builder(List<String> nestedPathHierarchy, ProjectionConverter<F, ? extends K> fromFieldValueConverter,
183+
ElasticsearchFieldCodec<F> codec) {
184+
super( nestedPathHierarchy );
185+
this.fromFieldValueConverter = fromFieldValueConverter;
186+
this.codec = codec;
187+
}
188+
189+
@Override
190+
AbstractExtractor<K> extractor(ElasticsearchSearchPredicate filter) {
191+
return new MetricFieldExtractor<>( nestedPathHierarchy, filter, fromFieldValueConverter, codec );
192+
}
193+
}
194+
}
195+
196+
private static class DoubleMetricFieldExtractor extends AbstractExtractor<Double> {
197+
protected DoubleMetricFieldExtractor(List<String> nestedPathHierarchy, ElasticsearchSearchPredicate filter) {
198+
super( nestedPathHierarchy, filter );
199+
}
200+
201+
@Override
202+
protected Double doExtract(JsonObject aggregationResult, AggregationExtractContext context) {
203+
Optional<Double> value = VALUE_ACCESSOR.get( aggregationResult );
204+
return value.orElse( null );
205+
}
206+
207+
private static class Builder extends AggregationExtractorBuilder<Double> {
208+
209+
private Builder(List<String> nestedPathHierarchy) {
210+
super( nestedPathHierarchy );
211+
}
212+
213+
@Override
214+
AbstractExtractor<Double> extractor(ElasticsearchSearchPredicate filter) {
215+
return new DoubleMetricFieldExtractor( nestedPathHierarchy, filter );
216+
}
217+
}
218+
}
219+
220+
private static class RawMetricFieldExtractor<K> extends AbstractExtractor<K> {
221+
222+
private final ProjectionConverter<JsonElement, K> projectionConverter;
223+
224+
protected RawMetricFieldExtractor(List<String> nestedPathHierarchy, ElasticsearchSearchPredicate filter,
225+
ProjectionConverter<JsonElement, K> projectionConverter) {
226+
super( nestedPathHierarchy, filter );
227+
this.projectionConverter = projectionConverter;
228+
}
229+
230+
@Override
231+
protected K doExtract(JsonObject aggregationResult, AggregationExtractContext context) {
232+
FromDocumentValueConvertContext convertContext = context.fromDocumentValueConvertContext();
233+
return projectionConverter.fromDocumentValue( aggregationResult, convertContext );
234+
}
235+
236+
private static class Builder<K> extends AggregationExtractorBuilder<K> {
237+
private final ProjectionConverter<JsonElement, K> projectionConverter;
238+
239+
private Builder(List<String> nestedPathHierarchy, ProjectionConverter<JsonElement, K> projectionConverter) {
240+
super( nestedPathHierarchy );
241+
this.projectionConverter = projectionConverter;
242+
}
243+
244+
@Override
245+
AbstractExtractor<K> extractor(ElasticsearchSearchPredicate filter) {
246+
return new RawMetricFieldExtractor<>( nestedPathHierarchy, filter, projectionConverter );
247+
}
248+
}
249+
}
250+
251+
private abstract static class AggregationExtractorBuilder<K> {
252+
protected final List<String> nestedPathHierarchy;
253+
254+
protected AggregationExtractorBuilder(List<String> nestedPathHierarchy) {
255+
this.nestedPathHierarchy = nestedPathHierarchy;
256+
}
257+
258+
abstract AbstractExtractor<K> extractor(ElasticsearchSearchPredicate filter);
178259
}
179260

180261
private static class Builder<F, K> extends AbstractBuilder<K>
181262
implements FieldMetricAggregationBuilder<K> {
182263

183-
private final ElasticsearchFieldCodec<F> codec;
184-
private final ProjectionConverter<F, ? extends K> fromFieldValueConverter;
264+
private final AggregationExtractorBuilder<K> metricFieldExtractorCreator;
185265
private final JsonAccessor<JsonObject> operation;
186266

187-
private Builder(ElasticsearchFieldCodec<F> codec, ElasticsearchSearchIndexScope<?> scope,
267+
private Builder(ElasticsearchSearchIndexScope<?> scope,
188268
ElasticsearchSearchIndexValueFieldContext<F> field,
189-
ProjectionConverter<F, ? extends K> fromFieldValueConverter, JsonAccessor<JsonObject> operation) {
269+
AggregationExtractorBuilder<K> metricFieldExtractorCreator,
270+
JsonAccessor<JsonObject> operation) {
190271
super( scope, field );
191-
this.codec = codec;
192-
this.fromFieldValueConverter = fromFieldValueConverter;
272+
this.metricFieldExtractorCreator = metricFieldExtractorCreator;
193273
this.operation = operation;
194274
}
195275

0 commit comments

Comments
 (0)