mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-18 19:05:06 +00:00
Strict dynamic setting: Refuse to index a document with fields not present in the mapping definition, closes #643.
This commit is contained in:
parent
3907c8c680
commit
ce4f09c2b1
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kimchy (shay.banon)
|
||||||
|
*/
|
||||||
|
public class StrictDynamicMappingException extends MapperException {
|
||||||
|
|
||||||
|
public StrictDynamicMappingException(String fieldName) {
|
||||||
|
super("mapping set to strict, dynamic introduction of [" + fieldName + "] is not allowed");
|
||||||
|
}
|
||||||
|
}
|
@ -53,15 +53,21 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||||||
|
|
||||||
public static class Defaults {
|
public static class Defaults {
|
||||||
public static final boolean ENABLED = true;
|
public static final boolean ENABLED = true;
|
||||||
public static final boolean DYNAMIC = true;
|
public static final Dynamic DYNAMIC = null; // not set, inherited from father
|
||||||
public static final ContentPath.Type PATH_TYPE = ContentPath.Type.FULL;
|
public static final ContentPath.Type PATH_TYPE = ContentPath.Type.FULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static enum Dynamic {
|
||||||
|
TRUE,
|
||||||
|
FALSE,
|
||||||
|
STRICT
|
||||||
|
}
|
||||||
|
|
||||||
public static class Builder<T extends Builder, Y extends ObjectMapper> extends XContentMapper.Builder<T, Y> {
|
public static class Builder<T extends Builder, Y extends ObjectMapper> extends XContentMapper.Builder<T, Y> {
|
||||||
|
|
||||||
protected boolean enabled = Defaults.ENABLED;
|
protected boolean enabled = Defaults.ENABLED;
|
||||||
|
|
||||||
protected boolean dynamic = Defaults.DYNAMIC;
|
protected Dynamic dynamic = Defaults.DYNAMIC;
|
||||||
|
|
||||||
protected ContentPath.Type pathType = Defaults.PATH_TYPE;
|
protected ContentPath.Type pathType = Defaults.PATH_TYPE;
|
||||||
|
|
||||||
@ -79,7 +85,7 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T dynamic(boolean dynamic) {
|
public T dynamic(Dynamic dynamic) {
|
||||||
this.dynamic = dynamic;
|
this.dynamic = dynamic;
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
@ -119,7 +125,7 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||||||
return (Y) objectMapper;
|
return (Y) objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ObjectMapper createMapper(String name, boolean enabled, boolean dynamic, ContentPath.Type pathType, Map<String, XContentMapper> mappers) {
|
protected ObjectMapper createMapper(String name, boolean enabled, Dynamic dynamic, ContentPath.Type pathType, Map<String, XContentMapper> mappers) {
|
||||||
return new ObjectMapper(name, enabled, dynamic, pathType, mappers);
|
return new ObjectMapper(name, enabled, dynamic, pathType, mappers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,7 +140,12 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||||||
Object fieldNode = entry.getValue();
|
Object fieldNode = entry.getValue();
|
||||||
|
|
||||||
if (fieldName.equals("dynamic")) {
|
if (fieldName.equals("dynamic")) {
|
||||||
builder.dynamic(nodeBooleanValue(fieldNode));
|
String value = fieldNode.toString();
|
||||||
|
if (value.equals("strict")) {
|
||||||
|
builder.dynamic(Dynamic.STRICT);
|
||||||
|
} else {
|
||||||
|
builder.dynamic(nodeBooleanValue(fieldNode) ? Dynamic.TRUE : Dynamic.FALSE);
|
||||||
|
}
|
||||||
} else if (fieldName.equals("type")) {
|
} else if (fieldName.equals("type")) {
|
||||||
String type = fieldNode.toString();
|
String type = fieldNode.toString();
|
||||||
if (!type.equals("object")) {
|
if (!type.equals("object")) {
|
||||||
@ -196,7 +207,7 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||||||
|
|
||||||
private final boolean enabled;
|
private final boolean enabled;
|
||||||
|
|
||||||
private final boolean dynamic;
|
private final Dynamic dynamic;
|
||||||
|
|
||||||
private final ContentPath.Type pathType;
|
private final ContentPath.Type pathType;
|
||||||
|
|
||||||
@ -211,11 +222,11 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected ObjectMapper(String name, boolean enabled, boolean dynamic, ContentPath.Type pathType) {
|
protected ObjectMapper(String name, boolean enabled, Dynamic dynamic, ContentPath.Type pathType) {
|
||||||
this(name, enabled, dynamic, pathType, null);
|
this(name, enabled, dynamic, pathType, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectMapper(String name, boolean enabled, boolean dynamic, ContentPath.Type pathType, Map<String, XContentMapper> mappers) {
|
ObjectMapper(String name, boolean enabled, Dynamic dynamic, ContentPath.Type pathType, Map<String, XContentMapper> mappers) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
this.dynamic = dynamic;
|
this.dynamic = dynamic;
|
||||||
@ -258,6 +269,10 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final Dynamic dynamic() {
|
||||||
|
return this.dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
public void parse(ParseContext context) throws IOException {
|
public void parse(ParseContext context) throws IOException {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
context.parser().skipChildren();
|
context.parser().skipChildren();
|
||||||
@ -315,7 +330,13 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||||||
if (objectMapper != null) {
|
if (objectMapper != null) {
|
||||||
objectMapper.parse(context);
|
objectMapper.parse(context);
|
||||||
} else {
|
} else {
|
||||||
if (dynamic) {
|
Dynamic dynamic = this.dynamic;
|
||||||
|
if (dynamic == null) {
|
||||||
|
dynamic = context.root().dynamic();
|
||||||
|
}
|
||||||
|
if (dynamic == Dynamic.STRICT) {
|
||||||
|
throw new StrictDynamicMappingException(currentFieldName);
|
||||||
|
} else if (dynamic == Dynamic.TRUE) {
|
||||||
// we sync here just so we won't add it twice. Its not the end of the world
|
// we sync here just so we won't add it twice. Its not the end of the world
|
||||||
// to sync here since next operations will get it before
|
// to sync here since next operations will get it before
|
||||||
synchronized (mutex) {
|
synchronized (mutex) {
|
||||||
@ -377,7 +398,14 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||||||
mapper.parse(context);
|
mapper.parse(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!dynamic) {
|
Dynamic dynamic = this.dynamic;
|
||||||
|
if (dynamic == null) {
|
||||||
|
dynamic = context.root().dynamic();
|
||||||
|
}
|
||||||
|
if (dynamic == Dynamic.STRICT) {
|
||||||
|
throw new StrictDynamicMappingException(currentFieldName);
|
||||||
|
}
|
||||||
|
if (dynamic == Dynamic.FALSE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// we sync here since we don't want to add this field twice to the document mapper
|
// we sync here since we don't want to add this field twice to the document mapper
|
||||||
@ -558,9 +586,17 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper {
|
|||||||
if (mappers.isEmpty()) { // only write the object content type if there are no properties, otherwise, it is automatically detected
|
if (mappers.isEmpty()) { // only write the object content type if there are no properties, otherwise, it is automatically detected
|
||||||
builder.field("type", CONTENT_TYPE);
|
builder.field("type", CONTENT_TYPE);
|
||||||
}
|
}
|
||||||
|
// grr, ugly! on root, dynamic defaults to TRUE, on childs, it defaults to null to
|
||||||
|
// inherit the root behavior
|
||||||
|
if (this instanceof RootObjectMapper) {
|
||||||
|
if (dynamic != Dynamic.TRUE) {
|
||||||
|
builder.field("dynamic", dynamic);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (dynamic != Defaults.DYNAMIC) {
|
if (dynamic != Defaults.DYNAMIC) {
|
||||||
builder.field("dynamic", dynamic);
|
builder.field("dynamic", dynamic);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (enabled != Defaults.ENABLED) {
|
if (enabled != Defaults.ENABLED) {
|
||||||
builder.field("enabled", enabled);
|
builder.field("enabled", enabled);
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ public class RootObjectMapper extends ObjectMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override protected ObjectMapper createMapper(String name, boolean enabled, boolean dynamic, ContentPath.Type pathType, Map<String, XContentMapper> mappers) {
|
@Override protected ObjectMapper createMapper(String name, boolean enabled, Dynamic dynamic, ContentPath.Type pathType, Map<String, XContentMapper> mappers) {
|
||||||
FormatDateTimeFormatter[] dates = null;
|
FormatDateTimeFormatter[] dates = null;
|
||||||
if (dateTimeFormatters == null) {
|
if (dateTimeFormatters == null) {
|
||||||
dates = new FormatDateTimeFormatter[0];
|
dates = new FormatDateTimeFormatter[0];
|
||||||
@ -102,6 +102,10 @@ public class RootObjectMapper extends ObjectMapper {
|
|||||||
} else {
|
} else {
|
||||||
dates = dateTimeFormatters.toArray(new FormatDateTimeFormatter[dateTimeFormatters.size()]);
|
dates = dateTimeFormatters.toArray(new FormatDateTimeFormatter[dateTimeFormatters.size()]);
|
||||||
}
|
}
|
||||||
|
// root dynamic must not be null, since its the default
|
||||||
|
if (dynamic == null) {
|
||||||
|
dynamic = Dynamic.TRUE;
|
||||||
|
}
|
||||||
return new RootObjectMapper(name, enabled, dynamic, pathType, mappers,
|
return new RootObjectMapper(name, enabled, dynamic, pathType, mappers,
|
||||||
dates,
|
dates,
|
||||||
dynamicTemplates.toArray(new DynamicTemplate[dynamicTemplates.size()]));
|
dynamicTemplates.toArray(new DynamicTemplate[dynamicTemplates.size()]));
|
||||||
@ -158,7 +162,7 @@ public class RootObjectMapper extends ObjectMapper {
|
|||||||
|
|
||||||
private volatile DynamicTemplate dynamicTemplates[];
|
private volatile DynamicTemplate dynamicTemplates[];
|
||||||
|
|
||||||
RootObjectMapper(String name, boolean enabled, boolean dynamic, ContentPath.Type pathType, Map<String, XContentMapper> mappers,
|
RootObjectMapper(String name, boolean enabled, Dynamic dynamic, ContentPath.Type pathType, Map<String, XContentMapper> mappers,
|
||||||
FormatDateTimeFormatter[] dateTimeFormatters, DynamicTemplate dynamicTemplates[]) {
|
FormatDateTimeFormatter[] dateTimeFormatters, DynamicTemplate dynamicTemplates[]) {
|
||||||
super(name, enabled, dynamic, pathType, mappers);
|
super(name, enabled, dynamic, pathType, mappers);
|
||||||
this.dynamicTemplates = dynamicTemplates;
|
this.dynamicTemplates = dynamicTemplates;
|
||||||
|
@ -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.dynamic;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
|
import org.elasticsearch.index.mapper.ParsedDocument;
|
||||||
|
import org.elasticsearch.index.mapper.StrictDynamicMappingException;
|
||||||
|
import org.elasticsearch.index.mapper.xcontent.MapperTests;
|
||||||
|
import org.elasticsearch.index.mapper.xcontent.XContentDocumentMapper;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.*;
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public class DynamicMappingTests {
|
||||||
|
|
||||||
|
@Test public void testDynamicTrue() throws IOException {
|
||||||
|
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
|
||||||
|
.field("dynamic", "true")
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("field1").field("type", "string").endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject().endObject().string();
|
||||||
|
|
||||||
|
XContentDocumentMapper defaultMapper = MapperTests.newParser().parse(mapping);
|
||||||
|
|
||||||
|
ParsedDocument doc = defaultMapper.parse("type", "1", XContentFactory.jsonBuilder()
|
||||||
|
.startObject()
|
||||||
|
.field("field1", "value1")
|
||||||
|
.field("field2", "value2")
|
||||||
|
.copiedBytes());
|
||||||
|
|
||||||
|
assertThat(doc.doc().get("field1"), equalTo("value1"));
|
||||||
|
assertThat(doc.doc().get("field2"), equalTo("value2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testDynamicFalse() throws IOException {
|
||||||
|
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
|
||||||
|
.field("dynamic", "false")
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("field1").field("type", "string").endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject().endObject().string();
|
||||||
|
|
||||||
|
XContentDocumentMapper defaultMapper = MapperTests.newParser().parse(mapping);
|
||||||
|
|
||||||
|
ParsedDocument doc = defaultMapper.parse("type", "1", XContentFactory.jsonBuilder()
|
||||||
|
.startObject()
|
||||||
|
.field("field1", "value1")
|
||||||
|
.field("field2", "value2")
|
||||||
|
.copiedBytes());
|
||||||
|
|
||||||
|
assertThat(doc.doc().get("field1"), equalTo("value1"));
|
||||||
|
assertThat(doc.doc().get("field2"), nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test public void testDynamicStrict() throws IOException {
|
||||||
|
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
|
||||||
|
.field("dynamic", "strict")
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("field1").field("type", "string").endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject().endObject().string();
|
||||||
|
|
||||||
|
XContentDocumentMapper defaultMapper = MapperTests.newParser().parse(mapping);
|
||||||
|
|
||||||
|
try {
|
||||||
|
defaultMapper.parse("type", "1", XContentFactory.jsonBuilder()
|
||||||
|
.startObject()
|
||||||
|
.field("field1", "value1")
|
||||||
|
.field("field2", "value2")
|
||||||
|
.copiedBytes());
|
||||||
|
assert false;
|
||||||
|
} catch (StrictDynamicMappingException e) {
|
||||||
|
// all is well
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testDynamicFalseWithInnerObjectButDynamicSetOnRoot() throws IOException {
|
||||||
|
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
|
||||||
|
.field("dynamic", "false")
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("obj1").startObject("properties")
|
||||||
|
.startObject("field1").field("type", "string").endObject()
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject().endObject().string();
|
||||||
|
|
||||||
|
XContentDocumentMapper defaultMapper = MapperTests.newParser().parse(mapping);
|
||||||
|
|
||||||
|
ParsedDocument doc = defaultMapper.parse("type", "1", XContentFactory.jsonBuilder()
|
||||||
|
.startObject().startObject("obj1")
|
||||||
|
.field("field1", "value1")
|
||||||
|
.field("field2", "value2")
|
||||||
|
.endObject()
|
||||||
|
.copiedBytes());
|
||||||
|
|
||||||
|
assertThat(doc.doc().get("obj1.field1"), equalTo("value1"));
|
||||||
|
assertThat(doc.doc().get("obj1.field2"), nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testDynamicStrictWithInnerObjectButDynamicSetOnRoot() throws IOException {
|
||||||
|
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
|
||||||
|
.field("dynamic", "strict")
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("obj1").startObject("properties")
|
||||||
|
.startObject("field1").field("type", "string").endObject()
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject().endObject().string();
|
||||||
|
|
||||||
|
XContentDocumentMapper defaultMapper = MapperTests.newParser().parse(mapping);
|
||||||
|
|
||||||
|
try {
|
||||||
|
defaultMapper.parse("type", "1", XContentFactory.jsonBuilder()
|
||||||
|
.startObject().startObject("obj1")
|
||||||
|
.field("field1", "value1")
|
||||||
|
.field("field2", "value2")
|
||||||
|
.endObject()
|
||||||
|
.copiedBytes());
|
||||||
|
assert false;
|
||||||
|
} catch (StrictDynamicMappingException e) {
|
||||||
|
// all is well
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -61,7 +61,7 @@ public class SearchSourceCompressTests extends AbstractNodesTests {
|
|||||||
verifySource(true);
|
verifySource(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void testSourceFieldPlainExplciit() throws IOException {
|
@Test public void testSourceFieldPlainExplicit() throws IOException {
|
||||||
verifySource(false);
|
verifySource(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user