Skip to content

Commit 3c96970

Browse files
nateaclaude
andcommitted
Detect copy_to non-existent field when dynamic mappings disabled (#112812)
This change adds validation to detect when copy_to targets a non-existent field when dynamic mappings are disabled (dynamic=false). Previously, this configuration would silently fail to copy values at runtime. Changes: - Added validation in FieldMapper.validate() to check if copy_to target fields exist when dynamic=false - Throws IllegalArgumentException with clear error message when validation fails - Added comprehensive unit tests covering the new validation logic The validation only applies when dynamic=false since other dynamic settings (true, strict, runtime) have different behaviors for handling unknown fields. Closes #112812 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent d356685 commit 3c96970

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,10 @@ public final void validate(MappingLookup mappers) {
332332
throw new IllegalArgumentException("[copy_to] may not be used to copy from a multi-field: [" + this.fullPath() + "]");
333333
}
334334

335+
// Check if dynamic mappings are disabled
336+
ObjectMapper.Dynamic rootDynamic = ObjectMapper.Dynamic.getRootDynamic(mappers);
337+
boolean isDynamicDisabled = rootDynamic == ObjectMapper.Dynamic.FALSE;
338+
335339
final String sourceScope = mappers.nestedLookup().getNestedParent(this.fullPath());
336340
for (String copyTo : this.copyTo().copyToFields()) {
337341
if (mappers.isMultiField(copyTo)) {
@@ -341,6 +345,13 @@ public final void validate(MappingLookup mappers) {
341345
throw new IllegalArgumentException("Cannot copy to field [" + copyTo + "] since it is mapped as an object");
342346
}
343347

348+
// When dynamic is false, check if the target field exists
349+
if (isDynamicDisabled && mappers.getMapper(copyTo) == null) {
350+
throw new IllegalArgumentException(
351+
"Cannot copy to field [" + copyTo + "] because it does not exist and dynamic mappings are disabled"
352+
);
353+
}
354+
344355
final String targetScope = mappers.nestedLookup().getNestedParent(copyTo);
345356
checkNestedScopeCompatibility(sourceScope, targetScope);
346357
}

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,62 @@
3232

3333
public class CopyToMapperTests extends MapperServiceTestCase {
3434

35+
public void testCopyToNonExistentFieldWithDynamicFalse() {
36+
Exception e = expectThrows(IllegalArgumentException.class, () -> createDocumentMapper(topMapping(b -> {
37+
b.field("dynamic", false);
38+
b.startObject("properties");
39+
{
40+
b.startObject("test_field");
41+
{
42+
b.field("type", "text");
43+
b.field("copy_to", "missing_field");
44+
}
45+
b.endObject();
46+
}
47+
b.endObject();
48+
})));
49+
assertThat(
50+
e.getMessage(),
51+
equalTo("Cannot copy to field [missing_field] because it does not exist and dynamic mappings are disabled")
52+
);
53+
}
54+
55+
public void testCopyToExistingFieldWithDynamicFalse() throws Exception {
56+
// This should succeed as the target field exists
57+
DocumentMapper mapper = createDocumentMapper(topMapping(b -> {
58+
b.field("dynamic", false);
59+
b.startObject("properties");
60+
{
61+
b.startObject("test_field");
62+
{
63+
b.field("type", "text");
64+
b.field("copy_to", "target_field");
65+
}
66+
b.endObject();
67+
b.startObject("target_field");
68+
{
69+
b.field("type", "text");
70+
}
71+
b.endObject();
72+
}
73+
b.endObject();
74+
}));
75+
assertNotNull(mapper);
76+
}
77+
78+
public void testCopyToNonExistentFieldWithDynamicTrue() throws Exception {
79+
// This should succeed as dynamic is true (default)
80+
MapperService mapperService = createMapperService(mapping(b -> {
81+
b.startObject("test_field");
82+
{
83+
b.field("type", "text");
84+
b.field("copy_to", "missing_field");
85+
}
86+
b.endObject();
87+
}));
88+
assertNotNull(mapperService.documentMapper());
89+
}
90+
3591
@SuppressWarnings("unchecked")
3692
public void testCopyToFieldsParsing() throws Exception {
3793

0 commit comments

Comments
 (0)