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
This commit is contained in:
Przemyslaw Gomulka 2018-10-02 14:32:56 +02:00 committed by GitHub
parent b1b0f3276b
commit 3f8cc89c9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 724 additions and 12 deletions

View File

@ -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 }

View File

@ -436,8 +436,9 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp
Token token = parser.currentToken(); Token token = parser.currentToken();
Map<String, CompletionInputMetaData> inputMap = new HashMap<>(1); Map<String, CompletionInputMetaData> inputMap = new HashMap<>(1);
// ignore null values if (context.externalValueSet()) {
if (token == Token.VALUE_NULL) { inputMap = getInputMapFromExternalValue(context);
} else if (token == Token.VALUE_NULL) { // ignore null values
return; return;
} else if (token == Token.START_ARRAY) { } else if (token == Token.START_ARRAY) {
while ((token = parser.nextToken()) != Token.END_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)); context.doc().add(new SuggestField(fieldType().name(), input, metaData.weight));
} }
} }
List<IndexableField> fields = new ArrayList<>(1); List<IndexableField> fields = new ArrayList<>(1);
createFieldNamesField(context, fields); createFieldNamesField(context, fields);
for (IndexableField field : fields) { for (IndexableField field : fields) {
context.doc().add(field); context.doc().add(field);
} }
multiFields.parse(this, context);
for (CompletionInputMetaData metaData: inputMap.values()) {
ParseContext externalValueContext = context.createExternalValueContext(metaData);
multiFields.parse(this, externalValueContext);
}
}
private Map<String, CompletionInputMetaData> getInputMapFromExternalValue(ParseContext context) {
Map<String, CompletionInputMetaData> 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<String, CompletionInputMetaData> inputMap) throws IOException { private void parse(ParseContext parseContext, Token token, XContentParser parser, Map<String, CompletionInputMetaData> inputMap) throws IOException {
String currentFieldName = null; String currentFieldName = null;
if (token == Token.VALUE_STRING) { if (token == Token.VALUE_STRING) {
inputMap.put(parser.text(), new CompletionInputMetaData(Collections.<String, Set<CharSequence>>emptyMap(), 1)); inputMap.put(parser.text(), new CompletionInputMetaData(parser.text(), Collections.emptyMap(), 1));
} else if (token == Token.START_OBJECT) { } else if (token == Token.START_OBJECT) {
Set<String> inputs = new HashSet<>(); Set<String> inputs = new HashSet<>();
int weight = 1; int weight = 1;
@ -561,7 +583,7 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp
} }
for (String input : inputs) { for (String input : inputs) {
if (inputMap.containsKey(input) == false || inputMap.get(input).weight < weight) { 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 { } else {
@ -570,13 +592,20 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp
} }
static class CompletionInputMetaData { static class CompletionInputMetaData {
public final String input;
public final Map<String, Set<CharSequence>> contexts; public final Map<String, Set<CharSequence>> contexts;
public final int weight; public final int weight;
CompletionInputMetaData(Map<String, Set<CharSequence>> contexts, int weight) { CompletionInputMetaData(String input, Map<String, Set<CharSequence>> contexts, int weight) {
this.input = input;
this.contexts = contexts; this.contexts = contexts;
this.weight = weight; this.weight = weight;
} }
@Override
public String toString() {
return input;
}
} }
@Override @Override

View File

@ -18,9 +18,11 @@
*/ */
package org.elasticsearch.index.mapper; package org.elasticsearch.index.mapper;
import org.apache.lucene.document.SortedSetDocValuesField;
import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.search.suggest.document.CompletionAnalyzer; 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.FuzzyCompletionQuery;
import org.apache.lucene.search.suggest.document.PrefixCompletionQuery; import org.apache.lucene.search.suggest.document.PrefixCompletionQuery;
import org.apache.lucene.search.suggest.document.RegexCompletionQuery; 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.IndexService;
import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.index.analysis.NamedAnalyzer;
import org.elasticsearch.test.ESSingleNodeTestCase; 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.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; 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.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf; 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()); 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 { public void testParsingMultiValued() throws Exception {
String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1") String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1")
.startObject("properties").startObject("completion") .startObject("properties").startObject("completion")
@ -199,7 +530,10 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase {
.endObject()), .endObject()),
XContentType.JSON)); XContentType.JSON));
IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name()); IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name());
assertSuggestFields(fields, 2); assertThat(fields, arrayContainingInAnyOrder(
suggestField("suggestion1"),
suggestField("suggestion2")
));
} }
public void testParsingWithWeight() throws Exception { public void testParsingWithWeight() throws Exception {
@ -222,7 +556,9 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase {
.endObject()), .endObject()),
XContentType.JSON)); XContentType.JSON));
IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name()); IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name());
assertSuggestFields(fields, 1); assertThat(fields, arrayContainingInAnyOrder(
suggestField("suggestion")
));
} }
public void testParsingMultiValueWithWeight() throws Exception { public void testParsingMultiValueWithWeight() throws Exception {
@ -245,7 +581,11 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase {
.endObject()), .endObject()),
XContentType.JSON)); XContentType.JSON));
IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name()); IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name());
assertSuggestFields(fields, 3); assertThat(fields, arrayContainingInAnyOrder(
suggestField("suggestion1"),
suggestField("suggestion2"),
suggestField("suggestion3")
));
} }
public void testParsingWithGeoFieldAlias() throws Exception { public void testParsingWithGeoFieldAlias() throws Exception {
@ -318,7 +658,11 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase {
.endObject()), .endObject()),
XContentType.JSON)); XContentType.JSON));
IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name()); IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name());
assertSuggestFields(fields, 3); assertThat(fields, arrayContainingInAnyOrder(
suggestField("suggestion1"),
suggestField("suggestion2"),
suggestField("suggestion3")
));
} }
public void testParsingMixed() throws Exception { public void testParsingMixed() throws Exception {
@ -351,7 +695,14 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase {
.endObject()), .endObject()),
XContentType.JSON)); XContentType.JSON));
IndexableField[] fields = parsedDocument.rootDoc().getFields(fieldMapper.name()); 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 { public void testNonContextEnabledParsingWithContexts() throws Exception {
@ -508,9 +859,13 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase {
} }
private static void assertSuggestFields(IndexableField[] fields, int expected) { 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; int actualFieldCount = 0;
for (IndexableField field : fields) { for (IndexableField field : fields) {
if (field instanceof SuggestField) { if (clazz.isInstance(field)) {
actualFieldCount++; actualFieldCount++;
} }
} }
@ -529,4 +884,33 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase {
); );
assertThat(e.getMessage(), containsString("name cannot be empty string")); assertThat(e.getMessage(), containsString("name cannot be empty string"));
} }
private Matcher<IndexableField> suggestField(String value) {
return Matchers.allOf(hasProperty(IndexableField::stringValue, equalTo(value)),
Matchers.instanceOf(SuggestField.class));
}
private Matcher<IndexableField> contextSuggestField(String value) {
return Matchers.allOf(hasProperty(IndexableField::stringValue, equalTo(value)),
Matchers.instanceOf(ContextSuggestField.class));
}
private CombinableMatcher<IndexableField> sortedSetDocValuesField(String value) {
return Matchers.both(hasProperty(IndexableField::binaryValue, equalTo(new BytesRef(value))))
.and(Matchers.instanceOf(SortedSetDocValuesField.class));
}
private CombinableMatcher<IndexableField> keywordField(String value) {
return Matchers.both(hasProperty(IndexableField::binaryValue, equalTo(new BytesRef(value))))
.and(hasProperty(IndexableField::fieldType, Matchers.instanceOf(KeywordFieldMapper.KeywordFieldType.class)));
}
private <T, V> Matcher<T> hasProperty(Function<? super T, ? extends V> property, Matcher<V> valueMatcher) {
return new FeatureMatcher<T, V>(valueMatcher, "object with", property.toString()) {
@Override
protected V featureValueOf(T actual) {
return property.apply(actual);
}
};
}
} }