Mapper: Dynamic Template Support, closes #397.

This commit is contained in:
kimchy 2010-10-01 16:56:04 +02:00
parent c657c7c6d0
commit 99fcfde307
13 changed files with 466 additions and 36 deletions

View File

@ -140,6 +140,7 @@
<w>unboxed</w>
<w>unicast</w>
<w>unmanaged</w>
<w>unmatch</w>
<w>unregister</w>
<w>uptime</w>
<w>uuid</w>

View File

@ -29,6 +29,66 @@ import java.util.regex.Pattern;
*/
public class Regex {
/**
* Match a String against the given pattern, supporting the following simple
* pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
* arbitrary number of pattern parts), as well as direct equality.
*
* @param pattern the pattern to match against
* @param str the String to match
* @return whether the String matches the given pattern
*/
public static boolean simpleMatch(String pattern, String str) {
if (pattern == null || str == null) {
return false;
}
int firstIndex = pattern.indexOf('*');
if (firstIndex == -1) {
return pattern.equals(str);
}
if (firstIndex == 0) {
if (pattern.length() == 1) {
return true;
}
int nextIndex = pattern.indexOf('*', firstIndex + 1);
if (nextIndex == -1) {
return str.endsWith(pattern.substring(1));
}
String part = pattern.substring(1, nextIndex);
int partIndex = str.indexOf(part);
while (partIndex != -1) {
if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()))) {
return true;
}
partIndex = str.indexOf(part, partIndex + 1);
}
return false;
}
return (str.length() >= firstIndex &&
pattern.substring(0, firstIndex).equals(str.substring(0, firstIndex)) &&
simpleMatch(pattern.substring(firstIndex), str.substring(firstIndex)));
}
/**
* Match a String against the given patterns, supporting the following simple
* pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
* arbitrary number of pattern parts), as well as direct equality.
*
* @param patterns the patterns to match against
* @param str the String to match
* @return whether the String matches any of the given patterns
*/
public static boolean simpleMatch(String[] patterns, String str) {
if (patterns != null) {
for (String pattern : patterns) {
if (simpleMatch(pattern, str)) {
return true;
}
}
}
return false;
}
public static Pattern compile(String regex, String flags) {
int pFlags = flags == null ? 0 : flagsFromString(flags);
return Pattern.compile(regex, pFlags);

View File

@ -33,6 +33,8 @@ public class ParseContext {
private final XContentDocumentMapper docMapper;
private final XContentDocumentMapperParser docMapperParser;
private final ContentPath path;
private XContentParser parser;
@ -63,9 +65,10 @@ public class ParseContext {
private AllEntries allEntries = new AllEntries();
public ParseContext(String index, XContentDocumentMapper docMapper, ContentPath path) {
public ParseContext(String index, XContentDocumentMapperParser docMapperParser, XContentDocumentMapper docMapper, ContentPath path) {
this.index = index;
this.docMapper = docMapper;
this.docMapperParser = docMapperParser;
this.path = path;
}
@ -81,6 +84,10 @@ public class ParseContext {
this.allEntries = new AllEntries();
}
public XContentDocumentMapperParser docMapperParser() {
return this.docMapperParser;
}
public boolean mappersAdded() {
return this.mappersAdded;
}

View File

@ -133,9 +133,9 @@ public class XContentDocumentMapper implements DocumentMapper, ToXContent {
return searchAnalyzer != null;
}
public XContentDocumentMapper build() {
public XContentDocumentMapper build(XContentDocumentMapperParser docMapperParser) {
Preconditions.checkNotNull(rootObjectMapper, "Mapper builder must have the root object mapper set");
return new XContentDocumentMapper(index, rootObjectMapper, attributes, uidFieldMapper, idFieldMapper, typeFieldMapper, indexFieldMapper,
return new XContentDocumentMapper(index, docMapperParser, rootObjectMapper, attributes, uidFieldMapper, idFieldMapper, typeFieldMapper, indexFieldMapper,
sourceFieldMapper, allFieldMapper, indexAnalyzer, searchAnalyzer, boostFieldMapper);
}
}
@ -143,7 +143,7 @@ public class XContentDocumentMapper implements DocumentMapper, ToXContent {
private ThreadLocal<ThreadLocals.CleanableValue<ParseContext>> cache = new ThreadLocal<ThreadLocals.CleanableValue<ParseContext>>() {
@Override protected ThreadLocals.CleanableValue<ParseContext> initialValue() {
return new ThreadLocals.CleanableValue<ParseContext>(new ParseContext(index, XContentDocumentMapper.this, new ContentPath(0)));
return new ThreadLocals.CleanableValue<ParseContext>(new ParseContext(index, docMapperParser, XContentDocumentMapper.this, new ContentPath(0)));
}
};
@ -151,6 +151,8 @@ public class XContentDocumentMapper implements DocumentMapper, ToXContent {
private final String type;
private final XContentDocumentMapperParser docMapperParser;
private volatile ImmutableMap<String, Object> attributes;
private volatile CompressedString mappingSource;
@ -183,7 +185,8 @@ public class XContentDocumentMapper implements DocumentMapper, ToXContent {
private final Object mutex = new Object();
public XContentDocumentMapper(String index, XContentObjectMapper rootObjectMapper,
public XContentDocumentMapper(String index, XContentDocumentMapperParser docMapperParser,
XContentObjectMapper rootObjectMapper,
ImmutableMap<String, Object> attributes,
XContentUidFieldMapper uidFieldMapper,
XContentIdFieldMapper idFieldMapper,
@ -195,6 +198,7 @@ public class XContentDocumentMapper implements DocumentMapper, ToXContent {
@Nullable XContentBoostFieldMapper boostFieldMapper) {
this.index = index;
this.type = rootObjectMapper.name();
this.docMapperParser = docMapperParser;
this.attributes = attributes;
this.rootObjectMapper = rootObjectMapper;
this.uidFieldMapper = uidFieldMapper;

View File

@ -90,6 +90,10 @@ public class XContentDocumentMapperParser extends AbstractIndexComponent impleme
}
}
public XContentTypeParser.ParserContext parserContext() {
return new XContentTypeParser.ParserContext(analysisService, typeParsers);
}
@Override public XContentDocumentMapper parse(String source) throws MapperParsingException {
return parse(null, source);
}
@ -120,7 +124,7 @@ public class XContentDocumentMapperParser extends AbstractIndexComponent impleme
}
}
XContentTypeParser.ParserContext parserContext = new XContentTypeParser.ParserContext(mapping, analysisService, typeParsers);
XContentTypeParser.ParserContext parserContext = new XContentTypeParser.ParserContext(analysisService, typeParsers);
XContentDocumentMapper.Builder docBuilder = doc(index.name(), (XContentObjectMapper.Builder) rootObjectTypeParser.parse(type, mapping, parserContext));
@ -165,7 +169,7 @@ public class XContentDocumentMapperParser extends AbstractIndexComponent impleme
}
docBuilder.attributes(attributes);
XContentDocumentMapper documentMapper = docBuilder.build();
XContentDocumentMapper documentMapper = docBuilder.build(this);
// update the source with the generated one
documentMapper.refreshSource();
return documentMapper;

View File

@ -0,0 +1,147 @@
/*
* 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;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.collect.Maps;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.index.mapper.MapperParsingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author kimchy (shay.banon)
*/
public class XContentDynamicTemplate {
public static enum MatchType {
SIMPLE,
REGEX;
public static MatchType fromString(String value) {
if ("simple".equals(value)) {
return SIMPLE;
} else if ("regex".equals(value)) {
return REGEX;
}
throw new ElasticSearchIllegalArgumentException("No matching pattern matched on [" + value + "]");
}
}
private final String match;
private final String unmatch;
private final MatchType matchType;
private final String matchMappingType;
private final Map<String, Object> mapping;
public static XContentDynamicTemplate parse(Map<String, Object> conf) throws MapperParsingException {
if (!conf.containsKey("match")) {
throw new MapperParsingException("template must have match set");
}
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")) {
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 XContentDynamicTemplate(match, unmatch, matchMappingType, MatchType.fromString(matchType), mapping);
}
public XContentDynamicTemplate(String match, String unmatch, String matchMappingType, MatchType matchType, Map<String, Object> mapping) {
this.match = match;
this.unmatch = unmatch;
this.matchType = matchType;
this.matchMappingType = matchMappingType;
this.mapping = mapping;
}
public boolean match(String name, String suggestedMappingType) {
if (!patternMatch(match, name)) {
return false;
}
if (patternMatch(unmatch, name)) {
return false;
}
if (matchMappingType != null) {
if (suggestedMappingType == null) {
return false;
}
if (!patternMatch(matchMappingType, suggestedMappingType)) {
return false;
}
}
return true;
}
public String mappingType() {
return mapping.containsKey("type") ? mapping.get("type").toString() : "object";
}
private boolean patternMatch(String pattern, String str) {
if (matchType == MatchType.SIMPLE) {
return Regex.simpleMatch(pattern, str);
}
return str.matches(pattern);
}
public Map<String, Object> mappingForName(String name) {
return processMap(mapping, name);
}
private Map<String, Object> processMap(Map<String, Object> map, String name) {
Map<String, Object> processedMap = Maps.newHashMap();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey().replace("{name}", name);
Object value = entry.getValue();
if (value instanceof Map) {
value = processMap((Map<String, Object>) value, name);
} else if (value instanceof List) {
value = processList((List) value, name);
} else if (value instanceof String) {
value = value.toString().replace("{name}", name);
}
processedMap.put(key, value);
}
return processedMap;
}
private List processList(List list, String name) {
List processedList = new ArrayList();
for (Object value : list) {
if (value instanceof Map) {
value = processMap((Map<String, Object>) value, name);
} else if (value instanceof List) {
value = processList((List) value, name);
} else if (value instanceof String) {
value = value.toString().replace("{name}", name);
}
processedList.add(value);
}
return processedList;
}
}

View File

@ -74,6 +74,8 @@ public class XContentObjectMapper implements XContentMapper, XContentIncludeInAl
private final List<XContentMapper.Builder> mappersBuilders = newArrayList();
private final List<XContentDynamicTemplate> dynamicTemplates = newArrayList();
public Builder(String name) {
super(name);
this.builder = this;
@ -121,6 +123,11 @@ public class XContentObjectMapper implements XContentMapper, XContentIncludeInAl
return this;
}
public Builder add(XContentDynamicTemplate dynamicTemplate) {
this.dynamicTemplates.add(dynamicTemplate);
return this;
}
public Builder add(XContentMapper.Builder builder) {
mappersBuilders.add(builder);
return this;
@ -144,7 +151,7 @@ public class XContentObjectMapper implements XContentMapper, XContentIncludeInAl
}
XContentObjectMapper objectMapper = new XContentObjectMapper(name, enabled, dynamic, pathType,
dateTimeFormatters.toArray(new FormatDateTimeFormatter[dateTimeFormatters.size()]),
mappers);
mappers, dynamicTemplates.toArray(new XContentDynamicTemplate[dynamicTemplates.size()]));
context.path().pathType(origPathType);
context.path().remove();
@ -171,6 +178,19 @@ public class XContentObjectMapper implements XContentMapper, XContentIncludeInAl
if (!type.equals("object")) {
throw new MapperParsingException("Trying to parse an object but has a different type [" + type + "] for [" + name + "]");
}
} else if (fieldName.equals("dynamic_templates")) {
// "dynamic_templates" : [
// {
// "match" : "*_test",
// "match_mapping_type" : "string",
// "mapping" : { "type" : "string", "store" : "yes" }
// }
// ]
List tmplNodes = (List) fieldNode;
for (Object tmplNode : tmplNodes) {
Map<String, Object> tmpl = (Map<String, Object>) tmplNode;
builder.add(XContentDynamicTemplate.parse(tmpl));
}
} else if (fieldName.equals("date_formats")) {
List<FormatDateTimeFormatter> dateTimeFormatters = newArrayList();
if (fieldNode instanceof List) {
@ -243,6 +263,8 @@ public class XContentObjectMapper implements XContentMapper, XContentIncludeInAl
private volatile ImmutableMap<String, XContentMapper> mappers = ImmutableMap.of();
private volatile XContentDynamicTemplate dynamicTemplates[];
private final Object mutex = new Object();
protected XContentObjectMapper(String name) {
@ -255,16 +277,17 @@ public class XContentObjectMapper implements XContentMapper, XContentIncludeInAl
protected XContentObjectMapper(String name, boolean enabled, boolean dynamic, ContentPath.Type pathType,
FormatDateTimeFormatter[] dateTimeFormatters) {
this(name, enabled, dynamic, pathType, dateTimeFormatters, null);
this(name, enabled, dynamic, pathType, dateTimeFormatters, null, null);
}
XContentObjectMapper(String name, boolean enabled, boolean dynamic, ContentPath.Type pathType,
FormatDateTimeFormatter[] dateTimeFormatters, Map<String, XContentMapper> mappers) {
FormatDateTimeFormatter[] dateTimeFormatters, Map<String, XContentMapper> mappers, XContentDynamicTemplate dynamicTemplates[]) {
this.name = name;
this.enabled = enabled;
this.dynamic = dynamic;
this.pathType = pathType;
this.dateTimeFormatters = dateTimeFormatters;
this.dynamicTemplates = dynamicTemplates == null ? new XContentDynamicTemplate[0] : dynamicTemplates;
if (mappers != null) {
this.mappers = copyOf(mappers);
}
@ -369,8 +392,12 @@ public class XContentObjectMapper implements XContentMapper, XContentIncludeInAl
objectMapper.parse(context);
} else {
BuilderContext builderContext = new BuilderContext(context.path());
objectMapper = XContentMapperBuilders.object(currentFieldName).enabled(true)
.dynamic(dynamic).pathType(pathType).dateTimeFormatter(dateTimeFormatters).build(builderContext);
XContentMapper.Builder builder = findTemplateBuilder(context, currentFieldName, "object");
if (builder == null) {
builder = XContentMapperBuilders.object(currentFieldName).enabled(true)
.dynamic(dynamic).pathType(pathType).dateTimeFormatter(dateTimeFormatters);
}
objectMapper = builder.build(builderContext);
putMapper(objectMapper);
objectMapper.parse(context);
context.addedMapper();
@ -408,7 +435,7 @@ public class XContentObjectMapper implements XContentMapper, XContentIncludeInAl
}
}
private void serializeValue(ParseContext context, String currentFieldName, XContentParser.Token token) throws IOException {
private void serializeValue(final ParseContext context, String currentFieldName, XContentParser.Token token) throws IOException {
XContentMapper mapper = mappers.get(currentFieldName);
if (mapper != null) {
mapper.parse(context);
@ -437,7 +464,11 @@ public class XContentObjectMapper implements XContentMapper, XContentIncludeInAl
for (FormatDateTimeFormatter dateTimeFormatter : dateTimeFormatters) {
try {
dateTimeFormatter.parser().parseMillis(text);
mapper = dateField(currentFieldName).dateTimeFormatter(dateTimeFormatter).build(builderContext);
XContentMapper.Builder builder = findTemplateBuilder(context, currentFieldName, "date");
if (builder == null) {
builder = dateField(currentFieldName).dateTimeFormatter(dateTimeFormatter);
}
mapper = builder.build(builderContext);
isDate = true;
break;
} catch (Exception e) {
@ -446,41 +477,100 @@ public class XContentObjectMapper implements XContentMapper, XContentIncludeInAl
}
}
if (!isDate) {
mapper = stringField(currentFieldName).build(builderContext);
XContentMapper.Builder builder = findTemplateBuilder(context, currentFieldName, "string");
if (builder == null) {
builder = stringField(currentFieldName);
}
mapper = builder.build(builderContext);
}
} else if (token == XContentParser.Token.VALUE_NUMBER) {
XContentParser.NumberType numberType = context.parser().numberType();
if (numberType == XContentParser.NumberType.INT) {
if (context.parser().estimatedNumberType()) {
mapper = longField(currentFieldName).build(builderContext);
XContentMapper.Builder builder = findTemplateBuilder(context, currentFieldName, "long");
if (builder == null) {
builder = longField(currentFieldName);
}
mapper = builder.build(builderContext);
} else {
mapper = integerField(currentFieldName).build(builderContext);
XContentMapper.Builder builder = findTemplateBuilder(context, currentFieldName, "integer");
if (builder == null) {
builder = integerField(currentFieldName);
}
mapper = builder.build(builderContext);
}
} else if (numberType == XContentParser.NumberType.LONG) {
mapper = longField(currentFieldName).build(builderContext);
XContentMapper.Builder builder = findTemplateBuilder(context, currentFieldName, "long");
if (builder == null) {
builder = longField(currentFieldName);
}
mapper = builder.build(builderContext);
} else if (numberType == XContentParser.NumberType.FLOAT) {
if (context.parser().estimatedNumberType()) {
mapper = doubleField(currentFieldName).build(builderContext);
XContentMapper.Builder builder = findTemplateBuilder(context, currentFieldName, "double");
if (builder == null) {
builder = doubleField(currentFieldName);
}
mapper = builder.build(builderContext);
} else {
mapper = floatField(currentFieldName).build(builderContext);
XContentMapper.Builder builder = findTemplateBuilder(context, currentFieldName, "float");
if (builder == null) {
builder = floatField(currentFieldName);
}
mapper = builder.build(builderContext);
}
} else if (numberType == XContentParser.NumberType.DOUBLE) {
mapper = doubleField(currentFieldName).build(builderContext);
XContentMapper.Builder builder = findTemplateBuilder(context, currentFieldName, "double");
if (builder == null) {
builder = doubleField(currentFieldName);
}
mapper = builder.build(builderContext);
}
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
mapper = booleanField(currentFieldName).build(builderContext);
XContentMapper.Builder builder = findTemplateBuilder(context, currentFieldName, "boolean");
if (builder == null) {
builder = booleanField(currentFieldName);
}
mapper = builder.build(builderContext);
} else {
// TODO how do we identify dynamically that its a binary value?
throw new ElasticSearchIllegalStateException("Can't handle serializing a dynamic type with content token [" + token + "] and field name [" + currentFieldName + "]");
XContentMapper.Builder builder = findTemplateBuilder(context, currentFieldName, null);
if (builder != null) {
mapper = builder.build(builderContext);
} else {
// TODO how do we identify dynamically that its a binary value?
throw new ElasticSearchIllegalStateException("Can't handle serializing a dynamic type with content token [" + token + "] and field name [" + currentFieldName + "]");
}
}
putMapper(mapper);
context.docMapper().addFieldMapper((FieldMapper) mapper);
mapper.traverse(new FieldMapperListener() {
@Override public void fieldMapper(FieldMapper fieldMapper) {
context.docMapper().addFieldMapper(fieldMapper);
}
});
mapper.parse(context);
context.addedMapper();
}
}
private XContentMapper.Builder findTemplateBuilder(ParseContext context, String name, String mappingType) {
XContentDynamicTemplate dynamicTemplate = findTemplate(name, mappingType);
if (dynamicTemplate == null) {
return null;
}
XContentTypeParser.ParserContext parserContext = context.docMapperParser().parserContext();
return parserContext.typeParser(dynamicTemplate.mappingType()).parse(name, dynamicTemplate.mappingForName(name), parserContext);
}
private XContentDynamicTemplate findTemplate(String name, String mappingType) {
for (XContentDynamicTemplate dynamicTemplate : dynamicTemplates) {
if (dynamicTemplate.match(name, mappingType)) {
return dynamicTemplate;
}
}
return null;
}
@Override public void merge(XContentMapper mergeWith, MergeContext mergeContext) throws MergeMappingException {
if (!(mergeWith instanceof XContentObjectMapper)) {
mergeContext.addConflict("Can't merge a non object mapping [" + mergeWith.name() + "] with an object mapping [" + name() + "]");

View File

@ -35,13 +35,10 @@ public interface XContentTypeParser {
private final AnalysisService analysisService;
private final Map<String, Object> rootNode;
private final ImmutableMap<String, XContentTypeParser> typeParsers;
public ParserContext(Map<String, Object> rootNode, AnalysisService analysisService, ImmutableMap<String, XContentTypeParser> typeParsers) {
public ParserContext(AnalysisService analysisService, ImmutableMap<String, XContentTypeParser> typeParsers) {
this.analysisService = analysisService;
this.rootNode = rootNode;
this.typeParsers = typeParsers;
}
@ -49,10 +46,6 @@ public interface XContentTypeParser {
return analysisService;
}
public Map<String, Object> rootNode() {
return this.rootNode;
}
public XContentTypeParser typeParser(String type) {
return typeParsers.get(Strings.toUnderscoreCase(type));
}

View File

@ -0,0 +1,89 @@
/*
* 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.simple;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.elasticsearch.index.mapper.FieldMappers;
import org.elasticsearch.index.mapper.xcontent.XContentDocumentMapper;
import org.elasticsearch.index.mapper.xcontent.XContentMapperTests;
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 SimpleDynamicTemplatesTests {
@Test public void testSimple() throws Exception {
String mapping = copyToStringFromClasspath("/org/elasticsearch/index/mapper/xcontent/dynamictemplate/simple/test-mapping.json");
XContentDocumentMapper docMapper = XContentMapperTests.newParser().parse(mapping);
byte[] json = copyToBytesFromClasspath("/org/elasticsearch/index/mapper/xcontent/dynamictemplate/simple/test-data.json");
Document doc = docMapper.parse(json).doc();
Field f = doc.getField("name");
assertThat(f.name(), equalTo("name"));
assertThat(f.stringValue(), equalTo("some name"));
assertThat(f.isIndexed(), equalTo(true));
assertThat(f.isTokenized(), equalTo(false));
FieldMappers fieldMappers = docMapper.mappers().fullName("name");
assertThat(fieldMappers.mappers().size(), equalTo(1));
f = doc.getField("multi1");
assertThat(f.name(), equalTo("multi1"));
assertThat(f.stringValue(), equalTo("multi 1"));
assertThat(f.isIndexed(), equalTo(true));
assertThat(f.isTokenized(), equalTo(true));
fieldMappers = docMapper.mappers().fullName("multi1");
assertThat(fieldMappers.mappers().size(), equalTo(1));
f = doc.getField("multi1.org");
assertThat(f.name(), equalTo("multi1.org"));
assertThat(f.stringValue(), equalTo("multi 1"));
assertThat(f.isIndexed(), equalTo(true));
assertThat(f.isTokenized(), equalTo(false));
fieldMappers = docMapper.mappers().fullName("multi1.org");
assertThat(fieldMappers.mappers().size(), equalTo(1));
f = doc.getField("multi2");
assertThat(f.name(), equalTo("multi2"));
assertThat(f.stringValue(), equalTo("multi 2"));
assertThat(f.isIndexed(), equalTo(true));
assertThat(f.isTokenized(), equalTo(true));
fieldMappers = docMapper.mappers().fullName("multi2");
assertThat(fieldMappers.mappers().size(), equalTo(1));
f = doc.getField("multi2.org");
assertThat(f.name(), equalTo("multi2.org"));
assertThat(f.stringValue(), equalTo("multi 2"));
assertThat(f.isIndexed(), equalTo(true));
assertThat(f.isTokenized(), equalTo(false));
fieldMappers = docMapper.mappers().fullName("multi2.org");
assertThat(fieldMappers.mappers().size(), equalTo(1));
}
}

View File

@ -0,0 +1,7 @@
{
"_id" : "1",
"name" : "some name",
"age" : 1,
"multi1" : "multi 1",
"multi2" : "multi 2"
}

View File

@ -0,0 +1,24 @@
{
"person" : {
"dynamic_templates" : [
{
"match" : "multi*",
"mapping" : {
"type" : "multi_field",
"fields" : {
"{name}" : {"type": "string", "index" : "analyzed", "store" : "yes"},
"org" : {"type": "string", "index" : "not_analyzed", "store" : "yes"}
}
}
},
{
"match" : "*",
"match_mapping_type" : "string",
"mapping" : {
"type" : "string",
"index" : "not_analyzed"
}
}
]
}
}

View File

@ -22,6 +22,7 @@ package org.elasticsearch.index.mapper.xcontent.multifield;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.elasticsearch.index.mapper.xcontent.XContentDocumentMapper;
import org.elasticsearch.index.mapper.xcontent.XContentDocumentMapperParser;
import org.elasticsearch.index.mapper.xcontent.XContentMapperTests;
import org.testng.annotations.Test;
@ -69,18 +70,19 @@ public class XContentMultiFieldTests {
}
@Test public void testBuildThenParse() throws Exception {
XContentDocumentMapperParser mapperParser = XContentMapperTests.newParser();
XContentDocumentMapper builderDocMapper = doc("test", object("person").add(
multiField("name")
.add(stringField("name").store(Field.Store.YES))
.add(stringField("indexed").index(Field.Index.ANALYZED))
.add(stringField("not_indexed").index(Field.Index.NO).store(Field.Store.YES))
)).build();
)).build(mapperParser);
builderDocMapper.refreshSource();
String builtMapping = builderDocMapper.mappingSource().string();
// System.out.println(builtMapping);
// reparse it
XContentDocumentMapper docMapper = XContentMapperTests.newParser().parse(builtMapping);
XContentDocumentMapper docMapper = mapperParser.parse(builtMapping);
byte[] json = copyToBytesFromClasspath("/org/elasticsearch/index/mapper/xcontent/multifield/test-data.json");

View File

@ -23,6 +23,7 @@ import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.mapper.xcontent.XContentDocumentMapper;
import org.elasticsearch.index.mapper.xcontent.XContentDocumentMapperParser;
import org.elasticsearch.index.mapper.xcontent.XContentMapperTests;
import org.testng.annotations.Test;
@ -38,10 +39,11 @@ import static org.hamcrest.Matchers.*;
public class SimpleXContentMapperTests {
@Test public void testSimpleMapper() throws Exception {
XContentDocumentMapperParser mapperParser = XContentMapperTests.newParser();
XContentDocumentMapper docMapper = doc("test",
object("person")
.add(object("name").add(stringField("first").store(YES).index(Field.Index.NO)))
).sourceField(source()).build();
).sourceField(source()).build(mapperParser);
byte[] json = copyToBytesFromClasspath("/org/elasticsearch/index/mapper/xcontent/simple/test1.json");
Document doc = docMapper.parse("person", "1", json).doc();