Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple field names/patterns for (path_)(un)match (#66364) #95558

Merged
merged 11 commits into from
Apr 27, 2023
Merged
95 changes: 93 additions & 2 deletions docs/reference/mapping/dynamic/templates.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ PUT my-index-000001/_doc/1
[[match-unmatch]]
==== `match` and `unmatch`

The `match` parameter uses a pattern to match on the field name, while
`unmatch` uses a pattern to exclude fields matched by `match`.
The `match` parameter uses one or more patterns to match on the field name, while
`unmatch` uses one or more patterns to exclude fields matched by `match`.

The `match_pattern` parameter adjusts the behavior of the `match` parameter
to support full Java regular expressions matching on the field name
Expand Down Expand Up @@ -231,6 +231,48 @@ PUT my-index-000001/_doc/1
<1> The `long_num` field is mapped as a `long`.
<2> The `long_text` field uses the default `string` mapping.


You can specify a list of patterns using a JSON array for either the
`match` or `unmatch` fields.

The next example matches all fields whose name starts with `ip_` or ends with `_ip`,
except for fields which start with `one` or end with `two` and maps them
as `ip` fields:

[source,console]
--------------------------------------------------
PUT my-index-000001
{
"mappings": {
"dynamic_templates": [
{
"ip_fields": {
"match": ["ip_*", "*_ip"],
"unmatch": ["one*", "*two"],
"mapping": {
"type": "ip"
}
}
}
]
}
}

PUT my-index/_doc/1
{
"one_ip": "will not match", <1>
"ip_two": "will not match", <2>
"three_ip": "12.12.12.12", <3>
"ip_four": "13.13.13.13" <4>
}
--------------------------------------------------

<1> The `one_ip` field is unmatched, so uses the default mapping of `text`.
<2> The `ip_two` field is unmatched, so uses the default mapping of `text`.
<3> The `three_ip` field is mapped as type `ip`.
<4> The `ip_four` field is mapped as type `ip`.


[[path-match-unmatch]]
==== `path_match` and `path_unmatch`

Expand Down Expand Up @@ -271,6 +313,55 @@ PUT my-index-000001/_doc/1
}
--------------------------------------------------

And the following example uses an array of patterns for both `path_match`
and `path_unmatch`.

The values of any fields in the `name` object or the `user.name` object
are copied to the top-level `full_name` field, except for the `middle`
and `midinitial` fields:

[source,console]
--------------------------------------------------
PUT my-index-000001
{
"mappings": {
"dynamic_templates": [
{
"full_name": {
"path_match": ["name.*", "user.name.*"],
"path_unmatch": ["*.middle", "*.midinitial"],
"mapping": {
"type": "text",
"copy_to": "full_name"
}
}
}
]
}
}

PUT my-index-000001/_doc/1
{
"name": {
"first": "John",
"middle": "Winston",
"last": "Lennon"
}
}

PUT my-index-000001/_doc/2
{
"user": {
"name": {
"first": "Jane",
"midinitial": "M",
"last": "Salazar"
}
}
}
--------------------------------------------------


Note that the `path_match` and `path_unmatch` parameters match on object paths
in addition to leaf fields. As an example, indexing the following document will
result in an error because the `path_match` setting also matches the object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.io.UncheckedIOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

Expand Down Expand Up @@ -166,7 +167,7 @@ private List<String> findRoutingPaths(String indexName, Settings allSettings, Li
extractPath(routingPaths, fieldMapper);
}
for (var template : mapperService.getAllDynamicTemplates()) {
if (template.pathMatch() == null) {
if (template.pathMatch().isEmpty()) {
continue;
}

Expand All @@ -178,10 +179,15 @@ private List<String> findRoutingPaths(String indexName, Settings allSettings, Li
}

MappingParserContext parserContext = mapperService.parserContext();
var mapper = parserContext.typeParser(mappingSnippetType)
.parse(template.pathMatch(), mappingSnippet, parserContext)
.build(MapperBuilderContext.root(false));
extractPath(routingPaths, mapper);
for (String pathMatch : template.pathMatch()) {
var mapper = parserContext.typeParser(mappingSnippetType)
// Since FieldMapper.parse modifies the Map passed in (removing entries for "type"), that means
// that only the first pathMatch passed in gets recognized as a time_series_dimension. To counteract
// that, we wrap the mappingSnippet in a new HashMap for each pathMatch instance.
.parse(pathMatch, new HashMap<>(mappingSnippet), parserContext)
.build(MapperBuilderContext.root(false));
extractPath(routingPaths, mapper);
}
}
return routingPaths;
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,51 @@ public void testGenerateRoutingPathFromDynamicTemplate() throws Exception {
assertThat(IndexMetadata.INDEX_ROUTING_PATH.get(result), containsInAnyOrder("host.id", "prometheus.labels.*"));
}

public void testGenerateRoutingPathFromDynamicTemplateWithMultiplePathMatchEntries() throws Exception {
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
TimeValue lookAheadTime = TimeValue.timeValueHours(2); // default
String mapping = """
{
"_doc": {
"dynamic_templates": [
{
"labels": {
"path_match": ["xprometheus.labels.*", "yprometheus.labels.*"],
"mapping": {
"type": "keyword",
"time_series_dimension": true
}
}
}
],
"properties": {
"host": {
"properties": {
"id": {
"type": "keyword",
"time_series_dimension": true
}
}
},
"another_field": {
"type": "keyword"
}
}
}
}
""";
Settings result = generateTsdbSettings(mapping, now);
assertThat(result.size(), equalTo(3));
assertThat(IndexSettings.TIME_SERIES_START_TIME.get(result), equalTo(now.minusMillis(lookAheadTime.getMillis())));
assertThat(IndexSettings.TIME_SERIES_END_TIME.get(result), equalTo(now.plusMillis(lookAheadTime.getMillis())));
assertThat(
IndexMetadata.INDEX_ROUTING_PATH.get(result),
containsInAnyOrder("host.id", "xprometheus.labels.*", "yprometheus.labels.*")
);
List<String> routingPathList = IndexMetadata.INDEX_ROUTING_PATH.get(result);
assertEquals(3, routingPathList.size());
}

public void testGenerateRoutingPathFromDynamicTemplate_templateWithNoPathMatch() throws Exception {
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
TimeValue lookAheadTime = TimeValue.timeValueHours(2); // default
Expand Down Expand Up @@ -486,20 +531,20 @@ public void testGenerateRoutingPathFromDynamicTemplate_nonKeywordTemplate() thro
"_doc": {
"dynamic_templates": [
{
"labels": {
"path_match": "prometheus.labels.*",
"docker.cpu.core.*.pct": {
"path_match": "docker.cpu.core.*.pct",
"mapping": {
"type": "keyword",
"time_series_dimension": true
"coerce": true,
"type": "float"
}
}
},
{
"docker.cpu.core.*.pct": {
"path_match": "docker.cpu.core.*.pct",
"labels": {
"path_match": "prometheus.labels.*",
"mapping": {
"coerce": true,
"type": "float"
"type": "keyword",
"time_series_dimension": true
}
}
}
Expand All @@ -524,6 +569,7 @@ public void testGenerateRoutingPathFromDynamicTemplate_nonKeywordTemplate() thro
assertThat(IndexSettings.TIME_SERIES_START_TIME.get(result), equalTo(now.minusMillis(lookAheadTime.getMillis())));
assertThat(IndexSettings.TIME_SERIES_END_TIME.get(result), equalTo(now.plusMillis(lookAheadTime.getMillis())));
assertThat(IndexMetadata.INDEX_ROUTING_PATH.get(result), containsInAnyOrder("host.id", "prometheus.labels.*"));
assertEquals(2, IndexMetadata.INDEX_ROUTING_PATH.get(result).size());
}

private Settings generateTsdbSettings(String mapping, Instant now) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
"Create index with dynamic_mappings, match is single-valued":

- do:
indices.create:
index: test_index
body:
mappings:
dynamic_templates:
- mytemplate:
match: "*name"
mapping:
type: keyword

- do:
indices.get_mapping:
index: test_index

- is_true: test_index.mappings

- match: { test_index.mappings.dynamic_templates.0.mytemplate.match: "*name"}
- match: { test_index.mappings.dynamic_templates.0.mytemplate.mapping.type: "keyword"}

---
"Create index with dynamic_mappings, using lists for some match/unmatch sections":
- skip:
version: " - 8.8.99"
reason: Arrays in dynamic templates added in 8.9
- do:
indices.create:
index: test_index
body:
mappings:
dynamic_templates:
- mytemplate:
match:
- "*name"
- "user*"
unmatch:
- "*one"
- "two*"
path_match:
- "name.*"
- "user.name.*"
path_unmatch: "*.middle"
mapping:
type: keyword

- do:
indices.get_mapping:
index: test_index

- is_true: test_index.mappings
- match: { test_index.mappings.dynamic_templates.0.mytemplate.match.0: "*name"}
- match: { test_index.mappings.dynamic_templates.0.mytemplate.match.1: "user*"}
- match: { test_index.mappings.dynamic_templates.0.mytemplate.unmatch.0: "*one"}
- match: { test_index.mappings.dynamic_templates.0.mytemplate.unmatch.1: "two*"}
- match: { test_index.mappings.dynamic_templates.0.mytemplate.path_match.0: "name.*"}
- match: { test_index.mappings.dynamic_templates.0.mytemplate.path_match.1: "user.name.*"}
- match: { test_index.mappings.dynamic_templates.0.mytemplate.path_unmatch: "*.middle"}
- match: { test_index.mappings.dynamic_templates.0.mytemplate.mapping.type: "keyword" }
Loading