diff --git a/muted-tests.yml b/muted-tests.yml index d1bf4d2c65a01..71bafb9dbaa52 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -612,60 +612,18 @@ tests: - class: org.elasticsearch.xpack.esql.action.CrossClusterAsyncQueryStopIT method: testStopQueryLocal issue: https://github.com/elastic/elasticsearch/issues/133481 -- class: org.elasticsearch.xpack.logsdb.qa.StandardVersusStandardReindexedIntoLogsDbChallengeRestIT - method: testEsqlSource - issue: https://github.com/elastic/elasticsearch/issues/132601 -- class: org.elasticsearch.xpack.logsdb.qa.BulkChallengeRestIT - method: testEsqlSource - issue: https://github.com/elastic/elasticsearch/issues/132600 -- class: org.elasticsearch.xpack.logsdb.qa.StoredSourceLogsDbVersusReindexedLogsDbChallengeRestIT - method: testEsqlSource - issue: https://github.com/elastic/elasticsearch/issues/132602 -- class: org.elasticsearch.xpack.logsdb.qa.BulkChallengeRestIT - method: testMatchAllQuery - issue: https://github.com/elastic/elasticsearch/issues/133497 -- class: org.elasticsearch.xpack.logsdb.qa.StandardVersusStandardReindexedIntoLogsDbChallengeRestIT - method: testMatchAllQuery - issue: https://github.com/elastic/elasticsearch/issues/133498 -- class: org.elasticsearch.xpack.logsdb.qa.StoredSourceLogsDbVersusReindexedLogsDbChallengeRestIT - method: testMatchAllQuery - issue: https://github.com/elastic/elasticsearch/issues/133499 -- class: org.elasticsearch.xpack.logsdb.qa.BulkChallengeRestIT - method: testRandomQueries - issue: https://github.com/elastic/elasticsearch/issues/133503 -- class: org.elasticsearch.xpack.logsdb.qa.StandardVersusStandardReindexedIntoLogsDbChallengeRestIT - method: testRandomQueries - issue: https://github.com/elastic/elasticsearch/issues/133504 -- class: org.elasticsearch.xpack.logsdb.qa.StoredSourceLogsDbVersusReindexedLogsDbChallengeRestIT - method: testRandomQueries - issue: https://github.com/elastic/elasticsearch/issues/133505 - class: org.elasticsearch.xpack.esql.qa.mixed.MixedClusterEsqlSpecIT method: test {csv-spec:spatial.ConvertFromStringParseError} issue: https://github.com/elastic/elasticsearch/issues/133507 - class: org.elasticsearch.test.rest.yaml.RcsCcsCommonYamlTestSuiteIT method: test {p0=search.vectors/90_sparse_vector/Indexing and searching multi-value sparse vectors in >=8.15} issue: https://github.com/elastic/elasticsearch/issues/133508 -- class: org.elasticsearch.xpack.logsdb.qa.BulkChallengeRestIT - method: testTermsQuery - issue: https://github.com/elastic/elasticsearch/issues/133516 -- class: org.elasticsearch.xpack.logsdb.qa.StandardVersusStandardReindexedIntoLogsDbChallengeRestIT - method: testTermsQuery - issue: https://github.com/elastic/elasticsearch/issues/133517 -- class: org.elasticsearch.xpack.logsdb.qa.StoredSourceLogsDbVersusReindexedLogsDbChallengeRestIT - method: testTermsQuery - issue: https://github.com/elastic/elasticsearch/issues/133518 -- class: org.elasticsearch.xpack.logsdb.qa.LogsDbVersusReindexedLogsDbChallengeRestIT - method: testEsqlSource - issue: https://github.com/elastic/elasticsearch/issues/133520 - class: org.elasticsearch.xpack.esql.action.EsqlActionBreakerIT method: testTopNPushedToLucene issue: https://github.com/elastic/elasticsearch/issues/133556 - class: org.elasticsearch.xpack.esql.action.EsqlActionBreakerIT method: testFilterNestedFields issue: https://github.com/elastic/elasticsearch/issues/133557 -- class: org.elasticsearch.xpack.logsdb.qa.LogsDbVersusReindexedLogsDbChallengeRestIT - method: testMatchAllQuery - issue: https://github.com/elastic/elasticsearch/issues/133560 - class: org.elasticsearch.test.rest.yaml.RcsCcsCommonYamlTestSuiteIT method: test {p0=search/10_source_filtering/no filtering} issue: https://github.com/elastic/elasticsearch/issues/133561 diff --git a/test/framework/src/main/java/org/elasticsearch/datageneration/matchers/source/FieldSpecificMatcher.java b/test/framework/src/main/java/org/elasticsearch/datageneration/matchers/source/FieldSpecificMatcher.java index d433e2d40f775..087d029c35e0f 100644 --- a/test/framework/src/main/java/org/elasticsearch/datageneration/matchers/source/FieldSpecificMatcher.java +++ b/test/framework/src/main/java/org/elasticsearch/datageneration/matchers/source/FieldSpecificMatcher.java @@ -51,13 +51,13 @@ static Map matchers( return new HashMap<>() { { put("keyword", new KeywordMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); - put("long", new NumberMatcher("long", actualMappings, actualSettings, expectedMappings, expectedSettings)); + put("long", new LongMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); put("unsigned_long", new UnsignedLongMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); - put("integer", new NumberMatcher("integer", actualMappings, actualSettings, expectedMappings, expectedSettings)); - put("short", new NumberMatcher("short", actualMappings, actualSettings, expectedMappings, expectedSettings)); - put("byte", new NumberMatcher("byte", actualMappings, actualSettings, expectedMappings, expectedSettings)); - put("double", new NumberMatcher("double", actualMappings, actualSettings, expectedMappings, expectedSettings)); - put("float", new NumberMatcher("float", actualMappings, actualSettings, expectedMappings, expectedSettings)); + put("integer", new IntegerMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); + put("short", new ShortMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); + put("byte", new ByteMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); + put("double", new DoubleMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); + put("float", new FloatMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); put("half_float", new HalfFloatMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); put("scaled_float", new ScaledFloatMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); put("counted_keyword", new CountedKeywordMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); @@ -327,9 +327,15 @@ Object convert(Object value, Object nullValue) { yield nullValueBigInt; } - yield s; + try { + yield new BigInteger(s); + } catch (NumberFormatException e) { + // malformed + yield value; + } } case Long l -> BigInteger.valueOf(l); + case Integer i -> BigInteger.valueOf(i); default -> value; }; @@ -356,31 +362,30 @@ Object convert(Object value, Object nullValue) { } } - class NumberMatcher extends GenericMappingAwareMatcher { + abstract class NumberMatcher extends GenericMappingAwareMatcher { - private final FieldType fieldType; private final NumberFieldMapper.NumberType numberType; - NumberMatcher( - String fieldType, + private NumberMatcher( + FieldType fieldType, XContentBuilder actualMappings, Settings.Builder actualSettings, XContentBuilder expectedMappings, Settings.Builder expectedSettings ) { - super(fieldType, actualMappings, actualSettings, expectedMappings, expectedSettings); - this.fieldType = FieldType.tryParse(fieldType); - this.numberType = NumberFieldMapper.NumberType.valueOf(this.fieldType.name()); + super(fieldType.toString(), actualMappings, actualSettings, expectedMappings, expectedSettings); + this.numberType = NumberFieldMapper.NumberType.valueOf(fieldType.name()); } @Override Object convert(Object value, Object nullValue) { if (value == null) { - return nullValue; + return cast(nullValue); } + // Special case for number coercion from strings if (value instanceof String s && s.isEmpty()) { - return nullValue; + return cast(nullValue); } // Attempt to coerce string values into numbers @@ -394,25 +399,111 @@ Object convert(Object value, Object nullValue) { } } - // When a number mapping is coerced, the expected value will come from the above parser and will have the correct java type. - // Whereas, if it fits, the actual value will be in an Integer or a Double. To correctly treat expected and actual values as - // equal the actual value must be cast to the appropriate type. - if (value instanceof Integer v) { - return switch (fieldType) { - case LONG -> v.longValue(); - case SHORT -> v.shortValue(); - case BYTE -> v.byteValue(); - default -> value; - }; - } - if (value instanceof Double v) { - return fieldType == FieldType.FLOAT ? v.floatValue() : value; - } + return cast(value); + } + + // When a number mapping is coerced, the expected value will come from the above parser and will have the correct java type. + // Whereas, if it fits, the actual value will be in an Integer or a Double. To correctly treat expected and actual values as + // equal the actual value must be cast to the appropriate type. + abstract Object cast(Object value); + } + + class LongMatcher extends NumberMatcher { + LongMatcher( + XContentBuilder actualMappings, + Settings.Builder actualSettings, + XContentBuilder expectedMappings, + Settings.Builder expectedSettings + ) { + super(FieldType.LONG, actualMappings, actualSettings, expectedMappings, expectedSettings); + } + + @Override + protected Object cast(Object value) { + return value instanceof Integer v ? v.longValue() : value; + } + } + + class IntegerMatcher extends NumberMatcher { + IntegerMatcher( + XContentBuilder actualMappings, + Settings.Builder actualSettings, + XContentBuilder expectedMappings, + Settings.Builder expectedSettings + ) { + super(FieldType.INTEGER, actualMappings, actualSettings, expectedMappings, expectedSettings); + } + + @Override + protected Object cast(Object value) { + return value; + } + } + + class ShortMatcher extends NumberMatcher { + ShortMatcher( + XContentBuilder actualMappings, + Settings.Builder actualSettings, + XContentBuilder expectedMappings, + Settings.Builder expectedSettings + ) { + super(FieldType.SHORT, actualMappings, actualSettings, expectedMappings, expectedSettings); + } + + @Override + protected Object cast(Object value) { + return value instanceof Integer v ? v.shortValue() : value; + } + } + + class ByteMatcher extends NumberMatcher { + ByteMatcher( + XContentBuilder actualMappings, + Settings.Builder actualSettings, + XContentBuilder expectedMappings, + Settings.Builder expectedSettings + ) { + super(FieldType.BYTE, actualMappings, actualSettings, expectedMappings, expectedSettings); + } + + @Override + protected Object cast(Object value) { + return value instanceof Integer v ? v.byteValue() : value; + } + } + class DoubleMatcher extends NumberMatcher { + DoubleMatcher( + XContentBuilder actualMappings, + Settings.Builder actualSettings, + XContentBuilder expectedMappings, + Settings.Builder expectedSettings + ) { + super(FieldType.DOUBLE, actualMappings, actualSettings, expectedMappings, expectedSettings); + } + + @Override + protected Object cast(Object value) { return value; } } + class FloatMatcher extends NumberMatcher { + FloatMatcher( + XContentBuilder actualMappings, + Settings.Builder actualSettings, + XContentBuilder expectedMappings, + Settings.Builder expectedSettings + ) { + super(FieldType.FLOAT, actualMappings, actualSettings, expectedMappings, expectedSettings); + } + + @Override + protected Object cast(Object value) { + return value instanceof Integer v ? v.floatValue() : value; + } + } + class BooleanMatcher extends GenericMappingAwareMatcher { BooleanMatcher( XContentBuilder actualMappings, diff --git a/test/framework/src/test/java/org/elasticsearch/logsdb/datageneration/SourceMatcherTests.java b/test/framework/src/test/java/org/elasticsearch/logsdb/datageneration/SourceMatcherTests.java index e41f336d44615..764087f6e4ef0 100644 --- a/test/framework/src/test/java/org/elasticsearch/logsdb/datageneration/SourceMatcherTests.java +++ b/test/framework/src/test/java/org/elasticsearch/logsdb/datageneration/SourceMatcherTests.java @@ -160,4 +160,52 @@ public void testCoercedNumberField() throws IOException { var sut = new SourceMatcher(Map.of(), mapping, Settings.builder(), mapping, Settings.builder(), actual, expected, false); assertTrue(sut.match().isMatch()); } + + public void testNullValueIntegerNumericMatcherCastsType() throws IOException { + String type = randomFrom("byte", "short", "integer", "long"); + int nullValue = 51; + List> actual = List.of(Map.of("field", List.of(5, nullValue, ""))); + List> expected = List.of(Map.of("field", List.of(5, nullValue, nullValue))); + + var mapping = XContentBuilder.builder(XContentType.JSON.xContent()); + mapping.startObject(); + mapping.startObject("_doc"); + { + mapping.startObject("field"); + { + mapping.field("type", type); + mapping.field("null_value", nullValue); + } + mapping.endObject(); + } + mapping.endObject(); + mapping.endObject(); + + var sut = new SourceMatcher(Map.of(), mapping, Settings.builder(), mapping, Settings.builder(), actual, expected, false); + assertTrue(sut.match().isMatch()); + } + + public void testNullValueFloatingPointNumericMatcherCastsType() throws IOException { + String type = randomFrom("float", "double"); + double nullValue = 51.123; + List> actual = List.of(Map.of("field", List.of(5, nullValue, ""))); + List> expected = List.of(Map.of("field", List.of(5, nullValue, nullValue))); + + var mapping = XContentBuilder.builder(XContentType.JSON.xContent()); + mapping.startObject(); + mapping.startObject("_doc"); + { + mapping.startObject("field"); + { + mapping.field("type", type); + mapping.field("null_value", nullValue); + } + mapping.endObject(); + } + mapping.endObject(); + mapping.endObject(); + + var sut = new SourceMatcher(Map.of(), mapping, Settings.builder(), mapping, Settings.builder(), actual, expected, false); + assertTrue(sut.match().isMatch()); + } }