From 50a27af250f3debfc9905760af7d0c425dd651e8 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Mon, 11 Oct 2021 11:53:36 -0400 Subject: [PATCH 1/4] Scale doubles to floats when necessary to match the field (#78344) This fixes a bug where the range aggregation always treats the range end points as doubles, even if the field value doesn't have enough resolution to fill a double. This was creating issues where the range would have a "more precise" approximation of an unrepresentable number than the field, causing the value to fall in the wrong bucket. Note 1: This does not resolve the case where we have a long value that is not precisely representable as a double. Since the wire format sends the range bounds as doubles, by the time we get to where this fix is operating, we've already lost the precision to act on a big long. Fixing that problem will require a wire format change, and I'm not convinced it's worth it right now. Note 2: This is probably still broken for ScaledFloats, since they don't implement NumberFieldType. Resolves #77033 --- rest-api-spec/build.gradle | 187 +++++++++++++++++- .../test/msearch/20_typed_keys.yml | 4 +- .../test/search.aggregation/40_range.yml | 66 ++++++- .../index/mapper/NumberFieldMapper.java | 15 ++ .../bucket/range/RangeAggregationBuilder.java | 11 +- .../support/ValuesSourceConfig.java | 13 ++ .../bucket/range/RangeAggregatorTests.java | 106 ++++++---- 7 files changed, 356 insertions(+), 46 deletions(-) diff --git a/rest-api-spec/build.gradle b/rest-api-spec/build.gradle index 12e5e977d5976..10be8f5ad317d 100644 --- a/rest-api-spec/build.gradle +++ b/rest-api-spec/build.gradle @@ -1,10 +1,11 @@ +import org.elasticsearch.gradle.internal.info.BuildParams - apply plugin: 'elasticsearch.build' apply plugin: 'elasticsearch.publish' apply plugin: 'elasticsearch.rest-resources' apply plugin: 'elasticsearch.validate-rest-spec' apply plugin: 'elasticsearch.internal-yaml-rest-test' +apply plugin: 'elasticsearch.yaml-rest-compat-test' restResources { restTests { @@ -34,10 +35,190 @@ artifacts { testClusters.configureEach { module ':modules:mapper-extras' + if (BuildParams.isSnapshotBuild() == false) { + systemProperty 'es.index_mode_feature_flag_registered', 'true' + } } -tasks.named("test").configure {enabled = false } -tasks.named("jarHell").configure {enabled = false } +tasks.named("test").configure { enabled = false } +tasks.named("jarHell").configure { enabled = false } + +tasks.named("yamlRestTestV7CompatTransform").configure { task -> + + task.skipTestsByFilePattern("**/cat*/*.yml", "Cat API are meant to be consumed by humans, so will not be supported by Compatible REST API") + task.skipTestsByFilePattern("**/indices.upgrade/*.yml", "upgrade api will only get a dummy endpoint returning an exception suggesting to use _reindex") + task.skipTestsByFilePattern("**/indices.stats/60_field_usage/*/*.yml", "field usage results will be different between lucene versions") + + task.skipTest("indices.create/20_mix_typeless_typeful/Implicitly create a typed index while there is a typeless template", "Type information about the type is removed and not passed down. The logic to check for this is also removed.") + task.skipTest("indices.create/20_mix_typeless_typeful/Implicitly create a typeless index while there is a typed template", "Type information about the type is removed and not passed down. The logic to check for this is also removed.") + task.skipTest("delete/70_mix_typeless_typeful/DELETE with typeless API on an index that has types", "Type information about the type is removed and not passed down. The logic to check for this is also removed."); + task.skipTest("get/100_mix_typeless_typeful/GET with typeless API on an index that has types", "Failing due to not recognising missing type (the type path param is ignored, will no be fixed"); + task.skipTest("indices.get_field_mapping/21_missing_field_with_types/Return empty object if field doesn't exist, but type and index do", "This test returns test_index.mappings:{} when {} was expected. difference between 20_missing_field and 21_missing_field_with_types?") + task.skipTest("indices.get_field_mapping/30_missing_type/Raise 404 when type doesn't exist", "The information about the type is not present in the index. hence it cannot know if the type exist or not.") + task.skipTest("indices.get_mapping/20_missing_type/Existent and non-existent type returns 404 and the existing type", " The information about the type is not present in the index. hence it cannot know if the type exist or not") + task.skipTest("indices.get_mapping/20_missing_type/Existent and non-existent types returns 404 and the existing type", "The information about the type is not present in the index. hence it cannot know if the type exist or not.") + task.skipTest("indices.get_mapping/20_missing_type/No type matching pattern returns 404", "The information about the type is not present in the index. hence it cannot know if the type exist or not.") + task.skipTest("indices.get_mapping/20_missing_type/Non-existent type returns 404", "The information about the type is not present in the index. hence it cannot know if the type exist or not.") + task.skipTest("indices.get_mapping/20_missing_type/Type missing when no types exist", "The information about the type is not present in the index. hence it cannot know if the type exist or not.") + task.skipTest("indices.put_mapping/20_mix_typeless_typeful/PUT mapping with _doc on an index that has types", "The information about the type is not present in the index. hence it cannot know if the type was already used or not") + task.skipTest("indices.put_mapping/20_mix_typeless_typeful/PUT mapping with typeless API on an index that has types", "The information about the type is not present in the index. hence it cannot know if the type was already used or not") + task.skipTest("search/160_exists_query/Test exists query on _type field", "There is a small distinction between empty mappings and no mappings at all. The code to implement this test was refactored #54003; field search on _type field- not implementing. The data for _type is considered incorrect in this search") + task.skipTest("termvectors/50_mix_typeless_typeful/Term vectors with typeless API on an index that has types", "type information is not stored, hence the the index will be found") + task.skipTest("mget/11_default_index_type/Default index/type", "mget - these use cases are no longer valid because we always default to _doc.; This mean test cases where there is assertion on not finding by type won't work") + task.skipTest("mget/16_basic_with_types/Basic multi-get", "mget - these use cases are no longer valid, because we always default to _doc.; This mean test cases where there is assertion on not finding by type won't work") + task.skipTest("explain/40_mix_typeless_typeful/Explain with typeless API on an index that has types", "asserting about type not found won't work as we ignore the type information") + task.skipTest("indices.stats/20_translog/Translog retention settings are deprecated", "translog settings removal is not supported under compatible api") + task.skipTest("indices.stats/20_translog/Translog retention without soft_deletes", "translog settings removal is not supported under compatible api") + task.skipTest("indices.stats/20_translog/Translog stats on closed indices without soft-deletes", "translog settings removal is not supported under compatible api") + task.skipTest("search.aggregation/370_doc_count_field/Test filters agg with doc_count", "Uses profiler for assertions which is not backwards compatible") + task.skipTest("indices.create/10_basic/Create index without soft deletes", "Make soft-deletes mandatory in 8.0 #51122 - settings changes are note supported in Rest Api compatibility") + task.skipTest("field_caps/30_filter/Field caps with index filter", "behaviour change after #63692 4digits dates are parsed as epoch and in quotes as year") + task.skipTest("indices.forcemerge/10_basic/Check deprecation warning when incompatible only_expunge_deletes and max_num_segments values are both set", "#44761 bug fix") + task.skipTest("search/340_type_query/type query", "#47207 type query throws exception in compatible mode") + task.skipTest("search.aggregation/200_top_hits_metric/top_hits aggregation with sequence numbers", "#42809 the use nested path and filter sort throws an exception") + task.skipTest("search/310_match_bool_prefix/multi_match multiple fields with cutoff_frequency throws exception", "#42654 cutoff_frequency, common terms are not supported. Throwing an exception") + task.skipTest("search.aggregation/20_terms/string profiler via global ordinals filters implementation", "The profiler results aren't backwards compatible.") + task.skipTest("search.aggregation/20_terms/string profiler via global ordinals native implementation", "The profiler results aren't backwards compatible.") + task.skipTest("search.aggregation/20_terms/string profiler via map", "The profiler results aren't backwards compatible.") + task.skipTest("search.aggregation/20_terms/numeric profiler", "The profiler results aren't backwards compatible.") + + task.skipTest("msearch/20_typed_keys/Multisearch test with typed_keys parameter", "Test contains a no longer valid work around for the float range bug") + + task.replaceValueInMatch("_type", "_doc") + task.addAllowedWarningRegex("\\[types removal\\].*") + task.replaceValueInMatch("nodes.\$node_id.roles.8", "ml", "node_info role test") + task.replaceValueInMatch("nodes.\$node_id.roles.9", "remote_cluster_client", "node_info role test") + task.removeMatch("nodes.\$node_id.roles.10", "node_info role test") + task.replaceIsTrue("test_index.mappings.type_1", "test_index.mappings._doc") + //override for indices.get and indices.create + //task.replaceIsFalse("test_index.mappings.type_1", "test_index.mappings._doc") + //overrides for indices.create/20_mix_typeless_typeful + task.replaceIsFalse("test-1.mappings._doc","false", "Create a typed index while there is a typeless template") + task.replaceIsFalse("test-1.mappings._doc","false", "Create a typeless index while there is a typed template") + + task.replaceIsTrue("test-1.mappings.my_type", "test-1.mappings._doc") + task.replaceIsTrue("test-1.mappings.my_type.properties.foo", "test-1.mappings._doc.properties.foo") + task.replaceIsTrue("test-1.mappings.my_type.properties.bar", "test-1.mappings._doc.properties.bar") + + // overrides for indices.get_field_mapping + task.replaceKeyInLength("test_index.mappings.test_type.text.mapping.text.type", + "test_index.mappings._doc.text.mapping.text.type" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.text.mapping.text.analyzer", + "test_index.mappings._doc.text.mapping.text.analyzer" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.t1.full_name", + "test_index.mappings._doc.t1.full_name" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.t2.full_name", + "test_index.mappings._doc.t2.full_name" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.obj\\.t1.full_name", + "test_index.mappings._doc.obj\\.t1.full_name" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.obj\\.i_t1.full_name", + "test_index.mappings._doc.obj\\.i_t1.full_name" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.obj\\.i_t3.full_name", + "test_index.mappings._doc.obj\\.i_t3.full_name" + ) + task.replaceKeyInLength("test_index.mappings.test_type", + "test_index.mappings._doc" + ) + task.replaceKeyInMatch("test_index_2.mappings.test_type_2.t1.full_name", + "test_index.mappings._doc.t1.full_name" + ) + task.replaceKeyInMatch("test_index_2.mappings.test_type_2.t2.full_name", + "test_index.mappings._doc.t2.full_name" + ) + task.replaceKeyInLength("test_index_2.mappings.test_type_2", + "test_index.mappings._doc" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.text.mapping.text.type", + "test_index.mappings._doc.text.mapping.text.type" + ) + // overrides for indices.put_mapping/11_basic_with_types + task.replaceKeyInMatch("test_index.mappings.test_type.properties.text1.type", + "test_index.mappings._doc.properties.text1.type" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.properties.text1.analyzer", + "test_index.mappings._doc.properties.text1.analyzer" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.properties.text2.type", + "test_index.mappings._doc.properties.text2.type" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.properties.text2.analyzer", + "test_index.mappings._doc.properties.text2.analyzer" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.properties.subfield.properties.text3.type", + "test_index.mappings._doc.properties.subfield.properties.text3.type" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.properties.text1.fields.text_raw.type", + "test_index.mappings._doc.properties.text1.fields.text_raw.type" + ) + // overrides for indices.put_mapping/all_path_options_with_types + task.replaceKeyInMatch("test_index1.mappings.test_type.properties.text.type", + "test_index1.mappings._doc.properties.text.type" + ) + task.replaceKeyInMatch("test_index1.mappings.test_type.properties.text.analyzer", + "test_index1.mappings._doc.properties.text.analyzer" + ) + task.replaceKeyInMatch("test_index2.mappings.test_type.properties.text.type", + "test_index2.mappings._doc.properties.text.type" + ) + task.replaceKeyInMatch("test_index2.mappings.test_type.properties.text.analyzer", + "test_index2.mappings._doc.properties.text.analyzer" + ) + task.replaceKeyInMatch("foo.mappings.test_type.properties.text.type", + "foo.mappings._doc.properties.text.type" + ) + task.replaceKeyInMatch("foo.mappings.test_type.properties.text.analyzer", + "foo.mappings._doc.properties.text.analyzer" + ) + // overrides for indices.get_mapping + task.replaceIsTrue("test_1.mappings.doc", "test_1.mappings._doc") + task.replaceIsTrue("test_2.mappings.doc", "test_2.mappings._doc") + // overrides for mget + task.replaceValueInMatch("docs.0._type", "_doc" , "Basic multi-get") // index found, but no doc + task.replaceValueInMatch("docs.0._type", "_doc", "Default index/type") + task.replaceValueInMatch("docs.0._type", "_doc", "Non-existent index") + task.replaceValueInMatch("docs.0._type", "_doc", "Missing metadata") + task.replaceValueInMatch("docs.0._type", "_doc", "Multi Get with alias that resolves to multiple indices") + task.replaceValueInMatch("docs.1._type", "_doc", "Multi Get with alias that resolves to multiple indices") + task.replaceValueInMatch("docs.2._type", "_doc", "Multi Get with alias that resolves to multiple indices") + task.replaceValueInMatch("docs.0._type", "_doc", "IDs") + task.replaceValueInMatch("docs.1._type", "_doc", "IDs") + task.replaceValueInMatch("docs.2._type", "_doc", "Routing") + + //overrides for indices.stats + //TODO fix to remove the below match + task.replaceKeyInMatch("_all.primaries.indexing.types.baz.index_total", + "_all.primaries.indexing.types._doc.index_total" + ) + task.replaceKeyInMatch("_all.primaries.indexing.types.bar.index_total", + "_all.primaries.indexing.types._doc.index_total" + ) + task.replaceValueInMatch("_all.primaries.indexing.types._doc.index_total", 2) + // points get touched by sorting in ES 8 + task.replaceValueInMatch("testindex.shards.0.stats.fields.price.points", 1) + + //override for "indices.open/10_basic/?wait_for_active_shards default is deprecated" and "indices.open/10_basic/?wait_for_active_shards=index-setting" + task.addAllowedWarningRegexForTest("\\?wait_for_active_shards=index-setting is now the default behaviour.*", "?wait_for_active_shards=index-setting") + task.removeWarningForTest("the default value for the ?wait_for_active_shards parameter will change from '0' to 'index-setting' in version 8; " + + "specify '?wait_for_active_shards=index-setting' to adopt the future default behaviour, or '?wait_for_active_shards=0' to preserve today's behaviour" + , "?wait_for_active_shards default is deprecated") + + // override for exception message change in #55291 tests cluster.voting_config_exclusions/10_basic/ + // 'Throw exception when adding voting config exclusion and specifying both node_ids and node_names', + // 'Throw exception when adding voting config exclusion without specifying nodes', + task.replaceValueTextByKeyValue("catch", + '/Please set node identifiers correctly. One and only one of \\[node_name\\], \\[node_names\\] and \\[node_ids\\] has to be set/', + '/You must set \\[node_names\\] or \\[node_ids\\] but not both/') + + // sync_id is no longer available in SegmentInfos.userData // "indices.flush/10_basic/Index synced flush rest test" + task.replaceIsTrue("indices.testing.shards.0.0.commit.user_data.sync_id", "indices.testing.shards.0.0.commit.user_data") + +} tasks.register('enforceYamlTestConvention').configure { doLast { diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/msearch/20_typed_keys.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/msearch/20_typed_keys.yml index 0be04fd01c0ed..89bc8bf53b8c7 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/msearch/20_typed_keys.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/msearch/20_typed_keys.yml @@ -64,7 +64,7 @@ setup: - index: test-* - {query: {match: {bool: true} }, size: 0, aggs: {test_filter: {filter: {range: {integer: {gte: 20} } } } } } - index: test-1 - - {query: {match_all: {} }, size: 0, aggs: {test_range: {range: {field: float, ranges: [ {to: 19.2499999}, {from: 19.25} ] } } } } + - {query: {match_all: {} }, size: 0, aggs: {test_range: {range: {field: float, ranges: [ {to: 19.25}, {from: 19.25} ] } } } } - index: test-* - {query: {bool: {filter: {range: {row: {lt: 5}}} } }, size: 0, aggs: {test_percentiles: {percentiles: {field: float} } } } # Testing suggesters @@ -78,7 +78,7 @@ setup: - match: { responses.0.hits.total: 3 } - match: { responses.0.aggregations.filter#test_filter.doc_count : 2 } - match: { responses.1.hits.total: 3 } - - match: { responses.1.aggregations.range#test_range.buckets.0.key : "*-19.2499999" } + - match: { responses.1.aggregations.range#test_range.buckets.0.key : "*-19.25" } - match: { responses.1.aggregations.range#test_range.buckets.0.doc_count : 2 } - match: { responses.1.aggregations.range#test_range.buckets.1.key : "19.25-*" } - match: { responses.1.aggregations.range#test_range.buckets.1.doc_count : 1 } diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/40_range.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/40_range.yml index a3a90ebc1fb09..9e9d46fb88391 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/40_range.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/40_range.yml @@ -11,6 +11,10 @@ setup: type: double long: type: long + float: + type: float + half_float: + type: half_float - do: cluster.health: @@ -22,15 +26,71 @@ setup: refresh: true body: - {"index": {}} - - { "double" : 42.1, "long": 25 } + - { "double" : 42.1, "long": 25, "float": 0.01, "half_float": 0.01 } - {"index": {}} - - { "double" : 100.7, "long": 80 } + - { "double" : 100.7, "long": 80, "float": 0.03, "half_float": 0.0152 } - {"index": {}} - - { "double" : 50.5, "long": 75} + - { "double" : 50.5, "long": 75, "float": 0.04, "half_float": 0.04 } # For testing missing values - {"index": {}} - {} +--- +"Float Endpoint Exclusive": + - skip: + version: " - 7.99.99" + reason: Bug fixed in 7.16.0 (backport pending) + - do: + search: + body: + size: 0 + aggs: + double_range: + range: + format: "0.0#" + field: "float" + ranges: + - + from: 0 + to: 0.04 + - from: 0.04 + to: 1.0 + - match: { hits.total.relation: "eq" } + - match: { hits.total.value: 4 } + - length: { aggregations.double_range.buckets: 2 } + - match: { aggregations.double_range.buckets.0.key: "0.0-0.04" } + - match: { aggregations.double_range.buckets.0.doc_count: 2 } + - match: { aggregations.double_range.buckets.1.key: "0.04-1.0" } + - match: { aggregations.double_range.buckets.1.doc_count: 1 } + +--- +"Half Float Endpoint Exclusive": + - skip: + version: " - 7.99.99" + reason: Bug fixed in 7.16.0 (backport pending) + - do: + search: + body: + size: 0 + aggs: + double_range: + range: + format: "0.0###" + field: "half_float" + ranges: + - + from: 0 + to: 0.0152 + - from: 0.0152 + to: 1.0 + - match: { hits.total.relation: "eq" } + - match: { hits.total.value: 4 } + - length: { aggregations.double_range.buckets: 2 } + - match: { aggregations.double_range.buckets.0.key: "0.0-0.0152" } + - match: { aggregations.double_range.buckets.0.doc_count: 1 } + - match: { aggregations.double_range.buckets.1.key: "0.0152-1.0" } + - match: { aggregations.double_range.buckets.1.doc_count: 2 } + --- "Double range": - do: diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java index e08bb36a7fe7f..cd7d37aafac51 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java @@ -1039,6 +1039,21 @@ public String typeName() { return type.name; } + /** + * This method reinterprets a double precision value based on the maximum precision of the stored number field. Mostly this + * corrects for unrepresentable values which have different approximations when cast from floats than when parsed as doubles. + * It may seem strange to convert a double to a double, and it is. This function's goal is to reduce the precision + * on the double in the case that the backing number type would have parsed the value differently. This is to address + * the problem where (e.g.) 0.04F < 0.04D, which causes problems for range aggregations. + */ + public double reduceToStoredPrecision(double value) { + if (Double.isInfinite(value)) { + // Trying to parse infinite values into ints/longs throws. Understandably. + return value; + } + return type.parse(value, false).doubleValue(); + } + public NumericType numericType() { return type.numericType(); } diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregationBuilder.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregationBuilder.java index 3803c2278888d..507cf39345ab3 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregationBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregationBuilder.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.util.Map; +import java.util.function.DoubleUnaryOperator; public class RangeAggregationBuilder extends AbstractRangeBuilder { public static final String NAME = "range"; @@ -152,12 +153,18 @@ protected RangeAggregatorFactory innerBuild( ) throws IOException { RangeAggregatorSupplier aggregatorSupplier = context.getValuesSourceRegistry().getAggregator(REGISTRY_KEY, config); + /* + This will downgrade the precision of the range bounds to match the field's precision. Fixes float/double issues, but not + long/double issues. See https://github.com/elastic/elasticsearch/issues/77033 + */ + DoubleUnaryOperator fixPrecision = config.reduceToStoredPrecisionFunction(); + // We need to call processRanges here so they are parsed before we make the decision of whether to cache the request Range[] ranges = processRanges(range -> { DocValueFormat parser = config.format(); assert parser != null; - Double from = range.from; - Double to = range.to; + Double from = fixPrecision.applyAsDouble(range.from); + Double to = fixPrecision.applyAsDouble(range.to); if (range.fromAsStr != null) { from = parser.parseDouble(range.fromAsStr, false, context::nowInMillis); } diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java b/server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java index 687c73ebb1e1d..7483419872ef3 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java @@ -13,6 +13,7 @@ import org.elasticsearch.index.fielddata.IndexGeoPointFieldData; import org.elasticsearch.index.fielddata.IndexNumericFieldData; import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.index.mapper.NumberFieldMapper; import org.elasticsearch.index.mapper.RangeFieldMapper; import org.elasticsearch.script.AggregationScript; import org.elasticsearch.script.Script; @@ -20,6 +21,7 @@ import java.io.IOException; import java.time.ZoneId; +import java.util.function.DoubleUnaryOperator; import java.util.function.Function; /** @@ -321,6 +323,17 @@ public FieldContext fieldContext() { return fieldContext; } + /** + * Returns a function from the mapper that adjusts a double value to the value it would have been had it been parsed by that mapper + * and then cast up to a double. Used to correct precision errors. + */ + public DoubleUnaryOperator reduceToStoredPrecisionFunction() { + if (fieldContext() != null && fieldType() instanceof NumberFieldMapper.NumberFieldType) { + return ((NumberFieldMapper.NumberFieldType) fieldType())::reduceToStoredPrecision; + } + return (value) -> value; + } + /** * Convenience method for looking up the mapped field type backing this values source, if it exists. */ diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java index d30bd9fbb6aaa..029df364d5c40 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java @@ -13,14 +13,11 @@ import org.apache.lucene.document.NumericDocValuesField; import org.apache.lucene.document.SortedNumericDocValuesField; import org.apache.lucene.document.SortedSetDocValuesField; -import org.apache.lucene.index.DirectoryReader; -import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.RandomIndexWriter; -import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; -import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.NumericUtils; import org.elasticsearch.core.CheckedConsumer; import org.elasticsearch.index.mapper.DateFieldMapper; import org.elasticsearch.index.mapper.DateFieldMapper.Resolution; @@ -115,6 +112,70 @@ public void testMatchesNumericDocValues() throws IOException { }); } + /** + * Confirm that a non-representable decimal stored as a double correctly follows the half-open interval rule + */ + public void testDoubleRangesExclusiveEndpoint() throws IOException { + final String fieldName = "double"; + MappedFieldType field = new NumberFieldMapper.NumberFieldType(fieldName, NumberType.DOUBLE); + testCase( + new RangeAggregationBuilder("range").field(fieldName).addRange("r1", 0, 0.04D).addRange("r2", 0.04D, 1.0D), + new MatchAllDocsQuery(), + iw -> { iw.addDocument( + org.elasticsearch.core.List.of(new SortedNumericDocValuesField(fieldName, NumericUtils.doubleToSortableLong(0.04D)))); }, + result -> { + InternalRange range = (InternalRange) result; + List ranges = range.getBuckets(); + assertEquals(2, ranges.size()); + assertEquals(0, ranges.get(0).getDocCount()); + assertEquals(1, ranges.get(1).getDocCount()); + }, + field + ); + } + + /** + * Confirm that a non-representable decimal stored as a float correctly follows the half-open interval rule + */ + public void testFloatRangesExclusiveEndpoint() throws IOException { + final String fieldName = "float"; + MappedFieldType field = new NumberFieldMapper.NumberFieldType(fieldName, NumberType.FLOAT); + testCase( + new RangeAggregationBuilder("range").field(fieldName).addRange("r1", 0, 0.04D).addRange("r2", 0.04D, 1.0D), + new MatchAllDocsQuery(), + iw -> { iw.addDocument(NumberType.FLOAT.createFields(fieldName, 0.04F, false, true, false)); }, + result -> { + InternalRange range = (InternalRange) result; + List ranges = range.getBuckets(); + assertEquals(2, ranges.size()); + assertEquals(0, ranges.get(0).getDocCount()); + assertEquals(1, ranges.get(1).getDocCount()); + }, + field + ); + } + + /** + * Confirm that a non-representable decimal stored as a half_float correctly follows the half-open interval rule + */ + public void testHalfFloatRangesExclusiveEndpoint() throws IOException { + final String fieldName = "halfFloat"; + MappedFieldType field = new NumberFieldMapper.NumberFieldType(fieldName, NumberType.HALF_FLOAT); + testCase( + new RangeAggregationBuilder("range").field(fieldName).addRange("r1", 0, 0.0152D).addRange("r2", 0.0152D, 1.0D), + new MatchAllDocsQuery(), + iw -> { iw.addDocument(NumberType.HALF_FLOAT.createFields(fieldName, 0.0152F, false, true, false)); }, + result -> { + InternalRange range = (InternalRange) result; + List ranges = range.getBuckets(); + assertEquals(2, ranges.size()); + assertEquals(0, ranges.get(0).getDocCount()); + assertEquals(1, ranges.get(1).getDocCount()); + }, + field + ); + } + public void testUnboundedRanges() throws IOException { testCase( new RangeAggregationBuilder("name").field(NUMBER_FIELD_NAME).addUnboundedTo(5).addUnboundedFrom(5), @@ -202,7 +263,7 @@ public void testDateFieldMillisecondResolution() throws IOException { new LongPoint(DATE_FIELD_NAME, milli2) ) ); - }, range -> { + }, (Consumer>) range -> { List ranges = range.getBuckets(); assertEquals(1, ranges.size()); assertEquals(1, ranges.get(0).getDocCount()); @@ -233,7 +294,7 @@ public void testDateFieldNanosecondResolution() throws IOException { testCase(aggregationBuilder, new MatchAllDocsQuery(), iw -> { iw.addDocument(singleton(new SortedNumericDocValuesField(DATE_FIELD_NAME, TimeUnit.MILLISECONDS.toNanos(milli1)))); iw.addDocument(singleton(new SortedNumericDocValuesField(DATE_FIELD_NAME, TimeUnit.MILLISECONDS.toNanos(milli2)))); - }, range -> { + }, (Consumer>) range -> { List ranges = range.getBuckets(); assertEquals(1, ranges.size()); assertEquals(1, ranges.get(0).getDocCount()); @@ -267,7 +328,7 @@ public void testMissingDateWithDateNanosField() throws IOException { iw.addDocument(singleton(new SortedNumericDocValuesField(DATE_FIELD_NAME, TimeUnit.MILLISECONDS.toNanos(milli2)))); // Missing will apply to this document iw.addDocument(singleton(new SortedNumericDocValuesField(NUMBER_FIELD_NAME, 7))); - }, range -> { + }, (Consumer>) range -> { List ranges = range.getBuckets(); assertEquals(1, ranges.size()); assertEquals(2, ranges.get(0).getDocCount()); @@ -306,7 +367,7 @@ public void testNotFitIntoDouble() throws IOException { ) ); } - }, range -> { + }, (Consumer>) range -> { List ranges = range.getBuckets(); assertThat(ranges, hasSize(3)); // If we had a native `double` range aggregator we'd get 50, 50, 50 @@ -338,7 +399,7 @@ public void testUnmappedWithMissingNumber() throws IOException { testCase(aggregationBuilder, new MatchAllDocsQuery(), iw -> { iw.addDocument(singleton(new NumericDocValuesField(NUMBER_FIELD_NAME, 7))); iw.addDocument(singleton(new NumericDocValuesField(NUMBER_FIELD_NAME, 1))); - }, range -> { + }, (Consumer>) range -> { List ranges = range.getBuckets(); assertEquals(1, ranges.size()); assertEquals(2, ranges.get(0).getDocCount()); @@ -590,31 +651,4 @@ private void simpleTestCase( iw.addDocument(singleton(new SortedNumericDocValuesField(NUMBER_FIELD_NAME, 3))); }, verify, fieldType); } - - private void testCase( - RangeAggregationBuilder aggregationBuilder, - Query query, - CheckedConsumer buildIndex, - Consumer>> verify, - MappedFieldType fieldType - ) throws IOException { - try (Directory directory = newDirectory()) { - RandomIndexWriter indexWriter = new RandomIndexWriter(random(), directory); - buildIndex.accept(indexWriter); - indexWriter.close(); - - try (IndexReader indexReader = DirectoryReader.open(directory)) { - IndexSearcher indexSearcher = newSearcher(indexReader, true, true); - - InternalRange> agg = searchAndReduce( - indexSearcher, - query, - aggregationBuilder, - fieldType - ); - verify.accept(agg); - - } - } - } } From a54f9adf37617e92adf5265b19136d2adcffddac Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Mon, 11 Oct 2021 12:05:14 -0400 Subject: [PATCH 2/4] fix skip version --- .../rest-api-spec/test/search.aggregation/40_range.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/40_range.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/40_range.yml index 9e9d46fb88391..643c72ab869d9 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/40_range.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/40_range.yml @@ -38,8 +38,8 @@ setup: --- "Float Endpoint Exclusive": - skip: - version: " - 7.99.99" - reason: Bug fixed in 7.16.0 (backport pending) + version: " - 7.15.99" + reason: Bug fixed in 7.16.0 - do: search: body: @@ -66,8 +66,8 @@ setup: --- "Half Float Endpoint Exclusive": - skip: - version: " - 7.99.99" - reason: Bug fixed in 7.16.0 (backport pending) + version: " - 7.15.99" + reason: Bug fixed in 7.16.0 - do: search: body: From ff951139e065644d490d7ddb9e1fd31bca86f70b Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Mon, 11 Oct 2021 12:23:05 -0400 Subject: [PATCH 3/4] Fixing rest tests, I hope --- rest-api-spec/build.gradle | 187 +------------------------------------ 1 file changed, 3 insertions(+), 184 deletions(-) diff --git a/rest-api-spec/build.gradle b/rest-api-spec/build.gradle index 10be8f5ad317d..12e5e977d5976 100644 --- a/rest-api-spec/build.gradle +++ b/rest-api-spec/build.gradle @@ -1,11 +1,10 @@ -import org.elasticsearch.gradle.internal.info.BuildParams + apply plugin: 'elasticsearch.build' apply plugin: 'elasticsearch.publish' apply plugin: 'elasticsearch.rest-resources' apply plugin: 'elasticsearch.validate-rest-spec' apply plugin: 'elasticsearch.internal-yaml-rest-test' -apply plugin: 'elasticsearch.yaml-rest-compat-test' restResources { restTests { @@ -35,190 +34,10 @@ artifacts { testClusters.configureEach { module ':modules:mapper-extras' - if (BuildParams.isSnapshotBuild() == false) { - systemProperty 'es.index_mode_feature_flag_registered', 'true' - } } -tasks.named("test").configure { enabled = false } -tasks.named("jarHell").configure { enabled = false } - -tasks.named("yamlRestTestV7CompatTransform").configure { task -> - - task.skipTestsByFilePattern("**/cat*/*.yml", "Cat API are meant to be consumed by humans, so will not be supported by Compatible REST API") - task.skipTestsByFilePattern("**/indices.upgrade/*.yml", "upgrade api will only get a dummy endpoint returning an exception suggesting to use _reindex") - task.skipTestsByFilePattern("**/indices.stats/60_field_usage/*/*.yml", "field usage results will be different between lucene versions") - - task.skipTest("indices.create/20_mix_typeless_typeful/Implicitly create a typed index while there is a typeless template", "Type information about the type is removed and not passed down. The logic to check for this is also removed.") - task.skipTest("indices.create/20_mix_typeless_typeful/Implicitly create a typeless index while there is a typed template", "Type information about the type is removed and not passed down. The logic to check for this is also removed.") - task.skipTest("delete/70_mix_typeless_typeful/DELETE with typeless API on an index that has types", "Type information about the type is removed and not passed down. The logic to check for this is also removed."); - task.skipTest("get/100_mix_typeless_typeful/GET with typeless API on an index that has types", "Failing due to not recognising missing type (the type path param is ignored, will no be fixed"); - task.skipTest("indices.get_field_mapping/21_missing_field_with_types/Return empty object if field doesn't exist, but type and index do", "This test returns test_index.mappings:{} when {} was expected. difference between 20_missing_field and 21_missing_field_with_types?") - task.skipTest("indices.get_field_mapping/30_missing_type/Raise 404 when type doesn't exist", "The information about the type is not present in the index. hence it cannot know if the type exist or not.") - task.skipTest("indices.get_mapping/20_missing_type/Existent and non-existent type returns 404 and the existing type", " The information about the type is not present in the index. hence it cannot know if the type exist or not") - task.skipTest("indices.get_mapping/20_missing_type/Existent and non-existent types returns 404 and the existing type", "The information about the type is not present in the index. hence it cannot know if the type exist or not.") - task.skipTest("indices.get_mapping/20_missing_type/No type matching pattern returns 404", "The information about the type is not present in the index. hence it cannot know if the type exist or not.") - task.skipTest("indices.get_mapping/20_missing_type/Non-existent type returns 404", "The information about the type is not present in the index. hence it cannot know if the type exist or not.") - task.skipTest("indices.get_mapping/20_missing_type/Type missing when no types exist", "The information about the type is not present in the index. hence it cannot know if the type exist or not.") - task.skipTest("indices.put_mapping/20_mix_typeless_typeful/PUT mapping with _doc on an index that has types", "The information about the type is not present in the index. hence it cannot know if the type was already used or not") - task.skipTest("indices.put_mapping/20_mix_typeless_typeful/PUT mapping with typeless API on an index that has types", "The information about the type is not present in the index. hence it cannot know if the type was already used or not") - task.skipTest("search/160_exists_query/Test exists query on _type field", "There is a small distinction between empty mappings and no mappings at all. The code to implement this test was refactored #54003; field search on _type field- not implementing. The data for _type is considered incorrect in this search") - task.skipTest("termvectors/50_mix_typeless_typeful/Term vectors with typeless API on an index that has types", "type information is not stored, hence the the index will be found") - task.skipTest("mget/11_default_index_type/Default index/type", "mget - these use cases are no longer valid because we always default to _doc.; This mean test cases where there is assertion on not finding by type won't work") - task.skipTest("mget/16_basic_with_types/Basic multi-get", "mget - these use cases are no longer valid, because we always default to _doc.; This mean test cases where there is assertion on not finding by type won't work") - task.skipTest("explain/40_mix_typeless_typeful/Explain with typeless API on an index that has types", "asserting about type not found won't work as we ignore the type information") - task.skipTest("indices.stats/20_translog/Translog retention settings are deprecated", "translog settings removal is not supported under compatible api") - task.skipTest("indices.stats/20_translog/Translog retention without soft_deletes", "translog settings removal is not supported under compatible api") - task.skipTest("indices.stats/20_translog/Translog stats on closed indices without soft-deletes", "translog settings removal is not supported under compatible api") - task.skipTest("search.aggregation/370_doc_count_field/Test filters agg with doc_count", "Uses profiler for assertions which is not backwards compatible") - task.skipTest("indices.create/10_basic/Create index without soft deletes", "Make soft-deletes mandatory in 8.0 #51122 - settings changes are note supported in Rest Api compatibility") - task.skipTest("field_caps/30_filter/Field caps with index filter", "behaviour change after #63692 4digits dates are parsed as epoch and in quotes as year") - task.skipTest("indices.forcemerge/10_basic/Check deprecation warning when incompatible only_expunge_deletes and max_num_segments values are both set", "#44761 bug fix") - task.skipTest("search/340_type_query/type query", "#47207 type query throws exception in compatible mode") - task.skipTest("search.aggregation/200_top_hits_metric/top_hits aggregation with sequence numbers", "#42809 the use nested path and filter sort throws an exception") - task.skipTest("search/310_match_bool_prefix/multi_match multiple fields with cutoff_frequency throws exception", "#42654 cutoff_frequency, common terms are not supported. Throwing an exception") - task.skipTest("search.aggregation/20_terms/string profiler via global ordinals filters implementation", "The profiler results aren't backwards compatible.") - task.skipTest("search.aggregation/20_terms/string profiler via global ordinals native implementation", "The profiler results aren't backwards compatible.") - task.skipTest("search.aggregation/20_terms/string profiler via map", "The profiler results aren't backwards compatible.") - task.skipTest("search.aggregation/20_terms/numeric profiler", "The profiler results aren't backwards compatible.") - - task.skipTest("msearch/20_typed_keys/Multisearch test with typed_keys parameter", "Test contains a no longer valid work around for the float range bug") - - task.replaceValueInMatch("_type", "_doc") - task.addAllowedWarningRegex("\\[types removal\\].*") - task.replaceValueInMatch("nodes.\$node_id.roles.8", "ml", "node_info role test") - task.replaceValueInMatch("nodes.\$node_id.roles.9", "remote_cluster_client", "node_info role test") - task.removeMatch("nodes.\$node_id.roles.10", "node_info role test") - task.replaceIsTrue("test_index.mappings.type_1", "test_index.mappings._doc") - //override for indices.get and indices.create - //task.replaceIsFalse("test_index.mappings.type_1", "test_index.mappings._doc") - //overrides for indices.create/20_mix_typeless_typeful - task.replaceIsFalse("test-1.mappings._doc","false", "Create a typed index while there is a typeless template") - task.replaceIsFalse("test-1.mappings._doc","false", "Create a typeless index while there is a typed template") - - task.replaceIsTrue("test-1.mappings.my_type", "test-1.mappings._doc") - task.replaceIsTrue("test-1.mappings.my_type.properties.foo", "test-1.mappings._doc.properties.foo") - task.replaceIsTrue("test-1.mappings.my_type.properties.bar", "test-1.mappings._doc.properties.bar") - - // overrides for indices.get_field_mapping - task.replaceKeyInLength("test_index.mappings.test_type.text.mapping.text.type", - "test_index.mappings._doc.text.mapping.text.type" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.text.mapping.text.analyzer", - "test_index.mappings._doc.text.mapping.text.analyzer" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.t1.full_name", - "test_index.mappings._doc.t1.full_name" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.t2.full_name", - "test_index.mappings._doc.t2.full_name" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.obj\\.t1.full_name", - "test_index.mappings._doc.obj\\.t1.full_name" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.obj\\.i_t1.full_name", - "test_index.mappings._doc.obj\\.i_t1.full_name" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.obj\\.i_t3.full_name", - "test_index.mappings._doc.obj\\.i_t3.full_name" - ) - task.replaceKeyInLength("test_index.mappings.test_type", - "test_index.mappings._doc" - ) - task.replaceKeyInMatch("test_index_2.mappings.test_type_2.t1.full_name", - "test_index.mappings._doc.t1.full_name" - ) - task.replaceKeyInMatch("test_index_2.mappings.test_type_2.t2.full_name", - "test_index.mappings._doc.t2.full_name" - ) - task.replaceKeyInLength("test_index_2.mappings.test_type_2", - "test_index.mappings._doc" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.text.mapping.text.type", - "test_index.mappings._doc.text.mapping.text.type" - ) - // overrides for indices.put_mapping/11_basic_with_types - task.replaceKeyInMatch("test_index.mappings.test_type.properties.text1.type", - "test_index.mappings._doc.properties.text1.type" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.properties.text1.analyzer", - "test_index.mappings._doc.properties.text1.analyzer" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.properties.text2.type", - "test_index.mappings._doc.properties.text2.type" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.properties.text2.analyzer", - "test_index.mappings._doc.properties.text2.analyzer" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.properties.subfield.properties.text3.type", - "test_index.mappings._doc.properties.subfield.properties.text3.type" - ) - task.replaceKeyInMatch("test_index.mappings.test_type.properties.text1.fields.text_raw.type", - "test_index.mappings._doc.properties.text1.fields.text_raw.type" - ) - // overrides for indices.put_mapping/all_path_options_with_types - task.replaceKeyInMatch("test_index1.mappings.test_type.properties.text.type", - "test_index1.mappings._doc.properties.text.type" - ) - task.replaceKeyInMatch("test_index1.mappings.test_type.properties.text.analyzer", - "test_index1.mappings._doc.properties.text.analyzer" - ) - task.replaceKeyInMatch("test_index2.mappings.test_type.properties.text.type", - "test_index2.mappings._doc.properties.text.type" - ) - task.replaceKeyInMatch("test_index2.mappings.test_type.properties.text.analyzer", - "test_index2.mappings._doc.properties.text.analyzer" - ) - task.replaceKeyInMatch("foo.mappings.test_type.properties.text.type", - "foo.mappings._doc.properties.text.type" - ) - task.replaceKeyInMatch("foo.mappings.test_type.properties.text.analyzer", - "foo.mappings._doc.properties.text.analyzer" - ) - // overrides for indices.get_mapping - task.replaceIsTrue("test_1.mappings.doc", "test_1.mappings._doc") - task.replaceIsTrue("test_2.mappings.doc", "test_2.mappings._doc") - // overrides for mget - task.replaceValueInMatch("docs.0._type", "_doc" , "Basic multi-get") // index found, but no doc - task.replaceValueInMatch("docs.0._type", "_doc", "Default index/type") - task.replaceValueInMatch("docs.0._type", "_doc", "Non-existent index") - task.replaceValueInMatch("docs.0._type", "_doc", "Missing metadata") - task.replaceValueInMatch("docs.0._type", "_doc", "Multi Get with alias that resolves to multiple indices") - task.replaceValueInMatch("docs.1._type", "_doc", "Multi Get with alias that resolves to multiple indices") - task.replaceValueInMatch("docs.2._type", "_doc", "Multi Get with alias that resolves to multiple indices") - task.replaceValueInMatch("docs.0._type", "_doc", "IDs") - task.replaceValueInMatch("docs.1._type", "_doc", "IDs") - task.replaceValueInMatch("docs.2._type", "_doc", "Routing") - - //overrides for indices.stats - //TODO fix to remove the below match - task.replaceKeyInMatch("_all.primaries.indexing.types.baz.index_total", - "_all.primaries.indexing.types._doc.index_total" - ) - task.replaceKeyInMatch("_all.primaries.indexing.types.bar.index_total", - "_all.primaries.indexing.types._doc.index_total" - ) - task.replaceValueInMatch("_all.primaries.indexing.types._doc.index_total", 2) - // points get touched by sorting in ES 8 - task.replaceValueInMatch("testindex.shards.0.stats.fields.price.points", 1) - - //override for "indices.open/10_basic/?wait_for_active_shards default is deprecated" and "indices.open/10_basic/?wait_for_active_shards=index-setting" - task.addAllowedWarningRegexForTest("\\?wait_for_active_shards=index-setting is now the default behaviour.*", "?wait_for_active_shards=index-setting") - task.removeWarningForTest("the default value for the ?wait_for_active_shards parameter will change from '0' to 'index-setting' in version 8; " + - "specify '?wait_for_active_shards=index-setting' to adopt the future default behaviour, or '?wait_for_active_shards=0' to preserve today's behaviour" - , "?wait_for_active_shards default is deprecated") - - // override for exception message change in #55291 tests cluster.voting_config_exclusions/10_basic/ - // 'Throw exception when adding voting config exclusion and specifying both node_ids and node_names', - // 'Throw exception when adding voting config exclusion without specifying nodes', - task.replaceValueTextByKeyValue("catch", - '/Please set node identifiers correctly. One and only one of \\[node_name\\], \\[node_names\\] and \\[node_ids\\] has to be set/', - '/You must set \\[node_names\\] or \\[node_ids\\] but not both/') - - // sync_id is no longer available in SegmentInfos.userData // "indices.flush/10_basic/Index synced flush rest test" - task.replaceIsTrue("indices.testing.shards.0.0.commit.user_data.sync_id", "indices.testing.shards.0.0.commit.user_data") - -} +tasks.named("test").configure {enabled = false } +tasks.named("jarHell").configure {enabled = false } tasks.register('enforceYamlTestConvention').configure { doLast { From 5bf7e54820808ba97b9be745fff9e3854de2be76 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Mon, 11 Oct 2021 13:13:35 -0400 Subject: [PATCH 4/4] formatting fix --- .../aggregations/bucket/range/RangeAggregatorTests.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java index 029df364d5c40..9ea6252eed45f 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java @@ -121,8 +121,11 @@ public void testDoubleRangesExclusiveEndpoint() throws IOException { testCase( new RangeAggregationBuilder("range").field(fieldName).addRange("r1", 0, 0.04D).addRange("r2", 0.04D, 1.0D), new MatchAllDocsQuery(), - iw -> { iw.addDocument( - org.elasticsearch.core.List.of(new SortedNumericDocValuesField(fieldName, NumericUtils.doubleToSortableLong(0.04D)))); }, + iw -> { + iw.addDocument( + org.elasticsearch.core.List.of(new SortedNumericDocValuesField(fieldName, NumericUtils.doubleToSortableLong(0.04D))) + ); + }, result -> { InternalRange range = (InternalRange) result; List ranges = range.getBuckets();