diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/aggregation/impl/ElasticsearchMetricDoubleAggregation.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/aggregation/impl/ElasticsearchMetricDoubleAggregation.java new file mode 100644 index 00000000000..9432d08d360 --- /dev/null +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/aggregation/impl/ElasticsearchMetricDoubleAggregation.java @@ -0,0 +1,83 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.backend.elasticsearch.search.aggregation.impl; + +import org.hibernate.search.backend.elasticsearch.search.common.impl.AbstractElasticsearchCodecAwareSearchQueryElementFactory; +import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexScope; +import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexValueFieldContext; +import org.hibernate.search.backend.elasticsearch.types.codec.impl.ElasticsearchDoubleFieldCodec; +import org.hibernate.search.backend.elasticsearch.types.codec.impl.ElasticsearchFieldCodec; +import org.hibernate.search.engine.search.aggregation.spi.SearchFilterableAggregationBuilder; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class ElasticsearchMetricDoubleAggregation extends AbstractElasticsearchNestableAggregation { + + private final String absoluteFieldPath; + private final String operation; + + public ElasticsearchMetricDoubleAggregation(Builder builder) { + super( builder ); + this.absoluteFieldPath = builder.field.absolutePath(); + this.operation = builder.operation; + } + + @Override + protected final JsonObject doRequest(AggregationRequestContext context) { + JsonObject outerObject = new JsonObject(); + JsonObject innerObject = new JsonObject(); + + outerObject.add( operation, innerObject ); + innerObject.addProperty( "field", absoluteFieldPath ); + return outerObject; + } + + @Override + protected Extractor extractor(AggregationRequestContext context) { + return new MetricDoubleExtractor(); + } + + public static class Factory + extends + AbstractElasticsearchCodecAwareSearchQueryElementFactory, F> { + + private final String operation; + + public Factory(ElasticsearchFieldCodec codec, String operation) { + super( codec ); + this.operation = operation; + } + + @Override + public SearchFilterableAggregationBuilder create(ElasticsearchSearchIndexScope scope, + ElasticsearchSearchIndexValueFieldContext field) { + return new Builder( scope, field, operation ); + } + } + + private static class MetricDoubleExtractor implements Extractor { + @Override + public Double extract(JsonObject aggregationResult, AggregationExtractContext context) { + JsonElement value = aggregationResult.get( "value" ); + return ElasticsearchDoubleFieldCodec.INSTANCE.decode( value ); + } + } + + private static class Builder extends AbstractBuilder implements SearchFilterableAggregationBuilder { + private final String operation; + + private Builder(ElasticsearchSearchIndexScope scope, ElasticsearchSearchIndexValueFieldContext field, + String operation) { + super( scope, field ); + this.operation = operation; + } + + @Override + public ElasticsearchMetricDoubleAggregation build() { + return new ElasticsearchMetricDoubleAggregation( this ); + } + } +} diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/aggregation/impl/ElasticsearchMetricFieldAggregation.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/aggregation/impl/ElasticsearchMetricFieldAggregation.java new file mode 100644 index 00000000000..05e0d5880d3 --- /dev/null +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/aggregation/impl/ElasticsearchMetricFieldAggregation.java @@ -0,0 +1,128 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.backend.elasticsearch.search.aggregation.impl; + +import org.hibernate.search.backend.elasticsearch.search.common.impl.AbstractElasticsearchCodecAwareSearchQueryElementFactory; +import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexScope; +import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexValueFieldContext; +import org.hibernate.search.backend.elasticsearch.types.codec.impl.ElasticsearchFieldCodec; +import org.hibernate.search.engine.backend.types.converter.runtime.FromDocumentValueConvertContext; +import org.hibernate.search.engine.backend.types.converter.spi.ProjectionConverter; +import org.hibernate.search.engine.search.aggregation.spi.FieldMetricAggregationBuilder; +import org.hibernate.search.engine.search.common.ValueConvert; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +/** + * @param The type of field values. + * @param The type of returned value. It can be {@code F} + * or a different type if value converters are used. + */ +public class ElasticsearchMetricFieldAggregation extends AbstractElasticsearchNestableAggregation { + + private final String absoluteFieldPath; + private final ProjectionConverter fromFieldValueConverter; + private final ElasticsearchFieldCodec codec; + private final String operation; + + public ElasticsearchMetricFieldAggregation(Builder builder) { + super( builder ); + this.absoluteFieldPath = builder.field.absolutePath(); + this.fromFieldValueConverter = builder.fromFieldValueConverter; + this.codec = builder.codec; + this.operation = builder.operation; + } + + @Override + protected final JsonObject doRequest(AggregationRequestContext context) { + JsonObject outerObject = new JsonObject(); + JsonObject innerObject = new JsonObject(); + + outerObject.add( operation, innerObject ); + innerObject.addProperty( "field", absoluteFieldPath ); + return outerObject; + } + + @Override + protected Extractor extractor(AggregationRequestContext context) { + return new MetricFieldExtractor(); + } + + public static class Factory + extends AbstractElasticsearchCodecAwareSearchQueryElementFactory { + + private final String operation; + + public Factory(ElasticsearchFieldCodec codec, String operation) { + super( codec ); + this.operation = operation; + } + + @Override + public FieldMetricAggregationBuilder.TypeSelector create(ElasticsearchSearchIndexScope scope, + ElasticsearchSearchIndexValueFieldContext field) { + return new ElasticsearchMetricFieldAggregation.TypeSelector<>( codec, scope, field, operation ); + } + } + + private static class TypeSelector implements FieldMetricAggregationBuilder.TypeSelector { + private final ElasticsearchFieldCodec codec; + private final ElasticsearchSearchIndexScope scope; + private final ElasticsearchSearchIndexValueFieldContext field; + private final String operation; + + private TypeSelector(ElasticsearchFieldCodec codec, + ElasticsearchSearchIndexScope scope, ElasticsearchSearchIndexValueFieldContext field, + String operation) { + this.codec = codec; + this.scope = scope; + this.field = field; + this.operation = operation; + } + + @Override + public Builder type(Class expectedType, ValueConvert convert) { + return new Builder<>( codec, scope, field, + field.type().projectionConverter( convert ).withConvertedType( expectedType, field ), + operation ); + } + } + + private class MetricFieldExtractor implements Extractor { + @Override + public K extract(JsonObject aggregationResult, AggregationExtractContext context) { + FromDocumentValueConvertContext convertContext = context.fromDocumentValueConvertContext(); + JsonElement value = aggregationResult.get( "value" ); + JsonElement valueAsString = aggregationResult.get( "value_as_string" ); + return fromFieldValueConverter.fromDocumentValue( + codec.decodeAggregationValue( value, valueAsString ), + convertContext + ); + } + } + + private static class Builder extends AbstractBuilder + implements FieldMetricAggregationBuilder { + + private final ElasticsearchFieldCodec codec; + private final ProjectionConverter fromFieldValueConverter; + private final String operation; + + private Builder(ElasticsearchFieldCodec codec, ElasticsearchSearchIndexScope scope, + ElasticsearchSearchIndexValueFieldContext field, + ProjectionConverter fromFieldValueConverter, String operation) { + super( scope, field ); + this.codec = codec; + this.fromFieldValueConverter = fromFieldValueConverter; + this.operation = operation; + } + + @Override + public ElasticsearchMetricFieldAggregation build() { + return new ElasticsearchMetricFieldAggregation<>( this ); + } + } +} diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/aggregation/impl/ElasticsearchMetricLongAggregation.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/aggregation/impl/ElasticsearchMetricLongAggregation.java new file mode 100644 index 00000000000..ee4098e3c12 --- /dev/null +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/aggregation/impl/ElasticsearchMetricLongAggregation.java @@ -0,0 +1,83 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.backend.elasticsearch.search.aggregation.impl; + +import org.hibernate.search.backend.elasticsearch.search.common.impl.AbstractElasticsearchCodecAwareSearchQueryElementFactory; +import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexScope; +import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexValueFieldContext; +import org.hibernate.search.backend.elasticsearch.types.codec.impl.ElasticsearchFieldCodec; +import org.hibernate.search.backend.elasticsearch.types.codec.impl.ElasticsearchLongFieldCodec; +import org.hibernate.search.engine.search.aggregation.spi.SearchFilterableAggregationBuilder; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class ElasticsearchMetricLongAggregation extends AbstractElasticsearchNestableAggregation { + + private final String absoluteFieldPath; + private final String operation; + + public ElasticsearchMetricLongAggregation(Builder builder) { + super( builder ); + this.absoluteFieldPath = builder.field.absolutePath(); + this.operation = builder.operation; + } + + @Override + protected final JsonObject doRequest(AggregationRequestContext context) { + JsonObject outerObject = new JsonObject(); + JsonObject innerObject = new JsonObject(); + + outerObject.add( operation, innerObject ); + innerObject.addProperty( "field", absoluteFieldPath ); + return outerObject; + } + + @Override + protected Extractor extractor(AggregationRequestContext context) { + return new MetricLongExtractor(); + } + + public static class Factory + extends + AbstractElasticsearchCodecAwareSearchQueryElementFactory, F> { + + private final String operation; + + public Factory(ElasticsearchFieldCodec codec, String operation) { + super( codec ); + this.operation = operation; + } + + @Override + public SearchFilterableAggregationBuilder create(ElasticsearchSearchIndexScope scope, + ElasticsearchSearchIndexValueFieldContext field) { + return new Builder( scope, field, operation ); + } + } + + private static class MetricLongExtractor implements Extractor { + @Override + public Long extract(JsonObject aggregationResult, AggregationExtractContext context) { + JsonElement value = aggregationResult.get( "value" ); + return ElasticsearchLongFieldCodec.INSTANCE.decode( value ); + } + } + + private static class Builder extends AbstractBuilder implements SearchFilterableAggregationBuilder { + private final String operation; + + private Builder(ElasticsearchSearchIndexScope scope, ElasticsearchSearchIndexValueFieldContext field, + String operation) { + super( scope, field ); + this.operation = operation; + } + + @Override + public ElasticsearchMetricLongAggregation build() { + return new ElasticsearchMetricLongAggregation( this ); + } + } +} diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBigDecimalFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBigDecimalFieldCodec.java index e7a4d434935..f9ef4b8399e 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBigDecimalFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBigDecimalFieldCodec.java @@ -12,6 +12,7 @@ import org.hibernate.search.backend.elasticsearch.gson.impl.JsonElementTypes; import org.hibernate.search.backend.elasticsearch.logging.impl.Log; import org.hibernate.search.engine.cfg.spi.NumberScaleConstants; +import org.hibernate.search.engine.cfg.spi.NumberUtils; import org.hibernate.search.util.common.logging.impl.LoggerFactory; import com.google.gson.JsonElement; @@ -63,6 +64,11 @@ public BigDecimal decode(JsonElement element) { return JsonElementTypes.BIG_DECIMAL.fromElement( element ); } + @Override + public BigDecimal decode(Double value) { + return NumberUtils.toBigDecimal( value ); + } + @Override public boolean isCompatibleWith(ElasticsearchFieldCodec obj) { if ( this == obj ) { diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBigIntegerFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBigIntegerFieldCodec.java index 5cd585a1fe9..a0d45e1f959 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBigIntegerFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBigIntegerFieldCodec.java @@ -13,6 +13,7 @@ import org.hibernate.search.backend.elasticsearch.gson.impl.JsonElementTypes; import org.hibernate.search.backend.elasticsearch.logging.impl.Log; import org.hibernate.search.engine.cfg.spi.NumberScaleConstants; +import org.hibernate.search.engine.cfg.spi.NumberUtils; import org.hibernate.search.util.common.logging.impl.LoggerFactory; import com.google.gson.JsonElement; @@ -65,6 +66,11 @@ public BigInteger decode(JsonElement element) { return JsonElementTypes.BIG_INTEGER.fromElement( element ); } + @Override + public BigInteger decode(Double value) { + return NumberUtils.toBigInteger( value ); + } + @Override public BigInteger decodeAggregationKey(JsonElement key, JsonElement keyAsString) { if ( key == null || key.isJsonNull() ) { diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBooleanFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBooleanFieldCodec.java index 72713a3a761..30968d9f489 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBooleanFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchBooleanFieldCodec.java @@ -32,6 +32,11 @@ public Boolean decode(JsonElement element) { return JsonElementTypes.BOOLEAN.fromElement( element ); } + @Override + public Boolean decode(Double value) { + return value != 0; + } + @Override public Boolean decodeAggregationKey(JsonElement key, JsonElement keyAsString) { if ( key == null || key.isJsonNull() ) { diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchByteFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchByteFieldCodec.java index c95043b6ce5..f2be3e11567 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchByteFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchByteFieldCodec.java @@ -5,6 +5,7 @@ package org.hibernate.search.backend.elasticsearch.types.codec.impl; import org.hibernate.search.backend.elasticsearch.gson.impl.JsonElementTypes; +import org.hibernate.search.engine.cfg.spi.NumberUtils; import com.google.gson.JsonElement; import com.google.gson.JsonNull; @@ -32,6 +33,11 @@ public Byte decode(JsonElement element) { return JsonElementTypes.BYTE.fromElement( element ); } + @Override + public Byte decode(Double value) { + return NumberUtils.toByte( value ); + } + @Override public boolean isCompatibleWith(ElasticsearchFieldCodec other) { return INSTANCE == other; diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchDoubleFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchDoubleFieldCodec.java index 664f3074fc8..7a4d5968680 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchDoubleFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchDoubleFieldCodec.java @@ -32,6 +32,11 @@ public Double decode(JsonElement element) { return JsonElementTypes.DOUBLE.fromElement( element ); } + @Override + public Double decode(Double value) { + return value; + } + @Override public boolean isCompatibleWith(ElasticsearchFieldCodec other) { return INSTANCE == other; diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchFieldCodec.java index 1ea704337ef..7ed2368ac62 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchFieldCodec.java @@ -37,6 +37,10 @@ default JsonElement encodeForAggregation(ElasticsearchSearchSyntax searchSyntax, F decode(JsonElement element); + default F decode(Double element) { + return null; + } + /** * Decodes the key returned by a term aggregation. * @param key The "key" property returned by the aggregation. @@ -49,6 +53,18 @@ default F decodeAggregationKey(JsonElement key, JsonElement keyAsString) { return decode( key ); } + default F decodeAggregationValue(JsonElement value, JsonElement valueAsString) { + if ( valueAsString != null ) { + return decode( valueAsString ); + } + if ( value == null || value.isJsonNull() ) { + return null; + } + + Double decoded = ElasticsearchDoubleFieldCodec.INSTANCE.decode( value ); + return decode( decoded ); + } + /** * Determine whether another codec is compatible with this one, i.e. whether it will encode/decode the information * to/from the document in a compatible way. diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchFloatFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchFloatFieldCodec.java index 263687d721d..ee89e4bce4e 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchFloatFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchFloatFieldCodec.java @@ -5,6 +5,7 @@ package org.hibernate.search.backend.elasticsearch.types.codec.impl; import org.hibernate.search.backend.elasticsearch.gson.impl.JsonElementTypes; +import org.hibernate.search.engine.cfg.spi.NumberUtils; import com.google.gson.JsonElement; import com.google.gson.JsonNull; @@ -32,6 +33,11 @@ public Float decode(JsonElement element) { return JsonElementTypes.FLOAT.fromElement( element ); } + @Override + public Float decode(Double value) { + return NumberUtils.toFloat( value ); + } + @Override public boolean isCompatibleWith(ElasticsearchFieldCodec other) { return INSTANCE == other; diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchIntegerFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchIntegerFieldCodec.java index 1675e1dede0..702ab799e3c 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchIntegerFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchIntegerFieldCodec.java @@ -5,6 +5,7 @@ package org.hibernate.search.backend.elasticsearch.types.codec.impl; import org.hibernate.search.backend.elasticsearch.gson.impl.JsonElementTypes; +import org.hibernate.search.engine.cfg.spi.NumberUtils; import com.google.gson.JsonElement; import com.google.gson.JsonNull; @@ -33,6 +34,11 @@ public Integer decode(JsonElement element) { return JsonElementTypes.INTEGER.fromElement( element ); } + @Override + public Integer decode(Double value) { + return NumberUtils.toInteger( value ); + } + @Override public boolean isCompatibleWith(ElasticsearchFieldCodec other) { return INSTANCE == other; diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchJsonElementFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchJsonElementFieldCodec.java index 6542a18388b..857c659be60 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchJsonElementFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchJsonElementFieldCodec.java @@ -7,6 +7,7 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonNull; +import com.google.gson.JsonPrimitive; public class ElasticsearchJsonElementFieldCodec implements ElasticsearchFieldCodec { @@ -32,6 +33,11 @@ public JsonElement decode(JsonElement element) { return element; } + @Override + public JsonElement decode(Double value) { + return new JsonPrimitive( value ); + } + @Override public boolean isCompatibleWith(ElasticsearchFieldCodec other) { if ( other == this ) { diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchLongFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchLongFieldCodec.java index 86ae531ead4..f6a6e8fcddf 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchLongFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchLongFieldCodec.java @@ -6,6 +6,7 @@ import org.hibernate.search.backend.elasticsearch.gson.impl.JsonElementTypes; import org.hibernate.search.backend.elasticsearch.lowlevel.syntax.search.impl.ElasticsearchSearchSyntax; +import org.hibernate.search.engine.cfg.spi.NumberUtils; import com.google.gson.JsonElement; import com.google.gson.JsonNull; @@ -38,6 +39,11 @@ public Long decode(JsonElement element) { return JsonElementTypes.LONG.fromElement( element ); } + @Override + public Long decode(Double value) { + return NumberUtils.toLong( value ); + } + @Override public boolean isCompatibleWith(ElasticsearchFieldCodec other) { return INSTANCE == other; diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchShortFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchShortFieldCodec.java index 78dc18202fe..3cc37688f00 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchShortFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchShortFieldCodec.java @@ -5,6 +5,7 @@ package org.hibernate.search.backend.elasticsearch.types.codec.impl; import org.hibernate.search.backend.elasticsearch.gson.impl.JsonElementTypes; +import org.hibernate.search.engine.cfg.spi.NumberUtils; import com.google.gson.JsonElement; import com.google.gson.JsonNull; @@ -32,6 +33,11 @@ public Short decode(JsonElement element) { return JsonElementTypes.SHORT.fromElement( element ); } + @Override + public Short decode(Double value) { + return NumberUtils.toShort( value ); + } + @Override public boolean isCompatibleWith(ElasticsearchFieldCodec other) { return INSTANCE == other; diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchStringFieldCodec.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchStringFieldCodec.java index 066fa143358..a63e3e181d9 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchStringFieldCodec.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/codec/impl/ElasticsearchStringFieldCodec.java @@ -33,6 +33,11 @@ public String decode(JsonElement element) { return JsonElementTypes.STRING.fromElement( element ); } + @Override + public String decode(Double value) { + return value.toString(); + } + @Override public boolean isCompatibleWith(ElasticsearchFieldCodec other) { return INSTANCE == other; diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/dsl/impl/AbstractElasticsearchNumericFieldTypeOptionsStep.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/dsl/impl/AbstractElasticsearchNumericFieldTypeOptionsStep.java index 61e37484bb2..a926f1de732 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/dsl/impl/AbstractElasticsearchNumericFieldTypeOptionsStep.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/types/dsl/impl/AbstractElasticsearchNumericFieldTypeOptionsStep.java @@ -4,6 +4,9 @@ */ package org.hibernate.search.backend.elasticsearch.types.dsl.impl; +import org.hibernate.search.backend.elasticsearch.search.aggregation.impl.ElasticsearchMetricDoubleAggregation; +import org.hibernate.search.backend.elasticsearch.search.aggregation.impl.ElasticsearchMetricFieldAggregation; +import org.hibernate.search.backend.elasticsearch.search.aggregation.impl.ElasticsearchMetricLongAggregation; import org.hibernate.search.backend.elasticsearch.search.aggregation.impl.ElasticsearchRangeAggregation; import org.hibernate.search.backend.elasticsearch.search.aggregation.impl.ElasticsearchTermsAggregation; import org.hibernate.search.backend.elasticsearch.search.predicate.impl.ElasticsearchExistsPredicate; @@ -63,6 +66,19 @@ protected final void complete() { builder.aggregable( true ); builder.queryElementFactory( AggregationTypeKeys.TERMS, new ElasticsearchTermsAggregation.Factory<>( codec ) ); builder.queryElementFactory( AggregationTypeKeys.RANGE, new ElasticsearchRangeAggregation.Factory<>( codec ) ); + builder.queryElementFactory( AggregationTypeKeys.SUM, + new ElasticsearchMetricFieldAggregation.Factory<>( codec, "sum" ) ); + builder.queryElementFactory( AggregationTypeKeys.MIN, + new ElasticsearchMetricFieldAggregation.Factory<>( codec, "min" ) ); + builder.queryElementFactory( AggregationTypeKeys.MAX, + new ElasticsearchMetricFieldAggregation.Factory<>( codec, "max" ) ); + builder.queryElementFactory( AggregationTypeKeys.COUNT, + new ElasticsearchMetricLongAggregation.Factory<>( codec, "value_count" ) ); + builder.queryElementFactory( AggregationTypeKeys.COUNT_DISTINCT, + new ElasticsearchMetricLongAggregation.Factory<>( codec, "cardinality" ) ); + builder.queryElementFactory( AggregationTypeKeys.AVG, + new ElasticsearchMetricDoubleAggregation.Factory<>( codec, "avg" ) + ); } } diff --git a/engine/src/main/java/org/hibernate/search/engine/backend/types/IndexFieldTraits.java b/engine/src/main/java/org/hibernate/search/engine/backend/types/IndexFieldTraits.java index 4cb6762d846..43ea75b6767 100644 --- a/engine/src/main/java/org/hibernate/search/engine/backend/types/IndexFieldTraits.java +++ b/engine/src/main/java/org/hibernate/search/engine/backend/types/IndexFieldTraits.java @@ -99,6 +99,12 @@ private Aggregations() { public static final String RANGE = "aggregation:range"; public static final String TERMS = "aggregation:terms"; + public static final String SUM = "aggregation:sum"; + public static final String MIN = "aggregation:min"; + public static final String MAX = "aggregation:max"; + public static final String COUNT = "aggregation:count"; + public static final String COUNT_DISTINCT = "aggregation:countDistinct"; + public static final String AVG = "aggregation:avg"; } diff --git a/engine/src/main/java/org/hibernate/search/engine/cfg/spi/NumberUtils.java b/engine/src/main/java/org/hibernate/search/engine/cfg/spi/NumberUtils.java new file mode 100644 index 00000000000..f2df266a047 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/cfg/spi/NumberUtils.java @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.cfg.spi; + +import java.math.BigDecimal; +import java.math.BigInteger; + +public final class NumberUtils { + + private NumberUtils() { + } + + public static BigDecimal toBigDecimal(Double value) { + if ( value == null ) { + return null; + } + return BigDecimal.valueOf( value ); + } + + public static BigInteger toBigInteger(Double value) { + if ( value == null ) { + return null; + } + return BigInteger.valueOf( value.longValue() ); + } + + public static Byte toByte(Double value) { + if ( value == null ) { + return null; + } + return value.byteValue(); + } + + public static Float toFloat(Double value) { + if ( value == null ) { + return null; + } + return value.floatValue(); + } + + public static Integer toInteger(Double value) { + if ( value == null ) { + return null; + } + return value.intValue(); + } + + public static Long toLong(Double value) { + if ( value == null ) { + return null; + } + return value.longValue(); + } + + public static Short toShort(Double value) { + if ( value == null ) { + return null; + } + return value.shortValue(); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/AvgAggregationFieldStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/AvgAggregationFieldStep.java new file mode 100644 index 00000000000..1f93b0b47a8 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/AvgAggregationFieldStep.java @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.common.ValueConvert; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The initial step in an "avg" aggregation definition, where the target field can be set. + * + * @param The type of factory used to create predicates in {@link AggregationFilterStep#filter(Function)}. + */ +public interface AvgAggregationFieldStep { + + /** + * Target the given field in the avg aggregation. + * + * @param fieldPath The path to the index field to aggregate. + * See {@link ValueConvert}. + * @return The next step. + */ + AvgAggregationOptionsStep field(String fieldPath); + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/AvgAggregationOptionsStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/AvgAggregationOptionsStep.java new file mode 100644 index 00000000000..0336f9e734e --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/AvgAggregationOptionsStep.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The final step in a "avg" aggregation definition, where optional parameters can be set. + * + * @param The "self" type (the actual exposed type of this step). + * @param The type of factory used to create predicates in {@link #filter(Function)}. + */ +public interface AvgAggregationOptionsStep< + S extends AvgAggregationOptionsStep, + PDF extends SearchPredicateFactory> + extends AggregationFinalStep, AggregationFilterStep { + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountAggregationFieldStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountAggregationFieldStep.java new file mode 100644 index 00000000000..f7e0e916f1e --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountAggregationFieldStep.java @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.common.ValueConvert; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The initial step in a "count" aggregation definition, where the target field can be set. + * + * @param The type of factory used to create predicates in {@link AggregationFilterStep#filter(Function)}. + */ +public interface CountAggregationFieldStep { + + /** + * Target the given field in the count aggregation. + * + * @param fieldPath The path to the index field to aggregate. + * See {@link ValueConvert}. + * @return The next step. + */ + CountAggregationOptionsStep field(String fieldPath); + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountAggregationOptionsStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountAggregationOptionsStep.java new file mode 100644 index 00000000000..eb77fdad964 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountAggregationOptionsStep.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The final step in a "count" aggregation definition, where optional parameters can be set. + * + * @param The "self" type (the actual exposed type of this step). + * @param The type of factory used to create predicates in {@link #filter(Function)}. + */ +public interface CountAggregationOptionsStep< + S extends CountAggregationOptionsStep, + PDF extends SearchPredicateFactory> + extends AggregationFinalStep, AggregationFilterStep { + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountDistinctAggregationFieldStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountDistinctAggregationFieldStep.java new file mode 100644 index 00000000000..1208c64c5da --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountDistinctAggregationFieldStep.java @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.common.ValueConvert; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The initial step in a "count distinct" aggregation definition, where the target field can be set. + * + * @param The type of factory used to create predicates in {@link AggregationFilterStep#filter(Function)}. + */ +public interface CountDistinctAggregationFieldStep { + + /** + * Target the given field in the count distinct aggregation. + * + * @param fieldPath The path to the index field to aggregate. + * See {@link ValueConvert}. + * @return The next step. + */ + CountDistinctAggregationOptionsStep field(String fieldPath); + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountDistinctAggregationOptionsStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountDistinctAggregationOptionsStep.java new file mode 100644 index 00000000000..6e1a9d6f64a --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/CountDistinctAggregationOptionsStep.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The final step in a "count distinct" aggregation definition, where optional parameters can be set. + * + * @param The "self" type (the actual exposed type of this step). + * @param The type of factory used to create predicates in {@link #filter(Function)}. + */ +public interface CountDistinctAggregationOptionsStep< + S extends CountDistinctAggregationOptionsStep, + PDF extends SearchPredicateFactory> + extends AggregationFinalStep, AggregationFilterStep { + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/ExtendedSearchAggregationFactory.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/ExtendedSearchAggregationFactory.java index 4d19b9f378e..471a0ba2e7d 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/ExtendedSearchAggregationFactory.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/ExtendedSearchAggregationFactory.java @@ -31,4 +31,23 @@ public interface ExtendedSearchAggregationFactory< @Override TermsAggregationFieldStep terms(); + + @Override + SumAggregationFieldStep sum(); + + @Override + MinAggregationFieldStep min(); + + @Override + MaxAggregationFieldStep max(); + + @Override + CountAggregationFieldStep count(); + + @Override + CountDistinctAggregationFieldStep countDistinct(); + + @Override + AvgAggregationFieldStep avg(); + } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MaxAggregationFieldStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MaxAggregationFieldStep.java new file mode 100644 index 00000000000..a42247818d7 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MaxAggregationFieldStep.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.common.ValueConvert; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The initial step in a "min" aggregation definition, where the target field can be set. + * + * @param The type of factory used to create predicates in {@link AggregationFilterStep#filter(Function)}. + */ +public interface MaxAggregationFieldStep { + + /** + * Target the given field in the min aggregation. + * + * @param fieldPath The path to the index field to aggregate. + * @param type The type of field values. + * @param The type of field values. + * @return The next step. + */ + default MaxAggregationOptionsStep field(String fieldPath, Class type) { + return field( fieldPath, type, ValueConvert.YES ); + } + + /** + * Target the given field in the min aggregation. + * + * @param fieldPath The path to the index field to aggregate. + * @param type The type of field values. + * @param The type of field values. + * @param convert Controls how the ranges passed to the next steps and fetched from the backend should be converted. + * See {@link ValueConvert}. + * @return The next step. + */ + MaxAggregationOptionsStep field(String fieldPath, Class type, + ValueConvert convert); + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MaxAggregationOptionsStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MaxAggregationOptionsStep.java new file mode 100644 index 00000000000..60b6e0755d4 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MaxAggregationOptionsStep.java @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The final step in a "max" aggregation definition, where optional parameters can be set. + * + * @param The "self" type (the actual exposed type of this step). + * @param The type of factory used to create predicates in {@link #filter(Function)}. + * @param The type of the targeted field. The type of result for this aggregation. + */ +public interface MaxAggregationOptionsStep< + S extends MaxAggregationOptionsStep, + PDF extends SearchPredicateFactory, + F> + extends AggregationFinalStep, AggregationFilterStep { + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MinAggregationFieldStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MinAggregationFieldStep.java new file mode 100644 index 00000000000..f51709912f9 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MinAggregationFieldStep.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.common.ValueConvert; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The initial step in a "min" aggregation definition, where the target field can be set. + * + * @param The type of factory used to create predicates in {@link AggregationFilterStep#filter(Function)}. + */ +public interface MinAggregationFieldStep { + + /** + * Target the given field in the min aggregation. + * + * @param fieldPath The path to the index field to aggregate. + * @param type The type of field values. + * @param The type of field values. + * @return The next step. + */ + default MinAggregationOptionsStep field(String fieldPath, Class type) { + return field( fieldPath, type, ValueConvert.YES ); + } + + /** + * Target the given field in the min aggregation. + * + * @param fieldPath The path to the index field to aggregate. + * @param type The type of field values. + * @param The type of field values. + * @param convert Controls how the ranges passed to the next steps and fetched from the backend should be converted. + * See {@link ValueConvert}. + * @return The next step. + */ + MinAggregationOptionsStep field(String fieldPath, Class type, + ValueConvert convert); + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MinAggregationOptionsStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MinAggregationOptionsStep.java new file mode 100644 index 00000000000..51a1c1ca9bb --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/MinAggregationOptionsStep.java @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The final step in a "min" aggregation definition, where optional parameters can be set. + * + * @param The "self" type (the actual exposed type of this step). + * @param The type of factory used to create predicates in {@link #filter(Function)}. + * @param The type of the targeted field. The type of result for this aggregation. + */ +public interface MinAggregationOptionsStep< + S extends MinAggregationOptionsStep, + PDF extends SearchPredicateFactory, + F> + extends AggregationFinalStep, AggregationFilterStep { + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/SearchAggregationFactory.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/SearchAggregationFactory.java index 3d33bc626a9..5f104b72b69 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/SearchAggregationFactory.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/SearchAggregationFactory.java @@ -61,6 +61,47 @@ public interface SearchAggregationFactory { */ TermsAggregationFieldStep terms(); + /** + * Perform the sum metrics aggregation. + * + * @return The next step. + */ + SumAggregationFieldStep sum(); + + /** + * Perform the min metrics aggregation. + * + * @return The next step. + */ + MinAggregationFieldStep min(); + + /** + * Perform the max metrics aggregation. + * + * @return The next step. + */ + MaxAggregationFieldStep max(); + + /** + * Perform the count metrics aggregation. + * + * @return The next step. + */ + CountAggregationFieldStep count(); + + /** + * Perform the count distinct metrics aggregation. + * + * @return The next step. + */ + CountDistinctAggregationFieldStep countDistinct(); + + /** + * Perform the avg metrics aggregation. + * + * @return the next step. + */ + AvgAggregationFieldStep avg(); /** * Delegating aggregation that creates the actual aggregation at query create time and provides access to query parameters. diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/SumAggregationFieldStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/SumAggregationFieldStep.java new file mode 100644 index 00000000000..ad02007881b --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/SumAggregationFieldStep.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.common.ValueConvert; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The initial step in a "sum" aggregation definition, where the target field can be set. + * + * @param The type of factory used to create predicates in {@link AggregationFilterStep#filter(Function)}. + */ +public interface SumAggregationFieldStep { + + /** + * Target the given field in the sum aggregation. + * + * @param fieldPath The path to the index field to aggregate. + * @param type The type of field values. + * @param The type of field values. + * @return The next step. + */ + default SumAggregationOptionsStep field(String fieldPath, Class type) { + return field( fieldPath, type, ValueConvert.YES ); + } + + /** + * Target the given field in the sum aggregation. + * + * @param fieldPath The path to the index field to aggregate. + * @param type The type of field values. + * @param The type of field values. + * @param convert Controls how the ranges passed to the next steps and fetched from the backend should be converted. + * See {@link ValueConvert}. + * @return The next step. + */ + SumAggregationOptionsStep field(String fieldPath, Class type, + ValueConvert convert); + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/SumAggregationOptionsStep.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/SumAggregationOptionsStep.java new file mode 100644 index 00000000000..974b36556d3 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/SumAggregationOptionsStep.java @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +/** + * The final step in a "sum" aggregation definition, where optional parameters can be set. + * + * @param The "self" type (the actual exposed type of this step). + * @param The type of factory used to create predicates in {@link #filter(Function)}. + * @param The type of the targeted field. The type of result for this aggregation. + */ +public interface SumAggregationOptionsStep< + S extends SumAggregationOptionsStep, + PDF extends SearchPredicateFactory, + F> + extends AggregationFinalStep, AggregationFilterStep { + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/AvgAggregationFieldStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/AvgAggregationFieldStepImpl.java new file mode 100644 index 00000000000..4e0da0c55f9 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/AvgAggregationFieldStepImpl.java @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import org.hibernate.search.engine.search.aggregation.dsl.AvgAggregationFieldStep; +import org.hibernate.search.engine.search.aggregation.dsl.AvgAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.AggregationTypeKeys; +import org.hibernate.search.engine.search.aggregation.spi.SearchFilterableAggregationBuilder; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +public class AvgAggregationFieldStepImpl + implements AvgAggregationFieldStep { + private final SearchAggregationDslContext dslContext; + + public AvgAggregationFieldStepImpl(SearchAggregationDslContext dslContext) { + this.dslContext = dslContext; + } + + @Override + public AvgAggregationOptionsStep field(String fieldPath) { + SearchFilterableAggregationBuilder builder = dslContext.scope() + .fieldQueryElement( fieldPath, AggregationTypeKeys.AVG ); + return new AvgAggregationOptionsStepImpl<>( builder, dslContext ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/AvgAggregationOptionsStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/AvgAggregationOptionsStepImpl.java new file mode 100644 index 00000000000..10502a6957f --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/AvgAggregationOptionsStepImpl.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.aggregation.SearchAggregation; +import org.hibernate.search.engine.search.aggregation.dsl.AvgAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.SearchFilterableAggregationBuilder; +import org.hibernate.search.engine.search.predicate.SearchPredicate; +import org.hibernate.search.engine.search.predicate.dsl.PredicateFinalStep; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +class AvgAggregationOptionsStepImpl + implements AvgAggregationOptionsStep, PDF> { + private final SearchFilterableAggregationBuilder builder; + private final SearchAggregationDslContext dslContext; + + AvgAggregationOptionsStepImpl(SearchFilterableAggregationBuilder builder, + SearchAggregationDslContext dslContext) { + this.builder = builder; + this.dslContext = dslContext; + } + + @Override + public AvgAggregationOptionsStepImpl filter( + Function clauseContributor) { + SearchPredicate predicate = clauseContributor.apply( dslContext.predicateFactory() ).toPredicate(); + return filter( predicate ); + } + + @Override + public AvgAggregationOptionsStepImpl filter(SearchPredicate searchPredicate) { + builder.filter( searchPredicate ); + return this; + } + + @Override + public SearchAggregation toAggregation() { + return builder.build(); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountAggregationFieldStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountAggregationFieldStepImpl.java new file mode 100644 index 00000000000..c9e8dc6d957 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountAggregationFieldStepImpl.java @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import org.hibernate.search.engine.search.aggregation.dsl.CountAggregationFieldStep; +import org.hibernate.search.engine.search.aggregation.dsl.CountAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.AggregationTypeKeys; +import org.hibernate.search.engine.search.aggregation.spi.SearchFilterableAggregationBuilder; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +public class CountAggregationFieldStepImpl + implements CountAggregationFieldStep { + private final SearchAggregationDslContext dslContext; + + public CountAggregationFieldStepImpl(SearchAggregationDslContext dslContext) { + this.dslContext = dslContext; + } + + @Override + public CountAggregationOptionsStep field(String fieldPath) { + SearchFilterableAggregationBuilder builder = dslContext.scope() + .fieldQueryElement( fieldPath, AggregationTypeKeys.COUNT ); + return new CountAggregationOptionsStepImpl<>( builder, dslContext ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountAggregationOptionsStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountAggregationOptionsStepImpl.java new file mode 100644 index 00000000000..b901b4ddf70 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountAggregationOptionsStepImpl.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.aggregation.SearchAggregation; +import org.hibernate.search.engine.search.aggregation.dsl.CountAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.SearchFilterableAggregationBuilder; +import org.hibernate.search.engine.search.predicate.SearchPredicate; +import org.hibernate.search.engine.search.predicate.dsl.PredicateFinalStep; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +class CountAggregationOptionsStepImpl + implements CountAggregationOptionsStep, PDF> { + private final SearchFilterableAggregationBuilder builder; + private final SearchAggregationDslContext dslContext; + + CountAggregationOptionsStepImpl(SearchFilterableAggregationBuilder builder, + SearchAggregationDslContext dslContext) { + this.builder = builder; + this.dslContext = dslContext; + } + + @Override + public CountAggregationOptionsStepImpl filter( + Function clauseContributor) { + SearchPredicate predicate = clauseContributor.apply( dslContext.predicateFactory() ).toPredicate(); + return filter( predicate ); + } + + @Override + public CountAggregationOptionsStepImpl filter(SearchPredicate searchPredicate) { + builder.filter( searchPredicate ); + return this; + } + + @Override + public SearchAggregation toAggregation() { + return builder.build(); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountDistinctAggregationFieldStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountDistinctAggregationFieldStepImpl.java new file mode 100644 index 00000000000..9fd3109d386 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountDistinctAggregationFieldStepImpl.java @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import org.hibernate.search.engine.search.aggregation.dsl.CountDistinctAggregationFieldStep; +import org.hibernate.search.engine.search.aggregation.dsl.CountDistinctAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.AggregationTypeKeys; +import org.hibernate.search.engine.search.aggregation.spi.SearchFilterableAggregationBuilder; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +public class CountDistinctAggregationFieldStepImpl + implements CountDistinctAggregationFieldStep { + private final SearchAggregationDslContext dslContext; + + public CountDistinctAggregationFieldStepImpl(SearchAggregationDslContext dslContext) { + this.dslContext = dslContext; + } + + @Override + public CountDistinctAggregationOptionsStep field(String fieldPath) { + SearchFilterableAggregationBuilder builder = dslContext.scope() + .fieldQueryElement( fieldPath, AggregationTypeKeys.COUNT_DISTINCT ); + return new CountDistinctAggregationOptionsStepImpl<>( builder, dslContext ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountDistinctAggregationOptionsStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountDistinctAggregationOptionsStepImpl.java new file mode 100644 index 00000000000..c70765f7cd5 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/CountDistinctAggregationOptionsStepImpl.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.aggregation.SearchAggregation; +import org.hibernate.search.engine.search.aggregation.dsl.CountDistinctAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.SearchFilterableAggregationBuilder; +import org.hibernate.search.engine.search.predicate.SearchPredicate; +import org.hibernate.search.engine.search.predicate.dsl.PredicateFinalStep; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +class CountDistinctAggregationOptionsStepImpl + implements CountDistinctAggregationOptionsStep, PDF> { + private final SearchFilterableAggregationBuilder builder; + private final SearchAggregationDslContext dslContext; + + CountDistinctAggregationOptionsStepImpl(SearchFilterableAggregationBuilder builder, + SearchAggregationDslContext dslContext) { + this.builder = builder; + this.dslContext = dslContext; + } + + @Override + public CountDistinctAggregationOptionsStepImpl filter( + Function clauseContributor) { + SearchPredicate predicate = clauseContributor.apply( dslContext.predicateFactory() ).toPredicate(); + return filter( predicate ); + } + + @Override + public CountDistinctAggregationOptionsStepImpl filter(SearchPredicate searchPredicate) { + builder.filter( searchPredicate ); + return this; + } + + @Override + public SearchAggregation toAggregation() { + return builder.build(); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MaxAggregationFieldStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MaxAggregationFieldStepImpl.java new file mode 100644 index 00000000000..7e5cae9afc8 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MaxAggregationFieldStepImpl.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import org.hibernate.search.engine.search.aggregation.dsl.MaxAggregationFieldStep; +import org.hibernate.search.engine.search.aggregation.dsl.MaxAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.AggregationTypeKeys; +import org.hibernate.search.engine.search.aggregation.spi.FieldMetricAggregationBuilder; +import org.hibernate.search.engine.search.common.ValueConvert; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; +import org.hibernate.search.util.common.impl.Contracts; + +public class MaxAggregationFieldStepImpl implements MaxAggregationFieldStep { + private final SearchAggregationDslContext dslContext; + + public MaxAggregationFieldStepImpl(SearchAggregationDslContext dslContext) { + this.dslContext = dslContext; + } + + @Override + public MaxAggregationOptionsStep field(String fieldPath, Class type, + ValueConvert convert) { + Contracts.assertNotNull( fieldPath, "fieldPath" ); + Contracts.assertNotNull( type, "type" ); + FieldMetricAggregationBuilder builder = dslContext.scope() + .fieldQueryElement( fieldPath, AggregationTypeKeys.MAX ).type( type, convert ); + return new MaxAggregationOptionsStepImpl<>( builder, dslContext ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MaxAggregationOptionsStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MaxAggregationOptionsStepImpl.java new file mode 100644 index 00000000000..061418467b0 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MaxAggregationOptionsStepImpl.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.aggregation.SearchAggregation; +import org.hibernate.search.engine.search.aggregation.dsl.MaxAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.FieldMetricAggregationBuilder; +import org.hibernate.search.engine.search.predicate.SearchPredicate; +import org.hibernate.search.engine.search.predicate.dsl.PredicateFinalStep; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +class MaxAggregationOptionsStepImpl + implements MaxAggregationOptionsStep, PDF, F> { + private final FieldMetricAggregationBuilder builder; + private final SearchAggregationDslContext dslContext; + + MaxAggregationOptionsStepImpl(FieldMetricAggregationBuilder builder, + SearchAggregationDslContext dslContext) { + this.builder = builder; + this.dslContext = dslContext; + } + + @Override + public MaxAggregationOptionsStepImpl filter( + Function clauseContributor) { + SearchPredicate predicate = clauseContributor.apply( dslContext.predicateFactory() ).toPredicate(); + + return filter( predicate ); + } + + @Override + public MaxAggregationOptionsStepImpl filter(SearchPredicate searchPredicate) { + builder.filter( searchPredicate ); + return this; + } + + @Override + public SearchAggregation toAggregation() { + return builder.build(); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MinAggregationFieldStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MinAggregationFieldStepImpl.java new file mode 100644 index 00000000000..9883b707b9f --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MinAggregationFieldStepImpl.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import org.hibernate.search.engine.search.aggregation.dsl.MinAggregationFieldStep; +import org.hibernate.search.engine.search.aggregation.dsl.MinAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.AggregationTypeKeys; +import org.hibernate.search.engine.search.aggregation.spi.FieldMetricAggregationBuilder; +import org.hibernate.search.engine.search.common.ValueConvert; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; +import org.hibernate.search.util.common.impl.Contracts; + +public class MinAggregationFieldStepImpl implements MinAggregationFieldStep { + private final SearchAggregationDslContext dslContext; + + public MinAggregationFieldStepImpl(SearchAggregationDslContext dslContext) { + this.dslContext = dslContext; + } + + @Override + public MinAggregationOptionsStep field(String fieldPath, Class type, + ValueConvert convert) { + Contracts.assertNotNull( fieldPath, "fieldPath" ); + Contracts.assertNotNull( type, "type" ); + FieldMetricAggregationBuilder builder = dslContext.scope() + .fieldQueryElement( fieldPath, AggregationTypeKeys.MIN ).type( type, convert ); + return new MinAggregationOptionsStepImpl<>( builder, dslContext ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MinAggregationOptionsStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MinAggregationOptionsStepImpl.java new file mode 100644 index 00000000000..914349e578e --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/MinAggregationOptionsStepImpl.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.aggregation.SearchAggregation; +import org.hibernate.search.engine.search.aggregation.dsl.MinAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.FieldMetricAggregationBuilder; +import org.hibernate.search.engine.search.predicate.SearchPredicate; +import org.hibernate.search.engine.search.predicate.dsl.PredicateFinalStep; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +class MinAggregationOptionsStepImpl + implements MinAggregationOptionsStep, PDF, F> { + private final FieldMetricAggregationBuilder builder; + private final SearchAggregationDslContext dslContext; + + MinAggregationOptionsStepImpl(FieldMetricAggregationBuilder builder, + SearchAggregationDslContext dslContext) { + this.builder = builder; + this.dslContext = dslContext; + } + + @Override + public MinAggregationOptionsStepImpl filter( + Function clauseContributor) { + SearchPredicate predicate = clauseContributor.apply( dslContext.predicateFactory() ).toPredicate(); + + return filter( predicate ); + } + + @Override + public MinAggregationOptionsStepImpl filter(SearchPredicate searchPredicate) { + builder.filter( searchPredicate ); + return this; + } + + @Override + public SearchAggregation toAggregation() { + return builder.build(); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/SumAggregationFieldStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/SumAggregationFieldStepImpl.java new file mode 100644 index 00000000000..468fd06a11f --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/SumAggregationFieldStepImpl.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import org.hibernate.search.engine.search.aggregation.dsl.SumAggregationFieldStep; +import org.hibernate.search.engine.search.aggregation.dsl.SumAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.AggregationTypeKeys; +import org.hibernate.search.engine.search.aggregation.spi.FieldMetricAggregationBuilder; +import org.hibernate.search.engine.search.common.ValueConvert; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; +import org.hibernate.search.util.common.impl.Contracts; + +public class SumAggregationFieldStepImpl implements SumAggregationFieldStep { + private final SearchAggregationDslContext dslContext; + + public SumAggregationFieldStepImpl(SearchAggregationDslContext dslContext) { + this.dslContext = dslContext; + } + + @Override + public SumAggregationOptionsStep field(String fieldPath, Class type, + ValueConvert convert) { + Contracts.assertNotNull( fieldPath, "fieldPath" ); + Contracts.assertNotNull( type, "type" ); + FieldMetricAggregationBuilder builder = dslContext.scope() + .fieldQueryElement( fieldPath, AggregationTypeKeys.SUM ).type( type, convert ); + return new SumAggregationOptionsStepImpl<>( builder, dslContext ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/SumAggregationOptionsStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/SumAggregationOptionsStepImpl.java new file mode 100644 index 00000000000..5a660f5220d --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/impl/SumAggregationOptionsStepImpl.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.dsl.impl; + +import java.util.function.Function; + +import org.hibernate.search.engine.search.aggregation.SearchAggregation; +import org.hibernate.search.engine.search.aggregation.dsl.SumAggregationOptionsStep; +import org.hibernate.search.engine.search.aggregation.dsl.spi.SearchAggregationDslContext; +import org.hibernate.search.engine.search.aggregation.spi.FieldMetricAggregationBuilder; +import org.hibernate.search.engine.search.predicate.SearchPredicate; +import org.hibernate.search.engine.search.predicate.dsl.PredicateFinalStep; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; + +class SumAggregationOptionsStepImpl + implements SumAggregationOptionsStep, PDF, F> { + private final FieldMetricAggregationBuilder builder; + private final SearchAggregationDslContext dslContext; + + SumAggregationOptionsStepImpl(FieldMetricAggregationBuilder builder, + SearchAggregationDslContext dslContext) { + this.builder = builder; + this.dslContext = dslContext; + } + + @Override + public SumAggregationOptionsStepImpl filter( + Function clauseContributor) { + SearchPredicate predicate = clauseContributor.apply( dslContext.predicateFactory() ).toPredicate(); + + return filter( predicate ); + } + + @Override + public SumAggregationOptionsStepImpl filter(SearchPredicate searchPredicate) { + builder.filter( searchPredicate ); + return this; + } + + @Override + public SearchAggregation toAggregation() { + return builder.build(); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/spi/AbstractSearchAggregationFactory.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/spi/AbstractSearchAggregationFactory.java index 7f88ffaa996..655225e993d 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/spi/AbstractSearchAggregationFactory.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/spi/AbstractSearchAggregationFactory.java @@ -8,11 +8,23 @@ import org.hibernate.search.engine.common.dsl.spi.DslExtensionState; import org.hibernate.search.engine.search.aggregation.dsl.AggregationFinalStep; +import org.hibernate.search.engine.search.aggregation.dsl.AvgAggregationFieldStep; +import org.hibernate.search.engine.search.aggregation.dsl.CountAggregationFieldStep; +import org.hibernate.search.engine.search.aggregation.dsl.CountDistinctAggregationFieldStep; import org.hibernate.search.engine.search.aggregation.dsl.ExtendedSearchAggregationFactory; +import org.hibernate.search.engine.search.aggregation.dsl.MaxAggregationFieldStep; +import org.hibernate.search.engine.search.aggregation.dsl.MinAggregationFieldStep; import org.hibernate.search.engine.search.aggregation.dsl.RangeAggregationFieldStep; import org.hibernate.search.engine.search.aggregation.dsl.SearchAggregationFactoryExtension; +import org.hibernate.search.engine.search.aggregation.dsl.SumAggregationFieldStep; import org.hibernate.search.engine.search.aggregation.dsl.TermsAggregationFieldStep; +import org.hibernate.search.engine.search.aggregation.dsl.impl.AvgAggregationFieldStepImpl; +import org.hibernate.search.engine.search.aggregation.dsl.impl.CountAggregationFieldStepImpl; +import org.hibernate.search.engine.search.aggregation.dsl.impl.CountDistinctAggregationFieldStepImpl; +import org.hibernate.search.engine.search.aggregation.dsl.impl.MaxAggregationFieldStepImpl; +import org.hibernate.search.engine.search.aggregation.dsl.impl.MinAggregationFieldStepImpl; import org.hibernate.search.engine.search.aggregation.dsl.impl.RangeAggregationFieldStepImpl; +import org.hibernate.search.engine.search.aggregation.dsl.impl.SumAggregationFieldStepImpl; import org.hibernate.search.engine.search.aggregation.dsl.impl.TermsAggregationFieldStepImpl; import org.hibernate.search.engine.search.aggregation.dsl.impl.WithParametersAggregationFinalStep; import org.hibernate.search.engine.search.aggregation.spi.SearchAggregationIndexScope; @@ -41,6 +53,35 @@ public TermsAggregationFieldStep terms() { return new TermsAggregationFieldStepImpl<>( dslContext ); } + @Override + public SumAggregationFieldStep sum() { + return new SumAggregationFieldStepImpl<>( dslContext ); + } + + @Override + public MinAggregationFieldStep min() { + return new MinAggregationFieldStepImpl<>( dslContext ); + } + + @Override + public MaxAggregationFieldStep max() { + return new MaxAggregationFieldStepImpl<>( dslContext ); + } + + @Override + public CountAggregationFieldStep count() { + return new CountAggregationFieldStepImpl<>( dslContext ); + } + + @Override + public CountDistinctAggregationFieldStep countDistinct() { + return new CountDistinctAggregationFieldStepImpl<>( dslContext ); + } + + public AvgAggregationFieldStep avg() { + return new AvgAggregationFieldStepImpl<>( dslContext ); + } + @Override public AggregationFinalStep withParameters( Function> aggregationCreator) { diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/spi/AggregationTypeKeys.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/spi/AggregationTypeKeys.java index e734213fb44..66801b4a1ae 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/spi/AggregationTypeKeys.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/spi/AggregationTypeKeys.java @@ -18,5 +18,17 @@ private AggregationTypeKeys() { of( IndexFieldTraits.Aggregations.TERMS ); public static final SearchQueryElementTypeKey RANGE = of( IndexFieldTraits.Aggregations.RANGE ); + public static final SearchQueryElementTypeKey SUM = + of( IndexFieldTraits.Aggregations.SUM ); + public static final SearchQueryElementTypeKey MIN = + of( IndexFieldTraits.Aggregations.MIN ); + public static final SearchQueryElementTypeKey MAX = + of( IndexFieldTraits.Aggregations.MAX ); + public static final SearchQueryElementTypeKey> COUNT = + of( IndexFieldTraits.Aggregations.COUNT ); + public static final SearchQueryElementTypeKey> COUNT_DISTINCT = + of( IndexFieldTraits.Aggregations.COUNT_DISTINCT ); + public static final SearchQueryElementTypeKey> AVG = + of( IndexFieldTraits.Aggregations.AVG ); } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/spi/FieldMetricAggregationBuilder.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/spi/FieldMetricAggregationBuilder.java new file mode 100644 index 00000000000..838ddb88e41 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/spi/FieldMetricAggregationBuilder.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.spi; + +import org.hibernate.search.engine.search.common.ValueConvert; + +/** + * Used by the all the aggregation metrics that are supposed to return a value having the same type of field, + * or if a projection converter is present and enabled, the same type of the latter. + * + * @param the type of the result of the aggregation + */ +public interface FieldMetricAggregationBuilder extends SearchFilterableAggregationBuilder { + + interface TypeSelector { + FieldMetricAggregationBuilder type(Class expectedType, ValueConvert convert); + } + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/aggregation/spi/SearchFilterableAggregationBuilder.java b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/spi/SearchFilterableAggregationBuilder.java new file mode 100644 index 00000000000..fac6fddf5bc --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/aggregation/spi/SearchFilterableAggregationBuilder.java @@ -0,0 +1,13 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.aggregation.spi; + +import org.hibernate.search.engine.search.predicate.SearchPredicate; + +public interface SearchFilterableAggregationBuilder extends SearchAggregationBuilder { + + void filter(SearchPredicate filter); + +} diff --git a/integrationtest/backend/elasticsearch/src/test/java/org/hibernate/search/integrationtest/backend/elasticsearch/tmp/ElasticsearchMetricAggregationsIT.java b/integrationtest/backend/elasticsearch/src/test/java/org/hibernate/search/integrationtest/backend/elasticsearch/tmp/ElasticsearchMetricAggregationsIT.java new file mode 100644 index 00000000000..0d4ec59ab81 --- /dev/null +++ b/integrationtest/backend/elasticsearch/src/test/java/org/hibernate/search/integrationtest/backend/elasticsearch/tmp/ElasticsearchMetricAggregationsIT.java @@ -0,0 +1,163 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.integrationtest.backend.elasticsearch.tmp; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hibernate.search.engine.backend.common.DocumentReference; +import org.hibernate.search.engine.backend.document.IndexFieldReference; +import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement; +import org.hibernate.search.engine.backend.types.Aggregable; +import org.hibernate.search.engine.search.aggregation.AggregationKey; +import org.hibernate.search.engine.search.query.SearchQuery; +import org.hibernate.search.engine.search.query.SearchResult; +import org.hibernate.search.integrationtest.backend.tck.testsupport.util.extension.SearchSetupHelper; +import org.hibernate.search.util.impl.integrationtest.mapper.stub.BulkIndexer; +import org.hibernate.search.util.impl.integrationtest.mapper.stub.SimpleMappedIndex; +import org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMappingScope; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class ElasticsearchMetricAggregationsIT { + + @RegisterExtension + public static final SearchSetupHelper setupHelper = SearchSetupHelper.create(); + + private final SimpleMappedIndex mainIndex = SimpleMappedIndex.of( IndexBinding::new ).name( "main" ); + private final AggregationKey sumIntegers = AggregationKey.of( "sumIntegers" ); + private final AggregationKey sumConverted = AggregationKey.of( "sumConverted" ); + private final AggregationKey minIntegers = AggregationKey.of( "minIntegers" ); + private final AggregationKey minConverted = AggregationKey.of( "minConverted" ); + private final AggregationKey maxIntegers = AggregationKey.of( "maxIntegers" ); + private final AggregationKey maxConverted = AggregationKey.of( "maxConverted" ); + private final AggregationKey countIntegers = AggregationKey.of( "countIntegers" ); + private final AggregationKey countConverted = AggregationKey.of( "countConverted" ); + private final AggregationKey countDistinctIntegers = AggregationKey.of( "countDistinctIntegers" ); + private final AggregationKey countDistinctConverted = AggregationKey.of( "countDistinctConverted" ); + private final AggregationKey avgIntegers = AggregationKey.of( "avgIntegers" ); + private final AggregationKey avgConverted = AggregationKey.of( "avgConverted" ); + + @BeforeEach + void setup() { + setupHelper.start().withIndexes( mainIndex ).setup().integration(); + initData(); + } + + @Test + public void test_filteringResults() { + StubMappingScope scope = mainIndex.createScope(); + SearchQuery query = scope.query() + .where( f -> f.match().field( "style" ).matching( "bla" ) ) + .aggregation( sumIntegers, f -> f.sum().field( "integer", Integer.class ) ) + .aggregation( sumConverted, f -> f.sum().field( "converted", String.class ) ) + .aggregation( minIntegers, f -> f.min().field( "integer", Integer.class ) ) + .aggregation( minConverted, f -> f.min().field( "converted", String.class ) ) + .aggregation( maxIntegers, f -> f.max().field( "integer", Integer.class ) ) + .aggregation( maxConverted, f -> f.max().field( "converted", String.class ) ) + .aggregation( countIntegers, f -> f.count().field( "integer" ) ) + .aggregation( countConverted, f -> f.count().field( "converted" ) ) + .aggregation( countDistinctIntegers, f -> f.countDistinct().field( "integer" ) ) + .aggregation( countDistinctConverted, f -> f.countDistinct().field( "converted" ) ) + .aggregation( avgIntegers, f -> f.avg().field( "integer" ) ) + .aggregation( avgConverted, f -> f.avg().field( "converted" ) ) + .toQuery(); + + SearchResult result = query.fetch( 0 ); + assertThat( result.aggregation( sumIntegers ) ).isEqualTo( 29 ); + assertThat( result.aggregation( sumConverted ) ).isEqualTo( "29" ); + assertThat( result.aggregation( minIntegers ) ).isEqualTo( 3 ); + assertThat( result.aggregation( minConverted ) ).isEqualTo( "3" ); + assertThat( result.aggregation( maxIntegers ) ).isEqualTo( 9 ); + assertThat( result.aggregation( maxConverted ) ).isEqualTo( "9" ); + assertThat( result.aggregation( countIntegers ) ).isEqualTo( 5 ); + assertThat( result.aggregation( countConverted ) ).isEqualTo( 5 ); + assertThat( result.aggregation( countDistinctIntegers ) ).isEqualTo( 3 ); + assertThat( result.aggregation( countDistinctConverted ) ).isEqualTo( 3 ); + assertThat( result.aggregation( avgIntegers ) ).isEqualTo( 5.8 ); + assertThat( result.aggregation( avgConverted ) ).isEqualTo( 5.8 ); + } + + @Test + public void test_allResults() { + StubMappingScope scope = mainIndex.createScope(); + SearchQuery query = scope.query() + .where( f -> f.matchAll() ) + .aggregation( sumIntegers, f -> f.sum().field( "integer", Integer.class ) ) + .aggregation( sumConverted, f -> f.sum().field( "converted", String.class ) ) + .aggregation( minIntegers, f -> f.min().field( "integer", Integer.class ) ) + .aggregation( minConverted, f -> f.min().field( "converted", String.class ) ) + .aggregation( maxIntegers, f -> f.max().field( "integer", Integer.class ) ) + .aggregation( maxConverted, f -> f.max().field( "converted", String.class ) ) + .aggregation( countIntegers, f -> f.count().field( "integer" ) ) + .aggregation( countConverted, f -> f.count().field( "converted" ) ) + .aggregation( countDistinctIntegers, f -> f.countDistinct().field( "integer" ) ) + .aggregation( countDistinctConverted, f -> f.countDistinct().field( "converted" ) ) + .aggregation( avgIntegers, f -> f.avg().field( "integer" ) ) + .aggregation( avgConverted, f -> f.avg().field( "converted" ) ) + .toQuery(); + + SearchResult result = query.fetch( 0 ); + assertThat( result.aggregation( sumIntegers ) ).isEqualTo( 55 ); + assertThat( result.aggregation( sumConverted ) ).isEqualTo( "55" ); + assertThat( result.aggregation( minIntegers ) ).isEqualTo( -10 ); + assertThat( result.aggregation( minConverted ) ).isEqualTo( "-10" ); + assertThat( result.aggregation( maxIntegers ) ).isEqualTo( 18 ); + assertThat( result.aggregation( maxConverted ) ).isEqualTo( "18" ); + assertThat( result.aggregation( countIntegers ) ).isEqualTo( 10 ); + assertThat( result.aggregation( countConverted ) ).isEqualTo( 10 ); + assertThat( result.aggregation( countDistinctIntegers ) ).isEqualTo( 6 ); + assertThat( result.aggregation( countDistinctConverted ) ).isEqualTo( 6 ); + assertThat( result.aggregation( avgIntegers ) ).isEqualTo( 5.5 ); + assertThat( result.aggregation( avgConverted ) ).isEqualTo( 5.5 ); + } + + private void initData() { + int[] integers = new int[] { 9, 18, 3, 18, 7, -10, 3, 0, 7, 0 }; + String[] styles = new String[] { "bla", "aaa" }; + + BulkIndexer bulkIndexer = mainIndex.bulkIndexer(); + for ( int i = 0; i < integers.length; i++ ) { + int value = integers[i]; + String style = styles[i % 2]; + String id = i + ":" + value + ":" + style; + + bulkIndexer.add( id, document -> { + document.addValue( mainIndex.binding().integer, value ); + document.addValue( mainIndex.binding().converted, value ); + document.addValue( mainIndex.binding().style, style ); + } ); + } + bulkIndexer.add( "empty", document -> {} ) + .join(); + } + + @SuppressWarnings("unused") + private static class IndexBinding { + final IndexFieldReference integer; + final IndexFieldReference converted; + final IndexFieldReference style; + + IndexBinding(IndexSchemaElement root) { + integer = root.field( + "integer", + f -> f.asInteger().aggregable( Aggregable.YES ) + ) + .toReference(); + converted = root.field( + "converted", + f -> f.asInteger().aggregable( Aggregable.YES ) + .projectionConverter( String.class, (value, context) -> value.toString() ) + ) + .toReference(); + style = root.field( + "style", + f -> f.asString() + ) + .toReference(); + } + } +}