1
0
Эх сурвалжийг харах

Handle merging dotted object names when merging V2 template mappings (#55982)

When merging component template, index template, and request mappings, we now treat any declaration
of a top-level field the same as replacing all sub-objects. For example, assuming two component
templates with mappings and template B taking precedence:

```
A: {foo: {...}}
B: {foo.bar: {...}}
Result: {foo.bar: {...}}

A: {foo.bar: {...}}
B: {foo: {...}}
Result: {foo: {...}}

A: {foo.bar: {...}}
B: {foo.baz: {...}}
Result: {foo.baz: {...}}
```

Relates to #53101
Lee Hinman 5 жил өмнө
parent
commit
89b538f857

+ 24 - 2
server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java

@@ -85,6 +85,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -579,7 +580,7 @@ public class MetadataCreateIndexService {
                 nonProperties = innerTemplateNonProperties;
 
                 if (maybeProperties != null) {
-                    properties.putAll(maybeProperties);
+                    properties = mergeIgnoringDots(properties, maybeProperties);
                 }
             }
         }
@@ -593,7 +594,7 @@ public class MetadataCreateIndexService {
             nonProperties = innerRequestNonProperties;
 
             if (maybeRequestProperties != null) {
-                properties.putAll(maybeRequestProperties);
+                properties = mergeIgnoringDots(properties, maybeRequestProperties);
             }
         }
 
@@ -602,6 +603,27 @@ public class MetadataCreateIndexService {
         return Collections.singletonMap(MapperService.SINGLE_MAPPING_NAME, finalMappings);
     }
 
+    /**
+     * Add the objects in the second map to the first, where the keys in the {@code second} map have
+     * higher predecence and overwrite the keys in the {@code first} map. In the event of a key with
+     * a dot in it (ie, "foo.bar"), the keys are treated as only the prefix counting towards
+     * equality. If the {@code second} map has a key such as "foo", all keys starting from "foo." in
+     * the {@code first} map are discarded.
+     */
+    static Map<String, Object> mergeIgnoringDots(Map<String, Object> first, Map<String, Object> second) {
+        Objects.requireNonNull(first, "merging requires two non-null maps but the first map was null");
+        Objects.requireNonNull(second, "merging requires two non-null maps but the second map was null");
+        Map<String, Object> results = new HashMap<>(first);
+        Set<String> prefixes = second.keySet().stream().map(MetadataCreateIndexService::prefix).collect(Collectors.toSet());
+        results.keySet().removeIf(k -> prefixes.contains(prefix(k)));
+        results.putAll(second);
+        return results;
+    }
+
+    private static String prefix(String s) {
+        return s.split("\\.", 2)[0];
+    }
+
     /**
      * Parses the provided mappings json and the inheritable mappings from the templates (if any)
      * into a map.

+ 64 - 0
server/src/test/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexServiceTests.java

@@ -1047,6 +1047,70 @@ public class MetadataCreateIndexServiceTests extends ESTestCase {
         assertThat(innerInnerResolved.get("foo"), equalTo(fooMappings));
     }
 
+    @SuppressWarnings("unchecked")
+    public void testMappingsMergingHandlesDots() throws Exception {
+        Template ctt1 = new Template(null,
+            new CompressedXContent("{\"_doc\":{\"properties\":{\"foo\":{\"properties\":{\"bar\":{\"type\": \"long\"}}}}}}"), null);
+        Template ctt2 = new Template(null,
+            new CompressedXContent("{\"_doc\":{\"properties\":{\"foo.bar\":{\"type\": \"text\",\"analyzer\":\"english\"}}}}"), null);
+
+        ComponentTemplate ct1 = new ComponentTemplate(ctt1, null, null);
+        ComponentTemplate ct2 = new ComponentTemplate(ctt2, null, null);
+
+        IndexTemplateV2 template = new IndexTemplateV2(Collections.singletonList("index"), null, Arrays.asList("ct2", "ct1"),
+            null, null, null);
+
+        ClusterState state = ClusterState.builder(ClusterState.EMPTY_STATE)
+            .metadata(Metadata.builder(Metadata.EMPTY_METADATA)
+                .put("ct1", ct1)
+                .put("ct2", ct2)
+                .put("index-template", template)
+                .build())
+            .build();
+
+        Map<String, Object> resolved =
+            MetadataCreateIndexService.resolveV2Mappings("{}", state,
+                "index-template", new NamedXContentRegistry(Collections.emptyList()));
+
+        assertThat("expected exactly one type but was: " + resolved, resolved.size(), equalTo(1));
+        Map<String, Object> innerResolved = (Map<String, Object>) resolved.get(MapperService.SINGLE_MAPPING_NAME);
+        assertThat("was: " + innerResolved, innerResolved.size(), equalTo(1));
+
+        Map<String, Object> innerInnerResolved = (Map<String, Object>) innerResolved.get("properties");
+        assertThat(innerInnerResolved.size(), equalTo(1));
+        assertThat(innerInnerResolved.get("foo"),
+            equalTo(Collections.singletonMap("properties", Collections.singletonMap("bar", Collections.singletonMap("type", "long")))));
+    }
+
+    public void testMergeIgnoringDots() throws Exception {
+        Map<String, Object> first = new HashMap<>();
+        first.put("foo", Collections.singletonMap("type", "long"));
+        Map<String, Object> second = new HashMap<>();
+        second.put("foo.bar", Collections.singletonMap("type", "long"));
+        Map<String, Object> results = MetadataCreateIndexService.mergeIgnoringDots(first, second);
+        assertThat(results, equalTo(second));
+
+        results = MetadataCreateIndexService.mergeIgnoringDots(second, first);
+        assertThat(results, equalTo(first));
+
+        second.clear();
+        Map<String, Object> inner = new HashMap<>();
+        inner.put("type", "text");
+        inner.put("analyzer", "english");
+        second.put("foo", inner);
+
+        results = MetadataCreateIndexService.mergeIgnoringDots(first, second);
+        assertThat(results, equalTo(second));
+
+        first.put("baz", 3);
+        second.put("egg", 7);
+
+        results = MetadataCreateIndexService.mergeIgnoringDots(first, second);
+        Map<String, Object> expected = new HashMap<>(second);
+        expected.put("baz", 3);
+        assertThat(results, equalTo(expected));
+    }
+
     private IndexTemplateMetadata addMatchingTemplate(Consumer<IndexTemplateMetadata.Builder> configurator) {
         IndexTemplateMetadata.Builder builder = templateMetadataBuilder("template1", "te*");
         configurator.accept(builder);