From 3c9697041d3db79b7b556ab36f0a05904ca50810 Mon Sep 17 00:00:00 2001 From: Nate Aune Date: Fri, 15 Aug 2025 10:20:37 -0400 Subject: [PATCH 1/2] Detect copy_to non-existent field when dynamic mappings disabled (#112812) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../index/mapper/FieldMapper.java | 11 ++++ .../index/mapper/CopyToMapperTests.java | 56 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java index a43575b8f990c..73d5ae6debc68 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java @@ -332,6 +332,10 @@ public final void validate(MappingLookup mappers) { throw new IllegalArgumentException("[copy_to] may not be used to copy from a multi-field: [" + this.fullPath() + "]"); } + // Check if dynamic mappings are disabled + ObjectMapper.Dynamic rootDynamic = ObjectMapper.Dynamic.getRootDynamic(mappers); + boolean isDynamicDisabled = rootDynamic == ObjectMapper.Dynamic.FALSE; + final String sourceScope = mappers.nestedLookup().getNestedParent(this.fullPath()); for (String copyTo : this.copyTo().copyToFields()) { if (mappers.isMultiField(copyTo)) { @@ -341,6 +345,13 @@ public final void validate(MappingLookup mappers) { throw new IllegalArgumentException("Cannot copy to field [" + copyTo + "] since it is mapped as an object"); } + // When dynamic is false, check if the target field exists + if (isDynamicDisabled && mappers.getMapper(copyTo) == null) { + throw new IllegalArgumentException( + "Cannot copy to field [" + copyTo + "] because it does not exist and dynamic mappings are disabled" + ); + } + final String targetScope = mappers.nestedLookup().getNestedParent(copyTo); checkNestedScopeCompatibility(sourceScope, targetScope); } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/CopyToMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/CopyToMapperTests.java index aa184ddf465d5..e4f6f07a5d00c 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/CopyToMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/CopyToMapperTests.java @@ -32,6 +32,62 @@ public class CopyToMapperTests extends MapperServiceTestCase { + public void testCopyToNonExistentFieldWithDynamicFalse() { + Exception e = expectThrows(IllegalArgumentException.class, () -> createDocumentMapper(topMapping(b -> { + b.field("dynamic", false); + b.startObject("properties"); + { + b.startObject("test_field"); + { + b.field("type", "text"); + b.field("copy_to", "missing_field"); + } + b.endObject(); + } + b.endObject(); + }))); + assertThat( + e.getMessage(), + equalTo("Cannot copy to field [missing_field] because it does not exist and dynamic mappings are disabled") + ); + } + + public void testCopyToExistingFieldWithDynamicFalse() throws Exception { + // This should succeed as the target field exists + DocumentMapper mapper = createDocumentMapper(topMapping(b -> { + b.field("dynamic", false); + b.startObject("properties"); + { + b.startObject("test_field"); + { + b.field("type", "text"); + b.field("copy_to", "target_field"); + } + b.endObject(); + b.startObject("target_field"); + { + b.field("type", "text"); + } + b.endObject(); + } + b.endObject(); + })); + assertNotNull(mapper); + } + + public void testCopyToNonExistentFieldWithDynamicTrue() throws Exception { + // This should succeed as dynamic is true (default) + MapperService mapperService = createMapperService(mapping(b -> { + b.startObject("test_field"); + { + b.field("type", "text"); + b.field("copy_to", "missing_field"); + } + b.endObject(); + })); + assertNotNull(mapperService.documentMapper()); + } + @SuppressWarnings("unchecked") public void testCopyToFieldsParsing() throws Exception { From 8e9ac692d6ca46c42b77b91cc29eddcf8737c98d Mon Sep 17 00:00:00 2001 From: Nate Aune Date: Fri, 15 Aug 2025 10:45:43 -0400 Subject: [PATCH 2/2] Add Claude Flow directories and temp files to .gitignore Exclude .claude-flow/, .swarm/ directories and temporary test/analysis files from version control to keep the repository clean. --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index cac5a799012e1..f72dde1cac3d1 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,12 @@ server/src/main/resources/transport/defined/manifest.txt # JEnv .java-version + +# Claude Flow and Swarm directories +.claude-flow/ +.swarm/ + +# Temporary test files +test-*.java +bug-*.json +*-bug-analysis.md