diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search/161_exists_query_within_nested_query.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search/161_exists_query_within_nested_query.yml new file mode 100644 index 0000000000000..592147c0c1d93 --- /dev/null +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search/161_exists_query_within_nested_query.yml @@ -0,0 +1,840 @@ +setup: + - skip: + features: ["headers", "allowed_warnings"] + + - do: + indices.create: + index: test + body: + mappings: + dynamic: false + properties: + nested: + type: nested + properties: + binary: + type: binary + doc_values: true + boolean: + type: boolean + date: + type: date + geo_point: + type: geo_point + ip: + type: ip + keyword: + type: keyword + byte: + type: byte + double: + type: double + float: + type: float + half_float: + type: half_float + integer: + type: integer + long: + type: long + short: + type: short + object: + type: object + properties: + inner1: + type: keyword + inner2: + type: keyword + text: + type: text + + - do: + headers: + Content-Type: application/json + index: + index: "test" + id: 1 + body: + nested: + - binary: "YWJjZGUxMjM0" + boolean: true + date: "2017-01-01" + geo_point: [0.0, 20.0] + ip: "192.168.0.1" + keyword: "foo" + - byte: 1 + double: 1.0 + float: 1.0 + half_float: 1.0 + integer: 1 + - long: 1 + short: 1 + object: + inner1: "foo" + inner2: "bar" + text: "foo bar" + + - do: + headers: + Content-Type: application/json + index: + index: "test" + id: 2 + body: + nested: + - binary: "YWJjZGUxMjM0" + boolean: false + date: "2017-01-01" + geo_point: [0.0, 20.0] + ip: "192.168.0.1" + keyword: "foo" + - byte: 1 + double: 1.0 + float: 1.0 + half_float: 1.0 + integer: 1 + - long: 1 + short: 1 + object: + inner1: "foo" + text: "foo bar" + + - do: + headers: + Content-Type: application/json + index: + index: "test" + id: 3 + routing: "route_me" + body: + nested: + - binary: "YWJjZGUxMjM0" + boolean: true + date: "2017-01-01" + geo_point: [0.0, 20.0] + ip: "192.168.0.1" + keyword: "foo" + - byte: 1 + double: 1.0 + float: 1.0 + half_float: 1.0 + integer: 1 + - long: 1 + short: 1 + object: + inner2: "bar" + text: "foo bar" + + - do: + index: + index: "test" + id: 4 + body: {} + + - do: + indices.create: + index: test-no-dv + body: + mappings: + dynamic: false + properties: + nested: + type: nested + properties: + binary: + type: binary + doc_values: false + store: true + boolean: + type: boolean + doc_values: false + date: + type: date + doc_values: false + geo_point: + type: geo_point + doc_values: false + ip: + type: ip + doc_values: false + keyword: + type: keyword + doc_values: false + byte: + type: byte + doc_values: false + double: + type: double + doc_values: false + float: + type: float + doc_values: false + half_float: + type: half_float + doc_values: false + integer: + type: integer + doc_values: false + long: + type: long + doc_values: false + short: + type: short + doc_values: false + object: + type: object + properties: + inner1: + type: keyword + doc_values: false + inner2: + type: keyword + doc_values: false + text: + type: text + + - do: + headers: + Content-Type: application/json + index: + index: "test-no-dv" + id: 1 + body: + nested: + - binary: "YWJjZGUxMjM0" + boolean: true + date: "2017-01-01" + geo_point: [0.0, 20.0] + ip: "192.168.0.1" + keyword: "foo" + - byte: 1 + double: 1.0 + float: 1.0 + half_float: 1.0 + integer: 1 + - long: 1 + short: 1 + object: + inner1: "foo" + inner2: "bar" + text: "foo bar" + + - do: + headers: + Content-Type: application/json + index: + index: "test-no-dv" + id: 2 + body: + nested: + - binary: "YWJjZGUxMjM0" + boolean: false + date: "2017-01-01" + geo_point: [0.0, 20.0] + ip: "192.168.0.1" + keyword: "foo" + - byte: 1 + double: 1.0 + float: 1.0 + half_float: 1.0 + integer: 1 + - long: 1 + short: 1 + object: + inner1: "foo" + text: "foo bar" + + - do: + headers: + Content-Type: application/json + index: + index: "test-no-dv" + id: 3 + routing: "route_me" + body: + nested: + - binary: "YWJjZGUxMjM0" + boolean: true + date: "2017-01-01" + geo_point: [0.0, 20.0] + ip: "192.168.0.1" + keyword: "foo" + - byte: 1 + double: 1.0 + float: 1.0 + half_float: 1.0 + integer: 1 + - long: 1 + short: 1 + object: + inner2: "bar" + text: "foo bar" + + - do: + index: + index: "test-no-dv" + id: 4 + body: {} + + - do: + indices.refresh: + index: [test, test-no-dv] + +--- +"Test exists query within nested query on mapped binary field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.binary + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped boolean field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.boolean + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped date field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.date + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped geo_point field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.geo_point + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped ip field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.ip + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped keyword field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.keyword + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped byte field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.byte + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped double field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.double + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped float field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.float + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped half_float field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.half_float + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped integer field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.integer + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped long field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.long + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped short field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.short + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped object field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.object + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped object inner field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.object.inner1 + + - match: {hits.total: 2} + +--- +"Test exists query within nested query on mapped text field": + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + nested: + path: nested + query: + exists: + field: nested.text + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped binary field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.binary + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped boolean field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.boolean + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped date field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.date + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped geo_point field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.geo_point + + - match: {hits.total: 3} + + +--- +"Test exists query within nested query on mapped ip field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.ip + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped keyword field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.keyword + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped byte field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.byte + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped double field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.double + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped float field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.float + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped half_float field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.half_float + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped integer field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.integer + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped long field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.long + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped short field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.short + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped object field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.object + + - match: {hits.total: 3} + +--- +"Test exists query within nested query on mapped object inner field with no doc values": + - skip: + version: " - 7.99.99" + reason: "Fixed in 7.16 (backport pending)" + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.object.inner1 + + - match: {hits.total: 2} + +--- +"Test exists query within nested query on mapped text field with no doc values": + - do: + search: + rest_total_hits_as_int: true + index: test-no-dv + body: + query: + nested: + path: nested + query: + exists: + field: nested.text + + - match: {hits.total: 3} diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java index 9c4f5f5e82822..da480df0d396d 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java @@ -87,7 +87,6 @@ protected void addDoc(LuceneDocument doc) { private final Function parserContextFunction; private final SourceToParse sourceToParse; private final Set ignoredFields; - private final Set fieldNameFields; private final List dynamicMappers; private final Set newFieldsSeen; private final Map dynamicObjectMappers; @@ -102,7 +101,6 @@ private DocumentParserContext(DocumentParserContext in) { this.parserContextFunction = in.parserContextFunction; this.sourceToParse = in.sourceToParse; this.ignoredFields = in.ignoredFields; - this.fieldNameFields = in.fieldNameFields; this.dynamicMappers = in.dynamicMappers; this.newFieldsSeen = in.newFieldsSeen; this.dynamicObjectMappers = in.dynamicObjectMappers; @@ -122,7 +120,6 @@ protected DocumentParserContext(MappingLookup mappingLookup, this.parserContextFunction = parserContextFunction; this.sourceToParse = source; this.ignoredFields = new HashSet<>(); - this.fieldNameFields = new HashSet<>(); this.dynamicMappers = new ArrayList<>(); this.newFieldsSeen = new HashSet<>(); this.dynamicObjectMappers = new HashMap<>(); @@ -178,14 +175,10 @@ public final Collection getIgnoredFields() { * or norms. */ public final void addToFieldNames(String field) { - fieldNameFields.add(field); - } - - /** - * Return the collection of fields to be added to the _field_names field - */ - public final Collection getFieldNames() { - return Collections.unmodifiableCollection(fieldNameFields); + FieldNamesFieldMapper fieldNamesFieldMapper = (FieldNamesFieldMapper) getMetadataMapper(FieldNamesFieldMapper.NAME); + if (fieldNamesFieldMapper != null) { + fieldNamesFieldMapper.addFieldNames( this, field ); + } } public final Field version() { diff --git a/server/src/main/java/org/elasticsearch/index/mapper/FieldNamesFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/FieldNamesFieldMapper.java index ec1b0d61772ee..c0de6f2fc9740 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/FieldNamesFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/FieldNamesFieldMapper.java @@ -18,7 +18,6 @@ import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.index.query.SearchExecutionContext; -import java.io.IOException; import java.util.Collections; import java.util.List; @@ -163,15 +162,12 @@ public FieldNamesFieldType fieldType() { return (FieldNamesFieldType) super.fieldType(); } - @Override - public void postParse(DocumentParserContext context) throws IOException { + public void addFieldNames(DocumentParserContext context, String field) { if (enabled.value() == false) { return; } - for (String field : context.getFieldNames()) { - assert noDocValues(field, context) : "Field " + field + " should not have docvalues"; - context.doc().add(new Field(NAME, field, Defaults.FIELD_TYPE)); - } + assert noDocValues(field, context) : "Field " + field + " should not have docvalues"; + context.doc().add(new Field(NAME, field, Defaults.FIELD_TYPE)); } private static boolean noDocValues(String field, DocumentParserContext context) { diff --git a/server/src/main/java/org/elasticsearch/index/mapper/LuceneDocument.java b/server/src/main/java/org/elasticsearch/index/mapper/LuceneDocument.java index af316a76cf022..22b5d8bfc8ffa 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/LuceneDocument.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/LuceneDocument.java @@ -136,4 +136,13 @@ public BytesRef getBinaryValue(String name) { return null; } + public Number getNumericValue(String name) { + for (IndexableField f : fields) { + if (f.name().equals(name) && f.numericValue() != null) { + return f.numericValue(); + } + } + return null; + } + } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java index dd784e4dea8d9..8c3e42edb5799 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java @@ -957,4 +957,240 @@ public void testMergeNestedMappingsFromDynamicUpdate() throws IOException { containsString("\"properties\":{\"object\":{\"type\":\"nested\",\"include_in_parent\":true}}") ); } + + public void testFieldNames() throws Exception { + DocumentMapper docMapper = createDocumentMapper(mapping(b -> { + b.startObject("nested1"); + { + b.field("type", "nested"); + b.startObject("properties"); + { + b.startObject("integer1").field("type", "integer").field("doc_values", true).endObject(); + b.startObject("nested2"); + { + b.field("type", "nested"); + b.startObject("properties"); + { + b.startObject("integer2").field("type", "integer").field("doc_values", false).endObject(); + } + b.endObject(); + } + b.endObject(); + } + b.endObject(); + } + b.endObject(); + })); + + XContentBuilder b = XContentFactory.jsonBuilder(); + b.startObject(); + b.array("nested1", nested1 -> { // doc 6 + nested1.startObject(); // doc 0 + { + nested1.field("integer1", 1); + } + nested1.endObject(); + nested1.startObject(); // doc 2 + { + nested1.field("integer1", 11); + nested1.array("nested2", nested2 -> { + nested2.startObject().endObject(); // doc 1 + }); + } + nested1.endObject(); + nested1.startObject(); // doc 5 + { + nested1.field("integer1", 21); + nested1.array("nested2", nested2 -> { + nested2.startObject(); // doc 3 + { + nested1.field("integer2", 22); + } + nested1.endObject(); + nested2.startObject().endObject(); // doc 4 + }); + } + nested1.endObject(); + }); + b.endObject(); + ParsedDocument doc = docMapper.parse(new SourceToParse("test", "1", BytesReference.bytes(b), XContentType.JSON)); + + // Note doc values are disabled for field "integer2", + // so the document only contains an IntPoint field whose stringValue method always returns null. + // Thus so we cannot use get() for this field, we must use getNumericValue(). + assertThat(doc.docs().size(), equalTo(7)); + // Only fields without doc values are added to field names. + assertThat(doc.docs().get(6).get("_field_names"), nullValue()); + assertThat(doc.docs().get(0).get("nested1.integer1"), equalTo("1")); + assertThat(doc.docs().get(0).get("nested1._field_names"), nullValue()); + assertThat(doc.docs().get(2).get("nested1.integer1"), equalTo("11")); + assertThat(doc.docs().get(2).get("_field_names"), nullValue()); + assertThat(doc.docs().get(1).getNumericValue("nested1.nested2.integer2"), nullValue()); + assertThat(doc.docs().get(1).get("_field_names"), nullValue()); + assertThat(doc.docs().get(5).get("nested1.integer1"), equalTo("21")); + assertThat(doc.docs().get(5).get("_field_names"), nullValue()); + assertThat(doc.docs().get(3).getNumericValue( "nested1.nested2.integer2" ), equalTo(22)); + assertThat(doc.docs().get(3).get("_field_names"), equalTo("nested1.nested2.integer2")); + assertThat(doc.docs().get(4).getNumericValue("nested1.nested2.integer2"), nullValue()); + assertThat(doc.docs().get(4).get("_field_names"), nullValue()); + } + + public void testFieldNamesIncludeInParent() throws Exception { + DocumentMapper docMapper = createDocumentMapper(mapping(b -> { + b.startObject("nested1"); + { + b.field("type", "nested"); + b.startObject("properties"); + { + b.startObject("integer1").field("type", "integer").field("doc_values", true).endObject(); + b.startObject("nested2"); + { + b.field("type", "nested"); + b.field("include_in_parent", true); + b.startObject("properties"); + { + b.startObject("integer2").field("type", "integer").field("doc_values", false).endObject(); + } + b.endObject(); + } + b.endObject(); + } + b.endObject(); + } + b.endObject(); + })); + + XContentBuilder b = XContentFactory.jsonBuilder(); + b.startObject(); + b.array("nested1", nested1 -> { // doc 6 + nested1.startObject(); // doc 0 + { + nested1.field("integer1", 1); + } + nested1.endObject(); + nested1.startObject(); // doc 2 + { + nested1.field("integer1", 11); + nested1.array("nested2", nested2 -> { + nested2.startObject().endObject(); // doc 1 + }); + } + nested1.endObject(); + nested1.startObject(); // doc 5 + { + nested1.field("integer1", 21); + nested1.array("nested2", nested2 -> { + nested2.startObject(); // doc 3 + { + nested1.field("integer2", 22); + } + nested1.endObject(); + nested2.startObject().endObject(); // doc 4 + }); + } + nested1.endObject(); + }); + b.endObject(); + ParsedDocument doc = docMapper.parse(new SourceToParse("test", "1", BytesReference.bytes(b), XContentType.JSON)); + + // Note doc values are disabled for field "integer2", + // so the document only contains an IntPoint field whose stringValue method always returns null. + // Thus so we cannot use get() for this field, we must use getNumericValue(). + assertThat(doc.docs().size(), equalTo(7)); + // Only fields without doc values are added to field names. + assertThat(doc.docs().get(6).get("_field_names"), nullValue()); + assertThat(doc.docs().get(0).get("nested1.integer1"), equalTo("1")); + assertThat(doc.docs().get(0).get("nested1._field_names"), nullValue()); + assertThat(doc.docs().get(2).get("nested1.integer1"), equalTo("11")); + assertThat(doc.docs().get(2).get("_field_names"), nullValue()); + assertThat(doc.docs().get(1).getNumericValue("nested1.nested2.integer2"), nullValue()); + assertThat(doc.docs().get(1).get("_field_names"), nullValue()); + assertThat(doc.docs().get(5).get("nested1.integer1"), equalTo("21")); + assertThat(doc.docs().get(5).getNumericValue( "nested1.nested2.integer2" ), equalTo(22)); + assertThat(doc.docs().get(5).get("_field_names"), equalTo("nested1.nested2.integer2")); + assertThat(doc.docs().get(3).getNumericValue( "nested1.nested2.integer2" ), equalTo(22)); + assertThat(doc.docs().get(3).get("_field_names"), equalTo("nested1.nested2.integer2")); + assertThat(doc.docs().get(4).getNumericValue("nested1.nested2.integer2"), nullValue()); + assertThat(doc.docs().get(4).get("_field_names"), nullValue()); + } + + public void testFieldNamesIncludeInRoot() throws Exception { + DocumentMapper docMapper = createDocumentMapper(mapping(b -> { + b.startObject("nested1"); + { + b.field("type", "nested"); + b.startObject("properties"); + { + b.startObject("integer1").field("type", "integer").field("doc_values", true).endObject(); + b.startObject("nested2"); + { + b.field("type", "nested"); + b.field("include_in_root", true); + b.startObject("properties"); + { + b.startObject("integer2").field("type", "integer").field("doc_values", false).endObject(); + } + b.endObject(); + } + b.endObject(); + } + b.endObject(); + } + b.endObject(); + })); + + XContentBuilder b = XContentFactory.jsonBuilder(); + b.startObject(); + b.array("nested1", nested1 -> { // doc 6 + nested1.startObject(); // doc 0 + { + nested1.field("integer1", 1); + } + nested1.endObject(); + nested1.startObject(); // doc 2 + { + nested1.field("integer1", 11); + nested1.array("nested2", nested2 -> { + nested2.startObject().endObject(); // doc 1 + }); + } + nested1.endObject(); + nested1.startObject(); // doc 5 + { + nested1.field("integer1", 21); + nested1.array("nested2", nested2 -> { + nested2.startObject(); // doc 3 + { + nested1.field("integer2", 22); + } + nested1.endObject(); + nested2.startObject().endObject(); // doc 4 + }); + } + nested1.endObject(); + }); + b.endObject(); + ParsedDocument doc = docMapper.parse(new SourceToParse("test", "1", BytesReference.bytes(b), XContentType.JSON)); + + // Note doc values are disabled for field "integer2", + // so the document only contains an IntPoint field whose stringValue method always returns null. + // Thus so we cannot use get() for this field, we must use getNumericValue(). + assertThat(doc.docs().size(), equalTo(7)); + // Only fields without doc values are added to field names. + assertThat(doc.docs().get(6).getNumericValue( "nested1.nested2.integer2" ), equalTo(22)); + assertThat(doc.docs().get(6).get("_field_names"), equalTo("nested1.nested2.integer2")); + assertThat(doc.docs().get(0).get("nested1.integer1"), equalTo("1")); + assertThat(doc.docs().get(0).get("nested1._field_names"), nullValue()); + assertThat(doc.docs().get(2).get("nested1.integer1"), equalTo("11")); + assertThat(doc.docs().get(2).get("_field_names"), nullValue()); + assertThat(doc.docs().get(1).getNumericValue("nested1.nested2.integer2"), nullValue()); + assertThat(doc.docs().get(1).get("_field_names"), nullValue()); + assertThat(doc.docs().get(5).get("nested1.integer1"), equalTo("21")); + assertThat(doc.docs().get(5).get("_field_names"), nullValue()); + assertThat(doc.docs().get(3).getNumericValue( "nested1.nested2.integer2" ), equalTo(22)); + assertThat(doc.docs().get(3).get("_field_names"), equalTo("nested1.nested2.integer2")); + assertThat(doc.docs().get(4).getNumericValue("nested1.nested2.integer2"), nullValue()); + assertThat(doc.docs().get(4).get("_field_names"), nullValue()); + } + }