Mapper: Add `path_match` for full object navigation path matching, closes #476.

This commit is contained in:
kimchy 2010-11-04 10:55:19 +02:00
parent 06ddf4547d
commit e2d6f82cd3
6 changed files with 159 additions and 18 deletions

View File

@ -20,6 +20,7 @@
package org.elasticsearch.index.mapper.xcontent; package org.elasticsearch.index.mapper.xcontent;
import org.elasticsearch.ElasticSearchIllegalArgumentException; import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.Maps; import org.elasticsearch.common.collect.Maps;
import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.index.mapper.MapperParsingException; 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 { public static DynamicTemplate parse(String name, Map<String, Object> conf) throws MapperParsingException {
if (!conf.containsKey("match")) { String match = null;
throw new MapperParsingException("template must have match set"); 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; if (match == null && pathMatch == null) {
String matchMappingType = conf.containsKey("match_mapping_type") ? conf.get("match_mapping_type").toString() : null; throw new MapperParsingException("template must have match or path_match set");
if (!conf.containsKey("mapping")) { }
if (mapping == null) {
throw new MapperParsingException("template must have mapping set"); throw new MapperParsingException("template must have mapping set");
} }
Map<String, Object> mapping = (Map<String, Object>) conf.get("mapping"); return new DynamicTemplate(name, conf, pathMatch, pathUnmatch, match, unmatch, matchMappingType, MatchType.fromString(matchPattern), 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);
} }
private final String name; private final String name;
private final Map<String, Object> conf; private final Map<String, Object> conf;
private final String pathMatch;
private final String pathUnmatch;
private final String match; private final String match;
private final String unmatch; private final String unmatch;
@ -76,9 +103,11 @@ public class DynamicTemplate {
private final Map<String, Object> mapping; 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.name = name;
this.conf = conf; this.conf = conf;
this.pathMatch = pathMatch;
this.pathUnmatch = pathUnmatch;
this.match = match; this.match = match;
this.unmatch = unmatch; this.unmatch = unmatch;
this.matchType = matchType; this.matchType = matchType;
@ -94,11 +123,17 @@ public class DynamicTemplate {
return this.conf; return this.conf;
} }
public boolean match(String name, String dynamicType) { public boolean match(ContentPath path, String name, String dynamicType) {
if (!patternMatch(match, name)) { if (pathMatch != null && !patternMatch(pathMatch, path.fullPathAsText(name))) {
return false; 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; return false;
} }
if (matchMappingType != null) { if (matchMappingType != null) {

View File

@ -154,7 +154,7 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
private void parseProperties(ObjectMapper.Builder objBuilder, Map<String, Object> propsNode, ParserContext parserContext) { private void parseProperties(ObjectMapper.Builder objBuilder, Map<String, Object> propsNode, ParserContext parserContext) {
for (Map.Entry<String, Object> entry : propsNode.entrySet()) { 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(); Map<String, Object> propNode = (Map<String, Object>) entry.getValue();
String type; String type;
@ -320,13 +320,18 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
if (objectMapper != null) { if (objectMapper != null) {
objectMapper.parse(context); objectMapper.parse(context);
} else { } else {
BuilderContext builderContext = new BuilderContext(context.path());
XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "object"); XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "object");
if (builder == null) { if (builder == null) {
builder = XContentMapperBuilders.object(currentFieldName).enabled(true).dynamic(dynamic).pathType(pathType); 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); objectMapper = builder.build(builderContext);
putMapper(objectMapper); putMapper(objectMapper);
// now re add it and parse...
context.path().add(currentFieldName);
objectMapper.parse(context); objectMapper.parse(context);
context.addedMapper(); context.addedMapper();
} }

View File

@ -167,7 +167,7 @@ public class RootObjectMapper extends ObjectMapper {
} }
public XContentMapper.Builder findTemplateBuilder(ParseContext context, String name, String dynamicType) { 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) { if (dynamicTemplate == null) {
return 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); 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) { for (DynamicTemplate dynamicTemplate : dynamicTemplates) {
if (dynamicTemplate.match(name, dynamicType)) { if (dynamicTemplate.match(path, name, dynamicType)) {
return dynamicTemplate; return dynamicTemplate;
} }
} }

View File

@ -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));
}
}

View File

@ -0,0 +1,10 @@
{
"_id" : "1",
"name" : "top_level",
"obj1" : {
"name" : "obj1_level",
"obj2" : {
"name" : "obj2_level"
}
}
}

View File

@ -0,0 +1,22 @@
{
"person" : {
"dynamic_templates" : [
{
"template_1" : {
"path_match" : "obj1.obj2.*",
"mapping" : {
"store" : "no"
}
}
},
{
"template_2" : {
"path_match" : "obj1.*",
"mapping" : {
"store" : "yes"
}
}
}
]
}
}