Skip to content

Commit

Permalink
Handle merging dotted object names when merging V2 template mappings (e…
Browse files Browse the repository at this point in the history
…lastic#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 elastic#53101
  • Loading branch information
dakrone authored Apr 30, 2020
1 parent e256bec commit 89b538f
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
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;
Expand Down Expand Up @@ -579,7 +580,7 @@ static Map<String, Object> parseV2Mappings(String mappingsJson, List<CompressedX
nonProperties = innerTemplateNonProperties;

if (maybeProperties != null) {
properties.putAll(maybeProperties);
properties = mergeIgnoringDots(properties, maybeProperties);
}
}
}
Expand All @@ -593,7 +594,7 @@ static Map<String, Object> parseV2Mappings(String mappingsJson, List<CompressedX
nonProperties = innerRequestNonProperties;

if (maybeRequestProperties != null) {
properties.putAll(maybeRequestProperties);
properties = mergeIgnoringDots(properties, maybeRequestProperties);
}
}

Expand All @@ -602,6 +603,27 @@ static Map<String, Object> parseV2Mappings(String mappingsJson, List<CompressedX
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,70 @@ public void testMappingsMergingIsSmart() throws Exception {
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);
Expand Down

0 comments on commit 89b538f

Please sign in to comment.