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

Add unmatch_mapping_type, and support array of types #103171

Merged
7 changes: 7 additions & 0 deletions docs/changelog/103171.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pr: 103171
summary: "Add `unmatch_mapping_type`, and support array of types"
area: Mapping
type: feature
issues:
- 102807
- 102795
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,32 @@
- 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" }

---
"Create index with dynamic_mappings, with wildcard match_mapping_type and an unmatch_mapping_type array":
- skip:
version: " - 8.12.99"
reason: unmatch_mapping_type in dynamic templates added in 8.13
- do:
indices.create:
index: test_index
body:
mappings:
dynamic_templates:
- mytemplate:
match_mapping_type: "*"
unmatch_mapping_type:
- "object"
- "boolean"
mapping:
type: long

- do:
indices.get_mapping:
index: test_index

- is_true: test_index.mappings
- match: { test_index.mappings.dynamic_templates.0.mytemplate.match_mapping_type: "*"}
- match: { test_index.mappings.dynamic_templates.0.mytemplate.unmatch_mapping_type.0: "object"}
- match: { test_index.mappings.dynamic_templates.0.mytemplate.unmatch_mapping_type.1: "boolean"}
- match: { test_index.mappings.dynamic_templates.0.mytemplate.mapping.type: "long" }
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class DynamicTemplate implements ToXContentObject {
Expand Down Expand Up @@ -257,7 +260,8 @@ static DynamicTemplate parse(String name, Map<String, Object> conf) throws Mappe
List<String> pathUnmatch = new ArrayList<>(4);
Map<String, Object> mapping = null;
boolean runtime = false;
String matchMappingType = null;
List<String> matchMappingType = new ArrayList<>(4);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the size of the array 4? Just to have a size that is lower than he default?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses the same initial size of the array list as the other lists a few lines above. I think this is because we expect most of these to have one or a few values, so a custom default value 4 aims for more efficiency than the generic default value.

List<String> unmatchMappingType = new ArrayList<>(4);
String matchPattern = MatchType.DEFAULT.toString();

for (Map.Entry<String, Object> entry : conf.entrySet()) {
Expand All @@ -271,7 +275,9 @@ static DynamicTemplate parse(String name, Map<String, Object> conf) throws Mappe
} else if ("path_unmatch".equals(propName)) {
addEntriesToPatternList(pathUnmatch, propName, entry);
} else if ("match_mapping_type".equals(propName)) {
matchMappingType = entry.getValue().toString();
addEntriesToPatternList(matchMappingType, propName, entry);
} else if ("unmatch_mapping_type".equals(propName)) {
addEntriesToPatternList(unmatchMappingType, propName, entry);
} else if ("match_pattern".equals(propName)) {
matchPattern = entry.getValue().toString();
} else if ("mapping".equals(propName)) {
Expand Down Expand Up @@ -301,30 +307,48 @@ static DynamicTemplate parse(String name, Map<String, Object> conf) throws Mappe
throw new MapperParsingException("template [" + name + "] must have either mapping or runtime set");
}

final XContentFieldType[] xContentFieldTypes;
if ("*".equals(matchMappingType) || (matchMappingType == null && matchPatternsAreDefined(match, pathMatch))) {
// match, path_match, and unmatch_mapping_type all imply
// "match_mapping_type: *" if not explicitly specified.
final boolean wildcardMatchMappingType = ((matchMappingType.isEmpty()
&& matchPatternsAreDefined(match, pathMatch, unmatchMappingType))
|| (matchMappingType.size() == 1 && matchMappingType.get(0).equals("*")));

Stream<XContentFieldType> matchXContentFieldTypes;
if (wildcardMatchMappingType) {
matchXContentFieldTypes = Stream.of(XContentFieldType.values());
} else {
if (runtime) {
xContentFieldTypes = Arrays.stream(XContentFieldType.values())
.filter(XContentFieldType::supportsRuntimeField)
.toArray(XContentFieldType[]::new);
} else {
xContentFieldTypes = XContentFieldType.values();
}
} else if (matchMappingType != null) {
final XContentFieldType xContentFieldType = XContentFieldType.fromString(matchMappingType);
if (runtime && xContentFieldType.supportsRuntimeField() == false) {
throw new MapperParsingException(
"Dynamic template ["
+ name
+ "] defines a runtime field but type ["
+ xContentFieldType
+ "] is not supported as runtime field"
);
final List<String> unsupported = matchMappingType.stream()
.map(XContentFieldType::fromString)
.filter(Predicate.not(XContentFieldType::supportsRuntimeField))
.map(XContentFieldType::toString)
.toList();
if (unsupported.isEmpty() == false) {
final int numUnsupported = unsupported.size();
throw new MapperParsingException(
"Dynamic template ["
+ name
+ "] defines a runtime field but type"
+ (numUnsupported == 1 ? "" : "s")
+ " ["
+ String.join(", ", unsupported)
+ "] "
+ (numUnsupported == 1 ? "is" : "are")
+ " not supported as runtime field"
);
}
}
xContentFieldTypes = new XContentFieldType[] { xContentFieldType };
} else {
xContentFieldTypes = new XContentFieldType[0];
matchXContentFieldTypes = matchMappingType.stream().map(XContentFieldType::fromString);
}
if (runtime) {
matchXContentFieldTypes = matchXContentFieldTypes.filter(XContentFieldType::supportsRuntimeField);
}
axw marked this conversation as resolved.
Show resolved Hide resolved

final Set<XContentFieldType> unmatchXContentFieldTypesSet = unmatchMappingType.stream()
.map(XContentFieldType::fromString)
.collect(Collectors.toSet());
final XContentFieldType[] xContentFieldTypes = matchXContentFieldTypes.filter(Predicate.not(unmatchXContentFieldTypesSet::contains))
.toArray(XContentFieldType[]::new);

final MatchType matchType = MatchType.fromString(matchPattern);
List<String> allPatterns = Stream.of(match.stream(), unmatch.stream(), pathMatch.stream(), pathUnmatch.stream())
Expand All @@ -336,16 +360,27 @@ static DynamicTemplate parse(String name, Map<String, Object> conf) throws Mappe
matchType.validate(pattern, name);
}

return new DynamicTemplate(name, pathMatch, pathUnmatch, match, unmatch, xContentFieldTypes, matchType, mapping, runtime);
return new DynamicTemplate(
name,
pathMatch,
pathUnmatch,
match,
unmatch,
matchMappingType,
unmatchMappingType,
xContentFieldTypes,
matchType,
mapping,
runtime
);
}

/**
* @param match list of match patterns (can be empty but not null)
* @param pathMatch list of pathMatch patterns (can be empty but not null)
* @return return true if there is at least 1 match or pathMatch pattern defined
* @param matchLists zero or more lists of match patterns (can be empty but not null)
* @return return true if any of the given lists is non-empty
*/
private static boolean matchPatternsAreDefined(List<String> match, List<String> pathMatch) {
return match.size() + pathMatch.size() > 0;
private static boolean matchPatternsAreDefined(final List<?>... matchLists) {
return Stream.of(matchLists).anyMatch(Predicate.not(List::isEmpty));
}

private static void addEntriesToPatternList(List<String> matchList, String propName, Map.Entry<String, Object> entry) {
Expand All @@ -372,6 +407,8 @@ private static void addEntriesToPatternList(List<String> matchList, String propN
private final List<String> match;
private final List<String> unmatch;
private final MatchType matchType;
private final List<String> matchMappingType;
private final List<String> unmatchMappingType;
private final XContentFieldType[] xContentFieldTypes;
private final Map<String, Object> mapping;
private final boolean runtimeMapping;
Expand All @@ -382,6 +419,8 @@ private DynamicTemplate(
List<String> pathUnmatch,
List<String> match,
List<String> unmatch,
List<String> matchMappingType,
List<String> unmatchMappingType,
XContentFieldType[] xContentFieldTypes,
MatchType matchType,
Map<String, Object> mapping,
Expand All @@ -393,6 +432,8 @@ private DynamicTemplate(
this.match = match;
this.unmatch = unmatch;
this.matchType = matchType;
this.matchMappingType = matchMappingType;
this.unmatchMappingType = unmatchMappingType;
this.xContentFieldTypes = xContentFieldTypes;
this.mapping = mapping;
this.runtimeMapping = runtimeMapping;
Expand Down Expand Up @@ -553,13 +594,19 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
builder.field("path_unmatch", pathUnmatch);
}
}
// We have more than one types when (1) `match_mapping_type` is "*", and (2) match and/or path_match are defined but
// not `match_mapping_type`. In the latter the template implicitly accepts all types and we don't need to serialize
// the `match_mapping_type` values.
if (xContentFieldTypes.length > 1 && match.isEmpty() && pathMatch.isEmpty()) {
builder.field("match_mapping_type", "*");
} else if (xContentFieldTypes.length == 1) {
builder.field("match_mapping_type", xContentFieldTypes[0]);
if (matchMappingType.isEmpty() == false) {
if (matchMappingType.size() == 1) {
builder.field("match_mapping_type", matchMappingType.get(0));
} else {
builder.field("match_mapping_type", matchMappingType);
}
}
if (unmatchMappingType.isEmpty() == false) {
if (unmatchMappingType.size() == 1) {
builder.field("unmatch_mapping_type", unmatchMappingType.get(0));
} else {
builder.field("unmatch_mapping_type", unmatchMappingType);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic seems similar...maybe extract a method just passing a list and a string?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me add that

}
if (matchType != MatchType.DEFAULT) {
builder.field("match_pattern", matchType);
Expand Down
Loading