Skip to content

Commit

Permalink
HSEARCH-5133 Support ES metric numeric aggregations
Browse files Browse the repository at this point in the history
  • Loading branch information
fax4ever committed Jun 26, 2024
1 parent dc1dc0b commit 1d8ac44
Show file tree
Hide file tree
Showing 49 changed files with 1,575 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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<Double> {

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<Double> extractor(AggregationRequestContext context) {
return new MetricDoubleExtractor();
}

public static class Factory<F>
extends
AbstractElasticsearchCodecAwareSearchQueryElementFactory<SearchFilterableAggregationBuilder<Double>, F> {

private final String operation;

public Factory(ElasticsearchFieldCodec<F> codec, String operation) {
super( codec );
this.operation = operation;
}

@Override
public SearchFilterableAggregationBuilder<Double> create(ElasticsearchSearchIndexScope<?> scope,
ElasticsearchSearchIndexValueFieldContext<F> field) {
return new Builder( scope, field, operation );
}
}

private static class MetricDoubleExtractor implements Extractor<Double> {
@Override
public Double extract(JsonObject aggregationResult, AggregationExtractContext context) {
JsonElement value = aggregationResult.get( "value" );
return ElasticsearchDoubleFieldCodec.INSTANCE.decode( value );
}
}

private static class Builder extends AbstractBuilder<Double> implements SearchFilterableAggregationBuilder<Double> {
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 );
}
}
}
Original file line number Diff line number Diff line change
@@ -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 <F> The type of field values.
* @param <K> The type of returned value. It can be {@code F}
* or a different type if value converters are used.
*/
public class ElasticsearchMetricFieldAggregation<F, K> extends AbstractElasticsearchNestableAggregation<K> {

private final String absoluteFieldPath;
private final ProjectionConverter<F, ? extends K> fromFieldValueConverter;
private final ElasticsearchFieldCodec<F> codec;
private final String operation;

public ElasticsearchMetricFieldAggregation(Builder<F, K> 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<K> extractor(AggregationRequestContext context) {
return new MetricFieldExtractor();
}

public static class Factory<F>
extends AbstractElasticsearchCodecAwareSearchQueryElementFactory<FieldMetricAggregationBuilder.TypeSelector, F> {

private final String operation;

public Factory(ElasticsearchFieldCodec<F> codec, String operation) {
super( codec );
this.operation = operation;
}

@Override
public FieldMetricAggregationBuilder.TypeSelector create(ElasticsearchSearchIndexScope<?> scope,
ElasticsearchSearchIndexValueFieldContext<F> field) {
return new ElasticsearchMetricFieldAggregation.TypeSelector<>( codec, scope, field, operation );
}
}

private static class TypeSelector<F> implements FieldMetricAggregationBuilder.TypeSelector {
private final ElasticsearchFieldCodec<F> codec;
private final ElasticsearchSearchIndexScope<?> scope;
private final ElasticsearchSearchIndexValueFieldContext<F> field;
private final String operation;

private TypeSelector(ElasticsearchFieldCodec<F> codec,
ElasticsearchSearchIndexScope<?> scope, ElasticsearchSearchIndexValueFieldContext<F> field,
String operation) {
this.codec = codec;
this.scope = scope;
this.field = field;
this.operation = operation;
}

@Override
public <T> Builder<F, T> type(Class<T> expectedType, ValueConvert convert) {
return new Builder<>( codec, scope, field,
field.type().projectionConverter( convert ).withConvertedType( expectedType, field ),
operation );
}
}

private class MetricFieldExtractor implements Extractor<K> {
@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<F, K> extends AbstractBuilder<K>
implements FieldMetricAggregationBuilder<K> {

private final ElasticsearchFieldCodec<F> codec;
private final ProjectionConverter<F, ? extends K> fromFieldValueConverter;
private final String operation;

private Builder(ElasticsearchFieldCodec<F> codec, ElasticsearchSearchIndexScope<?> scope,
ElasticsearchSearchIndexValueFieldContext<F> field,
ProjectionConverter<F, ? extends K> fromFieldValueConverter, String operation) {
super( scope, field );
this.codec = codec;
this.fromFieldValueConverter = fromFieldValueConverter;
this.operation = operation;
}

@Override
public ElasticsearchMetricFieldAggregation<F, K> build() {
return new ElasticsearchMetricFieldAggregation<>( this );
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Long> {

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<Long> extractor(AggregationRequestContext context) {
return new MetricLongExtractor();
}

public static class Factory<F>
extends
AbstractElasticsearchCodecAwareSearchQueryElementFactory<SearchFilterableAggregationBuilder<Long>, F> {

private final String operation;

public Factory(ElasticsearchFieldCodec<F> codec, String operation) {
super( codec );
this.operation = operation;
}

@Override
public SearchFilterableAggregationBuilder<Long> create(ElasticsearchSearchIndexScope<?> scope,
ElasticsearchSearchIndexValueFieldContext<F> field) {
return new Builder( scope, field, operation );
}
}

private static class MetricLongExtractor implements Extractor<Long> {
@Override
public Long extract(JsonObject aggregationResult, AggregationExtractContext context) {
JsonElement value = aggregationResult.get( "value" );
return ElasticsearchLongFieldCodec.INSTANCE.decode( value );
}
}

private static class Builder extends AbstractBuilder<Long> implements SearchFilterableAggregationBuilder<Long> {
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 );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 1d8ac44

Please sign in to comment.