Adds a methods to find (and dynamically create) the mappers for the parents of a field with dots in the field name

This commit is contained in:
Colin Goodheart-Smithe 2016-04-27 09:54:16 +01:00
parent 897fe9108a
commit ab3121c871
2 changed files with 271 additions and 52 deletions

View File

@ -23,6 +23,7 @@ import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexableField;
import org.elasticsearch.Version;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
@ -454,19 +455,24 @@ final class DocumentParser {
private static ObjectMapper parseObject(final ParseContext context, ObjectMapper mapper, String currentFieldName) throws IOException {
assert currentFieldName != null;
context.path().add(currentFieldName);
ObjectMapper update = null;
Mapper objectMapper = getMapper(mapper, currentFieldName);
if (objectMapper != null) {
context.path().add(currentFieldName);
parseObjectOrField(context, objectMapper);
context.path().remove();
} else {
ObjectMapper.Dynamic dynamic = dynamicOrDefault(mapper, context);
// TODO: why Strings.splitStringToArray instead of String.split?
final String[] paths = Strings.splitStringToArray(currentFieldName, '.');
currentFieldName = paths[paths.length - 1];
Tuple<Integer, ObjectMapper> parentMapperTuple = getDynamicParentMapper(context, paths, mapper);
ObjectMapper parentMapper = parentMapperTuple.v2();
ObjectMapper.Dynamic dynamic = dynamicOrDefault(parentMapper, context);
if (dynamic == ObjectMapper.Dynamic.STRICT) {
throw new StrictDynamicMappingException(mapper.fullPath(), currentFieldName);
} else if (dynamic == ObjectMapper.Dynamic.TRUE) {
// remove the current field name from path, since template search and the object builder add it as well...
context.path().remove();
Mapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "object");
if (builder == null) {
builder = new ObjectMapper.Builder(currentFieldName).enabled(true);
@ -476,13 +482,16 @@ final class DocumentParser {
context.addDynamicMapper(objectMapper);
context.path().add(currentFieldName);
parseObjectOrField(context, objectMapper);
context.path().remove();
} else {
// not dynamic, read everything up to end object
context.parser().skipChildren();
}
for (int i = 0; i < parentMapperTuple.v1(); i++) {
context.path().remove();
}
}
context.path().remove();
return update;
}
@ -500,6 +509,12 @@ final class DocumentParser {
}
} else {
// TODO: why Strings.splitStringToArray instead of String.split?
final String[] paths = Strings.splitStringToArray(arrayFieldName, '.');
arrayFieldName = paths[paths.length - 1];
lastFieldName = arrayFieldName;
Tuple<Integer, ObjectMapper> parentMapperTuple = getDynamicParentMapper(context, paths, parentMapper);
parentMapper = parentMapperTuple.v2();
ObjectMapper.Dynamic dynamic = dynamicOrDefault(parentMapper, context);
if (dynamic == ObjectMapper.Dynamic.STRICT) {
throw new StrictDynamicMappingException(parentMapper.fullPath(), arrayFieldName);
@ -507,23 +522,26 @@ final class DocumentParser {
Mapper.Builder builder = context.root().findTemplateBuilder(context, arrayFieldName, "object");
if (builder == null) {
parseNonDynamicArray(context, parentMapper, lastFieldName, arrayFieldName);
return;
}
Mapper.BuilderContext builderContext = new Mapper.BuilderContext(context.indexSettings(), context.path());
mapper = builder.build(builderContext);
assert mapper != null;
if (mapper instanceof ArrayValueMapperParser) {
context.addDynamicMapper(mapper);
context.path().add(arrayFieldName);
parseObjectOrField(context, mapper);
context.path().remove();
} else {
parseNonDynamicArray(context, parentMapper, lastFieldName, arrayFieldName);
Mapper.BuilderContext builderContext = new Mapper.BuilderContext(context.indexSettings(), context.path());
mapper = builder.build(builderContext);
assert mapper != null;
if (mapper instanceof ArrayValueMapperParser) {
context.addDynamicMapper(mapper);
context.path().add(arrayFieldName);
parseObjectOrField(context, mapper);
context.path().remove();
} else {
parseNonDynamicArray(context, parentMapper, lastFieldName, arrayFieldName);
}
}
} else {
// TODO: shouldn't this skip, not parse?
parseNonDynamicArray(context, parentMapper, lastFieldName, arrayFieldName);
}
for (int i = 0; i < parentMapperTuple.v1(); i++) {
context.path().remove();
}
}
}
@ -555,7 +573,16 @@ final class DocumentParser {
if (mapper != null) {
parseObjectOrField(context, mapper);
} else {
// TODO: why Strings.splitStringToArray instead of String.split?
final String[] paths = Strings.splitStringToArray(currentFieldName, '.');
currentFieldName = paths[paths.length - 1];
Tuple<Integer, ObjectMapper> parentMapperTuple = getDynamicParentMapper(context, paths, parentMapper);
parentMapper = parentMapperTuple.v2();
parseDynamicValue(context, parentMapper, currentFieldName, token);
for (int i = 0; i < parentMapperTuple.v1(); i++) {
context.path().remove();
}
}
}
@ -814,46 +841,58 @@ final class DocumentParser {
final String[] paths = field.split("\\.");
final String fieldName = paths[paths.length-1];
ObjectMapper mapper = context.root();
ObjectMapper[] mappers = new ObjectMapper[paths.length-1];
if (paths.length > 1) {
ObjectMapper parent = context.root();
for (int i = 0; i < paths.length-1; i++) {
mapper = context.docMapper().objectMappers().get(context.path().pathAsText(paths[i]));
if (mapper == null) {
// One mapping is missing, check if we are allowed to create a dynamic one.
ObjectMapper.Dynamic dynamic = dynamicOrDefault(parent, context);
switch (dynamic) {
case STRICT:
throw new StrictDynamicMappingException(parent.fullPath(), paths[i]);
case TRUE:
Mapper.Builder builder = context.root().findTemplateBuilder(context, paths[i], "object");
if (builder == null) {
builder = new ObjectMapper.Builder(paths[i]).enabled(true);
}
Mapper.BuilderContext builderContext = new Mapper.BuilderContext(context.indexSettings(), context.path());
mapper = (ObjectMapper) builder.build(builderContext);
if (mapper.nested() != ObjectMapper.Nested.NO) {
throw new MapperParsingException("It is forbidden to create dynamic nested objects ([" + context.path().pathAsText(paths[i]) + "]) through `copy_to`");
}
context.addDynamicMapper(mapper);
break;
case FALSE:
// Maybe we should log something to tell the user that the copy_to is ignored in this case.
break;
}
}
context.path().add(paths[i]);
mappers[i] = mapper;
parent = mapper;
}
}
Tuple<Integer, ObjectMapper> parentMapperTuple = getDynamicParentMapper(context, paths, null);
ObjectMapper mapper = parentMapperTuple.v2();
parseDynamicValue(context, mapper, fieldName, context.parser().currentToken());
for (int i = 0; i < parentMapperTuple.v1(); i++) {
context.path().remove();
}
}
}
private static Tuple<Integer, ObjectMapper> getDynamicParentMapper(ParseContext context, final String[] paths,
ObjectMapper currentParent) {
ObjectMapper mapper = currentParent == null ? context.root() : currentParent;
ObjectMapper[] mappers = new ObjectMapper[paths.length-1];
int pathsAdded = 0;
if (paths.length > 1) {
ObjectMapper parent = mapper;
for (int i = 0; i < paths.length-1; i++) {
mapper = context.docMapper().objectMappers().get(context.path().pathAsText(paths[i]));
if (mapper == null) {
// One mapping is missing, check if we are allowed to create a dynamic one.
ObjectMapper.Dynamic dynamic = dynamicOrDefault(parent, context);
switch (dynamic) {
case STRICT:
throw new StrictDynamicMappingException(parent.fullPath(), paths[i]);
case TRUE:
Mapper.Builder builder = context.root().findTemplateBuilder(context, paths[i], "object");
if (builder == null) {
builder = new ObjectMapper.Builder(paths[i]).enabled(true);
}
Mapper.BuilderContext builderContext = new Mapper.BuilderContext(context.indexSettings(), context.path());
mapper = (ObjectMapper) builder.build(builderContext);
if (mapper.nested() != ObjectMapper.Nested.NO) {
throw new MapperParsingException("It is forbidden to create dynamic nested objects ([" + context.path().pathAsText(paths[i]) + "]) through `copy_to`");
}
context.addDynamicMapper(mapper);
break;
case FALSE:
// Should not dynamically create any more mappers so return the last mapper
return new Tuple<Integer, ObjectMapper>(pathsAdded, parent);
}
}
context.path().add(paths[i]);
pathsAdded++;
mappers[i] = mapper;
parent = mapper;
}
}
return new Tuple<Integer, ObjectMapper>(pathsAdded, mapper);
}
// find what the dynamic setting is given the current parse context and parent
private static ObjectMapper.Dynamic dynamicOrDefault(ObjectMapper parentMapper, ParseContext context) {
ObjectMapper.Dynamic dynamic = parentMapper.dynamic();

View File

@ -497,4 +497,184 @@ public class DocumentParserTests extends ESSingleNodeTestCase {
ParsedDocument doc = mapper.parse("test", "type", "1", bytes);
assertEquals(0, doc.rootDoc().getFields("foo").length);
}
public void testDynamicDottedFieldNameLongArray() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().startArray("foo.bar.baz")
.value(0)
.value(1)
.endArray().endObject().bytes();
ParsedDocument doc = mapper.parse("test", "type", "1", bytes);
assertEquals(4, doc.rootDoc().getFields("foo.bar.baz").length);
}
public void testDynamicDottedFieldNameLongArrayWithParentTemplate() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.startArray("dynamic_templates").startObject().startObject("georule")
.field("match", "foo*")
.startObject("mapping").field("type", "object").endObject()
.endObject().endObject().endArray().endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().startArray("foo.bar.baz")
.value(0)
.value(1)
.endArray().endObject().bytes();
ParsedDocument doc = mapper.parse("test", "type", "1", bytes);
assertEquals(4, doc.rootDoc().getFields("foo.bar.baz").length);
}
public void testDynamicFalseDottedFieldNameLongArray() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").field("dynamic", "false")
.endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().startArray("foo.bar.baz")
.value(0)
.value(1)
.endArray().endObject().bytes();
ParsedDocument doc = mapper.parse("test", "type", "1", bytes);
assertEquals(0, doc.rootDoc().getFields("foo.bar.baz").length);
}
public void testDynamicStrictDottedFieldNameLongArray() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").field("dynamic", "strict")
.endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().startArray("foo.bar.baz")
.value(0)
.value(1)
.endArray().endObject().bytes();
StrictDynamicMappingException exception = expectThrows(StrictDynamicMappingException.class,
() -> mapper.parse("test", "type", "1", bytes));
assertEquals("mapping set to strict, dynamic introduction of [foo] within [type] is not allowed", exception.getMessage());
}
public void testDynamicDottedFieldNameLong() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().field("foo.bar.baz", 0)
.endObject().bytes();
ParsedDocument doc = mapper.parse("test", "type", "1", bytes);
assertEquals(2, doc.rootDoc().getFields("foo.bar.baz").length);
}
public void testDynamicDottedFieldNameLongWithParentTemplate() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.startArray("dynamic_templates").startObject().startObject("georule")
.field("match", "foo*")
.startObject("mapping").field("type", "object").endObject()
.endObject().endObject().endArray().endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().field("foo.bar.baz", 0)
.endObject().bytes();
ParsedDocument doc = mapper.parse("test", "type", "1", bytes);
assertEquals(2, doc.rootDoc().getFields("foo.bar.baz").length);
}
public void testDynamicFalseDottedFieldNameLong() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").field("dynamic", "false")
.endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().field("foo.bar.baz", 0)
.endObject().bytes();
ParsedDocument doc = mapper.parse("test", "type", "1", bytes);
assertEquals(0, doc.rootDoc().getFields("foo.bar.baz").length);
}
public void testDynamicStrictDottedFieldNameLong() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").field("dynamic", "strict")
.endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().field("foo.bar.baz", 0)
.endObject().bytes();
StrictDynamicMappingException exception = expectThrows(StrictDynamicMappingException.class,
() -> mapper.parse("test", "type", "1", bytes));
assertEquals("mapping set to strict, dynamic introduction of [foo] within [type] is not allowed", exception.getMessage());
}
public void testDynamicDottedFieldNameObject() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().startObject("foo.bar.baz")
.field("a", 0)
.endObject().endObject().bytes();
ParsedDocument doc = mapper.parse("test", "type", "1", bytes);
assertEquals(2, doc.rootDoc().getFields("foo.bar.baz.a").length);
}
public void testDynamicDottedFieldNameObjectWithParentTemplate() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.startArray("dynamic_templates").startObject().startObject("georule")
.field("match", "foo*")
.startObject("mapping").field("type", "object").endObject()
.endObject().endObject().endArray().endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().startObject("foo.bar.baz")
.field("a", 0)
.endObject().endObject().bytes();
ParsedDocument doc = mapper.parse("test", "type", "1", bytes);
assertEquals(2, doc.rootDoc().getFields("foo.bar.baz.a").length);
}
public void testDynamicFalseDottedFieldNameObject() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").field("dynamic", "false")
.endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().startObject("foo.bar.baz")
.field("a", 0)
.endObject().endObject().bytes();
ParsedDocument doc = mapper.parse("test", "type", "1", bytes);
assertEquals(0, doc.rootDoc().getFields("foo.bar.baz.a").length);
}
public void testDynamicStrictDottedFieldNameObject() throws Exception {
DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").field("dynamic", "strict")
.endObject().endObject().string();
DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping));
BytesReference bytes = XContentFactory.jsonBuilder()
.startObject().startObject("foo.bar.baz")
.field("a", 0)
.endObject().endObject().bytes();
StrictDynamicMappingException exception = expectThrows(StrictDynamicMappingException.class,
() -> mapper.parse("test", "type", "1", bytes));
assertEquals("mapping set to strict, dynamic introduction of [foo] within [type] is not allowed", exception.getMessage());
}
}