From 3f8cc89c9f667cd3012bf8e813285f2e5d3a4485 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Tue, 2 Oct 2018 14:32:56 +0200 Subject: [PATCH] Completion types with multi-fields support (#34081) Mappings with completion type and multi-fields, were not able to index array or object format on completion fields. Only string format was supported. This is fixed by providing multiField parser with externalValueContext with already parsed object closes #15115 --- .../50_completion_with_multi_fields.yml | 299 +++++++++++++ .../index/mapper/CompletionFieldMapper.java | 41 +- .../mapper/CompletionFieldMapperTests.java | 396 +++++++++++++++++- 3 files changed, 724 insertions(+), 12 deletions(-) create mode 100644 rest-api-spec/src/main/resources/rest-api-spec/test/suggest/50_completion_with_multi_fields.yml diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/suggest/50_completion_with_multi_fields.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/suggest/50_completion_with_multi_fields.yml new file mode 100644 index 00000000000..42207a073fb --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/suggest/50_completion_with_multi_fields.yml @@ -0,0 +1,299 @@ + +--- +"Search by suggestion and by keyword sub-field should work": + + - skip: + version: " - 6.99.99" + reason: "Search by suggestion with multi-fields was introduced 7.0.0" + + - do: + indices.create: + index: completion_with_sub_keyword + body: + mappings: + test: + "properties": + "suggest_1": + "type" : "completion" + "fields": + "text_raw": + "type" : "keyword" + + - do: + index: + index: completion_with_sub_keyword + type: test + id: 1 + body: + suggest_1: "bar" + + - do: + index: + index: completion_with_sub_keyword + type: test + id: 2 + body: + suggest_1: "baz" + + - do: + indices.refresh: {} + + - do: + search: + index: completion_with_sub_keyword + body: + suggest: + result: + text: "b" + completion: + field: suggest_1 + + - length: { suggest.result: 1 } + - length: { suggest.result.0.options: 2 } + + + - do: + search: + index: completion_with_sub_keyword + body: + query: { term: { suggest_1.text_raw: "bar" }} + + - match: { hits.total: 1 } + + + +--- +"Search by suggestion on sub field should work": + + - skip: + version: " - 6.99.99" + reason: "Search by suggestion with multi-fields was introduced 7.0.0" + + - do: + indices.create: + index: completion_with_sub_completion + body: + mappings: + test: + "properties": + "suggest_1": + "type": "completion" + "fields": + "suggest_2": + "type": "completion" + + - do: + index: + index: completion_with_sub_completion + type: test + id: 1 + body: + suggest_1: "bar" + + - do: + index: + index: completion_with_sub_completion + type: test + id: 2 + body: + suggest_1: "baz" + + - do: + indices.refresh: {} + + - do: + search: + index: completion_with_sub_completion + body: + suggest: + result: + text: "b" + completion: + field: suggest_1.suggest_2 + + - length: { suggest.result: 1 } + - length: { suggest.result.0.options: 2 } + +--- +"Search by suggestion on sub field with context should work": + + - skip: + version: " - 6.99.99" + reason: "Search by suggestion with multi-fields was introduced 7.0.0" + + - do: + indices.create: + index: completion_with_context + body: + mappings: + test: + "properties": + "suggest_1": + "type": "completion" + "contexts": + - + "name": "color" + "type": "category" + "fields": + "suggest_2": + "type": "completion" + "contexts": + - + "name": "color" + "type": "category" + + + - do: + index: + index: completion_with_context + type: test + id: 1 + body: + suggest_1: + input: "foo red" + contexts: + color: "red" + + - do: + index: + index: completion_with_context + type: test + id: 2 + body: + suggest_1: + input: "foo blue" + contexts: + color: "blue" + + - do: + indices.refresh: {} + + - do: + search: + index: completion_with_context + body: + suggest: + result: + prefix: "foo" + completion: + field: suggest_1.suggest_2 + contexts: + color: "red" + + - length: { suggest.result: 1 } + - length: { suggest.result.0.options: 1 } + - match: { suggest.result.0.options.0.text: "foo red" } + + +--- +"Search by suggestion on sub field with weight should work": + + - skip: + version: " - 6.99.99" + reason: "Search by suggestion with multi-fields was introduced 7.0.0" + + - do: + indices.create: + index: completion_with_weight + body: + mappings: + test: + "properties": + "suggest_1": + "type": "completion" + "fields": + "suggest_2": + "type": "completion" + + - do: + index: + index: completion_with_weight + type: test + id: 1 + body: + suggest_1: + input: "bar" + weight: 2 + + - do: + index: + index: completion_with_weight + type: test + id: 2 + body: + suggest_1: + input: "baz" + weight: 3 + + - do: + indices.refresh: {} + + - do: + search: + index: completion_with_weight + body: + suggest: + result: + text: "b" + completion: + field: suggest_1.suggest_2 + + - length: { suggest.result: 1 } + - length: { suggest.result.0.options: 2 } + - match: { suggest.result.0.options.0.text: "baz" } + - match: { suggest.result.0.options.1.text: "bar" } + +--- +"Search by suggestion on geofield-hash on sub field should work": + + - skip: + version: " - 6.99.99" + reason: "Search by suggestion with multi-fields was introduced 7.0.0" + + - do: + indices.create: + index: geofield_with_completion + body: + mappings: + test: + "properties": + "geofield": + "type": "geo_point" + "fields": + "suggest_1": + "type": "completion" + + - do: + index: + index: geofield_with_completion + type: test + id: 1 + body: + geofield: "hgjhrwysvqw7" + #41.12,-72.34,12 + + - do: + index: + index: geofield_with_completion + type: test + id: 1 + body: + geofield: "hgm4psywmkn7" + #41.12,-71.34,12 + + - do: + indices.refresh: {} + + - do: + search: + index: geofield_with_completion + body: + suggest: + result: + prefix: "hgm" + completion: + field: geofield.suggest_1 + + + - length: { suggest.result: 1 } + - length: { suggest.result.0.options: 1 } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java index db04e64b164..0635cdd0661 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java @@ -436,8 +436,9 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp Token token = parser.currentToken(); Map inputMap = new HashMap<>(1); - // ignore null values - if (token == Token.VALUE_NULL) { + if (context.externalValueSet()) { + inputMap = getInputMapFromExternalValue(context); + } else if (token == Token.VALUE_NULL) { // ignore null values return; } else if (token == Token.START_ARRAY) { while ((token = parser.nextToken()) != Token.END_ARRAY) { @@ -471,12 +472,33 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp context.doc().add(new SuggestField(fieldType().name(), input, metaData.weight)); } } + List fields = new ArrayList<>(1); createFieldNamesField(context, fields); for (IndexableField field : fields) { context.doc().add(field); } - multiFields.parse(this, context); + + for (CompletionInputMetaData metaData: inputMap.values()) { + ParseContext externalValueContext = context.createExternalValueContext(metaData); + multiFields.parse(this, externalValueContext); + } + } + + private Map getInputMapFromExternalValue(ParseContext context) { + Map inputMap; + if (isExternalValueOfClass(context, CompletionInputMetaData.class)) { + CompletionInputMetaData inputAndMeta = (CompletionInputMetaData) context.externalValue(); + inputMap = Collections.singletonMap(inputAndMeta.input, inputAndMeta); + } else { + String fieldName = context.externalValue().toString(); + inputMap = Collections.singletonMap(fieldName, new CompletionInputMetaData(fieldName, Collections.emptyMap(), 1)); + } + return inputMap; + } + + private boolean isExternalValueOfClass(ParseContext context, Class clazz) { + return context.externalValue().getClass().equals(clazz); } /** @@ -487,7 +509,7 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp private void parse(ParseContext parseContext, Token token, XContentParser parser, Map inputMap) throws IOException { String currentFieldName = null; if (token == Token.VALUE_STRING) { - inputMap.put(parser.text(), new CompletionInputMetaData(Collections.>emptyMap(), 1)); + inputMap.put(parser.text(), new CompletionInputMetaData(parser.text(), Collections.emptyMap(), 1)); } else if (token == Token.START_OBJECT) { Set inputs = new HashSet<>(); int weight = 1; @@ -561,7 +583,7 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp } for (String input : inputs) { if (inputMap.containsKey(input) == false || inputMap.get(input).weight < weight) { - inputMap.put(input, new CompletionInputMetaData(contextsMap, weight)); + inputMap.put(input, new CompletionInputMetaData(input, contextsMap, weight)); } } } else { @@ -570,13 +592,20 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp } static class CompletionInputMetaData { + public final String input; public final Map> contexts; public final int weight; - CompletionInputMetaData(Map> contexts, int weight) { + CompletionInputMetaData(String input, Map> contexts, int weight) { + this.input = input; this.contexts = contexts; this.weight = weight; } + + @Override + public String toString() { + return input; + } } @Override diff --git a/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java index e3739eed336..cc09ae16c05 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java @@ -18,9 +18,11 @@ */ package org.elasticsearch.index.mapper; +import org.apache.lucene.document.SortedSetDocValuesField; import org.apache.lucene.index.IndexableField; import org.apache.lucene.search.Query; import org.apache.lucene.search.suggest.document.CompletionAnalyzer; +import org.apache.lucene.search.suggest.document.ContextSuggestField; import org.apache.lucene.search.suggest.document.FuzzyCompletionQuery; import org.apache.lucene.search.suggest.document.PrefixCompletionQuery; import org.apache.lucene.search.suggest.document.RegexCompletionQuery; @@ -42,11 +44,18 @@ import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.test.ESSingleNodeTestCase; +import org.hamcrest.FeatureMatcher; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.hamcrest.core.CombinableMatcher; import java.io.IOException; import java.util.Map; +import java.util.function.Function; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; +import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; @@ -182,6 +191,328 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase { assertEquals("failed to parse [completion]: expected text or object, but got VALUE_NUMBER", e.getCause().getMessage()); } + public void testKeywordWithSubCompletionAndContext() throws Exception { + String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1") + .startObject("properties") + .startObject("keywordfield") + .field("type", "keyword") + .startObject("fields") + .startObject("subsuggest") + .field("type", "completion") + .startArray("contexts") + .startObject() + .field("name","place_type") + .field("type","category") + .field("path","cat") + .endObject() + .endArray() + .endObject() + .endObject() + .endObject().endObject() + .endObject().endObject() + ); + + DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse("type1", new CompressedXContent(mapping)); + + ParsedDocument parsedDocument = defaultMapper.parse(SourceToParse.source("test", "type1", "1", BytesReference + .bytes(XContentFactory.jsonBuilder() + .startObject() + .array("keywordfield", "key1", "key2", "key3") + .endObject()), + XContentType.JSON)); + + ParseContext.Document indexableFields = parsedDocument.rootDoc(); + + assertThat(indexableFields.getFields("keywordfield"), arrayContainingInAnyOrder( + keywordField("key1"), + sortedSetDocValuesField("key1"), + keywordField("key2"), + sortedSetDocValuesField("key2"), + keywordField("key3"), + sortedSetDocValuesField("key3") + )); + assertThat(indexableFields.getFields("keywordfield.subsuggest"), arrayContainingInAnyOrder( + contextSuggestField("key1"), + contextSuggestField("key2"), + contextSuggestField("key3") + )); + } + + public void testCompletionWithContextAndSubCompletion() throws Exception { + String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1") + .startObject("properties") + .startObject("suggest") + .field("type", "completion") + .startArray("contexts") + .startObject() + .field("name","place_type") + .field("type","category") + .field("path","cat") + .endObject() + .endArray() + .startObject("fields") + .startObject("subsuggest") + .field("type", "completion") + .startArray("contexts") + .startObject() + .field("name","place_type") + .field("type","category") + .field("path","cat") + .endObject() + .endArray() + .endObject() + .endObject() + .endObject().endObject() + .endObject().endObject() + ); + + DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse("type1", new CompressedXContent(mapping)); + + ParsedDocument parsedDocument = defaultMapper.parse(SourceToParse.source("test", "type1", "1", BytesReference + .bytes(XContentFactory.jsonBuilder() + .startObject() + .startObject("suggest") + .array("input","timmy","starbucks") + .startObject("contexts") + .array("place_type","cafe","food") + .endObject() + .field("weight", 3) + .endObject() + .endObject()), + XContentType.JSON)); + + ParseContext.Document indexableFields = parsedDocument.rootDoc(); + assertThat(indexableFields.getFields("suggest"), arrayContainingInAnyOrder( + contextSuggestField("timmy"), + contextSuggestField("starbucks") + )); + assertThat(indexableFields.getFields("suggest.subsuggest"), arrayContainingInAnyOrder( + contextSuggestField("timmy"), + contextSuggestField("starbucks") + )); + //unable to assert about context, covered in a REST test + } + + public void testCompletionWithContextAndSubCompletionIndexByPath() throws Exception { + String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1") + .startObject("properties") + .startObject("suggest") + .field("type", "completion") + .startArray("contexts") + .startObject() + .field("name","place_type") + .field("type","category") + .field("path","cat") + .endObject() + .endArray() + .startObject("fields") + .startObject("subsuggest") + .field("type", "completion") + .startArray("contexts") + .startObject() + .field("name","place_type") + .field("type","category") + .field("path","cat") + .endObject() + .endArray() + .endObject() + .endObject() + .endObject().endObject() + .endObject().endObject() + ); + + DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse("type1", new CompressedXContent(mapping)); + + ParsedDocument parsedDocument = defaultMapper.parse(SourceToParse.source("test", "type1", "1", BytesReference + .bytes(XContentFactory.jsonBuilder() + .startObject() + .array("suggest", "timmy","starbucks") + .array("cat","cafe","food") + .endObject()), + XContentType.JSON)); + + ParseContext.Document indexableFields = parsedDocument.rootDoc(); + assertThat(indexableFields.getFields("suggest"), arrayContainingInAnyOrder( + contextSuggestField("timmy"), + contextSuggestField("starbucks") + )); + assertThat(indexableFields.getFields("suggest.subsuggest"), arrayContainingInAnyOrder( + contextSuggestField("timmy"), + contextSuggestField("starbucks") + )); + //unable to assert about context, covered in a REST test + } + + + public void testKeywordWithSubCompletionAndStringInsert() throws Exception { + String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1") + .startObject("properties").startObject("geofield") + .field("type", "geo_point") + .startObject("fields") + .startObject("analyzed") + .field("type", "completion") + .endObject() + .endObject() + .endObject().endObject() + .endObject().endObject() + ); + + DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse("type1", new CompressedXContent(mapping)); + + ParsedDocument parsedDocument = defaultMapper.parse(SourceToParse.source("test", "type1", "1", BytesReference + .bytes(XContentFactory.jsonBuilder() + .startObject() + .field("geofield", "drm3btev3e86")//"41.12,-71.34" + .endObject()), + XContentType.JSON)); + + ParseContext.Document indexableFields = parsedDocument.rootDoc(); + assertThat(indexableFields.getFields("geofield"), arrayWithSize(2)); + assertThat(indexableFields.getFields("geofield.analyzed"), arrayContainingInAnyOrder( + suggestField("drm3btev3e86") + )); + //unable to assert about geofield content, covered in a REST test + } + + public void testCompletionTypeWithSubCompletionFieldAndStringInsert() throws Exception { + String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1") + .startObject("properties").startObject("suggest") + .field("type", "completion") + .startObject("fields") + .startObject("subsuggest") + .field("type", "completion") + .endObject() + .endObject() + .endObject().endObject() + .endObject().endObject() + ); + + DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse("type1", new CompressedXContent(mapping)); + + ParsedDocument parsedDocument = defaultMapper.parse(SourceToParse.source("test", "type1", "1", BytesReference + .bytes(XContentFactory.jsonBuilder() + .startObject() + .field("suggest", "suggestion") + .endObject()), + XContentType.JSON)); + + ParseContext.Document indexableFields = parsedDocument.rootDoc(); + assertThat(indexableFields.getFields("suggest"), arrayContainingInAnyOrder( + suggestField("suggestion") + )); + assertThat(indexableFields.getFields("suggest.subsuggest"), arrayContainingInAnyOrder( + suggestField("suggestion") + )); + } + + public void testCompletionTypeWithSubCompletionFieldAndObjectInsert() throws Exception { + String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1") + .startObject("properties").startObject("completion") + .field("type", "completion") + .startObject("fields") + .startObject("analyzed") + .field("type", "completion") + .endObject() + .endObject() + .endObject().endObject() + .endObject().endObject() + ); + + DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse("type1", new CompressedXContent(mapping)); + + ParsedDocument parsedDocument = defaultMapper.parse(SourceToParse.source("test", "type1", "1", BytesReference + .bytes(XContentFactory.jsonBuilder() + .startObject() + .startObject("completion") + .array("input","New York", "NY") + .field("weight",34) + .endObject() + .endObject()), + XContentType.JSON)); + + ParseContext.Document indexableFields = parsedDocument.rootDoc(); + assertThat(indexableFields.getFields("completion"), arrayContainingInAnyOrder( + suggestField("New York"), + suggestField("NY") + )); + assertThat(indexableFields.getFields("completion.analyzed"), arrayContainingInAnyOrder( + suggestField("New York"), + suggestField("NY") + )); + //unable to assert about weight, covered in a REST test + } + + public void testCompletionTypeWithSubKeywordFieldAndObjectInsert() throws Exception { + String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1") + .startObject("properties").startObject("completion") + .field("type", "completion") + .startObject("fields") + .startObject("analyzed") + .field("type", "keyword") + .endObject() + .endObject() + .endObject().endObject() + .endObject().endObject() + ); + + DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse("type1", new CompressedXContent(mapping)); + + ParsedDocument parsedDocument = defaultMapper.parse(SourceToParse.source("test", "type1", "1", BytesReference + .bytes(XContentFactory.jsonBuilder() + .startObject() + .startObject("completion") + .array("input","New York", "NY") + .field("weight",34) + .endObject() + .endObject()), + XContentType.JSON)); + + ParseContext.Document indexableFields = parsedDocument.rootDoc(); + assertThat(indexableFields.getFields("completion"), arrayContainingInAnyOrder( + suggestField("New York"), + suggestField("NY") + )); + assertThat(indexableFields.getFields("completion.analyzed"), arrayContainingInAnyOrder( + keywordField("New York"), + sortedSetDocValuesField("New York"), + keywordField("NY"), + sortedSetDocValuesField("NY") + )); + //unable to assert about weight, covered in a REST test + } + + public void testCompletionTypeWithSubKeywordFieldAndStringInsert() throws Exception { + String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1") + .startObject("properties").startObject("completion") + .field("type", "completion") + .startObject("fields") + .startObject("analyzed") + .field("type", "keyword") + .endObject() + .endObject() + .endObject().endObject() + .endObject().endObject() + ); + + DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser().parse("type1", new CompressedXContent(mapping)); + + ParsedDocument parsedDocument = defaultMapper.parse(SourceToParse.source("test", "type1", "1", BytesReference + .bytes(XContentFactory.jsonBuilder() + .startObject() + .field("completion", "suggestion") + .endObject()), + XContentType.JSON)); + + ParseContext.Document indexableFields = parsedDocument.rootDoc(); + assertThat(indexableFields.getFields("completion"), arrayContainingInAnyOrder( + suggestField("suggestion") + )); + assertThat(indexableFields.getFields("completion.analyzed"), arrayContainingInAnyOrder( + keywordField("suggestion"), + sortedSetDocValuesField("suggestion") + )); + } + public void testParsingMultiValued() throws Exception { String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1") .startObject("properties").startObject("completion") @@ -199,7 +530,10 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase { .endObject()), XContentType.JSON)); IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name()); - assertSuggestFields(fields, 2); + assertThat(fields, arrayContainingInAnyOrder( + suggestField("suggestion1"), + suggestField("suggestion2") + )); } public void testParsingWithWeight() throws Exception { @@ -222,7 +556,9 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase { .endObject()), XContentType.JSON)); IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name()); - assertSuggestFields(fields, 1); + assertThat(fields, arrayContainingInAnyOrder( + suggestField("suggestion") + )); } public void testParsingMultiValueWithWeight() throws Exception { @@ -245,7 +581,11 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase { .endObject()), XContentType.JSON)); IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name()); - assertSuggestFields(fields, 3); + assertThat(fields, arrayContainingInAnyOrder( + suggestField("suggestion1"), + suggestField("suggestion2"), + suggestField("suggestion3") + )); } public void testParsingWithGeoFieldAlias() throws Exception { @@ -318,7 +658,11 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase { .endObject()), XContentType.JSON)); IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name()); - assertSuggestFields(fields, 3); + assertThat(fields, arrayContainingInAnyOrder( + suggestField("suggestion1"), + suggestField("suggestion2"), + suggestField("suggestion3") + )); } public void testParsingMixed() throws Exception { @@ -351,7 +695,14 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase { .endObject()), XContentType.JSON)); IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name()); - assertSuggestFields(fields, 6); + assertThat(fields, arrayContainingInAnyOrder( + suggestField("suggestion1"), + suggestField("suggestion2"), + suggestField("suggestion3"), + suggestField("suggestion4"), + suggestField("suggestion5"), + suggestField("suggestion6") + )); } public void testNonContextEnabledParsingWithContexts() throws Exception { @@ -508,9 +859,13 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase { } private static void assertSuggestFields(IndexableField[] fields, int expected) { + assertFieldsOfType(fields, SuggestField.class, expected); + } + + private static void assertFieldsOfType(IndexableField[] fields, Class clazz, int expected) { int actualFieldCount = 0; for (IndexableField field : fields) { - if (field instanceof SuggestField) { + if (clazz.isInstance(field)) { actualFieldCount++; } } @@ -529,4 +884,33 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase { ); assertThat(e.getMessage(), containsString("name cannot be empty string")); } + + private Matcher suggestField(String value) { + return Matchers.allOf(hasProperty(IndexableField::stringValue, equalTo(value)), + Matchers.instanceOf(SuggestField.class)); + } + + private Matcher contextSuggestField(String value) { + return Matchers.allOf(hasProperty(IndexableField::stringValue, equalTo(value)), + Matchers.instanceOf(ContextSuggestField.class)); + } + + private CombinableMatcher sortedSetDocValuesField(String value) { + return Matchers.both(hasProperty(IndexableField::binaryValue, equalTo(new BytesRef(value)))) + .and(Matchers.instanceOf(SortedSetDocValuesField.class)); + } + + private CombinableMatcher keywordField(String value) { + return Matchers.both(hasProperty(IndexableField::binaryValue, equalTo(new BytesRef(value)))) + .and(hasProperty(IndexableField::fieldType, Matchers.instanceOf(KeywordFieldMapper.KeywordFieldType.class))); + } + + private Matcher hasProperty(Function property, Matcher valueMatcher) { + return new FeatureMatcher(valueMatcher, "object with", property.toString()) { + @Override + protected V featureValueOf(T actual) { + return property.apply(actual); + } + }; + } }