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