Skip to content

Commit 80e1f43

Browse files
authored
Raw mapping merge fix for properties field (#108867) (#109147)
(cherry picked from commit 92dc76e)
1 parent bf8ef51 commit 80e1f43

File tree

4 files changed

+110
-4
lines changed

4 files changed

+110
-4
lines changed

docs/changelog/108867.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 108867
2+
summary: Fix for raw mapping merge of fields named "properties"
3+
area: Mapping
4+
type: bug
5+
issues:
6+
- 108866

server/src/main/java/org/elasticsearch/common/xcontent/XContentHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ public static void mergeDefaults(Map<String, Object> content, Map<String, Object
415415
* Merges the map provided as the second parameter into the content of the first. Only does recursive merge for inner maps.
416416
* If a non-null {@link CustomMerge} is provided, it is applied whenever a merge is required, meaning - whenever both the first and
417417
* the second map has values for the same key. Otherwise, values from the first map will always have precedence, meaning - if the
418-
* first map contains a key, its value will not be overriden.
418+
* first map contains a key, its value will not be overridden.
419419
* @param first the map which serves as the merge base
420420
* @param second the map of which contents are merged into the base map
421421
* @param customMerge a custom merge rule to apply whenever a key has concrete values (i.e. not a map or a collection) in both maps

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,11 @@ public Object merge(String parent, String key, Object oldValue, Object newValue)
475475
if (baseMap.containsKey("subobjects")) {
476476
mergedMappings.put("subobjects", baseMap.get("subobjects"));
477477
}
478-
// recursively merge these two field mappings
479-
XContentHelper.merge(key, mergedMappings, mapToMerge, INSTANCE);
478+
// Recursively merge these two field mappings.
479+
// Since "key" is an arbitrary field name, for which we only need plain mapping subtrees merge, no need to pass it
480+
// to the recursion as it shouldn't affect the merge logic. Specifically, passing a parent may cause merge
481+
// failures of fields named "properties". See https://github.com/elastic/elasticsearch/issues/108866
482+
XContentHelper.merge(mergedMappings, mapToMerge, INSTANCE);
480483
return mergedMappings;
481484
} else {
482485
// non-mergeable types - replace the entire mapping subtree for this field

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

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1147,7 +1147,7 @@ public void testMultipleTypeMerges() throws IOException {
11471147
}""", Strings.toString(mapperService.documentMapper().mapping(), true, true));
11481148
}
11491149

1150-
public void testPropertiesField() throws IOException {
1150+
public void testPropertiesFieldSingleChildMerge() throws IOException {
11511151
CompressedXContent mapping1 = new CompressedXContent("""
11521152
{
11531153
"properties": {
@@ -1238,6 +1238,103 @@ public void testPropertiesField() throws IOException {
12381238
assertEquals("keyword", grandchildMapper.typeName());
12391239
}
12401240

1241+
public void testPropertiesFieldMultiChildMerge() throws IOException {
1242+
CompressedXContent mapping1 = new CompressedXContent("""
1243+
{
1244+
"properties": {
1245+
"properties": {
1246+
"properties": {
1247+
"child1": {
1248+
"type": "text",
1249+
"fields": {
1250+
"keyword": {
1251+
"type": "keyword"
1252+
}
1253+
}
1254+
},
1255+
"child2": {
1256+
"type": "text"
1257+
},
1258+
"child3": {
1259+
"properties": {
1260+
"grandchild": {
1261+
"type": "text"
1262+
}
1263+
}
1264+
}
1265+
}
1266+
}
1267+
}
1268+
}""");
1269+
1270+
CompressedXContent mapping2 = new CompressedXContent("""
1271+
{
1272+
"properties": {
1273+
"properties": {
1274+
"properties": {
1275+
"child2": {
1276+
"type": "integer"
1277+
},
1278+
"child3": {
1279+
"properties": {
1280+
"grandchild": {
1281+
"type": "long"
1282+
}
1283+
}
1284+
}
1285+
}
1286+
}
1287+
}
1288+
}""");
1289+
1290+
MapperService mapperService = createMapperService(mapping(b -> {}));
1291+
mapperService.merge("_doc", List.of(mapping1, mapping2), MergeReason.INDEX_TEMPLATE);
1292+
assertEquals("""
1293+
{
1294+
"_doc" : {
1295+
"properties" : {
1296+
"properties" : {
1297+
"properties" : {
1298+
"child1" : {
1299+
"type" : "text",
1300+
"fields" : {
1301+
"keyword" : {
1302+
"type" : "keyword"
1303+
}
1304+
}
1305+
},
1306+
"child2" : {
1307+
"type" : "integer"
1308+
},
1309+
"child3" : {
1310+
"properties" : {
1311+
"grandchild" : {
1312+
"type" : "long"
1313+
}
1314+
}
1315+
}
1316+
}
1317+
}
1318+
}
1319+
}
1320+
}""", Strings.toString(mapperService.documentMapper().mapping(), true, true));
1321+
1322+
Mapper propertiesMapper = mapperService.documentMapper().mapping().getRoot().getMapper("properties");
1323+
assertThat(propertiesMapper, instanceOf(ObjectMapper.class));
1324+
Mapper childMapper = ((ObjectMapper) propertiesMapper).getMapper("child1");
1325+
assertThat(childMapper, instanceOf(FieldMapper.class));
1326+
assertEquals("text", childMapper.typeName());
1327+
assertEquals(2, childMapper.getTotalFieldsCount());
1328+
childMapper = ((ObjectMapper) propertiesMapper).getMapper("child2");
1329+
assertThat(childMapper, instanceOf(FieldMapper.class));
1330+
assertEquals("integer", childMapper.typeName());
1331+
assertEquals(1, childMapper.getTotalFieldsCount());
1332+
childMapper = ((ObjectMapper) propertiesMapper).getMapper("child3");
1333+
assertThat(childMapper, instanceOf(ObjectMapper.class));
1334+
Mapper grandchildMapper = ((ObjectMapper) childMapper).getMapper("grandchild");
1335+
assertEquals("long", grandchildMapper.typeName());
1336+
}
1337+
12411338
public void testMergeUntilLimit() throws IOException {
12421339
CompressedXContent mapping1 = new CompressedXContent("""
12431340
{

0 commit comments

Comments
 (0)