Mappings: Support dots in field names in mapping parsing

This change adds support for treating dots in field names found in
mappings as path separators, like was previously done for dynamic
mappings and document parsing.

closes #19443
This commit is contained in:
Ryan Ernst 2016-08-09 14:35:35 -07:00
parent 6efbe54255
commit 38d4382565
2 changed files with 48 additions and 20 deletions

View File

@ -153,6 +153,10 @@ public class ObjectMapper extends Mapper implements AllFieldMapper.IncludeInAll,
Map<String, Mapper> mappers = new HashMap<>();
for (Mapper.Builder builder : mappersBuilders) {
Mapper mapper = builder.build(context);
Mapper existing = mappers.get(mapper.simpleName());
if (existing != null) {
mapper = existing.merge(mapper, false);
}
mappers.put(mapper.simpleName(), mapper);
}
context.path().remove();
@ -248,9 +252,6 @@ public class ObjectMapper extends Mapper implements AllFieldMapper.IncludeInAll,
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
String fieldName = entry.getKey();
if (fieldName.contains(".")) {
throw new MapperParsingException("Field name [" + fieldName + "] cannot contain '.'");
}
// Should accept empty arrays, as a work around for when the
// user can't provide an empty Map. (PHP for example)
boolean isEmptyList = entry.getValue() instanceof List && ((List<?>) entry.getValue()).isEmpty();
@ -281,7 +282,15 @@ public class ObjectMapper extends Mapper implements AllFieldMapper.IncludeInAll,
if (typeParser == null) {
throw new MapperParsingException("No handler for type [" + type + "] declared on field [" + fieldName + "]");
}
objBuilder.add(typeParser.parse(fieldName, propNode, parserContext));
String[] fieldNameParts = fieldName.split("\\.");
String realFieldName = fieldNameParts[fieldNameParts.length - 1];
Mapper.Builder<?,?> fieldBuilder = typeParser.parse(realFieldName, propNode, parserContext);
for (int i = fieldNameParts.length - 2; i >= 0; --i) {
ObjectMapper.Builder<?, ?> intermediate = new ObjectMapper.Builder<>(fieldNameParts[i]);
intermediate.add(fieldBuilder);
fieldBuilder = intermediate;
}
objBuilder.add(fieldBuilder);
propNode.remove("type");
DocumentMapperParser.checkNoRemainingFields(fieldName, propNode, parserContext.indexVersionCreated());
iterator.remove();

View File

@ -40,9 +40,6 @@ import static org.elasticsearch.test.StreamsUtils.copyToBytesFromClasspath;
import static org.elasticsearch.test.StreamsUtils.copyToStringFromClasspath;
import static org.hamcrest.Matchers.equalTo;
/**
*
*/
public class SimpleMapperTests extends ESSingleNodeTestCase {
public void testSimpleMapper() throws Exception {
IndexService indexService = createIndex("test");
@ -112,25 +109,47 @@ public class SimpleMapperTests extends ESSingleNodeTestCase {
indexService.mapperService()).build(indexService.mapperService());
BytesReference json = new BytesArray("".getBytes(StandardCharsets.UTF_8));
try {
docMapper.parse("test", "person", "1", json).rootDoc();
fail("this point is never reached");
} catch (MapperParsingException e) {
assertThat(e.getMessage(), equalTo("failed to parse, document is empty"));
}
MapperParsingException e = expectThrows(MapperParsingException.class, () ->
docMapper.parse("test", "person", "1", json).rootDoc());
assertThat(e.getMessage(), equalTo("failed to parse, document is empty"));
}
public void testHazardousFieldNames() throws Exception {
public void testFieldNameWithDots() throws Exception {
IndexService indexService = createIndex("test");
DocumentMapperParser mapperParser = indexService.mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties")
.startObject("foo.bar").field("type", "text").endObject()
.startObject("foo.baz").field("type", "keyword").endObject()
.endObject().endObject().endObject().string();
try {
mapperParser.parse("type", new CompressedXContent(mapping));
fail("Mapping parse should have failed");
} catch (MapperParsingException e) {
assertTrue(e.getMessage(), e.getMessage().contains("cannot contain '.'"));
}
DocumentMapper docMapper = mapperParser.parse("type", new CompressedXContent(mapping));
assertNotNull(docMapper.mappers().getMapper("foo.bar"));
assertNotNull(docMapper.mappers().getMapper("foo.baz"));
assertNotNull(docMapper.objectMappers().get("foo"));
}
public void testFieldNameWithDeepDots() throws Exception {
IndexService indexService = createIndex("test");
DocumentMapperParser mapperParser = indexService.mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties")
.startObject("foo.bar").field("type", "text").endObject()
.startObject("foo.baz").startObject("properties")
.startObject("deep.field").field("type", "keyword").endObject().endObject()
.endObject().endObject().endObject().endObject().string();
DocumentMapper docMapper = mapperParser.parse("type", new CompressedXContent(mapping));
assertNotNull(docMapper.mappers().getMapper("foo.bar"));
assertNotNull(docMapper.mappers().getMapper("foo.baz.deep.field"));
assertNotNull(docMapper.objectMappers().get("foo"));
}
public void testFieldNameWithDotsConflict() throws Exception {
IndexService indexService = createIndex("test");
DocumentMapperParser mapperParser = indexService.mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties")
.startObject("foo").field("type", "text").endObject()
.startObject("foo.baz").field("type", "keyword").endObject()
.endObject().endObject().endObject().string();
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () ->
mapperParser.parse("type", new CompressedXContent(mapping)));
assertTrue(e.getMessage(), e.getMessage().contains("mapper [foo] of different type"));
}
}