Mapper: Add `path_match` for full object navigation path matching, closes #476.
This commit is contained in:
parent
06ddf4547d
commit
e2d6f82cd3
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.index.mapper.xcontent;
|
||||
|
||||
import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.collect.Maps;
|
||||
import org.elasticsearch.common.regex.Regex;
|
||||
import org.elasticsearch.index.mapper.MapperParsingException;
|
||||
|
@ -48,24 +49,50 @@ public class DynamicTemplate {
|
|||
}
|
||||
|
||||
public static DynamicTemplate parse(String name, Map<String, Object> conf) throws MapperParsingException {
|
||||
if (!conf.containsKey("match")) {
|
||||
throw new MapperParsingException("template must have match set");
|
||||
String match = null;
|
||||
String pathMatch = null;
|
||||
String unmatch = null;
|
||||
String pathUnmatch = null;
|
||||
Map<String, Object> mapping = null;
|
||||
String matchMappingType = null;
|
||||
String matchPattern = "simple";
|
||||
|
||||
for (Map.Entry<String, Object> entry : conf.entrySet()) {
|
||||
String propName = Strings.toUnderscoreCase(entry.getKey());
|
||||
if ("match".equals(propName)) {
|
||||
match = entry.getValue().toString();
|
||||
} else if ("path_match".equals(propName)) {
|
||||
pathMatch = entry.getValue().toString();
|
||||
} else if ("unmatch".equals(propName)) {
|
||||
unmatch = entry.getValue().toString();
|
||||
} else if ("path_unmatch".equals(propName)) {
|
||||
pathUnmatch = entry.getValue().toString();
|
||||
} else if ("match_mapping_type".equals(propName)) {
|
||||
matchMappingType = entry.getValue().toString();
|
||||
} else if ("match_pattern".equals(propName)) {
|
||||
matchPattern = entry.getValue().toString();
|
||||
} else if ("mapping".equals(propName)) {
|
||||
mapping = (Map<String, Object>) entry.getValue();
|
||||
}
|
||||
}
|
||||
String match = conf.get("match").toString();
|
||||
String unmatch = conf.containsKey("unmatch") ? conf.get("unmatch").toString() : null;
|
||||
String matchMappingType = conf.containsKey("match_mapping_type") ? conf.get("match_mapping_type").toString() : null;
|
||||
if (!conf.containsKey("mapping")) {
|
||||
|
||||
if (match == null && pathMatch == null) {
|
||||
throw new MapperParsingException("template must have match or path_match set");
|
||||
}
|
||||
if (mapping == null) {
|
||||
throw new MapperParsingException("template must have mapping set");
|
||||
}
|
||||
Map<String, Object> mapping = (Map<String, Object>) conf.get("mapping");
|
||||
String matchType = conf.containsKey("match_pattern") ? conf.get("match_pattern").toString() : "simple";
|
||||
return new DynamicTemplate(name, conf, match, unmatch, matchMappingType, MatchType.fromString(matchType), mapping);
|
||||
return new DynamicTemplate(name, conf, pathMatch, pathUnmatch, match, unmatch, matchMappingType, MatchType.fromString(matchPattern), mapping);
|
||||
}
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Map<String, Object> conf;
|
||||
|
||||
private final String pathMatch;
|
||||
|
||||
private final String pathUnmatch;
|
||||
|
||||
private final String match;
|
||||
|
||||
private final String unmatch;
|
||||
|
@ -76,9 +103,11 @@ public class DynamicTemplate {
|
|||
|
||||
private final Map<String, Object> mapping;
|
||||
|
||||
public DynamicTemplate(String name, Map<String, Object> conf, String match, String unmatch, String matchMappingType, MatchType matchType, Map<String, Object> mapping) {
|
||||
public DynamicTemplate(String name, Map<String, Object> conf, String pathMatch, String pathUnmatch, String match, String unmatch, String matchMappingType, MatchType matchType, Map<String, Object> mapping) {
|
||||
this.name = name;
|
||||
this.conf = conf;
|
||||
this.pathMatch = pathMatch;
|
||||
this.pathUnmatch = pathUnmatch;
|
||||
this.match = match;
|
||||
this.unmatch = unmatch;
|
||||
this.matchType = matchType;
|
||||
|
@ -94,11 +123,17 @@ public class DynamicTemplate {
|
|||
return this.conf;
|
||||
}
|
||||
|
||||
public boolean match(String name, String dynamicType) {
|
||||
if (!patternMatch(match, name)) {
|
||||
public boolean match(ContentPath path, String name, String dynamicType) {
|
||||
if (pathMatch != null && !patternMatch(pathMatch, path.fullPathAsText(name))) {
|
||||
return false;
|
||||
}
|
||||
if (patternMatch(unmatch, name)) {
|
||||
if (match != null && !patternMatch(match, name)) {
|
||||
return false;
|
||||
}
|
||||
if (pathUnmatch != null && patternMatch(pathUnmatch, path.fullPathAsText(name))) {
|
||||
return false;
|
||||
}
|
||||
if (unmatch != null && patternMatch(unmatch, name)) {
|
||||
return false;
|
||||
}
|
||||
if (matchMappingType != null) {
|
||||
|
|
|
@ -154,7 +154,7 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||
|
||||
private void parseProperties(ObjectMapper.Builder objBuilder, Map<String, Object> propsNode, ParserContext parserContext) {
|
||||
for (Map.Entry<String, Object> entry : propsNode.entrySet()) {
|
||||
String propName = entry.getKey();
|
||||
String propName = Strings.toUnderscoreCase(entry.getKey());
|
||||
Map<String, Object> propNode = (Map<String, Object>) entry.getValue();
|
||||
|
||||
String type;
|
||||
|
@ -320,13 +320,18 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||
if (objectMapper != null) {
|
||||
objectMapper.parse(context);
|
||||
} else {
|
||||
BuilderContext builderContext = new BuilderContext(context.path());
|
||||
XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "object");
|
||||
if (builder == null) {
|
||||
builder = XContentMapperBuilders.object(currentFieldName).enabled(true).dynamic(dynamic).pathType(pathType);
|
||||
}
|
||||
// remove the current field name from path, since the object builder adds it as well...
|
||||
context.path().remove();
|
||||
BuilderContext builderContext = new BuilderContext(context.path());
|
||||
objectMapper = builder.build(builderContext);
|
||||
putMapper(objectMapper);
|
||||
|
||||
// now re add it and parse...
|
||||
context.path().add(currentFieldName);
|
||||
objectMapper.parse(context);
|
||||
context.addedMapper();
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ public class RootObjectMapper extends ObjectMapper {
|
|||
}
|
||||
|
||||
public XContentMapper.Builder findTemplateBuilder(ParseContext context, String name, String dynamicType) {
|
||||
DynamicTemplate dynamicTemplate = findTemplate(name, dynamicType);
|
||||
DynamicTemplate dynamicTemplate = findTemplate(context.path(), name, dynamicType);
|
||||
if (dynamicTemplate == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -175,9 +175,9 @@ public class RootObjectMapper extends ObjectMapper {
|
|||
return parserContext.typeParser(dynamicTemplate.mappingType(dynamicType)).parse(name, dynamicTemplate.mappingForName(name, dynamicType), parserContext);
|
||||
}
|
||||
|
||||
public DynamicTemplate findTemplate(String name, String dynamicType) {
|
||||
public DynamicTemplate findTemplate(ContentPath path, String name, String dynamicType) {
|
||||
for (DynamicTemplate dynamicTemplate : dynamicTemplates) {
|
||||
if (dynamicTemplate.match(name, dynamicType)) {
|
||||
if (dynamicTemplate.match(path, name, dynamicType)) {
|
||||
return dynamicTemplate;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search licenses this
|
||||
* file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.index.mapper.xcontent.dynamictemplate.pathmatch;
|
||||
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.elasticsearch.index.mapper.FieldMappers;
|
||||
import org.elasticsearch.index.mapper.xcontent.MapperTests;
|
||||
import org.elasticsearch.index.mapper.xcontent.XContentDocumentMapper;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.elasticsearch.common.io.Streams.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
public class PathMatchDynamicTempalteTests {
|
||||
|
||||
@Test public void testSimple() throws Exception {
|
||||
String mapping = copyToStringFromClasspath("/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/test-mapping.json");
|
||||
XContentDocumentMapper docMapper = MapperTests.newParser().parse(mapping);
|
||||
byte[] json = copyToBytesFromClasspath("/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/test-data.json");
|
||||
Document doc = docMapper.parse(json).doc();
|
||||
|
||||
Field f = doc.getField("name");
|
||||
assertThat(f.name(), equalTo("name"));
|
||||
assertThat(f.stringValue(), equalTo("top_level"));
|
||||
assertThat(f.isStored(), equalTo(false));
|
||||
|
||||
FieldMappers fieldMappers = docMapper.mappers().fullName("name");
|
||||
assertThat(fieldMappers.mappers().size(), equalTo(1));
|
||||
assertThat(fieldMappers.mapper().stored(), equalTo(false));
|
||||
|
||||
f = doc.getField("obj1.name");
|
||||
assertThat(f.name(), equalTo("obj1.name"));
|
||||
assertThat(f.isStored(), equalTo(true));
|
||||
|
||||
fieldMappers = docMapper.mappers().fullName("obj1.name");
|
||||
assertThat(fieldMappers.mappers().size(), equalTo(1));
|
||||
assertThat(fieldMappers.mapper().stored(), equalTo(true));
|
||||
|
||||
f = doc.getField("obj1.obj2.name");
|
||||
assertThat(f.name(), equalTo("obj1.obj2.name"));
|
||||
assertThat(f.isStored(), equalTo(false));
|
||||
|
||||
fieldMappers = docMapper.mappers().fullName("obj1.obj2.name");
|
||||
assertThat(fieldMappers.mappers().size(), equalTo(1));
|
||||
assertThat(fieldMappers.mapper().stored(), equalTo(false));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"_id" : "1",
|
||||
"name" : "top_level",
|
||||
"obj1" : {
|
||||
"name" : "obj1_level",
|
||||
"obj2" : {
|
||||
"name" : "obj2_level"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"person" : {
|
||||
"dynamic_templates" : [
|
||||
{
|
||||
"template_1" : {
|
||||
"path_match" : "obj1.obj2.*",
|
||||
"mapping" : {
|
||||
"store" : "no"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"template_2" : {
|
||||
"path_match" : "obj1.*",
|
||||
"mapping" : {
|
||||
"store" : "yes"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue