|
@@ -21,6 +21,7 @@ package org.elasticsearch.search.fetch.subphase;
|
|
|
|
|
|
import org.elasticsearch.Version;
|
|
|
import org.elasticsearch.cluster.metadata.IndexMetadata;
|
|
|
+import org.elasticsearch.common.Nullable;
|
|
|
import org.elasticsearch.common.Strings;
|
|
|
import org.elasticsearch.common.bytes.BytesReference;
|
|
|
import org.elasticsearch.common.document.DocumentField;
|
|
@@ -35,6 +36,7 @@ import org.elasticsearch.index.query.QueryShardContext;
|
|
|
import org.elasticsearch.search.lookup.SourceLookup;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
+import java.util.Collections;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Set;
|
|
@@ -58,7 +60,7 @@ public class FieldFetcherTests extends MapperServiceTestCase {
|
|
|
List<FieldAndFormat> fieldAndFormats = List.of(
|
|
|
new FieldAndFormat("field", null),
|
|
|
new FieldAndFormat("object.field", null));
|
|
|
- Map<String, DocumentField> fields = fetchFields(mapperService, source, fieldAndFormats);
|
|
|
+ Map<String, DocumentField> fields = fetchFields(mapperService, source, fieldAndFormats, null);
|
|
|
assertThat(fields.size(), equalTo(2));
|
|
|
|
|
|
DocumentField field = fields.get("field");
|
|
@@ -238,7 +240,8 @@ public class FieldFetcherTests extends MapperServiceTestCase {
|
|
|
|
|
|
Map<String, DocumentField> fields = fetchFields(mapperService, source, List.of(
|
|
|
new FieldAndFormat("field", null),
|
|
|
- new FieldAndFormat("date_field", "yyyy/MM/dd")));
|
|
|
+ new FieldAndFormat("date_field", "yyyy/MM/dd")),
|
|
|
+ null);
|
|
|
assertThat(fields.size(), equalTo(2));
|
|
|
|
|
|
DocumentField field = fields.get("field");
|
|
@@ -393,21 +396,289 @@ public class FieldFetcherTests extends MapperServiceTestCase {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private static Map<String, DocumentField> fetchFields(MapperService mapperService, XContentBuilder source, String fieldPattern)
|
|
|
- throws IOException {
|
|
|
+ public void testSimpleUnmappedFields() throws IOException {
|
|
|
+ MapperService mapperService = createMapperService();
|
|
|
+
|
|
|
+ XContentBuilder source = XContentFactory.jsonBuilder()
|
|
|
+ .startObject()
|
|
|
+ .field("unmapped_f1", "some text")
|
|
|
+ .field("unmapped_f2", "some text")
|
|
|
+ .field("unmapped_f3", "some text")
|
|
|
+ .field("something_else", "some text")
|
|
|
+ .nullField("null_value")
|
|
|
+ .startObject("object")
|
|
|
+ .field("a", "foo")
|
|
|
+ .endObject()
|
|
|
+ .field("object.b", "bar")
|
|
|
+ .endObject();
|
|
|
+
|
|
|
+ Map<String, DocumentField> fields = fetchFields(mapperService, source, fieldAndFormatList("unmapped_f*", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(3));
|
|
|
+ assertThat(fields.keySet(), containsInAnyOrder("unmapped_f1", "unmapped_f2", "unmapped_f3"));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("un*1", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(1));
|
|
|
+ assertThat(fields.keySet(), containsInAnyOrder("unmapped_f1"));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("*thing*", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(1));
|
|
|
+ assertThat(fields.keySet(), containsInAnyOrder("something_else"));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("null*", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(0));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("object.a", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(1));
|
|
|
+ assertEquals("foo", fields.get("object.a").getValues().get(0));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("object.b", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(1));
|
|
|
+ assertEquals("bar", fields.get("object.b").getValues().get(0));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testSimpleUnmappedArray() throws IOException {
|
|
|
+ MapperService mapperService = createMapperService();
|
|
|
+
|
|
|
+ XContentBuilder source = XContentFactory.jsonBuilder().startObject().array("unmapped_field", "foo", "bar").endObject();
|
|
|
+
|
|
|
+ Map<String, DocumentField> fields = fetchFields(mapperService, source, fieldAndFormatList("unmapped_field", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(1));
|
|
|
+ assertThat(fields.keySet(), containsInAnyOrder("unmapped_field"));
|
|
|
+ DocumentField field = fields.get("unmapped_field");
|
|
|
+
|
|
|
+ assertThat(field.getValues().size(), equalTo(2));
|
|
|
+ assertThat(field.getValues(), hasItems("foo", "bar"));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testSimpleUnmappedArrayWithObjects() throws IOException {
|
|
|
+ MapperService mapperService = createMapperService();
|
|
|
+
|
|
|
+ XContentBuilder source = XContentFactory.jsonBuilder().startObject()
|
|
|
+ .startArray("unmapped_field")
|
|
|
+ .startObject()
|
|
|
+ .field("f1", "a")
|
|
|
+ .endObject()
|
|
|
+ .startObject()
|
|
|
+ .field("f2", "b")
|
|
|
+ .endObject()
|
|
|
+ .endArray()
|
|
|
+ .endObject();
|
|
|
|
|
|
- List<FieldAndFormat> fields = List.of(new FieldAndFormat(fieldPattern, null));
|
|
|
- return fetchFields(mapperService, source, fields);
|
|
|
+ Map<String, DocumentField> fields = fetchFields(mapperService, source, fieldAndFormatList("unmapped_field", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(0));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("unmapped_field.f*", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(2));
|
|
|
+ assertThat(fields.get("unmapped_field.f1").getValue(), equalTo("a"));
|
|
|
+ assertThat(fields.get("unmapped_field.f2").getValue(), equalTo("b"));
|
|
|
+
|
|
|
+ source = XContentFactory.jsonBuilder().startObject()
|
|
|
+ .startArray("unmapped_field")
|
|
|
+ .startObject()
|
|
|
+ .field("f1", "a")
|
|
|
+ .array("f2", 1, 2)
|
|
|
+ .array("f3", 1, 2)
|
|
|
+ .endObject()
|
|
|
+ .startObject()
|
|
|
+ .field("f1", "b") // same field name, this should result in a list returned
|
|
|
+ .array("f2", 3, 4)
|
|
|
+ .array("f3", "foo")
|
|
|
+ .endObject()
|
|
|
+ .endArray()
|
|
|
+ .endObject();
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("unmapped_field.f1", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(1));
|
|
|
+ DocumentField field = fields.get("unmapped_field.f1");
|
|
|
+ assertThat(field.getValues().size(), equalTo(2));
|
|
|
+ assertThat(field.getValues(), hasItems("a", "b"));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("unmapped_field.f2", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(1));
|
|
|
+ field = fields.get("unmapped_field.f2");
|
|
|
+ assertThat(field.getValues().size(), equalTo(4));
|
|
|
+ assertThat(field.getValues(), hasItems(1, 2, 3, 4));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("unmapped_field.f3", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(1));
|
|
|
+ field = fields.get("unmapped_field.f3");
|
|
|
+ assertThat(field.getValues().size(), equalTo(3));
|
|
|
+ assertThat(field.getValues(), hasItems(1, 2, "foo"));
|
|
|
}
|
|
|
|
|
|
- private static Map<String, DocumentField> fetchFields(MapperService mapperService, XContentBuilder source, List<FieldAndFormat> fields)
|
|
|
+ public void testUnmappedFieldsInsideObject() throws IOException {
|
|
|
+ XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
|
|
|
+ .startObject("_doc")
|
|
|
+ .startObject("properties")
|
|
|
+ .startObject("obj")
|
|
|
+ .field("type", "object")
|
|
|
+ .field("dynamic", "false")
|
|
|
+ .startObject("properties")
|
|
|
+ .startObject("f1").field("type", "keyword").endObject()
|
|
|
+ .endObject()
|
|
|
+ .endObject()
|
|
|
+ .endObject()
|
|
|
+ .endObject()
|
|
|
+ .endObject();
|
|
|
+
|
|
|
+ MapperService mapperService = createMapperService(mapping);
|
|
|
+
|
|
|
+ XContentBuilder source = XContentFactory.jsonBuilder().startObject()
|
|
|
+ .field("obj.f1", "value1")
|
|
|
+ .field("obj.f2", "unmapped_value_f2")
|
|
|
+ .field("obj.innerObj.f3", "unmapped_value_f3")
|
|
|
+ .field("obj.innerObj.f4", "unmapped_value_f4")
|
|
|
+ .endObject();
|
|
|
+
|
|
|
+ Map<String, DocumentField> fields = fetchFields(mapperService, source, fieldAndFormatList("*", null, false), null);
|
|
|
+
|
|
|
+ // without unmapped fields this should only return "obj.f1"
|
|
|
+ assertThat(fields.size(), equalTo(1));
|
|
|
+ assertThat(fields.keySet(), containsInAnyOrder("obj.f1"));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("*", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(4));
|
|
|
+ assertThat(fields.keySet(), containsInAnyOrder("obj.f1", "obj.f2", "obj.innerObj.f3", "obj.innerObj.f4"));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testUnmappedFieldsInsideDisabledObject() throws IOException {
|
|
|
+ XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
|
|
|
+ .startObject("_doc")
|
|
|
+ .startObject("properties")
|
|
|
+ .startObject("obj")
|
|
|
+ .field("type", "object")
|
|
|
+ .field("enabled", "false")
|
|
|
+ .endObject()
|
|
|
+ .endObject()
|
|
|
+ .endObject()
|
|
|
+ .endObject();
|
|
|
+
|
|
|
+ MapperService mapperService = createMapperService(mapping);
|
|
|
+
|
|
|
+ XContentBuilder source = XContentFactory.jsonBuilder().startObject()
|
|
|
+ .startArray("obj")
|
|
|
+ .value("string_value")
|
|
|
+ .startObject()
|
|
|
+ .field("a", "b")
|
|
|
+ .endObject()
|
|
|
+ .startArray()
|
|
|
+ .value(1).value(2).value(3)
|
|
|
+ .endArray()
|
|
|
+ .endArray()
|
|
|
+ .endObject();
|
|
|
+
|
|
|
+ Map<String, DocumentField> fields = fetchFields(mapperService, source, fieldAndFormatList("*", null, false), null);
|
|
|
+ // without unmapped fields this should return nothing
|
|
|
+ assertThat(fields.size(), equalTo(0));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("*", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(2));
|
|
|
+ assertThat(fields.keySet(), containsInAnyOrder("obj", "obj.a"));
|
|
|
+
|
|
|
+ List<Object> obj = fields.get("obj").getValues();
|
|
|
+ assertEquals(4, obj.size());
|
|
|
+ assertThat(obj, hasItems("string_value", 1, 2, 3));
|
|
|
+
|
|
|
+ List<Object> innerObj = fields.get("obj.a").getValues();
|
|
|
+ assertEquals(1, innerObj.size());
|
|
|
+ assertEquals("b", fields.get("obj.a").getValue());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * If a mapped field for some reason contains a "_source" value that is not returned by the
|
|
|
+ * mapped retrieval mechanism (e.g. because its malformed), we don't want to fetch it from _source.
|
|
|
+ */
|
|
|
+ public void testMappedFieldNotOverwritten() throws IOException {
|
|
|
+ XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
|
|
|
+ .startObject("_doc")
|
|
|
+ .startObject("properties")
|
|
|
+ .startObject("f1")
|
|
|
+ .field("type", "integer")
|
|
|
+ .field("ignore_malformed", "true")
|
|
|
+ .endObject()
|
|
|
+ .endObject()
|
|
|
+ .endObject()
|
|
|
+ .endObject();
|
|
|
+
|
|
|
+ MapperService mapperService = createMapperService(mapping);
|
|
|
+
|
|
|
+ XContentBuilder source = XContentFactory.jsonBuilder().startObject()
|
|
|
+ .field("f1", "malformed")
|
|
|
+ .endObject();
|
|
|
+
|
|
|
+ // this should not return a field bc. f1 is in the ignored fields
|
|
|
+ Map<String, DocumentField> fields = fetchFields(mapperService, source, List.of(new FieldAndFormat("*", null, true)), Set.of("f1"));
|
|
|
+ assertThat(fields.size(), equalTo(0));
|
|
|
+
|
|
|
+ // and this should neither
|
|
|
+ fields = fetchFields(mapperService, source, List.of(new FieldAndFormat("*", null, true)), Set.of("f1"));
|
|
|
+ assertThat(fields.size(), equalTo(0));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, List.of(new FieldAndFormat("f1", null, true)), Set.of("f1"));
|
|
|
+ assertThat(fields.size(), equalTo(0));
|
|
|
+
|
|
|
+ // check this also does not overwrite with arrays
|
|
|
+ source = XContentFactory.jsonBuilder().startObject()
|
|
|
+ .array("f1", "malformed")
|
|
|
+ .endObject();
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, List.of(new FieldAndFormat("f1", null, true)), Set.of("f1"));
|
|
|
+ assertThat(fields.size(), equalTo(0));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testUnmappedFieldsWildcard() throws IOException {
|
|
|
+ MapperService mapperService = createMapperService();
|
|
|
+
|
|
|
+ XContentBuilder source = XContentFactory.jsonBuilder().startObject()
|
|
|
+ .startObject("unmapped_object")
|
|
|
+ .field("a", "foo")
|
|
|
+ .field("b", "bar")
|
|
|
+ .endObject()
|
|
|
+ .endObject();
|
|
|
+
|
|
|
+ Map<String, DocumentField> fields = fetchFields(mapperService, source, fieldAndFormatList("unmapped_object", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(0));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("unmap*object", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(0));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("unmapped_object.*", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(2));
|
|
|
+ assertThat(fields.keySet(), containsInAnyOrder("unmapped_object.a", "unmapped_object.b"));
|
|
|
+
|
|
|
+ assertThat(fields.get("unmapped_object.a").getValue(), equalTo("foo"));
|
|
|
+ assertThat(fields.get("unmapped_object.b").getValue(), equalTo("bar"));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("unmapped_object.a", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(1));
|
|
|
+ assertThat(fields.get("unmapped_object.a").getValue(), equalTo("foo"));
|
|
|
+
|
|
|
+ fields = fetchFields(mapperService, source, fieldAndFormatList("unmapped_object.b", null, true), null);
|
|
|
+ assertThat(fields.size(), equalTo(1));
|
|
|
+ assertThat(fields.get("unmapped_object.b").getValue(), equalTo("bar"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<FieldAndFormat> fieldAndFormatList(String name, String format, boolean includeUnmapped) {
|
|
|
+ return Collections.singletonList(new FieldAndFormat(name, format, includeUnmapped));
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, DocumentField> fetchFields(MapperService mapperService, XContentBuilder source, String fieldPattern)
|
|
|
throws IOException {
|
|
|
+ return fetchFields(mapperService, source, fieldAndFormatList(fieldPattern, null, false), null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Map<String, DocumentField> fetchFields(
|
|
|
+ MapperService mapperService,
|
|
|
+ XContentBuilder source,
|
|
|
+ List<FieldAndFormat> fields,
|
|
|
+ @Nullable Set<String> ignoreFields
|
|
|
+ ) throws IOException {
|
|
|
|
|
|
SourceLookup sourceLookup = new SourceLookup();
|
|
|
sourceLookup.setSource(BytesReference.bytes(source));
|
|
|
|
|
|
FieldFetcher fieldFetcher = FieldFetcher.create(newQueryShardContext(mapperService), null, fields);
|
|
|
- return fieldFetcher.fetch(sourceLookup, Set.of());
|
|
|
+ return fieldFetcher.fetch(sourceLookup, ignoreFields != null ? ignoreFields : Collections.emptySet());
|
|
|
}
|
|
|
|
|
|
public MapperService createMapperService() throws IOException {
|