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:
parent
b1b0f3276b
commit
3f8cc89c9f
|
@ -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 }
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue