diff --git a/test/framework/src/main/java/org/elasticsearch/datageneration/FieldType.java b/test/framework/src/main/java/org/elasticsearch/datageneration/FieldType.java index eab2149019204..ef6cc21e90d8a 100644 --- a/test/framework/src/main/java/org/elasticsearch/datageneration/FieldType.java +++ b/test/framework/src/main/java/org/elasticsearch/datageneration/FieldType.java @@ -23,6 +23,7 @@ import org.elasticsearch.datageneration.fields.leaf.IpFieldDataGenerator; import org.elasticsearch.datageneration.fields.leaf.KeywordFieldDataGenerator; import org.elasticsearch.datageneration.fields.leaf.LongFieldDataGenerator; +import org.elasticsearch.datageneration.fields.leaf.MatchOnlyTextFieldDataGenerator; import org.elasticsearch.datageneration.fields.leaf.ScaledFloatFieldDataGenerator; import org.elasticsearch.datageneration.fields.leaf.ShortFieldDataGenerator; import org.elasticsearch.datageneration.fields.leaf.TextFieldDataGenerator; @@ -50,7 +51,8 @@ public enum FieldType { TEXT("text"), IP("ip"), CONSTANT_KEYWORD("constant_keyword"), - WILDCARD("wildcard"); + WILDCARD("wildcard"), + MATCH_ONLY_TEXT("match_only_text"); private final String name; @@ -78,6 +80,7 @@ public FieldDataGenerator generator(String fieldName, DataSource dataSource) { case IP -> new IpFieldDataGenerator(dataSource); case CONSTANT_KEYWORD -> new ConstantKeywordFieldDataGenerator(); case WILDCARD -> new WildcardFieldDataGenerator(dataSource); + case MATCH_ONLY_TEXT -> new MatchOnlyTextFieldDataGenerator(dataSource); }; } @@ -101,6 +104,7 @@ public static FieldType tryParse(String name) { case "ip" -> FieldType.IP; case "constant_keyword" -> FieldType.CONSTANT_KEYWORD; case "wildcard" -> FieldType.WILDCARD; + case "match_only_text" -> FieldType.MATCH_ONLY_TEXT; default -> null; }; } diff --git a/test/framework/src/main/java/org/elasticsearch/datageneration/datasource/DefaultMappingParametersHandler.java b/test/framework/src/main/java/org/elasticsearch/datageneration/datasource/DefaultMappingParametersHandler.java index 2e234f8aec41c..324f1f56e25f8 100644 --- a/test/framework/src/main/java/org/elasticsearch/datageneration/datasource/DefaultMappingParametersHandler.java +++ b/test/framework/src/main/java/org/elasticsearch/datageneration/datasource/DefaultMappingParametersHandler.java @@ -22,6 +22,7 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.function.Supplier; @@ -37,17 +38,18 @@ public DataSourceResponse.LeafMappingParametersGenerator handle(DataSourceReques } return new DataSourceResponse.LeafMappingParametersGenerator(switch (fieldType) { - case KEYWORD -> keywordMapping(request); + case KEYWORD -> keywordMapping(false, request); case LONG, INTEGER, SHORT, BYTE, DOUBLE, FLOAT, HALF_FLOAT, UNSIGNED_LONG -> numberMapping(fieldType); case SCALED_FLOAT -> scaledFloatMapping(); case COUNTED_KEYWORD -> countedKeywordMapping(); case BOOLEAN -> booleanMapping(); case DATE -> dateMapping(); case GEO_POINT -> geoPointMapping(); - case TEXT -> textMapping(request); + case TEXT -> textMapping(false, request); case IP -> ipMapping(); case CONSTANT_KEYWORD -> constantKeywordMapping(); - case WILDCARD -> wildcardMapping(); + case WILDCARD -> wildcardMapping(false, request); + case MATCH_ONLY_TEXT -> matchOnlyTextMapping(false, request); }); } @@ -77,7 +79,7 @@ private Supplier> numberMapping(FieldType fieldType) { }; } - private Supplier> keywordMapping(DataSourceRequest.LeafMappingParametersGenerator request) { + private Supplier> keywordMapping(boolean hasParent, DataSourceRequest.LeafMappingParametersGenerator request) { return () -> { var mapping = commonMappingParameters(); @@ -96,12 +98,15 @@ private Supplier> keywordMapping(DataSourceRequest.LeafMappi } } - if (ESTestCase.randomDouble() <= 0.2) { + if (ESTestCase.randomDouble() <= 0.3) { mapping.put("ignore_above", ESTestCase.randomIntBetween(1, 100)); } if (ESTestCase.randomDouble() <= 0.2) { mapping.put("null_value", ESTestCase.randomAlphaOfLengthBetween(0, 10)); } + if (hasParent == false && ESTestCase.randomDouble() <= 0.2) { + mapping.put("fields", stringSubField(FieldType.KEYWORD, request)); + } return mapping; }; @@ -196,19 +201,15 @@ private Supplier> geoPointMapping() { }; } - private Supplier> textMapping(DataSourceRequest.LeafMappingParametersGenerator request) { + private Supplier> textMapping(boolean hasParent, DataSourceRequest.LeafMappingParametersGenerator request) { return () -> { var mapping = new HashMap(); mapping.put("store", ESTestCase.randomBoolean()); mapping.put("index", ESTestCase.randomBoolean()); - if (ESTestCase.randomDouble() <= 0.1) { - var keywordMultiFieldMapping = keywordMapping(request).get(); - keywordMultiFieldMapping.put("type", "keyword"); - keywordMultiFieldMapping.remove("copy_to"); - - mapping.put("fields", Map.of("kwd", keywordMultiFieldMapping)); + if (hasParent == false && ESTestCase.randomDouble() <= 0.2) { + mapping.put("fields", stringSubField(FieldType.TEXT, request)); } return mapping; @@ -243,21 +244,56 @@ private Supplier> constantKeywordMapping() { }; } - private Supplier> wildcardMapping() { + private Supplier> wildcardMapping(boolean hasParent, DataSourceRequest.LeafMappingParametersGenerator request) { return () -> { var mapping = new HashMap(); - if (ESTestCase.randomDouble() <= 0.2) { + if (ESTestCase.randomDouble() <= 0.3) { mapping.put("ignore_above", ESTestCase.randomIntBetween(1, 100)); } if (ESTestCase.randomDouble() <= 0.2) { mapping.put("null_value", ESTestCase.randomAlphaOfLengthBetween(0, 10)); } + if (hasParent == false && ESTestCase.randomDouble() <= 0.2) { + mapping.put("fields", stringSubField(FieldType.WILDCARD, request)); + } + return mapping; + }; + } + private Supplier> matchOnlyTextMapping( + boolean hasParent, + DataSourceRequest.LeafMappingParametersGenerator request + ) { + return () -> { + var mapping = new HashMap(); + if (hasParent == false && ESTestCase.randomDouble() <= 0.2) { + mapping.put("fields", stringSubField(FieldType.MATCH_ONLY_TEXT, request)); + } return mapping; }; } + private Map stringSubField(FieldType parent, DataSourceRequest.LeafMappingParametersGenerator request) { + + List stringTypes = List.of(FieldType.TEXT, FieldType.MATCH_ONLY_TEXT, FieldType.KEYWORD, FieldType.WILDCARD); + var childType = ESTestCase.randomValueOtherThan(parent, () -> ESTestCase.randomFrom(stringTypes)); + var child = switch (childType) { + case TEXT -> textMapping(true, request).get(); + case MATCH_ONLY_TEXT -> matchOnlyTextMapping(true, request).get(); + case WILDCARD -> wildcardMapping(true, request).get(); + case KEYWORD -> { + var mapping = keywordMapping(true, request).get(); + mapping.remove("copy_to"); + yield mapping; + } + default -> throw new AssertionError("unreachable"); + }; + + child.put("type", childType.toString()); + return Map.of("subfield_" + childType, child); + } + public static HashMap commonMappingParameters() { var map = new HashMap(); map.put("store", ESTestCase.randomBoolean()); diff --git a/test/framework/src/main/java/org/elasticsearch/datageneration/fields/leaf/MatchOnlyTextFieldDataGenerator.java b/test/framework/src/main/java/org/elasticsearch/datageneration/fields/leaf/MatchOnlyTextFieldDataGenerator.java new file mode 100644 index 0000000000000..f4493fd9b4ee9 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/datageneration/fields/leaf/MatchOnlyTextFieldDataGenerator.java @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.datageneration.fields.leaf; + +import org.elasticsearch.datageneration.FieldDataGenerator; +import org.elasticsearch.datageneration.datasource.DataSource; + +import java.util.Map; + +public class MatchOnlyTextFieldDataGenerator implements FieldDataGenerator { + private final FieldDataGenerator textGenerator; + + public MatchOnlyTextFieldDataGenerator(DataSource dataSource) { + this.textGenerator = new TextFieldDataGenerator(dataSource); + } + + @Override + public Object generateValue(Map fieldMapping) { + return textGenerator.generateValue(fieldMapping); + } +} 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 7adf98ef9d6ee..99b29297fa4d1 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 @@ -62,7 +62,8 @@ static Map matchers( put("geo_shape", new ExactMatcher("geo_shape", actualMappings, actualSettings, expectedMappings, expectedSettings)); put("shape", new ExactMatcher("shape", actualMappings, actualSettings, expectedMappings, expectedSettings)); put("geo_point", new GeoPointMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); - put("text", new TextMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); + put("text", new TextMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings, false)); + put("match_only_text", new TextMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings, true)); put("ip", new IpMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); put("constant_keyword", new ConstantKeywordMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); put("wildcard", new WildcardMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings)); @@ -608,17 +609,20 @@ class TextMatcher implements FieldSpecificMatcher { private final Settings.Builder actualSettings; private final XContentBuilder expectedMappings; private final Settings.Builder expectedSettings; + private final boolean isMatchOnlyText; TextMatcher( XContentBuilder actualMappings, Settings.Builder actualSettings, XContentBuilder expectedMappings, - Settings.Builder expectedSettings + Settings.Builder expectedSettings, + boolean isMatchOnlyText ) { this.actualMappings = actualMappings; this.actualSettings = actualSettings; this.expectedMappings = expectedMappings; this.expectedSettings = expectedSettings; + this.isMatchOnlyText = isMatchOnlyText; } @Override @@ -640,23 +644,24 @@ public MatchResult match( // In some cases synthetic source for text fields is synthesized using the keyword multi field. // So in this case it's appropriate to match it using keyword matching logic (mainly to cover `null_value`). var multiFields = (Map) getMappingParameter("fields", actualMapping, expectedMapping); - if (multiFields != null) { + if (multiFields != null && multiFields.containsKey("subfield_keyword")) { var keywordMatcher = new KeywordMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings); - var keywordFieldMapping = (Map) multiFields.get("kwd"); + var keywordFieldMapping = (Map) multiFields.get("subfield_keyword"); var keywordMatchResult = keywordMatcher.match(actual, expected, keywordFieldMapping, keywordFieldMapping); if (keywordMatchResult.isMatch()) { return MatchResult.match(); } } + var typeName = isMatchOnlyText ? "match_only_text" : "text"; return MatchResult.noMatch( formatErrorMessage( actualMappings, actualSettings, expectedMappings, expectedSettings, - "Values of type [text] don't match, " + prettyPrintCollections(actual, expected) + "Values of type [" + typeName + "] don't match, " + prettyPrintCollections(actual, expected) ) ); }