From 52718626cf019c72a529c41f45242c22b91a7528 Mon Sep 17 00:00:00 2001 From: Fabio Massimo Ercoli Date: Fri, 26 Jul 2024 12:38:35 +0200 Subject: [PATCH] HSEARCH-5133 Test metric aggregations --- .../aggregation/MetricAggregationsIT.java | 235 ++++++++++++++++++ .../MetricTemporalFieldsAggregationsIT.java | 177 +++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/aggregation/MetricAggregationsIT.java create mode 100644 integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/aggregation/MetricTemporalFieldsAggregationsIT.java diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/aggregation/MetricAggregationsIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/aggregation/MetricAggregationsIT.java new file mode 100644 index 00000000000..e1b55c55f76 --- /dev/null +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/aggregation/MetricAggregationsIT.java @@ -0,0 +1,235 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.integrationtest.backend.tck.search.aggregation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; + +import org.hibernate.search.engine.backend.common.DocumentReference; +import org.hibernate.search.engine.backend.document.DocumentElement; +import org.hibernate.search.engine.backend.document.IndexFieldReference; +import org.hibernate.search.engine.backend.document.IndexObjectFieldReference; +import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement; +import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaObjectField; +import org.hibernate.search.engine.backend.types.Aggregable; +import org.hibernate.search.engine.backend.types.ObjectStructure; +import org.hibernate.search.engine.search.aggregation.AggregationKey; +import org.hibernate.search.engine.search.common.ValueModel; +import org.hibernate.search.engine.search.query.SearchQuery; +import org.hibernate.search.engine.search.query.SearchResult; +import org.hibernate.search.engine.search.query.dsl.SearchQueryOptionsStep; +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.StubLoadingOptionsStep; +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; + +class MetricAggregationsIT { + + @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 sumConvertedNoConversion = AggregationKey.of( "sumConvertedNoConversion" ); + private final AggregationKey sumFiltered = AggregationKey.of( "sumFiltered" ); + 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" ); + private final AggregationKey avgIntegersAsDouble = AggregationKey.of( "avgIntegersAsDouble" ); + private final AggregationKey avgIntegersAsDoubleFiltered = AggregationKey.of( "avgIntegersAsDoubleFiltered" ); + private final AggregationKey sumDoubles = AggregationKey.of( "sumDoubles" ); + private final AggregationKey sumFloats = AggregationKey.of( "sumFloats" ); + private final AggregationKey sumBigIntegers = AggregationKey.of( "sumBigIntegers" ); + private final AggregationKey sumBigDecimals = AggregationKey.of( "sumBigDecimals" ); + private final AggregationKey avgDoubles = AggregationKey.of( "avgDoubles" ); + private final AggregationKey avgFloats = AggregationKey.of( "avgFloats" ); + private final AggregationKey avgBigIntegers = AggregationKey.of( "avgBigIntegers" ); + private final AggregationKey avgBigDecimals = AggregationKey.of( "avgBigDecimals" ); + + @BeforeEach + void setup() { + setupHelper.start().withIndexes( mainIndex ).setup().integration(); + initData(); + } + + @Test + void test_filteringResults() { + StubMappingScope scope = mainIndex.createScope(); + SearchQueryOptionsStep options = scope.query() + .where( f -> f.match().field( "style" ).matching( "bla" ) ); + SearchQuery query = defineAggregations( options ); + + SearchResult result = query.fetch( 0 ); + assertThat( result.aggregation( sumIntegers ) ).isEqualTo( 29 ); + assertThat( result.aggregation( sumConverted ) ).isEqualTo( "29" ); + assertThat( result.aggregation( sumConvertedNoConversion ) ).isEqualTo( 29 ); + assertThat( result.aggregation( sumFiltered ) ).isEqualTo( 23 ); + 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 ); + assertThat( result.aggregation( avgConverted ) ).isEqualTo( "5" ); + assertThat( result.aggregation( avgIntegersAsDouble ) ).isEqualTo( 5.8 ); + assertThat( result.aggregation( avgIntegersAsDoubleFiltered ) ).isEqualTo( 7.666666666666667 ); + assertThat( result.aggregation( sumDoubles ) ).isEqualTo( 29.0 ); + assertThat( result.aggregation( sumFloats ) ).isEqualTo( 29F ); + assertThat( result.aggregation( sumBigIntegers ) ).isEqualTo( BigInteger.valueOf( 29 ) ); + assertThat( result.aggregation( sumBigDecimals ).setScale( 2, RoundingMode.CEILING ) ) + .isEqualTo( BigDecimal.valueOf( 2900, 2 ) ); + assertThat( result.aggregation( avgDoubles ) ).isEqualTo( 5.8 ); + assertThat( result.aggregation( avgFloats ) ).isEqualTo( 5.8F ); + assertThat( result.aggregation( avgBigIntegers ) ).isEqualTo( BigInteger.valueOf( 5 ) ); + assertThat( result.aggregation( avgBigDecimals ).setScale( 2, RoundingMode.CEILING ) ) + .isEqualTo( BigDecimal.valueOf( 580, 2 ) ); + } + + @Test + void test_allResults() { + StubMappingScope scope = mainIndex.createScope(); + SearchQueryOptionsStep options = scope.query() + .where( f -> f.matchAll() ); + SearchQuery query = defineAggregations( options ); + + SearchResult result = query.fetch( 0 ); + assertThat( result.aggregation( sumIntegers ) ).isEqualTo( 55 ); + assertThat( result.aggregation( sumConverted ) ).isEqualTo( "55" ); + assertThat( result.aggregation( sumConvertedNoConversion ) ).isEqualTo( 55 ); + assertThat( result.aggregation( sumFiltered ) ).isEqualTo( 59 ); + 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 ); + assertThat( result.aggregation( avgConverted ) ).isEqualTo( "5" ); + assertThat( result.aggregation( avgIntegersAsDouble ) ).isEqualTo( 5.5 ); + assertThat( result.aggregation( avgIntegersAsDoubleFiltered ) ).isEqualTo( 11.8 ); + assertThat( result.aggregation( sumDoubles ) ).isEqualTo( 55.0 ); + assertThat( result.aggregation( sumFloats ) ).isEqualTo( 55F ); + assertThat( result.aggregation( sumBigIntegers ) ).isEqualTo( BigInteger.valueOf( 55 ) ); + assertThat( result.aggregation( sumBigDecimals ).setScale( 2, RoundingMode.CEILING ) ) + .isEqualTo( BigDecimal.valueOf( 5500, 2 ) ); + assertThat( result.aggregation( avgDoubles ) ).isEqualTo( 5.5 ); + assertThat( result.aggregation( avgFloats ) ).isEqualTo( 5.5F ); + assertThat( result.aggregation( avgBigIntegers ) ).isEqualTo( BigInteger.valueOf( 5 ) ); + assertThat( result.aggregation( avgBigDecimals ).setScale( 2, RoundingMode.CEILING ) ) + .isEqualTo( BigDecimal.valueOf( 550, 2 ) ); + } + + private SearchQuery defineAggregations( + SearchQueryOptionsStep options) { + return options + .aggregation( sumIntegers, f -> f.sum().field( "integer", Integer.class ) ) + .aggregation( sumConverted, f -> f.sum().field( "converted", String.class ) ) + .aggregation( + sumConvertedNoConversion, f -> f.sum().field( "converted", Integer.class, ValueModel.INDEX ) ) + .aggregation( sumFiltered, f -> f.sum().field( "object.nestedInteger", Integer.class ) + .filter( ff -> ff.range().field( "object.nestedInteger" ).atLeast( 5 ) ) ) + .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", Integer.class ) ) + .aggregation( avgConverted, f -> f.avg().field( "converted", String.class ) ) + .aggregation( avgIntegersAsDouble, f -> f.avg().field( "integer", Double.class ) ) + .aggregation( avgIntegersAsDoubleFiltered, f -> f.avg().field( "object.nestedInteger", Double.class ) + .filter( ff -> ff.range().field( "object.nestedInteger" ).atLeast( 5 ) ) ) + .aggregation( sumDoubles, f -> f.sum().field( "doubleF", Double.class ) ) + .aggregation( sumFloats, f -> f.sum().field( "floatF", Float.class ) ) + .aggregation( sumBigIntegers, f -> f.sum().field( "bigInteger", BigInteger.class ) ) + .aggregation( sumBigDecimals, f -> f.sum().field( "bigDecimal", BigDecimal.class ) ) + .aggregation( avgDoubles, f -> f.avg().field( "doubleF", Double.class ) ) + .aggregation( avgFloats, f -> f.avg().field( "floatF", Float.class ) ) + .aggregation( avgBigIntegers, f -> f.avg().field( "bigInteger", BigInteger.class ) ) + .aggregation( avgBigDecimals, f -> f.avg().field( "bigDecimal", BigDecimal.class ) ) + .toQuery(); + } + + 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().doubleF, (double) value ); + document.addValue( mainIndex.binding().floatF, (float) value ); + document.addValue( mainIndex.binding().bigInteger, BigInteger.valueOf( value ) ); + document.addValue( mainIndex.binding().bigDecimal, BigDecimal.valueOf( value ) ); + document.addValue( mainIndex.binding().style, style ); + + DocumentElement object = document.addObject( mainIndex.binding().object ); + object.addValue( mainIndex.binding().nestedInteger, value ); + } ); + } + bulkIndexer.add( "empty", document -> {} ) + .join(); + } + + @SuppressWarnings("unused") + private static class IndexBinding { + final IndexFieldReference integer; + final IndexFieldReference converted; + final IndexFieldReference doubleF; + final IndexFieldReference floatF; + final IndexFieldReference bigInteger; + final IndexFieldReference bigDecimal; + final IndexFieldReference style; + final IndexObjectFieldReference object; + final IndexFieldReference nestedInteger; + + 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(); + doubleF = root.field( "doubleF", f -> f.asDouble().aggregable( Aggregable.YES ) ).toReference(); + floatF = root.field( "floatF", f -> f.asFloat().aggregable( Aggregable.YES ) ).toReference(); + bigInteger = root.field( "bigInteger", f -> f.asBigInteger().decimalScale( 0 ).aggregable( Aggregable.YES ) ) + .toReference(); + bigDecimal = root.field( "bigDecimal", f -> f.asBigDecimal().decimalScale( 2 ).aggregable( Aggregable.YES ) ) + .toReference(); + style = root.field( "style", f -> f.asString() ).toReference(); + + IndexSchemaObjectField nested = root.objectField( "object", ObjectStructure.NESTED ); + object = nested.toReference(); + nestedInteger = nested.field( "nestedInteger", f -> f.asInteger().aggregable( Aggregable.YES ) ) + .toReference(); + } + } +} diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/aggregation/MetricTemporalFieldsAggregationsIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/aggregation/MetricTemporalFieldsAggregationsIT.java new file mode 100644 index 00000000000..8d0870808c8 --- /dev/null +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/aggregation/MetricTemporalFieldsAggregationsIT.java @@ -0,0 +1,177 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.integrationtest.backend.tck.search.aggregation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; +import java.time.Month; + +import org.hibernate.search.engine.backend.common.DocumentReference; +import org.hibernate.search.engine.backend.document.DocumentElement; +import org.hibernate.search.engine.backend.document.IndexFieldReference; +import org.hibernate.search.engine.backend.document.IndexObjectFieldReference; +import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement; +import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaObjectField; +import org.hibernate.search.engine.backend.types.Aggregable; +import org.hibernate.search.engine.backend.types.ObjectStructure; +import org.hibernate.search.engine.search.aggregation.AggregationKey; +import org.hibernate.search.engine.search.common.ValueModel; +import org.hibernate.search.engine.search.query.SearchQuery; +import org.hibernate.search.engine.search.query.SearchResult; +import org.hibernate.search.engine.search.query.dsl.SearchQueryOptionsStep; +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.StubLoadingOptionsStep; +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; + +class MetricTemporalFieldsAggregationsIT { + + @RegisterExtension + public static final SearchSetupHelper setupHelper = SearchSetupHelper.create(); + + private final SimpleMappedIndex mainIndex = SimpleMappedIndex.of( IndexBinding::new ).name( "main" ); + private final AggregationKey sumDates = AggregationKey.of( "sumDates" ); + private final AggregationKey sumConverted = AggregationKey.of( "sumConverted" ); + private final AggregationKey sumConvertedNoConversion = AggregationKey.of( "sumConvertedNoConversion" ); + private final AggregationKey sumFiltered = AggregationKey.of( "sumFiltered" ); + private final AggregationKey minDates = AggregationKey.of( "minDates" ); + private final AggregationKey minConverted = AggregationKey.of( "minConverted" ); + private final AggregationKey maxDates = AggregationKey.of( "maxDates" ); + private final AggregationKey maxConverted = AggregationKey.of( "maxConverted" ); + private final AggregationKey countDates = AggregationKey.of( "countDates" ); + private final AggregationKey countConverted = AggregationKey.of( "countConverted" ); + private final AggregationKey countDistinctDates = AggregationKey.of( "countDistinctDates" ); + private final AggregationKey countDistinctConverted = AggregationKey.of( "countDistinctConverted" ); + private final AggregationKey avgDates = AggregationKey.of( "avgDates" ); + private final AggregationKey avgConverted = AggregationKey.of( "avgConverted" ); + + @BeforeEach + void setup() { + setupHelper.start().withIndexes( mainIndex ).setup().integration(); + initData(); + } + + @Test + void test_filteringResults() { + StubMappingScope scope = mainIndex.createScope(); + SearchQueryOptionsStep options = scope.query() + .where( f -> f.match().field( "style" ).matching( "bla" ) ); + SearchQuery query = defineAggregations( options ); + + SearchResult result = query.fetch( 0 ); + assertThat( result.aggregation( sumDates ) ).isEqualTo( LocalDate.of( 2204, Month.SEPTEMBER, 16 ) ); + assertThat( result.aggregation( sumConverted ) ).isEqualTo( "2204-09-16" ); + assertThat( result.aggregation( sumConvertedNoConversion ) ).isEqualTo( LocalDate.of( 2204, Month.SEPTEMBER, 16 ) ); + assertThat( result.aggregation( sumFiltered ) ).isEqualTo( LocalDate.of( 2063, Month.NOVEMBER, 25 ) ); + assertThat( result.aggregation( minDates ) ).isEqualTo( LocalDate.of( 2016, Month.DECEMBER, 6 ) ); + assertThat( result.aggregation( minConverted ) ).isEqualTo( "2016-12-06" ); + assertThat( result.aggregation( maxDates ) ).isEqualTo( LocalDate.of( 2016, Month.DECEMBER, 14 ) ); + assertThat( result.aggregation( maxConverted ) ).isEqualTo( "2016-12-14" ); + assertThat( result.aggregation( countDates ) ).isEqualTo( 5 ); + assertThat( result.aggregation( countConverted ) ).isEqualTo( 5 ); + assertThat( result.aggregation( countDistinctDates ) ).isEqualTo( 5 ); + assertThat( result.aggregation( countDistinctConverted ) ).isEqualTo( 5 ); + assertThat( result.aggregation( avgDates ) ).isEqualTo( LocalDate.of( 2016, Month.DECEMBER, 10 ) ); + assertThat( result.aggregation( avgConverted ) ).isEqualTo( "2016-12-10" ); + } + + @Test + void test_allResults() { + StubMappingScope scope = mainIndex.createScope(); + SearchQueryOptionsStep options = scope.query() + .where( f -> f.matchAll() ); + SearchQuery query = defineAggregations( options ); + + SearchResult result = query.fetch( 0 ); + assertThat( result.aggregation( sumDates ) ).isEqualTo( LocalDate.of( 2439, Month.JUNE, 6 ) ); + assertThat( result.aggregation( sumConverted ) ).isEqualTo( "2439-06-06" ); + assertThat( result.aggregation( sumConvertedNoConversion ) ).isEqualTo( LocalDate.of( 2439, Month.JUNE, 6 ) ); + assertThat( result.aggregation( sumFiltered ) ).isEqualTo( LocalDate.of( 2204, Month.OCTOBER, 1 ) ); + assertThat( result.aggregation( minDates ) ).isEqualTo( LocalDate.of( 2016, Month.DECEMBER, 6 ) ); + assertThat( result.aggregation( minConverted ) ).isEqualTo( "2016-12-06" ); + assertThat( result.aggregation( maxDates ) ).isEqualTo( LocalDate.of( 2016, Month.DECEMBER, 15 ) ); + assertThat( result.aggregation( maxConverted ) ).isEqualTo( "2016-12-15" ); + assertThat( result.aggregation( countDates ) ).isEqualTo( 10 ); + assertThat( result.aggregation( countConverted ) ).isEqualTo( 10 ); + assertThat( result.aggregation( countDistinctDates ) ).isEqualTo( 10 ); + assertThat( result.aggregation( countDistinctConverted ) ).isEqualTo( 10 ); + assertThat( result.aggregation( avgDates ) ).isEqualTo( LocalDate.of( 2016, Month.DECEMBER, 10 ) ); + assertThat( result.aggregation( avgConverted ) ).isEqualTo( "2016-12-10" ); + } + + private SearchQuery defineAggregations( + SearchQueryOptionsStep options) { + return options + .aggregation( sumDates, f -> f.sum().field( "date", LocalDate.class ) ) + .aggregation( sumConverted, f -> f.sum().field( "converted", String.class ) ) + .aggregation( + sumConvertedNoConversion, f -> f.sum().field( "converted", LocalDate.class, ValueModel.INDEX ) ) + .aggregation( sumFiltered, f -> f.sum().field( "object.nestedDate", LocalDate.class ) + .filter( ff -> ff.range().field( "object.nestedDate" ) + .atLeast( LocalDate.of( 2016, Month.DECEMBER, 11 ) ) ) ) + .aggregation( minDates, f -> f.min().field( "date", LocalDate.class ) ) + .aggregation( minConverted, f -> f.min().field( "converted", String.class ) ) + .aggregation( maxDates, f -> f.max().field( "date", LocalDate.class ) ) + .aggregation( maxConverted, f -> f.max().field( "converted", String.class ) ) + .aggregation( countDates, f -> f.count().field( "date" ) ) + .aggregation( countConverted, f -> f.count().field( "converted" ) ) + .aggregation( countDistinctDates, f -> f.countDistinct().field( "date" ) ) + .aggregation( countDistinctConverted, f -> f.countDistinct().field( "converted" ) ) + .aggregation( avgDates, f -> f.avg().field( "date", LocalDate.class ) ) + .aggregation( avgConverted, f -> f.avg().field( "converted", String.class ) ) + .toQuery(); + } + + private void initData() { + LocalDate baseDate = LocalDate.of( 2016, Month.DECEMBER, 6 ); + 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; + LocalDate date = baseDate.plusDays( i ); + + bulkIndexer.add( id, document -> { + document.addValue( mainIndex.binding().date, date ); + document.addValue( mainIndex.binding().converted, date ); + document.addValue( mainIndex.binding().style, style ); + + DocumentElement object = document.addObject( mainIndex.binding().object ); + object.addValue( mainIndex.binding().nestedDate, date ); + } ); + } + bulkIndexer.add( "empty", document -> {} ).join(); + } + + @SuppressWarnings("unused") + private static class IndexBinding { + final IndexFieldReference date; + final IndexFieldReference converted; + final IndexFieldReference style; + final IndexObjectFieldReference object; + final IndexFieldReference nestedDate; + + IndexBinding(IndexSchemaElement root) { + date = root.field( "date", f -> f.asLocalDate().aggregable( Aggregable.YES ) ).toReference(); + converted = root.field( "converted", f -> f.asLocalDate().aggregable( Aggregable.YES ) + .projectionConverter( String.class, (value, context) -> value.toString() ) ).toReference(); + style = root.field( "style", f -> f.asString() ).toReference(); + + IndexSchemaObjectField nested = root.objectField( "object", ObjectStructure.NESTED ); + object = nested.toReference(); + nestedDate = nested.field( "nestedDate", f -> f.asLocalDate().aggregable( Aggregable.YES ) ) + .toReference(); + } + } +}