From 0c467ec39d378a8b4ad20cc7e8ccda4a6a2cfa21 Mon Sep 17 00:00:00 2001 From: markharwood Date: Wed, 3 Nov 2021 16:49:23 +0000 Subject: [PATCH 1/6] New field mapping flag - allow_multiple_values. When false documents that presents arrays instead of single values are rejected. --- .../mapper/LegacyGeoShapeFieldMapper.java | 1 + .../extras/SearchAsYouTypeFieldMapper.java | 2 +- .../AnnotatedTextFieldMapper.java | 9 +- .../mapper/AbstractGeometryFieldMapper.java | 19 +++- .../AbstractPointGeometryFieldMapper.java | 6 +- .../AbstractShapeGeometryFieldMapper.java | 5 +- .../index/mapper/BinaryFieldMapper.java | 2 +- .../index/mapper/BooleanFieldMapper.java | 1 + .../index/mapper/DateFieldMapper.java | 10 ++- .../index/mapper/DocumentParser.java | 39 ++++++--- .../index/mapper/FieldMapper.java | 87 ++++++++++++++++++- .../index/mapper/GeoPointFieldMapper.java | 11 ++- .../index/mapper/GeoShapeFieldMapper.java | 1 + .../index/mapper/IpFieldMapper.java | 10 ++- .../index/mapper/KeywordFieldMapper.java | 1 + .../index/mapper/NestedObjectMapper.java | 15 +++- .../index/mapper/NumberFieldMapper.java | 10 ++- .../index/mapper/ObjectMapper.java | 37 +++++++- .../index/mapper/RangeFieldMapper.java | 5 +- .../index/mapper/RootObjectMapper.java | 2 +- .../index/mapper/TextFieldMapper.java | 2 +- .../index/mapper/BinaryFieldMapperTests.java | 13 +++ .../index/mapper/BooleanFieldMapperTests.java | 10 +++ .../index/mapper/DateFieldMapperTests.java | 9 ++ .../FieldAliasMapperValidationTests.java | 2 +- .../index/mapper/IpFieldMapperTests.java | 11 +++ .../index/mapper/KeywordFieldMapperTests.java | 10 +++ .../index/mapper/MappingLookupTests.java | 3 +- .../index/mapper/NestedObjectMapperTests.java | 10 +++ .../index/mapper/NumberFieldMapperTests.java | 13 +++ .../index/mapper/ObjectMapperTests.java | 39 +++++++++ .../index/mapper/RangeFieldMapperTests.java | 29 +++++++ .../index/mapper/TextFieldMapperTests.java | 9 ++ .../index/mapper/MockFieldMapper.java | 11 ++- .../VersionStringFieldMapper.java | 8 +- .../VersionStringFieldMapperTests.java | 10 +++ .../GeoShapeWithDocValuesFieldMapper.java | 1 + .../index/mapper/PointFieldMapper.java | 1 + .../index/mapper/ShapeFieldMapper.java | 1 + .../wildcard/mapper/WildcardFieldMapper.java | 4 +- .../mapper/WildcardFieldMapperTests.java | 13 +++ 41 files changed, 440 insertions(+), 42 deletions(-) diff --git a/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/mapper/LegacyGeoShapeFieldMapper.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/mapper/LegacyGeoShapeFieldMapper.java index d2664f643695b..318171aaffbb2 100644 --- a/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/mapper/LegacyGeoShapeFieldMapper.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/mapper/LegacyGeoShapeFieldMapper.java @@ -563,6 +563,7 @@ public LegacyGeoShapeFieldMapper( builder.orientation.get(), multiFields, copyTo, + builder.getAllowMultipleValues(), parser ); this.indexCreatedVersion = builder.indexCreatedVersion; diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/SearchAsYouTypeFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/SearchAsYouTypeFieldMapper.java index 1cea384e7fb08..a11a71060014a 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/SearchAsYouTypeFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/SearchAsYouTypeFieldMapper.java @@ -646,7 +646,7 @@ public SearchAsYouTypeFieldMapper( ShingleFieldMapper[] shingleFields, Builder builder ) { - super(simpleName, mappedFieldType, indexAnalyzers, MultiFields.empty(), copyTo, false, null); + super(simpleName, mappedFieldType, indexAnalyzers, MultiFields.empty(), copyTo, builder.getAllowMultipleValues(), false, null); this.prefixField = prefixField; this.shingleFields = shingleFields; this.maxShingleSize = builder.maxShingleSize.getValue(); diff --git a/plugins/mapper-annotated-text/src/main/java/org/elasticsearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java b/plugins/mapper-annotated-text/src/main/java/org/elasticsearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java index e77362a43bee0..3e97a01996b37 100644 --- a/plugins/mapper-annotated-text/src/main/java/org/elasticsearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java +++ b/plugins/mapper-annotated-text/src/main/java/org/elasticsearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java @@ -531,7 +531,14 @@ protected AnnotatedTextFieldMapper( CopyTo copyTo, Builder builder ) { - super(simpleName, mappedFieldType, wrapAnalyzer(builder.analyzers.getIndexAnalyzer()), multiFields, copyTo); + super( + simpleName, + mappedFieldType, + wrapAnalyzer(builder.analyzers.getIndexAnalyzer()), + multiFields, + copyTo, + builder.getAllowMultipleValues() + ); assert fieldType.tokenized(); this.fieldType = fieldType; this.builder = builder; diff --git a/server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java index 47cba3b182e9a..8f0b89cdc81d2 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java @@ -123,9 +123,10 @@ protected AbstractGeometryFieldMapper( Explicit ignoreZValue, MultiFields multiFields, CopyTo copyTo, + boolean allowMultipleValues, Parser parser ) { - super(simpleName, mappedFieldType, indexAnalyzers, multiFields, copyTo, false, null); + super(simpleName, mappedFieldType, indexAnalyzers, multiFields, copyTo, allowMultipleValues, false, null); this.ignoreMalformed = ignoreMalformed; this.ignoreZValue = ignoreZValue; this.parser = parser; @@ -138,9 +139,20 @@ protected AbstractGeometryFieldMapper( Explicit ignoreZValue, MultiFields multiFields, CopyTo copyTo, + boolean allowMultipleValues, Parser parser ) { - this(simpleName, mappedFieldType, Collections.emptyMap(), ignoreMalformed, ignoreZValue, multiFields, copyTo, parser); + this( + simpleName, + mappedFieldType, + Collections.emptyMap(), + ignoreMalformed, + ignoreZValue, + multiFields, + copyTo, + allowMultipleValues, + parser + ); } protected AbstractGeometryFieldMapper( @@ -148,10 +160,11 @@ protected AbstractGeometryFieldMapper( MappedFieldType mappedFieldType, MultiFields multiFields, CopyTo copyTo, + boolean allowMultipleValues, Parser parser, String onScriptError ) { - super(simpleName, mappedFieldType, Collections.emptyMap(), multiFields, copyTo, true, onScriptError); + super(simpleName, mappedFieldType, Collections.emptyMap(), multiFields, copyTo, allowMultipleValues, true, onScriptError); this.ignoreMalformed = new Explicit<>(false, true); this.ignoreZValue = new Explicit<>(false, true); this.parser = parser; diff --git a/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java index 3151be092a48a..3e78f51669d53 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java @@ -40,9 +40,10 @@ protected AbstractPointGeometryFieldMapper( Explicit ignoreZValue, T nullValue, CopyTo copyTo, + boolean allowMultipleValues, Parser parser ) { - super(simpleName, mappedFieldType, ignoreMalformed, ignoreZValue, multiFields, copyTo, parser); + super(simpleName, mappedFieldType, ignoreMalformed, ignoreZValue, multiFields, copyTo, allowMultipleValues, parser); this.nullValue = nullValue; } @@ -51,10 +52,11 @@ protected AbstractPointGeometryFieldMapper( MappedFieldType mappedFieldType, MultiFields multiFields, CopyTo copyTo, + boolean allowMultipleValues, Parser parser, String onScriptError ) { - super(simpleName, mappedFieldType, multiFields, copyTo, parser, onScriptError); + super(simpleName, mappedFieldType, multiFields, copyTo, allowMultipleValues, parser, onScriptError); this.nullValue = null; } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/AbstractShapeGeometryFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/AbstractShapeGeometryFieldMapper.java index 5ae0d924a9cc1..bcda4828792c6 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/AbstractShapeGeometryFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/AbstractShapeGeometryFieldMapper.java @@ -69,9 +69,10 @@ protected AbstractShapeGeometryFieldMapper( Explicit orientation, MultiFields multiFields, CopyTo copyTo, + boolean allowMultipleValues, Parser parser ) { - super(simpleName, mappedFieldType, indexAnalyzers, ignoreMalformed, ignoreZValue, multiFields, copyTo, parser); + super(simpleName, mappedFieldType, indexAnalyzers, ignoreMalformed, ignoreZValue, multiFields, copyTo, allowMultipleValues, parser); this.coerce = coerce; this.orientation = orientation; } @@ -85,6 +86,7 @@ protected AbstractShapeGeometryFieldMapper( Explicit orientation, MultiFields multiFields, CopyTo copyTo, + boolean allowMultipleValues, Parser parser ) { this( @@ -97,6 +99,7 @@ protected AbstractShapeGeometryFieldMapper( orientation, multiFields, copyTo, + allowMultipleValues, parser ); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/BinaryFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/BinaryFieldMapper.java index fbc6d630f56ec..da6eb4bdf2f01 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/BinaryFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/BinaryFieldMapper.java @@ -142,7 +142,7 @@ protected BinaryFieldMapper( CopyTo copyTo, Builder builder ) { - super(simpleName, mappedFieldType, multiFields, copyTo); + super(simpleName, mappedFieldType, multiFields, copyTo, builder.allowMultipleValues); this.stored = builder.stored.getValue(); this.hasDocValues = builder.hasDocValues.getValue(); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java index 84da90600b403..279bdc8bbcf52 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java @@ -277,6 +277,7 @@ protected BooleanFieldMapper( Lucene.KEYWORD_ANALYZER, multiFields, copyTo, + builder.allowMultipleValues, builder.script.get() != null, builder.onScriptError.getValue() ); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java index 1c07dc454f113..b01824b127306 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java @@ -720,7 +720,15 @@ private DateFieldMapper( Resolution resolution, Builder builder ) { - super(simpleName, mappedFieldType, multiFields, copyTo, builder.script.get() != null, builder.onScriptError.get()); + super( + simpleName, + mappedFieldType, + multiFields, + copyTo, + builder.allowMultipleValues, + builder.script.get() != null, + builder.onScriptError.get() + ); this.store = builder.store.getValue(); this.indexed = builder.index.getValue(); this.hasDocValues = builder.docValues.getValue(); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java index f0d5c4c1dc4ac..7818cf569ec57 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java @@ -463,7 +463,7 @@ private static void innerParseObject(DocumentParserContext context, ObjectMapper currentFieldName = context.parser().currentName(); splitAndValidatePath(currentFieldName); } else if (token == XContentParser.Token.START_OBJECT) { - parseObject(context, mapper, currentFieldName); + parseObject(context, mapper, currentFieldName, false); } else if (token == XContentParser.Token.START_ARRAY) { parseArray(context, mapper, currentFieldName); } else if (token == XContentParser.Token.VALUE_NULL) { @@ -477,7 +477,7 @@ private static void innerParseObject(DocumentParserContext context, ObjectMapper + "] as object, but got EOF, has a concrete value been provided to it?" ); } else if (token.isValue()) { - parseValue(context, mapper, currentFieldName, token); + parseValue(context, mapper, currentFieldName, token, false); } token = context.parser().nextToken(); } @@ -534,10 +534,21 @@ private static DocumentParserContext nestedContext(DocumentParserContext context } static void parseObjectOrField(DocumentParserContext context, Mapper mapper) throws IOException { + parseObjectOrField(context, mapper, false); + } + + static void parseObjectOrField(DocumentParserContext context, Mapper mapper, boolean isArrayValue) throws IOException { if (mapper instanceof ObjectMapper) { + ObjectMapper objectMapper = (ObjectMapper) mapper; + if (isArrayValue && objectMapper.allowMultipleValues() == false) { + throw new MapperParsingException("Object [" + mapper.name() + "] cannot be a multi-valued field."); + } parseObjectOrNested(context, (ObjectMapper) mapper); } else if (mapper instanceof FieldMapper) { FieldMapper fieldMapper = (FieldMapper) mapper; + if (isArrayValue && fieldMapper.allowMultipleValues() == false) { + throw new MapperParsingException("Field [" + mapper.name() + "] cannot be a multi-valued field."); + } fieldMapper.parse(context); List copyToFields = fieldMapper.copyTo().copyToFields(); if (context.isWithinCopyTo() == false && copyToFields.isEmpty() == false) { @@ -564,12 +575,17 @@ static void parseObjectOrField(DocumentParserContext context, Mapper mapper) thr } } - private static void parseObject(final DocumentParserContext context, ObjectMapper mapper, String currentFieldName) throws IOException { + private static void parseObject( + final DocumentParserContext context, + ObjectMapper mapper, + String currentFieldName, + boolean isInsideArray + ) throws IOException { assert currentFieldName != null; Mapper objectMapper = getMapper(context, mapper, currentFieldName); if (objectMapper != null) { context.path().add(currentFieldName); - parseObjectOrField(context, objectMapper); + parseObjectOrField(context, objectMapper, isInsideArray); context.path().remove(); } else { ObjectMapper.Dynamic dynamic = dynamicOrDefault(mapper, context); @@ -608,7 +624,7 @@ private static void parseArray(DocumentParserContext context, ObjectMapper paren // expects an array, if so we pass the context straight to the mapper and if not // we serialize the array components if (parsesArrayValue(mapper)) { - parseObjectOrField(context, mapper); + parseObjectOrField(context, mapper, true); } else { parseNonDynamicArray(context, parentMapper, lastFieldName, lastFieldName); } @@ -627,7 +643,7 @@ private static void parseArray(DocumentParserContext context, ObjectMapper paren if (parsesArrayValue(objectMapperFromTemplate)) { context.addDynamicMapper(objectMapperFromTemplate); context.path().add(lastFieldName); - parseObjectOrField(context, objectMapperFromTemplate); + parseObjectOrField(context, objectMapperFromTemplate, true); context.path().remove(); } else { parseNonDynamicArray(context, parentMapper, lastFieldName, lastFieldName); @@ -653,7 +669,7 @@ private static void parseNonDynamicArray( splitAndValidatePath(lastFieldName); while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { if (token == XContentParser.Token.START_OBJECT) { - parseObject(context, mapper, lastFieldName); + parseObject(context, mapper, lastFieldName, true); } else if (token == XContentParser.Token.START_ARRAY) { parseArray(context, mapper, lastFieldName); } else if (token == XContentParser.Token.VALUE_NULL) { @@ -668,7 +684,7 @@ private static void parseNonDynamicArray( ); } else { assert token.isValue(); - parseValue(context, mapper, lastFieldName, token); + parseValue(context, mapper, lastFieldName, token, true); } } } @@ -677,7 +693,8 @@ private static void parseValue( final DocumentParserContext context, ObjectMapper parentMapper, String currentFieldName, - XContentParser.Token token + XContentParser.Token token, + boolean isArrayValue ) throws IOException { if (currentFieldName == null) { throw new MapperParsingException( @@ -691,7 +708,7 @@ private static void parseValue( } Mapper mapper = getLeafMapper(context, parentMapper, currentFieldName); if (mapper != null) { - parseObjectOrField(context, mapper); + parseObjectOrField(context, mapper, isArrayValue); } else { parseDynamicValue(context, parentMapper, currentFieldName, token); } @@ -896,7 +913,7 @@ protected String contentType() { private static class NoOpObjectMapper extends ObjectMapper { NoOpObjectMapper(String name, String fullPath) { - super(name, fullPath, new Explicit<>(true, false), Dynamic.RUNTIME, Collections.emptyMap()); + super(name, fullPath, new Explicit<>(true, false), Dynamic.RUNTIME, Collections.emptyMap(), true); } } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java index b0a9404bafd6f..8e48faeaf021b 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java @@ -17,6 +17,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.xcontent.support.XContentMapValues; +import org.elasticsearch.core.Booleans; import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptType; @@ -60,18 +61,19 @@ public abstract class FieldMapper extends Mapper implements Cloneable { protected final Map indexAnalyzers; protected final MultiFields multiFields; protected final CopyTo copyTo; + protected final boolean allowMultipleValues; protected final boolean hasScript; protected final String onScriptError; /** - * Create a FieldMapper with no index analyzers + * Create a FieldMapper with no index analyzers or cardinality restrictions * @param simpleName the leaf name of the mapper * @param mappedFieldType the MappedFieldType associated with this mapper * @param multiFields sub fields of this mapper * @param copyTo copyTo fields of this mapper */ protected FieldMapper(String simpleName, MappedFieldType mappedFieldType, MultiFields multiFields, CopyTo copyTo) { - this(simpleName, mappedFieldType, Collections.emptyMap(), multiFields, copyTo, false, null); + this(simpleName, mappedFieldType, Collections.emptyMap(), multiFields, copyTo, true, false, null); } /** @@ -80,6 +82,25 @@ protected FieldMapper(String simpleName, MappedFieldType mappedFieldType, MultiF * @param mappedFieldType the MappedFieldType associated with this mapper * @param multiFields sub fields of this mapper * @param copyTo copyTo fields of this mapper + * @param allowMultipleValues true if the JSON document can present arrays for this field + */ + protected FieldMapper( + String simpleName, + MappedFieldType mappedFieldType, + MultiFields multiFields, + CopyTo copyTo, + boolean allowMultipleValues + ) { + this(simpleName, mappedFieldType, Collections.emptyMap(), multiFields, copyTo, allowMultipleValues, false, null); + } + + /** + * Create a FieldMapper with no index analyzers + * @param simpleName the leaf name of the mapper + * @param mappedFieldType the MappedFieldType associated with this mapper + * @param multiFields sub fields of this mapper + * @param copyTo copyTo fields of this mapper + * @param allowMultipleValues true if the JSON document can present arrays for this field * @param hasScript whether a script is defined for the field * @param onScriptError the behaviour for when the defined script fails at runtime */ @@ -88,10 +109,40 @@ protected FieldMapper( MappedFieldType mappedFieldType, MultiFields multiFields, CopyTo copyTo, + boolean allowMultipleValues, boolean hasScript, String onScriptError ) { - this(simpleName, mappedFieldType, Collections.emptyMap(), multiFields, copyTo, hasScript, onScriptError); + this(simpleName, mappedFieldType, Collections.emptyMap(), multiFields, copyTo, allowMultipleValues, hasScript, onScriptError); + } + + /** + * Create a FieldMapper with a single associated index analyzer + * @param simpleName the leaf name of the mapper + * @param mappedFieldType the MappedFieldType associated with this mapper + * @param indexAnalyzer the index-time analyzer to use for this field + * @param multiFields sub fields of this mapper + * @param copyTo copyTo fields of this mapper + * @param allowMultipleValues true if the JSON document can present arrays for this field + */ + protected FieldMapper( + String simpleName, + MappedFieldType mappedFieldType, + NamedAnalyzer indexAnalyzer, + MultiFields multiFields, + CopyTo copyTo, + boolean allowMultipleValues + ) { + this( + simpleName, + mappedFieldType, + Collections.singletonMap(mappedFieldType.name(), indexAnalyzer), + multiFields, + copyTo, + allowMultipleValues, + false, + null + ); } /** @@ -115,6 +166,7 @@ protected FieldMapper( Collections.singletonMap(mappedFieldType.name(), indexAnalyzer), multiFields, copyTo, + true, false, null ); @@ -127,6 +179,7 @@ protected FieldMapper( * @param indexAnalyzer the index-time analyzer to use for this field * @param multiFields sub fields of this mapper * @param copyTo copyTo fields of this mapper + * @param allowMultipleValues true if the JSON document can present arrays for this field * @param hasScript whether a script is defined for the field * @param onScriptError the behaviour for when the defined script fails at runtime */ @@ -136,6 +189,7 @@ protected FieldMapper( NamedAnalyzer indexAnalyzer, MultiFields multiFields, CopyTo copyTo, + boolean allowMultipleValues, boolean hasScript, String onScriptError ) { @@ -145,6 +199,7 @@ protected FieldMapper( Collections.singletonMap(mappedFieldType.name(), indexAnalyzer), multiFields, copyTo, + allowMultipleValues, hasScript, onScriptError ); @@ -158,6 +213,7 @@ protected FieldMapper( * the mapper will add * @param multiFields sub fields of this mapper * @param copyTo copyTo fields of this mapper + * @param allowMultipleValues true if the JSON document can present arrays for this field * @param hasScript whether a script is defined for the field * @param onScriptError the behaviour for when the defined script fails at runtime */ @@ -167,6 +223,7 @@ protected FieldMapper( Map indexAnalyzers, MultiFields multiFields, CopyTo copyTo, + boolean allowMultipleValues, boolean hasScript, String onScriptError ) { @@ -178,6 +235,7 @@ protected FieldMapper( this.indexAnalyzers = indexAnalyzers; this.multiFields = multiFields; this.copyTo = Objects.requireNonNull(copyTo); + this.allowMultipleValues = allowMultipleValues; this.hasScript = hasScript; this.onScriptError = onScriptError; } @@ -203,6 +261,13 @@ public CopyTo copyTo() { return copyTo; } + /** + * True if documents can present arrays as well as single-values for this field + */ + public boolean allowMultipleValues() { + return allowMultipleValues; + } + public MultiFields multiFields() { return multiFields; } @@ -439,6 +504,9 @@ protected void doXContentBody(XContentBuilder builder, Params params) throws IOE getMergeBuilder().toXContent(builder, params); multiFields.toXContent(builder, params); copyTo.toXContent(builder); + if (allowMultipleValues != Builder.DEFAULT_ALLOW_MULTIPLE_VALUES) { + builder.field("allow_multiple_values", allowMultipleValues); + } } protected abstract String contentType(); @@ -1121,7 +1189,6 @@ void check() { String message = "Mapper for [" + mapperName + "] conflicts with existing mapper:\n\t" + String.join("\n\t", conflicts); throw new IllegalArgumentException(message); } - } /** @@ -1131,6 +1198,8 @@ public abstract static class Builder extends Mapper.Builder implements ToXConten protected final MultiFields.Builder multiFieldsBuilder = new MultiFields.Builder(); protected final CopyTo.Builder copyTo = new CopyTo.Builder(); + public static final boolean DEFAULT_ALLOW_MULTIPLE_VALUES = true; + protected boolean allowMultipleValues = DEFAULT_ALLOW_MULTIPLE_VALUES; /** * Creates a new Builder with a field name @@ -1160,6 +1229,7 @@ private void merge(FieldMapper in, Conflicts conflicts) { multiFieldsBuilder.update(newSubField, MapperBuilderContext.forPath(parentPath(newSubField.name()))); } this.copyTo.reset(in.copyTo); + this.allowMultipleValues = in.allowMultipleValues; validate(); } @@ -1169,6 +1239,10 @@ private void validate() { } } + public boolean getAllowMultipleValues() { + return allowMultipleValues; + } + /** * @return the list of parameters defined for this mapper */ @@ -1237,6 +1311,11 @@ public final void parse(String name, MappingParserContext parserContext, Map parser, Builder builder) { - super(simpleName, mappedFieldType, MultiFields.empty(), CopyTo.empty(), parser, builder.onScriptError.get()); + super( + simpleName, + mappedFieldType, + MultiFields.empty(), + CopyTo.empty(), + builder.allowMultipleValues, + parser, + builder.onScriptError.get() + ); this.builder = builder; this.scriptValues = builder.scriptValues(); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeFieldMapper.java index e3090af62c65f..9c6bf7f19d4f3 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeFieldMapper.java @@ -184,6 +184,7 @@ public GeoShapeFieldMapper( builder.orientation.get(), multiFields, copyTo, + builder.allowMultipleValues, parser ); this.builder = builder; diff --git a/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java index ee39f775eeb20..cfc58fdcfac82 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java @@ -392,7 +392,15 @@ public boolean isDimension() { private final ScriptCompiler scriptCompiler; private IpFieldMapper(String simpleName, MappedFieldType mappedFieldType, MultiFields multiFields, CopyTo copyTo, Builder builder) { - super(simpleName, mappedFieldType, multiFields, copyTo, builder.script.get() != null, builder.onScriptError.get()); + super( + simpleName, + mappedFieldType, + multiFields, + copyTo, + builder.allowMultipleValues, + builder.script.get() != null, + builder.onScriptError.get() + ); this.ignoreMalformedByDefault = builder.ignoreMalformedByDefault; this.indexed = builder.indexed.getValue(); this.hasDocValues = builder.hasDocValues.getValue(); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java index e079603cf02d6..38ebaad6cbcdc 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java @@ -544,6 +544,7 @@ private KeywordFieldMapper( mappedFieldType.normalizer, multiFields, copyTo, + builder.allowMultipleValues, builder.script.get() != null, builder.onScriptError.getValue() ); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java index af28eaeea9281..f7b7b67505ad6 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java @@ -48,6 +48,19 @@ Builder includeInParent(boolean includeInParent) { return this; } + @Override + public Builder allowMultipleValues(boolean allowMultipleValues) { + if (allowMultipleValues == false) { + // The whole raison d'etre of nested fields is to deal with multi-value objects. + // Prevent users from disabling multi-values. + throw new MapperParsingException( + "Nested object field [" + name() + "] cannot have \"allow_multiple_values\" set to false." + ); + } + super.allowMultipleValues(allowMultipleValues); + return this; + } + @Override public NestedObjectMapper build(MapperBuilderContext context) { return new NestedObjectMapper(name, context.buildFullName(name), buildMappers(false, context), this); @@ -93,7 +106,7 @@ protected static void parseNested(String name, Map node, NestedO private final Query nestedTypeFilter; NestedObjectMapper(String name, String fullPath, Map mappers, Builder builder) { - super(name, fullPath, builder.enabled, builder.dynamic, mappers); + super(name, fullPath, builder.enabled, builder.dynamic, mappers, builder.allowMultipleValues); if (builder.indexCreatedVersion.before(Version.V_8_0_0)) { this.nestedTypePath = "__" + fullPath; } else { diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java index 1d5b852067219..d87c763a2d5b3 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java @@ -1298,7 +1298,15 @@ public MetricType getMetricType() { private final MetricType metricType; private NumberFieldMapper(String simpleName, MappedFieldType mappedFieldType, MultiFields multiFields, CopyTo copyTo, Builder builder) { - super(simpleName, mappedFieldType, multiFields, copyTo, builder.script.get() != null, builder.onScriptError.getValue()); + super( + simpleName, + mappedFieldType, + multiFields, + copyTo, + builder.allowMultipleValues, + builder.script.get() != null, + builder.onScriptError.getValue() + ); this.type = builder.type; this.indexed = builder.indexed.getValue(); this.hasDocValues = builder.hasDocValues.getValue(); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java index ab322c8cea291..e5ca6e3aaa1a5 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java @@ -37,6 +37,7 @@ public class ObjectMapper extends Mapper implements Cloneable { public static class Defaults { public static final boolean ENABLED = true; + public static final boolean ALLOW_MULTIPLE_VALUES = true; } public enum Dynamic { @@ -66,6 +67,8 @@ public static class Builder extends Mapper.Builder { protected Dynamic dynamic; + protected boolean allowMultipleValues = Defaults.ALLOW_MULTIPLE_VALUES; + protected final List mappersBuilders = new ArrayList<>(); public Builder(String name) { @@ -82,6 +85,11 @@ public Builder dynamic(Dynamic dynamic) { return this; } + public Builder allowMultipleValues(boolean allowMultipleValues) { + this.allowMultipleValues = allowMultipleValues; + return this; + } + public Builder add(Mapper.Builder builder) { mappersBuilders.add(builder); return this; @@ -109,7 +117,7 @@ protected final Map buildMappers(boolean root, MapperBuilderCont @Override public ObjectMapper build(MapperBuilderContext context) { - return new ObjectMapper(name, context.buildFullName(name), enabled, dynamic, buildMappers(false, context)); + return new ObjectMapper(name, context.buildFullName(name), enabled, dynamic, buildMappers(false, context), allowMultipleValues); } } @@ -150,6 +158,9 @@ protected static boolean parseObjectOrDocumentTypeProperties( } else if (fieldName.equals("enabled")) { builder.enabled(XContentMapValues.nodeBooleanValue(fieldNode, fieldName + ".enabled")); return true; + } else if (fieldName.equals("allow_multiple_values")) { + builder.allowMultipleValues(XContentMapValues.nodeBooleanValue(fieldNode, fieldName + ".allow_multiple_values")); + return true; } else if (fieldName.equals("properties")) { if (fieldNode instanceof Collection && ((Collection) fieldNode).isEmpty()) { // nothing to do here, empty (to support "properties: []" case) @@ -238,11 +249,20 @@ protected static void parseProperties( protected Explicit enabled; + private final boolean allowMultipleValues; + protected volatile Dynamic dynamic; protected volatile CopyOnWriteHashMap mappers; - ObjectMapper(String name, String fullPath, Explicit enabled, Dynamic dynamic, Map mappers) { + ObjectMapper( + String name, + String fullPath, + Explicit enabled, + Dynamic dynamic, + Map mappers, + boolean allowMultipleValues + ) { super(name); if (name.isEmpty()) { throw new IllegalArgumentException("name cannot be empty string"); @@ -250,6 +270,7 @@ protected static void parseProperties( this.fullPath = fullPath; this.enabled = enabled; this.dynamic = dynamic; + this.allowMultipleValues = allowMultipleValues; if (mappers == null) { this.mappers = new CopyOnWriteHashMap<>(); } else { @@ -298,6 +319,10 @@ public boolean isEnabled() { return this.enabled.value(); } + public boolean allowMultipleValues() { + return this.allowMultipleValues; + } + public boolean isNested() { return false; } @@ -355,6 +380,10 @@ protected void doMerge(final ObjectMapper mergeWith, MergeReason reason) { this.dynamic = mergeWith.dynamic; } + if (allowMultipleValues() != mergeWith.allowMultipleValues()) { + throw new MapperException("the [allow_multiple_values] parameter can't be updated for the object mapping [" + name() + "]"); + } + if (mergeWith.enabled.explicit()) { if (reason == MergeReason.INDEX_TEMPLATE) { this.enabled = mergeWith.enabled; @@ -411,6 +440,10 @@ void toXContent(XContentBuilder builder, Params params, ToXContent custom) throw builder.field("enabled", enabled.value()); } + if (allowMultipleValues() != Defaults.ALLOW_MULTIPLE_VALUES) { + builder.field("allow_multiple_values", allowMultipleValues); + } + if (custom != null) { custom.toXContent(builder, params); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java index 28a5c8889ec23..3125b3989d649 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java @@ -164,7 +164,7 @@ protected RangeFieldType setupFieldType(MapperBuilderContext context) { @Override public RangeFieldMapper build(MapperBuilderContext context) { RangeFieldType ft = setupFieldType(context); - return new RangeFieldMapper(name, ft, multiFieldsBuilder.build(this, context), copyTo.build(), type, this); + return new RangeFieldMapper(name, ft, multiFieldsBuilder.build(this, context), copyTo.build(), allowMultipleValues, type, this); } } @@ -336,10 +336,11 @@ private RangeFieldMapper( MappedFieldType mappedFieldType, MultiFields multiFields, CopyTo copyTo, + boolean allowMultipleValues, RangeType type, Builder builder ) { - super(simpleName, mappedFieldType, multiFields, copyTo); + super(simpleName, mappedFieldType, multiFields, copyTo, allowMultipleValues); this.type = type; this.index = builder.index.getValue(); this.hasDocValues = builder.hasDocValues.getValue(); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java index d7f0f4beaa0fc..1dd5c538646fe 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java @@ -252,7 +252,7 @@ private boolean processField( Explicit dateDetection, Explicit numericDetection ) { - super(name, name, enabled, dynamic, mappers); + super(name, name, enabled, dynamic, mappers, true); this.runtimeFields = runtimeFields; this.dynamicTemplates = dynamicTemplates; this.dynamicDateTimeFormatters = dynamicDateTimeFormatters; diff --git a/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java index 496f84f54d599..8989850215331 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java @@ -918,7 +918,7 @@ protected TextFieldMapper( CopyTo copyTo, Builder builder ) { - super(simpleName, mappedFieldType, indexAnalyzers, multiFields, copyTo, false, null); + super(simpleName, mappedFieldType, indexAnalyzers, multiFields, copyTo, builder.allowMultipleValues, false, null); assert mappedFieldType.getTextSearchInfo().isTokenized(); assert mappedFieldType.hasDocValues() == false; if (fieldType.indexOptions() == IndexOptions.NONE && fieldType().fielddata()) { diff --git a/server/src/test/java/org/elasticsearch/index/mapper/BinaryFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/BinaryFieldMapperTests.java index 3808a3894a239..36688aab43511 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/BinaryFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/BinaryFieldMapperTests.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Base64; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; public class BinaryFieldMapperTests extends MapperTestCase { @@ -120,6 +121,18 @@ public void testStoredValue() throws IOException { } } + public void testNoArrays() throws IOException { + // A simple binary value + final byte[] binaryValue1 = new byte[100]; + binaryValue1[56] = 1; + SourceToParse sourceWithArrays = source(b -> b.array("field", binaryValue1, binaryValue1)); + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "binary").field("allow_multiple_values", false))); + Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArrays)); + assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); + DocumentMapper okmapper = createDocumentMapper(fieldMapping(b -> b.field("type", "binary"))); + okmapper.parse(sourceWithArrays); + } + @Override protected Object generateRandomInputValue(MappedFieldType ft) { if (rarely()) return null; diff --git a/server/src/test/java/org/elasticsearch/index/mapper/BooleanFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/BooleanFieldMapperTests.java index 382faf8182a08..0c1580a2fa83c 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/BooleanFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/BooleanFieldMapperTests.java @@ -20,6 +20,7 @@ import java.io.IOException; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; public class BooleanFieldMapperTests extends MapperTestCase { @@ -67,6 +68,15 @@ public void testDefaults() throws IOException { }); } + public void testNoArrays() throws IOException { + SourceToParse sourceWithArrays = source(b -> b.array("field", true, false)); + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "boolean").field("allow_multiple_values", false))); + Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArrays)); + assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); + DocumentMapper okmapper = createDocumentMapper(fieldMapping(b -> b.field("type", "boolean"))); + okmapper.parse(sourceWithArrays); + } + public void testSerialization() throws IOException { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(this::minimalMapping)); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/DateFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/DateFieldMapperTests.java index 8f79ab289794d..d4d1998be235e 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/DateFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/DateFieldMapperTests.java @@ -96,6 +96,15 @@ public void testNotIndexed() throws Exception { assertEquals(DocValuesType.SORTED_NUMERIC, dvField.fieldType().docValuesType()); } + public void testNoArrays() throws IOException { + SourceToParse sourceWithArrays = source(b -> b.array("field", "2016-03-11", "2016-03-12")); + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "date").field("allow_multiple_values", false))); + Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArrays)); + assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); + DocumentMapper okmapper = createDocumentMapper(fieldMapping(b -> b.field("type", "date"))); + okmapper.parse(sourceWithArrays); + } + public void testNoDocValues() throws Exception { DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "date").field("doc_values", false))); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java b/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java index 28efe01b44349..84edbfee1c17f 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java @@ -162,7 +162,7 @@ private static FieldMapper createFieldMapper(String parent, String name) { } private static ObjectMapper createObjectMapper(String name) { - return new ObjectMapper(name, name, new Explicit<>(true, false), ObjectMapper.Dynamic.FALSE, emptyMap()); + return new ObjectMapper(name, name, new Explicit<>(true, false), ObjectMapper.Dynamic.FALSE, emptyMap(), true); } private static NestedObjectMapper createNestedObjectMapper(String name) { diff --git a/server/src/test/java/org/elasticsearch/index/mapper/IpFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/IpFieldMapperTests.java index 7ea421ab26455..2611ba4401b9f 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/IpFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/IpFieldMapperTests.java @@ -115,6 +115,17 @@ public void testNoDocValues() throws Exception { assertEquals(new TermQuery(new Term(FieldNamesFieldMapper.NAME, "field")), existsQuery); } + public void testNoArrays() throws IOException { + SourceToParse sourceWithArrays = source(b -> b.array("field", "::1", "::1")); + + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "ip").field("allow_multiple_values", false))); + Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArrays)); + assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); + + DocumentMapper okMapper = createDocumentMapper(fieldMapping(b -> b.field("type", "ip"))); + okMapper.parse(sourceWithArrays); + } + public void testStore() throws Exception { DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> { b.field("type", "ip"); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/KeywordFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/KeywordFieldMapperTests.java index 8cb9fe10702eb..dd4c883a73af2 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/KeywordFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/KeywordFieldMapperTests.java @@ -230,6 +230,16 @@ public void testIgnoreAbove() throws IOException { assertEquals("field", fields[0].stringValue()); } + public void testNoArrays() throws IOException { + SourceToParse sourceWithArrays = source(b -> b.array("field", "foo", "bar")); + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "keyword").field("allow_multiple_values", false))); + Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArrays)); + assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); + + DocumentMapper okmapper = createDocumentMapper(fieldMapping(b -> b.field("type", "keyword"))); + okmapper.parse(sourceWithArrays); + } + public void testNullValue() throws IOException { DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping)); ParsedDocument doc = mapper.parse(source(b -> b.nullField("field"))); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/MappingLookupTests.java b/server/src/test/java/org/elasticsearch/index/mapper/MappingLookupTests.java index 14129d3e12bf5..0a0de1a87b534 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/MappingLookupTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/MappingLookupTests.java @@ -78,7 +78,8 @@ public void testSubfieldOverride() { "object", new Explicit<>(true, true), ObjectMapper.Dynamic.TRUE, - Collections.singletonMap("object.subfield", fieldMapper) + Collections.singletonMap("object.subfield", fieldMapper), + true ); MappingLookup mappingLookup = createMappingLookup( Collections.singletonList(fieldMapper), diff --git a/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java index d9677ac35a57f..8677b85e9ca78 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java @@ -730,6 +730,16 @@ public void testMultipleLevelsIncludeRootWithMerge() throws Exception { assertThat(fields.size(), equalTo(new HashSet<>(fields).size())); } + public void testNoArraysNotAllowed() throws IOException { + // The whole point of nested is to deal with arrays of objects. Check that base class ObjectMapper's setting + // for disabling arrays is not enabled. + Exception e = expectThrows( + MapperParsingException.class, + () -> createDocumentMapper(fieldMapping(b -> b.field("type", "nested").field("allow_multiple_values", false))) + ); + assertThat(e.getMessage(), containsString("Nested object field [field] cannot have \"allow_multiple_values\" set to false.")); + } + public void testNestedArrayStrict() throws Exception { DocumentMapper docMapper = createDocumentMapper(mapping(b -> { b.startObject("nested1"); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldMapperTests.java index 2c1bd847b394a..9a89afc64ed56 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldMapperTests.java @@ -126,6 +126,19 @@ public void testNoDocValues() throws Exception { assertEquals(123, pointField.numericValue().doubleValue(), 0d); } + public void testNoArrays() throws IOException { + SourceToParse sourceWithArray = source(b -> b.array("field", 122, 123)); + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> { + minimalMapping(b); + b.field("allow_multiple_values", false); + })); + Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArray)); + assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); + + DocumentMapper okmapper = createDocumentMapper(fieldMapping(b -> { minimalMapping(b); })); + okmapper.parse(sourceWithArray); + } + public void testStore() throws Exception { DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> { minimalMapping(b); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperTests.java index 67ae6f0eb2e8a..ebdeb0ee7efbf 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperTests.java @@ -173,6 +173,45 @@ public void testMergeEnabledForIndexTemplates() throws IOException { assertTrue(objectMapper.isEnabled()); } + public void testNoArrays() throws IOException { + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "object").field("allow_multiple_values", false))); + Exception e = expectThrows( + MapperParsingException.class, + () -> mapper.parse( + source( + b -> b.startArray("field") + .startObject() + .field("foo", "bar") + .endObject() + + .startObject() + .field("foo", "bar") + .endObject() + + .endArray() + ) + ) + ); + assertThat(e.getMessage(), containsString("Object [field] cannot be a multi-valued field.")); + + // Check default behaviour of arrays-of-objects-allowed works. + DocumentMapper arraysOKmapper = createDocumentMapper(fieldMapping(b -> b.field("type", "object"))); + arraysOKmapper.parse( + source( + b -> b.startArray("field") + .startObject() + .field("foo", "bar") + .endObject() + + .startObject() + .field("foo", "bar") + .endObject() + + .endArray() + ) + ); + } + public void testFieldReplacementForIndexTemplates() throws IOException { MapperService mapperService = createMapperService(mapping(b -> {})); String mapping = Strings.toString( diff --git a/server/src/test/java/org/elasticsearch/index/mapper/RangeFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/RangeFieldMapperTests.java index 90c6df6389e20..d3ec244bce679 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/RangeFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/RangeFieldMapperTests.java @@ -111,6 +111,35 @@ public final void testNoDocValues() throws Exception { assertEquals(2, pointField.fieldType().pointIndexDimensionCount()); } + public final void testNoArrays() throws Exception { + SourceToParse sourceWithArrays = source(b -> { + b.startArray("field"); + b.startObject(); + b.field("gte", rangeValue()); + b.field("lte", rangeValue()); + b.endObject(); + + b.startObject(); + b.field("gte", rangeValue()); + b.field("lte", rangeValue()); + b.endObject(); + + b.endArray(); + }); + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> { + minimalMapping(b); + b.field("allow_multiple_values", false); + })); + Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArrays)); + assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); + + DocumentMapper okmapper = createDocumentMapper(fieldMapping(b -> { + minimalMapping(b); + b.field("allow_multiple_values", true); + })); + okmapper.parse(sourceWithArrays); + } + protected abstract String storedValue(); public final void testStore() throws Exception { diff --git a/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java index 8f64d575699c2..a5945d1c400f1 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java @@ -310,6 +310,15 @@ public void testIndexOptions() throws IOException { } } + public void testNoArrays() throws IOException { + SourceToParse sourceWithArrays = source(b -> b.array("field", "Hello", "World")); + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "text").field("allow_multiple_values", false))); + Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArrays)); + assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); + DocumentMapper okmapper = createDocumentMapper(fieldMapping(b -> b.field("type", "text"))); + okmapper.parse(sourceWithArrays); + } + public void testDefaultPositionIncrementGap() throws IOException { MapperService mapperService = createMapperService(fieldMapping(this::minimalMapping)); ParsedDocument doc = mapperService.documentMapper().parse(source(b -> b.array("field", new String[] { "a", "b" }))); diff --git a/test/framework/src/main/java/org/elasticsearch/index/mapper/MockFieldMapper.java b/test/framework/src/main/java/org/elasticsearch/index/mapper/MockFieldMapper.java index 51a96639539c3..88fbb582f8c69 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/mapper/MockFieldMapper.java +++ b/test/framework/src/main/java/org/elasticsearch/index/mapper/MockFieldMapper.java @@ -27,7 +27,16 @@ public MockFieldMapper(MappedFieldType fieldType) { } public MockFieldMapper(MappedFieldType fieldType, Map indexAnalyzers) { - super(findSimpleName(fieldType.name()), fieldType, indexAnalyzers, MultiFields.empty(), new CopyTo.Builder().build(), false, null); + super( + findSimpleName(fieldType.name()), + fieldType, + indexAnalyzers, + MultiFields.empty(), + new CopyTo.Builder().build(), + true, + false, + null + ); } public MockFieldMapper(String fullName, MappedFieldType fieldType, MultiFields multifields, CopyTo copyTo) { diff --git a/x-pack/plugin/mapper-version/src/main/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapper.java b/x-pack/plugin/mapper-version/src/main/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapper.java index 5b5044bb0c930..eb7708b9c7815 100644 --- a/x-pack/plugin/mapper-version/src/main/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapper.java +++ b/x-pack/plugin/mapper-version/src/main/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapper.java @@ -110,7 +110,8 @@ public VersionStringFieldMapper build(MapperBuilderContext context) { fieldtype, buildFieldType(context, fieldtype), multiFieldsBuilder.build(this, context), - copyTo.build() + copyTo.build(), + allowMultipleValues ); } @@ -324,9 +325,10 @@ private VersionStringFieldMapper( FieldType fieldType, MappedFieldType mappedFieldType, MultiFields multiFields, - CopyTo copyTo + CopyTo copyTo, + boolean allowMultipleValues ) { - super(simpleName, mappedFieldType, Lucene.KEYWORD_ANALYZER, multiFields, copyTo); + super(simpleName, mappedFieldType, Lucene.KEYWORD_ANALYZER, multiFields, copyTo, allowMultipleValues); this.fieldType = fieldType; } diff --git a/x-pack/plugin/mapper-version/src/test/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapperTests.java b/x-pack/plugin/mapper-version/src/test/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapperTests.java index 56f1b74fc4480..6f3cf3f29c152 100644 --- a/x-pack/plugin/mapper-version/src/test/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapperTests.java +++ b/x-pack/plugin/mapper-version/src/test/java/org/elasticsearch/xpack/versionfield/VersionStringFieldMapperTests.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.Collections; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; public class VersionStringFieldMapperTests extends MapperTestCase { @@ -108,6 +109,15 @@ public void testParsesNestedEmptyObjectStrict() throws IOException { ); } + public void testNoArrays() throws IOException { + SourceToParse sourceWithArrays = source(b -> b.array("field", randomVersionNumber(), randomVersionNumber())); + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "version").field("allow_multiple_values", false))); + Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArrays)); + assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); + DocumentMapper okmapper = createDocumentMapper(fieldMapping(b -> b.field("type", "version"))); + okmapper.parse(sourceWithArrays); + } + public void testFailsParsingNestedList() throws IOException { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(this::minimalMapping)); BytesReference source = BytesReference.bytes( diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java index eab5e5c41b2fe..2ec860bb86936 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java @@ -286,6 +286,7 @@ public GeoShapeWithDocValuesFieldMapper( builder.orientation.get(), multiFields, copyTo, + builder.getAllowMultipleValues(), parser ); this.builder = builder; diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapper.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapper.java index a33bf502fc8b4..8c6c1ae3c6db3 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapper.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapper.java @@ -136,6 +136,7 @@ public PointFieldMapper( builder.ignoreZValue.get(), builder.nullValue.get(), copyTo, + builder.getAllowMultipleValues(), parser ); this.builder = builder; diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapper.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapper.java index 5c3c02bdfea7d..42b3e7454b4ae 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapper.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapper.java @@ -153,6 +153,7 @@ public ShapeFieldMapper( builder.orientation.get(), multiFields, copyTo, + builder.getAllowMultipleValues(), parser ); this.builder = builder; diff --git a/x-pack/plugin/wildcard/src/main/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapper.java b/x-pack/plugin/wildcard/src/main/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapper.java index d6af156282da9..09c3b38fb1854 100644 --- a/x-pack/plugin/wildcard/src/main/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapper.java +++ b/x-pack/plugin/wildcard/src/main/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapper.java @@ -234,6 +234,7 @@ public WildcardFieldMapper build(MapperBuilderContext context) { ignoreAbove.get(), multiFieldsBuilder.build(this, context), copyTo.build(), + allowMultipleValues, nullValue.get(), indexVersionCreated ); @@ -863,10 +864,11 @@ private WildcardFieldMapper( int ignoreAbove, MultiFields multiFields, CopyTo copyTo, + boolean allowMultipleValues, String nullValue, Version indexVersionCreated ) { - super(simpleName, mappedFieldType, mappedFieldType.analyzer, multiFields, copyTo); + super(simpleName, mappedFieldType, mappedFieldType.analyzer, multiFields, copyTo, allowMultipleValues); this.nullValue = nullValue; this.ignoreAbove = ignoreAbove; this.indexVersionCreated = indexVersionCreated; diff --git a/x-pack/plugin/wildcard/src/test/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapperTests.java b/x-pack/plugin/wildcard/src/test/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapperTests.java index a1a056b5f3863..6b4e53bd8a6a9 100644 --- a/x-pack/plugin/wildcard/src/test/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapperTests.java +++ b/x-pack/plugin/wildcard/src/test/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapperTests.java @@ -58,8 +58,10 @@ import org.elasticsearch.index.mapper.LuceneDocument; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MapperBuilderContext; +import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.mapper.MapperTestCase; import org.elasticsearch.index.mapper.ParsedDocument; +import org.elasticsearch.index.mapper.SourceToParse; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.search.lookup.SearchLookup; @@ -79,6 +81,7 @@ import java.util.function.Supplier; import static java.util.Collections.emptyMap; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -231,6 +234,16 @@ public void testBWCIndexVersion() throws IOException { dir.close(); } + public void testNoArrays() throws IOException { + SourceToParse sourceWithArrays = source(b -> b.array("field", "a", "b")); + DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "wildcard").field("allow_multiple_values", false))); + Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArrays)); + assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); + + DocumentMapper okmapper = createDocumentMapper(fieldMapping(b -> b.field("type", "wildcard"))); + okmapper.parse(sourceWithArrays); + } + // Test long query strings don't cause exceptions public void testTooBigQueryField() throws IOException { Directory dir = newDirectory(); From a042bfd087b4337e31f3418bae86e4cdad7cbcb9 Mon Sep 17 00:00:00 2001 From: markharwood Date: Thu, 18 Nov 2021 16:59:04 +0000 Subject: [PATCH 2/6] =?UTF-8?q?Added=20docs,=20tests=20and=20opt-outs=20fo?= =?UTF-8?q?r=20support=20of=20allow=5Fmultiple=5Fvalues=20for=20fields=20g?= =?UTF-8?q?eo=20point/shape,=20histogram,=20dense=5Fvector=20text=20and=20?= =?UTF-8?q?join.=20At=20some=20point=20some=20of=20these=20fields=20may=20?= =?UTF-8?q?later=20offer=20user=20control=20over=20`allow=5Fmultiple=5Fval?= =?UTF-8?q?ues`=20but=20is=20complicated=20in=20that=20some=20of=20them=20?= =?UTF-8?q?take=20arrays=20for=20the=20single=20value=20eg.=20a=20point=20?= =?UTF-8?q?or=20vector.=20What=20we=E2=80=99d=20have=20to=20control=20is?= =?UTF-8?q?=20that=20arrays=20are=20accepted=20bu=20arrays=20of=20arrays?= =?UTF-8?q?=20are=20not.=20That=E2=80=99s=20more=20than=20enough=20complex?= =?UTF-8?q?ity=20for=20one=20PR=20so=20for=20now=20we=20reject=20the=20all?= =?UTF-8?q?ow=5Fmultiple=5Fvalues=20setting.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/reference/mapping/types.asciidoc | 2 +- docs/reference/mapping/types/array.asciidoc | 11 +++++++++++ docs/reference/mapping/types/boolean.asciidoc | 6 ++++++ docs/reference/mapping/types/date.asciidoc | 6 ++++++ docs/reference/mapping/types/geo-shape.asciidoc | 4 ++++ docs/reference/mapping/types/ip.asciidoc | 6 ++++++ docs/reference/mapping/types/keyword.asciidoc | 6 ++++++ docs/reference/mapping/types/numeric.asciidoc | 6 ++++++ docs/reference/mapping/types/object.asciidoc | 6 ++++++ docs/reference/mapping/types/range.asciidoc | 5 +++++ docs/reference/mapping/types/shape.asciidoc | 2 ++ docs/reference/mapping/types/version.asciidoc | 10 ++++++++-- docs/reference/mapping/types/wildcard.asciidoc | 5 +++++ .../index/mapper/extras/RankFeatureFieldMapper.java | 6 ++++++ .../index/mapper/extras/RankFeaturesFieldMapper.java | 6 ++++++ .../mapper/extras/SearchAsYouTypeFieldMapper.java | 8 +++++++- .../index/mapper/extras/TokenCountFieldMapper.java | 6 ++++++ .../join/mapper/ParentJoinFieldMapper.java | 7 +++++++ .../mapper/AbstractPointGeometryFieldMapper.java | 6 ++---- .../org/elasticsearch/index/mapper/FieldMapper.java | 7 +++++++ .../index/mapper/GeoPointFieldMapper.java | 8 ++++++-- .../elasticsearch/index/mapper/TextFieldMapper.java | 10 ++++++++++ .../xpack/analytics/mapper/HistogramFieldMapper.java | 6 ++++++ .../xpack/unsignedlong/UnsignedLongFieldMapper.java | 2 +- .../unsignedlong/UnsignedLongFieldMapperTests.java | 12 ++++++++++++ .../xpack/spatial/index/mapper/PointFieldMapper.java | 7 ++++++- .../xpack/vectors/mapper/DenseVectorFieldMapper.java | 6 ++++++ 27 files changed, 160 insertions(+), 12 deletions(-) diff --git a/docs/reference/mapping/types.asciidoc b/docs/reference/mapping/types.asciidoc index 2db659de63ef4..ba4b6ac205d10 100644 --- a/docs/reference/mapping/types.asciidoc +++ b/docs/reference/mapping/types.asciidoc @@ -112,7 +112,7 @@ as-you-type completion. === Arrays In {es}, arrays do not require a dedicated field data type. Any field can contain zero or more values by default, however, all values in the array must be of the -same field type. See <>. +same field type. See <> for more detail (including how to reject array values). [discrete] [[types-multi-fields]] diff --git a/docs/reference/mapping/types/array.asciidoc b/docs/reference/mapping/types/array.asciidoc index 2ffb191caf48e..1ca077c24f55e 100644 --- a/docs/reference/mapping/types/array.asciidoc +++ b/docs/reference/mapping/types/array.asciidoc @@ -81,3 +81,14 @@ GET my-index-000001/_search <3> The second document contains no arrays, but can be indexed into the same fields. <4> The query looks for `elasticsearch` in the `tags` field, and matches both documents. +.Rejecting documents with arrays +[NOTE] +==================================================== + +It is possible to reject documents with array values for a field +by using the `allow_multiple_values` setting in the field's index mapping. +The default value for fields is `true`, meaning arrays are accepted. +When this property is set to `false` documents that present arrays instead of single +values are rejected with an error. + +==================================================== diff --git a/docs/reference/mapping/types/boolean.asciidoc b/docs/reference/mapping/types/boolean.asciidoc index 9a9dafa37a2c1..b3d13871ed68e 100644 --- a/docs/reference/mapping/types/boolean.asciidoc +++ b/docs/reference/mapping/types/boolean.asciidoc @@ -166,6 +166,12 @@ The following parameters are accepted by `boolean` fields: [horizontal] +<>:: + + Should the field permit arrays of values? Accepts `true` + (default) or `false`. When `false`, documents presenting arrays + of values for this field will be rejected with an error. + <>:: Should the field be stored on disk in a column-stride fashion, so that it diff --git a/docs/reference/mapping/types/date.asciidoc b/docs/reference/mapping/types/date.asciidoc index 5c0c923250b1d..2823ba877db56 100644 --- a/docs/reference/mapping/types/date.asciidoc +++ b/docs/reference/mapping/types/date.asciidoc @@ -112,6 +112,12 @@ The following parameters are accepted by `date` fields: [horizontal] +<>:: + + Should the field permit arrays of values? Accepts `true` + (default) or `false`. When `false`, documents presenting arrays + of values for this field will be rejected with an error. + <>:: Should the field be stored on disk in a column-stride fashion, so that it diff --git a/docs/reference/mapping/types/geo-shape.asciidoc b/docs/reference/mapping/types/geo-shape.asciidoc index 2565ae5f320ff..c46eddb003608 100644 --- a/docs/reference/mapping/types/geo-shape.asciidoc +++ b/docs/reference/mapping/types/geo-shape.asciidoc @@ -60,6 +60,10 @@ and reject the whole document. |`coerce` |If `true` unclosed linear rings in polygons will be automatically closed. | `false` +|`allow_multiple_values` |If `false` will reject with an error documents presenting arrays of shapes +| `true` + + |======================================================================= diff --git a/docs/reference/mapping/types/ip.asciidoc b/docs/reference/mapping/types/ip.asciidoc index b81c63da98c10..4efb5312853e9 100644 --- a/docs/reference/mapping/types/ip.asciidoc +++ b/docs/reference/mapping/types/ip.asciidoc @@ -43,6 +43,12 @@ NOTE: You can also store ip ranges in a single field using an <>:: + + Should the field permit arrays of values? Accepts `true` + (default) or `false`. When `false`, documents presenting arrays + of values for this field will be rejected with an error. + <>:: Should the field be stored on disk in a column-stride fashion, so that it diff --git a/docs/reference/mapping/types/keyword.asciidoc b/docs/reference/mapping/types/keyword.asciidoc index deb2e5e49a1da..5311f5d0e24bb 100644 --- a/docs/reference/mapping/types/keyword.asciidoc +++ b/docs/reference/mapping/types/keyword.asciidoc @@ -53,6 +53,12 @@ include::numeric.asciidoc[tag=map-ids-as-keyword] The following parameters are accepted by `keyword` fields: +<>:: + + Should the field permit arrays of values? Accepts `true` + (default) or `false`. When `false`, documents presenting arrays + of values for this field will be rejected with an error. + <>:: Should the field be stored on disk in a column-stride fashion, so that it diff --git a/docs/reference/mapping/types/numeric.asciidoc b/docs/reference/mapping/types/numeric.asciidoc index d80f4ed35085f..c58ed8cc2087a 100644 --- a/docs/reference/mapping/types/numeric.asciidoc +++ b/docs/reference/mapping/types/numeric.asciidoc @@ -111,6 +111,12 @@ the data as both a `keyword` _and_ a numeric data type. The following parameters are accepted by numeric types: +<>:: + + Should the field permit arrays of values? Accepts `true` + (default) or `false`. When `false`, documents presenting arrays + of values for this field will be rejected with an error. + <>:: Try to convert strings to numbers and truncate fractions for integers. diff --git a/docs/reference/mapping/types/object.asciidoc b/docs/reference/mapping/types/object.asciidoc index ab20f8b22f557..1fa607953d993 100644 --- a/docs/reference/mapping/types/object.asciidoc +++ b/docs/reference/mapping/types/object.asciidoc @@ -79,6 +79,12 @@ You are not required to set the field `type` to `object` explicitly, as this is The following parameters are accepted by `object` fields: [horizontal] +<>:: + + Should the field permit arrays of values? Accepts `true` + (default) or `false`. When `false`, documents presenting arrays + of values for this field will be rejected with an error. + <>:: Whether or not new `properties` should be added dynamically diff --git a/docs/reference/mapping/types/range.asciidoc b/docs/reference/mapping/types/range.asciidoc index 3b1ce6a108178..068ef9a9f7e40 100644 --- a/docs/reference/mapping/types/range.asciidoc +++ b/docs/reference/mapping/types/range.asciidoc @@ -214,6 +214,11 @@ PUT range_index/_doc/2 The following parameters are accepted by range types: [horizontal] +<>:: + + Should the field permit arrays of values? Accepts `true` + (default) or `false`. When `false`, documents presenting arrays + of values for this field will be rejected with an error. <>:: diff --git a/docs/reference/mapping/types/shape.asciidoc b/docs/reference/mapping/types/shape.asciidoc index f4f3693f66485..45b149e020bd3 100644 --- a/docs/reference/mapping/types/shape.asciidoc +++ b/docs/reference/mapping/types/shape.asciidoc @@ -52,6 +52,8 @@ and reject the whole document. |`coerce` |If `true` unclosed linear rings in polygons will be automatically closed. | `false` +|`allow_multiple_values` |If `false` will reject with an error documents presenting arrays of shapes +| `true` |======================================================================= [[shape-indexing-approach]] diff --git a/docs/reference/mapping/types/version.asciidoc b/docs/reference/mapping/types/version.asciidoc index d639b4fbc4fa8..32cba55182af7 100644 --- a/docs/reference/mapping/types/version.asciidoc +++ b/docs/reference/mapping/types/version.asciidoc @@ -9,7 +9,7 @@ The `version` field type is a specialization of the `keyword` field for handling software version values and to support specialized precedence rules for them. Precedence is defined following the rules outlined by https://semver.org/[Semantic Versioning], which for example means that -major, minor and patch version parts are sorted numerically (i.e. +major, minor and patch version parts are sorted numerically (i.e. "2.1.0" < "2.4.1" < "2.11.2") and pre-release versions are sorted before release versions (i.e. "1.0.0-alpha" < "1.0.0"). @@ -30,7 +30,7 @@ PUT my-index-000001 -------------------------------------------------- -The field offers the same search capabilities as a regular keyword field. It +The field offers the same search capabilities as a regular keyword field. It can e.g. be searched for exact matches using `match` or `term` queries and supports prefix and wildcard searches. The main benefit is that `range` queries will honor Semver ordering, so a `range` query between "1.0.0" and "1.5.0" @@ -56,6 +56,12 @@ The following parameters are accepted by `version` fields: [horizontal] +<>:: + + Should the field permit arrays of values? Accepts `true` + (default) or `false`. When `false`, documents presenting arrays + of values for this field will be rejected with an error. + <>:: Metadata about the field. diff --git a/docs/reference/mapping/types/wildcard.asciidoc b/docs/reference/mapping/types/wildcard.asciidoc index 98b32c5d744d2..2303d2dee3442 100644 --- a/docs/reference/mapping/types/wildcard.asciidoc +++ b/docs/reference/mapping/types/wildcard.asciidoc @@ -114,6 +114,11 @@ GET my-index-000001/_search The following parameters are accepted by `wildcard` fields: [horizontal] +<>:: + + Should the field permit arrays of values? Accepts `true` + (default) or `false`. When `false`, documents presenting arrays + of values for this field will be rejected with an error. <>:: diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/RankFeatureFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/RankFeatureFieldMapper.java index f86c4fdd6a9c2..ca2e2cb96fa95 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/RankFeatureFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/RankFeatureFieldMapper.java @@ -74,6 +74,12 @@ protected List> getParameters() { return Arrays.asList(positiveScoreImpact, meta); } + @Override + protected boolean supportsAllowMultipleValuesChoice() { + // We don't allow users to redefine if multiple values are allowed - they are never allowed. + return false; + } + @Override public RankFeatureFieldMapper build(MapperBuilderContext context) { return new RankFeatureFieldMapper( diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/RankFeaturesFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/RankFeaturesFieldMapper.java index 7ae4f0129f75e..5860c915b3e03 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/RankFeaturesFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/RankFeaturesFieldMapper.java @@ -59,6 +59,12 @@ protected List> getParameters() { return List.of(positiveScoreImpact, meta); } + @Override + protected boolean supportsAllowMultipleValuesChoice() { + // We don't allow users to redefine if multiple values are allowed - they are never allowed. + return false; + } + @Override public RankFeaturesFieldMapper build(MapperBuilderContext context) { return new RankFeaturesFieldMapper( diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/SearchAsYouTypeFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/SearchAsYouTypeFieldMapper.java index a11a71060014a..15866457db044 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/SearchAsYouTypeFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/SearchAsYouTypeFieldMapper.java @@ -141,6 +141,12 @@ public static class Builder extends FieldMapper.Builder { private final Parameter> meta = Parameter.metaParam(); + @Override + protected boolean supportsAllowMultipleValuesChoice() { + // We don't allow users to redefine if multiple values are allowed - they are always allowed. + return false; + } + public Builder(String name, IndexAnalyzers indexAnalyzers) { super(name); this.analyzers = new TextParams.Analyzers( @@ -646,7 +652,7 @@ public SearchAsYouTypeFieldMapper( ShingleFieldMapper[] shingleFields, Builder builder ) { - super(simpleName, mappedFieldType, indexAnalyzers, MultiFields.empty(), copyTo, builder.getAllowMultipleValues(), false, null); + super(simpleName, mappedFieldType, indexAnalyzers, MultiFields.empty(), copyTo, true, false, null); this.prefixField = prefixField; this.shingleFields = shingleFields; this.maxShingleSize = builder.maxShingleSize.getValue(); diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/TokenCountFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/TokenCountFieldMapper.java index bfa230a926519..4c3a164d72e59 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/TokenCountFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/TokenCountFieldMapper.java @@ -72,6 +72,12 @@ protected List> getParameters() { return Arrays.asList(index, hasDocValues, store, analyzer, nullValue, enablePositionIncrements, meta); } + @Override + protected boolean supportsAllowMultipleValuesChoice() { + // Disable the default of parsing `allow_multiple_values` setting. + return false; + } + @Override public TokenCountFieldMapper build(MapperBuilderContext context) { if (analyzer.getValue() == null) { diff --git a/modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentJoinFieldMapper.java b/modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentJoinFieldMapper.java index a608bcfef9007..7853f2409450c 100644 --- a/modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentJoinFieldMapper.java +++ b/modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentJoinFieldMapper.java @@ -119,6 +119,12 @@ protected List> getParameters() { return Arrays.asList(eagerGlobalOrdinals, relations, meta); } + @Override + protected boolean supportsAllowMultipleValuesChoice() { + // Disable the default of parsing `allow_multiple_values` setting. + return false; + } + @Override public ParentJoinFieldMapper build(MapperBuilderContext context) { checkObjectOrNested(context, name); @@ -208,6 +214,7 @@ protected ParentJoinFieldMapper( boolean eagerGlobalOrdinals, List relations ) { + // TODO pass allowMultipleValues = false here? DocumentParser would then reject any arrays super(simpleName, mappedFieldType, Lucene.KEYWORD_ANALYZER, MultiFields.empty(), CopyTo.empty()); this.parentIdFields = parentIdFields; this.eagerGlobalOrdinals = eagerGlobalOrdinals; diff --git a/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java index 3e78f51669d53..78608f424dfbf 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/AbstractPointGeometryFieldMapper.java @@ -40,10 +40,9 @@ protected AbstractPointGeometryFieldMapper( Explicit ignoreZValue, T nullValue, CopyTo copyTo, - boolean allowMultipleValues, Parser parser ) { - super(simpleName, mappedFieldType, ignoreMalformed, ignoreZValue, multiFields, copyTo, allowMultipleValues, parser); + super(simpleName, mappedFieldType, ignoreMalformed, ignoreZValue, multiFields, copyTo, true, parser); this.nullValue = nullValue; } @@ -52,11 +51,10 @@ protected AbstractPointGeometryFieldMapper( MappedFieldType mappedFieldType, MultiFields multiFields, CopyTo copyTo, - boolean allowMultipleValues, Parser parser, String onScriptError ) { - super(simpleName, mappedFieldType, multiFields, copyTo, allowMultipleValues, parser, onScriptError); + super(simpleName, mappedFieldType, multiFields, copyTo, true, parser, onScriptError); this.nullValue = null; } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java index 8e48faeaf021b..d96edc7bf19c1 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java @@ -1312,6 +1312,9 @@ public final void parse(String name, MappingParserContext parserContext, Map toType(m).metricType, TimeSeriesParams.MetricType.histogram); } + @Override + protected boolean supportsAllowMultipleValuesChoice() { + // Disable the default of parsing `allow_multiple_values` setting. + return false; + } + @Override protected List> getParameters() { return List.of(ignoreMalformed, meta, metric); diff --git a/x-pack/plugin/mapper-unsigned-long/src/main/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapper.java b/x-pack/plugin/mapper-unsigned-long/src/main/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapper.java index 939b1697cf3a3..b605536bd063c 100644 --- a/x-pack/plugin/mapper-unsigned-long/src/main/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapper.java +++ b/x-pack/plugin/mapper-unsigned-long/src/main/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapper.java @@ -480,7 +480,7 @@ private UnsignedLongFieldMapper( CopyTo copyTo, Builder builder ) { - super(simpleName, mappedFieldType, multiFields, copyTo); + super(simpleName, mappedFieldType, multiFields, copyTo, builder.getAllowMultipleValues()); this.indexed = builder.indexed.getValue(); this.hasDocValues = builder.hasDocValues.getValue(); this.stored = builder.stored.getValue(); diff --git a/x-pack/plugin/mapper-unsigned-long/src/test/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapperTests.java b/x-pack/plugin/mapper-unsigned-long/src/test/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapperTests.java index 04aef6e4c2f8d..2126118287868 100644 --- a/x-pack/plugin/mapper-unsigned-long/src/test/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapperTests.java +++ b/x-pack/plugin/mapper-unsigned-long/src/test/java/org/elasticsearch/xpack/unsignedlong/UnsignedLongFieldMapperTests.java @@ -16,6 +16,7 @@ import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.MapperTestCase; import org.elasticsearch.index.mapper.ParsedDocument; +import org.elasticsearch.index.mapper.SourceToParse; import org.elasticsearch.index.termvectors.TermVectorsService; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.xcontent.XContentBuilder; @@ -236,6 +237,17 @@ public void testIndexingOutOfRangeValues() throws Exception { } } + public void testNoArrays() throws IOException { + SourceToParse sourceWithArrays = source(b -> b.array("field", 1L, 2L)); + DocumentMapper mapper = createDocumentMapper( + fieldMapping(b -> b.field("type", "unsigned_long").field("allow_multiple_values", false)) + ); + Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArrays)); + assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); + DocumentMapper okmapper = createDocumentMapper(fieldMapping(b -> b.field("type", "unsigned_long"))); + okmapper.parse(sourceWithArrays); + } + public void testExistsQueryDocValuesDisabled() throws IOException { MapperService mapperService = createMapperService(fieldMapping(b -> { minimalMapping(b); diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapper.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapper.java index 8c6c1ae3c6db3..a738a458ea5fb 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapper.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapper.java @@ -90,6 +90,12 @@ private static CartesianPoint parseNullValue(Object nullValue, boolean ignoreZVa return point; } + @Override + protected boolean supportsAllowMultipleValuesChoice() { + // Disable the default of parsing `allow_multiple_values` setting. + return false; + } + @Override public FieldMapper build(MapperBuilderContext context) { if (multiFieldsBuilder.hasMultiFields()) { @@ -136,7 +142,6 @@ public PointFieldMapper( builder.ignoreZValue.get(), builder.nullValue.get(), copyTo, - builder.getAllowMultipleValues(), parser ); this.builder = builder; diff --git a/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapper.java b/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapper.java index a02a333db5779..2d7ed31624034 100644 --- a/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapper.java +++ b/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/mapper/DenseVectorFieldMapper.java @@ -89,6 +89,12 @@ public static class Builder extends FieldMapper.Builder { } }); + @Override + protected boolean supportsAllowMultipleValuesChoice() { + // Disable the default of parsing `allow_multiple_values` setting. + return false; + } + private final Parameter indexed = Parameter.indexParam(m -> toType(m).indexed, false); private final Parameter similarity = Parameter.enumParam( "similarity", From 48dc22b4cc4babeea4ab336d6d8068a4ac3a3f98 Mon Sep 17 00:00:00 2001 From: markharwood Date: Fri, 19 Nov 2021 09:41:09 +0000 Subject: [PATCH 3/6] Doc fixes --- docs/reference/mapping/types/boolean.asciidoc | 2 +- docs/reference/mapping/types/date.asciidoc | 2 +- docs/reference/mapping/types/geo-shape.asciidoc | 4 ---- docs/reference/mapping/types/ip.asciidoc | 2 +- docs/reference/mapping/types/keyword.asciidoc | 2 +- docs/reference/mapping/types/numeric.asciidoc | 2 +- docs/reference/mapping/types/object.asciidoc | 2 +- docs/reference/mapping/types/range.asciidoc | 2 +- docs/reference/mapping/types/shape.asciidoc | 2 -- docs/reference/mapping/types/version.asciidoc | 2 +- docs/reference/mapping/types/wildcard.asciidoc | 2 +- 11 files changed, 9 insertions(+), 15 deletions(-) diff --git a/docs/reference/mapping/types/boolean.asciidoc b/docs/reference/mapping/types/boolean.asciidoc index b3d13871ed68e..cb0afc7401ce1 100644 --- a/docs/reference/mapping/types/boolean.asciidoc +++ b/docs/reference/mapping/types/boolean.asciidoc @@ -166,7 +166,7 @@ The following parameters are accepted by `boolean` fields: [horizontal] -<>:: +`allow_multiple_values`:: Should the field permit arrays of values? Accepts `true` (default) or `false`. When `false`, documents presenting arrays diff --git a/docs/reference/mapping/types/date.asciidoc b/docs/reference/mapping/types/date.asciidoc index 2823ba877db56..2920b1b8545cb 100644 --- a/docs/reference/mapping/types/date.asciidoc +++ b/docs/reference/mapping/types/date.asciidoc @@ -112,7 +112,7 @@ The following parameters are accepted by `date` fields: [horizontal] -<>:: +`allow_multiple_values`:: Should the field permit arrays of values? Accepts `true` (default) or `false`. When `false`, documents presenting arrays diff --git a/docs/reference/mapping/types/geo-shape.asciidoc b/docs/reference/mapping/types/geo-shape.asciidoc index c46eddb003608..2565ae5f320ff 100644 --- a/docs/reference/mapping/types/geo-shape.asciidoc +++ b/docs/reference/mapping/types/geo-shape.asciidoc @@ -60,10 +60,6 @@ and reject the whole document. |`coerce` |If `true` unclosed linear rings in polygons will be automatically closed. | `false` -|`allow_multiple_values` |If `false` will reject with an error documents presenting arrays of shapes -| `true` - - |======================================================================= diff --git a/docs/reference/mapping/types/ip.asciidoc b/docs/reference/mapping/types/ip.asciidoc index 4efb5312853e9..ee84535f35589 100644 --- a/docs/reference/mapping/types/ip.asciidoc +++ b/docs/reference/mapping/types/ip.asciidoc @@ -43,7 +43,7 @@ NOTE: You can also store ip ranges in a single field using an <>:: +`allow_multiple_values`:: Should the field permit arrays of values? Accepts `true` (default) or `false`. When `false`, documents presenting arrays diff --git a/docs/reference/mapping/types/keyword.asciidoc b/docs/reference/mapping/types/keyword.asciidoc index 5311f5d0e24bb..e61998005bc99 100644 --- a/docs/reference/mapping/types/keyword.asciidoc +++ b/docs/reference/mapping/types/keyword.asciidoc @@ -53,7 +53,7 @@ include::numeric.asciidoc[tag=map-ids-as-keyword] The following parameters are accepted by `keyword` fields: -<>:: +`allow_multiple_values`:: Should the field permit arrays of values? Accepts `true` (default) or `false`. When `false`, documents presenting arrays diff --git a/docs/reference/mapping/types/numeric.asciidoc b/docs/reference/mapping/types/numeric.asciidoc index c58ed8cc2087a..9feb337d8560f 100644 --- a/docs/reference/mapping/types/numeric.asciidoc +++ b/docs/reference/mapping/types/numeric.asciidoc @@ -111,7 +111,7 @@ the data as both a `keyword` _and_ a numeric data type. The following parameters are accepted by numeric types: -<>:: +`allow_multiple_values`:: Should the field permit arrays of values? Accepts `true` (default) or `false`. When `false`, documents presenting arrays diff --git a/docs/reference/mapping/types/object.asciidoc b/docs/reference/mapping/types/object.asciidoc index 1fa607953d993..3f751a52625d9 100644 --- a/docs/reference/mapping/types/object.asciidoc +++ b/docs/reference/mapping/types/object.asciidoc @@ -79,7 +79,7 @@ You are not required to set the field `type` to `object` explicitly, as this is The following parameters are accepted by `object` fields: [horizontal] -<>:: +`allow_multiple_values`:: Should the field permit arrays of values? Accepts `true` (default) or `false`. When `false`, documents presenting arrays diff --git a/docs/reference/mapping/types/range.asciidoc b/docs/reference/mapping/types/range.asciidoc index 068ef9a9f7e40..e51d484ed154c 100644 --- a/docs/reference/mapping/types/range.asciidoc +++ b/docs/reference/mapping/types/range.asciidoc @@ -214,7 +214,7 @@ PUT range_index/_doc/2 The following parameters are accepted by range types: [horizontal] -<>:: +`allow_multiple_values`:: Should the field permit arrays of values? Accepts `true` (default) or `false`. When `false`, documents presenting arrays diff --git a/docs/reference/mapping/types/shape.asciidoc b/docs/reference/mapping/types/shape.asciidoc index 45b149e020bd3..f4f3693f66485 100644 --- a/docs/reference/mapping/types/shape.asciidoc +++ b/docs/reference/mapping/types/shape.asciidoc @@ -52,8 +52,6 @@ and reject the whole document. |`coerce` |If `true` unclosed linear rings in polygons will be automatically closed. | `false` -|`allow_multiple_values` |If `false` will reject with an error documents presenting arrays of shapes -| `true` |======================================================================= [[shape-indexing-approach]] diff --git a/docs/reference/mapping/types/version.asciidoc b/docs/reference/mapping/types/version.asciidoc index 32cba55182af7..8f938e7998556 100644 --- a/docs/reference/mapping/types/version.asciidoc +++ b/docs/reference/mapping/types/version.asciidoc @@ -56,7 +56,7 @@ The following parameters are accepted by `version` fields: [horizontal] -<>:: +`allow_multiple_values`:: Should the field permit arrays of values? Accepts `true` (default) or `false`. When `false`, documents presenting arrays diff --git a/docs/reference/mapping/types/wildcard.asciidoc b/docs/reference/mapping/types/wildcard.asciidoc index 2303d2dee3442..4ce0b4e509313 100644 --- a/docs/reference/mapping/types/wildcard.asciidoc +++ b/docs/reference/mapping/types/wildcard.asciidoc @@ -114,7 +114,7 @@ GET my-index-000001/_search The following parameters are accepted by `wildcard` fields: [horizontal] -<>:: +`allow_multiple_values`:: Should the field permit arrays of values? Accepts `true` (default) or `false`. When `false`, documents presenting arrays From 9e17115bcd830fc3cec2088621cbf33596a2da06 Mon Sep 17 00:00:00 2001 From: markharwood Date: Fri, 19 Nov 2021 09:59:19 +0000 Subject: [PATCH 4/6] Formatting --- .../java/org/elasticsearch/index/mapper/FieldMapper.java | 2 +- .../elasticsearch/index/mapper/GeoPointFieldMapper.java | 9 +-------- .../org/elasticsearch/index/mapper/TextFieldMapper.java | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java index d96edc7bf19c1..8ff7192cf7bea 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java @@ -1394,7 +1394,7 @@ public final void parse(String name, MappingParserContext parserContext, Map parser, Builder builder) { - super( - simpleName, - mappedFieldType, - MultiFields.empty(), - CopyTo.empty(), - parser, - builder.onScriptError.get() - ); + super(simpleName, mappedFieldType, MultiFields.empty(), CopyTo.empty(), parser, builder.onScriptError.get()); this.builder = builder; this.scriptValues = builder.scriptValues(); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java index eabae1906c1c8..0627783568f3e 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java @@ -271,7 +271,7 @@ public Builder(String name, IndexAnalyzers indexAnalyzers) { @Override protected boolean supportsAllowMultipleValuesChoice() { // Disable the default of parsing `allow_multiple_values` setting. - // I'm not sure when/why it makes sense to allow users to control if multi-values are allowed. + // I'm not sure when/why it makes sense to allow users to control if multi-values are allowed. // Text fields are inherently "multi-valued" anyway because analyzers create multiple tokens // from a given input string. We can revisit this override if required but for now it seems // like the right thing to do. From 1e9285910f83ac9425c7c6e8d787e339029671ab Mon Sep 17 00:00:00 2001 From: markharwood Date: Fri, 19 Nov 2021 10:38:52 +0000 Subject: [PATCH 5/6] Removed irrelevant test --- .../elasticsearch/index/mapper/TextFieldMapperTests.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java index a5945d1c400f1..8f64d575699c2 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java @@ -310,15 +310,6 @@ public void testIndexOptions() throws IOException { } } - public void testNoArrays() throws IOException { - SourceToParse sourceWithArrays = source(b -> b.array("field", "Hello", "World")); - DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "text").field("allow_multiple_values", false))); - Exception e = expectThrows(MapperParsingException.class, () -> mapper.parse(sourceWithArrays)); - assertThat(e.getMessage(), containsString("Field [field] cannot be a multi-valued field")); - DocumentMapper okmapper = createDocumentMapper(fieldMapping(b -> b.field("type", "text"))); - okmapper.parse(sourceWithArrays); - } - public void testDefaultPositionIncrementGap() throws IOException { MapperService mapperService = createMapperService(fieldMapping(this::minimalMapping)); ParsedDocument doc = mapperService.documentMapper().parse(source(b -> b.array("field", new String[] { "a", "b" }))); From 93420ad4725bbf368fe6c1e7a068a390a955c21d Mon Sep 17 00:00:00 2001 From: markharwood Date: Fri, 19 Nov 2021 12:04:15 +0000 Subject: [PATCH 6/6] Remove mapping support for allow_multiple_values in flattened field --- .../index/mapper/flattened/FlattenedFieldMapper.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/main/java/org/elasticsearch/index/mapper/flattened/FlattenedFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/flattened/FlattenedFieldMapper.java index 5706f15129bc6..2d28664b2708a 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/flattened/FlattenedFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/flattened/FlattenedFieldMapper.java @@ -126,6 +126,11 @@ public static class Builder extends FieldMapper.Builder { } }); + @Override + protected boolean supportsAllowMultipleValuesChoice() { + return false; + } + private final Parameter indexed = Parameter.indexParam(m -> builder(m).indexed.get(), true); private final Parameter hasDocValues = Parameter.docValuesParam(m -> builder(m).hasDocValues.get(), true);