Skip to content

Commit 19dc884

Browse files
authored
Fix synthetic source for empty nested objects (#111943)
1 parent e6b830e commit 19dc884

File tree

5 files changed

+106
-17
lines changed

5 files changed

+106
-17
lines changed

docs/changelog/111943.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 111943
2+
summary: Fix synthetic source for empty nested objects
3+
area: Mapping
4+
type: bug
5+
issues:
6+
- 111811

modules/data-streams/src/javaRestTest/java/org/elasticsearch/datastreams/logsdb/qa/StandardVersusLogsIndexModeRandomDataChallengeRestIT.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@ public StandardVersusLogsIndexModeRandomDataChallengeRestIT() {
4242
this.subobjectsDisabled = randomBoolean();
4343

4444
var specificationBuilder = DataGeneratorSpecification.builder();
45-
// TODO enable nested fields when subobjects are enabled
46-
// It currently hits a bug with empty nested objects
47-
// Nested fields don't work with subobjects: false.
48-
specificationBuilder = specificationBuilder.withNestedFieldsLimit(0);
45+
if (subobjectsDisabled) {
46+
specificationBuilder = specificationBuilder.withNestedFieldsLimit(0);
47+
}
4948
this.dataGenerator = new DataGenerator(specificationBuilder.withDataSourceHandlers(List.of(new DataSourceHandler() {
5049
@Override
5150
public DataSourceResponse.FieldTypeGenerator handle(DataSourceRequest.FieldTypeGenerator request) {

server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -463,17 +463,14 @@ public boolean hasValue() {
463463
public void write(XContentBuilder b) throws IOException {
464464
assert (children != null && children.size() > 0);
465465
if (children.size() == 1) {
466-
b.startObject(leafName());
466+
b.field(leafName());
467467
leafStoredFieldLoader.advanceTo(children.get(0));
468468
leafSourceLoader.write(leafStoredFieldLoader, children.get(0), b);
469-
b.endObject();
470469
} else {
471470
b.startArray(leafName());
472471
for (int childId : children) {
473-
b.startObject();
474472
leafStoredFieldLoader.advanceTo(childId);
475473
leafSourceLoader.write(leafStoredFieldLoader, childId, b);
476-
b.endObject();
477474
}
478475
b.endArray();
479476
}

server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -843,12 +843,10 @@ public void write(XContentBuilder b) throws IOException {
843843
return;
844844
}
845845

846-
if (isFragment == false) {
847-
if (isRoot()) {
848-
b.startObject();
849-
} else {
850-
b.startObject(leafName());
851-
}
846+
if (isRoot() || isFragment) {
847+
b.startObject();
848+
} else {
849+
b.startObject(leafName());
852850
}
853851

854852
if (ignoredValues != null && ignoredValues.isEmpty() == false) {
@@ -875,9 +873,7 @@ public void write(XContentBuilder b) throws IOException {
875873
}
876874
}
877875
hasValue = false;
878-
if (isFragment == false) {
879-
b.endObject();
880-
}
876+
b.endObject();
881877
}
882878

883879
@Override

server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,6 +1737,97 @@ public void testSyntheticNestedWithIncludeInRoot() throws IOException {
17371737
{"path":{"bar":"B","foo":"A"}}""", syntheticSource);
17381738
}
17391739

1740+
public void testSyntheticNestedWithEmptyObject() throws IOException {
1741+
DocumentMapper documentMapper = createMapperService(syntheticSourceMapping(b -> {
1742+
b.startObject("path").field("type", "nested");
1743+
{
1744+
b.startObject("properties");
1745+
{
1746+
b.startObject("foo").field("type", "keyword").endObject();
1747+
}
1748+
b.endObject();
1749+
}
1750+
b.endObject();
1751+
})).documentMapper();
1752+
var syntheticSource = syntheticSource(documentMapper, b -> { b.startObject("path").nullField("foo").endObject(); });
1753+
assertEquals("""
1754+
{"path":{}}""", syntheticSource);
1755+
}
1756+
1757+
public void testSyntheticNestedWithEmptySubObject() throws IOException {
1758+
DocumentMapper documentMapper = createMapperService(syntheticSourceMapping(b -> {
1759+
b.startObject("path").field("type", "nested");
1760+
{
1761+
b.startObject("properties");
1762+
{
1763+
b.startObject("to").startObject("properties");
1764+
{
1765+
b.startObject("foo").field("type", "keyword").endObject();
1766+
}
1767+
b.endObject().endObject();
1768+
}
1769+
b.endObject();
1770+
}
1771+
b.endObject();
1772+
})).documentMapper();
1773+
var syntheticSource = syntheticSource(documentMapper, b -> {
1774+
b.startObject("path");
1775+
{
1776+
b.startObject("to").nullField("foo").endObject();
1777+
}
1778+
b.endObject();
1779+
});
1780+
assertEquals("""
1781+
{"path":{}}""", syntheticSource);
1782+
}
1783+
1784+
public void testSyntheticNestedWithArrayContainingEmptyObject() throws IOException {
1785+
DocumentMapper documentMapper = createMapperService(syntheticSourceMapping(b -> {
1786+
b.startObject("path").field("type", "nested");
1787+
{
1788+
b.startObject("properties");
1789+
{
1790+
b.startObject("foo").field("type", "keyword").endObject();
1791+
}
1792+
b.endObject();
1793+
}
1794+
b.endObject();
1795+
})).documentMapper();
1796+
var syntheticSource = syntheticSource(documentMapper, b -> {
1797+
b.startArray("path");
1798+
{
1799+
b.startObject().field("foo", "A").endObject();
1800+
b.startObject().nullField("foo").endObject();
1801+
}
1802+
b.endArray();
1803+
});
1804+
assertEquals("""
1805+
{"path":[{"foo":"A"},{}]}""", syntheticSource);
1806+
}
1807+
1808+
public void testSyntheticNestedWithArrayContainingOnlyEmptyObject() throws IOException {
1809+
DocumentMapper documentMapper = createMapperService(syntheticSourceMapping(b -> {
1810+
b.startObject("path").field("type", "nested");
1811+
{
1812+
b.startObject("properties");
1813+
{
1814+
b.startObject("foo").field("type", "keyword").endObject();
1815+
}
1816+
b.endObject();
1817+
}
1818+
b.endObject();
1819+
})).documentMapper();
1820+
var syntheticSource = syntheticSource(documentMapper, b -> {
1821+
b.startArray("path");
1822+
{
1823+
b.startObject().nullField("foo").endObject();
1824+
}
1825+
b.endArray();
1826+
});
1827+
assertEquals("""
1828+
{"path":{}}""", syntheticSource);
1829+
}
1830+
17401831
private NestedObjectMapper createNestedObjectMapperWithAllParametersSet(CheckedConsumer<XContentBuilder, IOException> propertiesBuilder)
17411832
throws IOException {
17421833
DocumentMapper mapper = createDocumentMapper(mapping(b -> {

0 commit comments

Comments
 (0)