Răsfoiți Sursa

Convert NumberFieldMapper to parametrized form (#61092)

In addition, this commit converts ScaledFloatFieldMapper as it was relying
on a number of static values taken from NumberFieldMapper that had changed
or been removed.
Alan Woodward 5 ani în urmă
părinte
comite
dbd4fd0254

+ 1 - 1
modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/MapperExtrasPlugin.java

@@ -35,7 +35,7 @@ public class MapperExtrasPlugin extends Plugin implements MapperPlugin, SearchPl
     @Override
     public Map<String, Mapper.TypeParser> getMappers() {
         Map<String, Mapper.TypeParser> mappers = new LinkedHashMap<>();
-        mappers.put(ScaledFloatFieldMapper.CONTENT_TYPE, new ScaledFloatFieldMapper.TypeParser());
+        mappers.put(ScaledFloatFieldMapper.CONTENT_TYPE, ScaledFloatFieldMapper.PARSER);
         mappers.put(TokenCountFieldMapper.CONTENT_TYPE, new TokenCountFieldMapper.TypeParser());
         mappers.put(RankFeatureFieldMapper.CONTENT_TYPE, new RankFeatureFieldMapper.TypeParser());
         mappers.put(RankFeaturesFieldMapper.CONTENT_TYPE, new RankFeaturesFieldMapper.TypeParser());

+ 77 - 157
modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java

@@ -20,9 +20,7 @@
 package org.elasticsearch.index.mapper;
 
 import org.apache.lucene.document.Field;
-import org.apache.lucene.document.FieldType;
 import org.apache.lucene.index.DocValues;
-import org.apache.lucene.index.IndexOptions;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.NumericDocValues;
 import org.apache.lucene.index.SortedNumericDocValues;
@@ -34,13 +32,12 @@ import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.util.BytesRef;
 import org.elasticsearch.common.Explicit;
 import org.elasticsearch.common.settings.Setting;
-import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentParser.Token;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
 import org.elasticsearch.index.fielddata.FieldData;
 import org.elasticsearch.index.fielddata.IndexFieldData;
-import org.elasticsearch.index.fielddata.IndexFieldDataCache;
 import org.elasticsearch.index.fielddata.IndexNumericFieldData;
 import org.elasticsearch.index.fielddata.LeafNumericFieldData;
 import org.elasticsearch.index.fielddata.NumericDoubleValues;
@@ -48,9 +45,7 @@ import org.elasticsearch.index.fielddata.ScriptDocValues;
 import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
 import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;
 import org.elasticsearch.index.fielddata.plain.SortedNumericIndexFieldData;
-import org.elasticsearch.index.mapper.NumberFieldMapper.Defaults;
 import org.elasticsearch.index.query.QueryShardContext;
-import org.elasticsearch.indices.breaker.CircuitBreakerService;
 import org.elasticsearch.search.DocValueFormat;
 import org.elasticsearch.search.aggregations.support.ValuesSourceType;
 
@@ -59,124 +54,82 @@ import java.math.BigDecimal;
 import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
 /** A {@link FieldMapper} for scaled floats. Values are internally multiplied
  *  by a scaling factor and rounded to the closest long. */
-public class ScaledFloatFieldMapper extends FieldMapper {
+public class ScaledFloatFieldMapper extends ParametrizedFieldMapper {
 
     public static final String CONTENT_TYPE = "scaled_float";
+
     // use the same default as numbers
     private static final Setting<Boolean> COERCE_SETTING = NumberFieldMapper.COERCE_SETTING;
-    private static final FieldType FIELD_TYPE = new FieldType();
-    static {
-        FIELD_TYPE.setIndexOptions(IndexOptions.DOCS);
+
+    private static ScaledFloatFieldMapper toType(FieldMapper in) {
+        return (ScaledFloatFieldMapper) in;
     }
 
-    public static class Builder extends FieldMapper.Builder<Builder> {
+    public static class Builder extends ParametrizedFieldMapper.Builder {
 
-        private boolean scalingFactorSet = false;
-        private double scalingFactor;
-        private Boolean ignoreMalformed;
-        private Boolean coerce;
-        private Double nullValue;
+        private final Parameter<Boolean> indexed = Parameter.indexParam(m -> toType(m).indexed, true);
+        private final Parameter<Boolean> hasDocValues = Parameter.docValuesParam(m -> toType(m).hasDocValues, true);
+        private final Parameter<Boolean> stored = Parameter.storeParam(m -> toType(m).stored, false);
 
-        public Builder(String name) {
-            super(name, FIELD_TYPE);
-            builder = this;
-        }
+        private final Parameter<Explicit<Boolean>> ignoreMalformed;
+        private final Parameter<Explicit<Boolean>> coerce;
 
-        public Builder ignoreMalformed(boolean ignoreMalformed) {
-            this.ignoreMalformed = ignoreMalformed;
-            return builder;
-        }
+        private final Parameter<Double> scalingFactor = new Parameter<>("scaling_factor", false, () -> null,
+            (n, c, o) -> XContentMapValues.nodeDoubleValue(o), m -> toType(m).scalingFactor)
+            .setValidator(v -> {
+                if (v == null) {
+                    throw new IllegalArgumentException("Field [scaling_factor] is required");
+                }
+                if (Double.isFinite(v) == false || v <= 0) {
+                    throw new IllegalArgumentException("[scaling_factor] must be a positive number, got [" + v + "]");
+                }
+            });
+        private final Parameter<Double> nullValue = new Parameter<>("null_value", false, () -> null,
+            (n, c, o) -> XContentMapValues.nodeDoubleValue(o), m -> toType(m).nullValue);
 
-        @Override
-        public Builder indexOptions(IndexOptions indexOptions) {
-            throw new MapperParsingException(
-                    "index_options not allowed in field [" + name + "] of type [" + CONTENT_TYPE + "]");
-        }
+        private final Parameter<Map<String, String>> meta = Parameter.metaParam();
 
-        protected Explicit<Boolean> ignoreMalformed(BuilderContext context) {
-            if (ignoreMalformed != null) {
-                return new Explicit<>(ignoreMalformed, true);
-            }
-            if (context.indexSettings() != null) {
-                return new Explicit<>(IGNORE_MALFORMED_SETTING.get(context.indexSettings()), false);
-            }
-            return Defaults.IGNORE_MALFORMED;
+        public Builder(String name, Settings settings) {
+            this(name, IGNORE_MALFORMED_SETTING.get(settings), COERCE_SETTING.get(settings));
         }
 
-        public Builder coerce(boolean coerce) {
-            this.coerce = coerce;
-            return builder;
+        public Builder(String name, boolean ignoreMalformedByDefault, boolean coerceByDefault) {
+            super(name);
+            this.ignoreMalformed
+                = Parameter.explicitBoolParam("ignore_malformed", true, m -> toType(m).ignoreMalformed, ignoreMalformedByDefault);
+            this.coerce
+                = Parameter.explicitBoolParam("coerce", true, m -> toType(m).coerce, coerceByDefault);
         }
 
-        public Builder scalingFactor(double scalingFactor) {
-            this.scalingFactor = scalingFactor;
-            scalingFactorSet = true;
+        Builder scalingFactor(double scalingFactor) {
+            this.scalingFactor.setValue(scalingFactor);
             return this;
         }
 
-        public Builder nullValue(Double nullValue) {
-            this.nullValue = nullValue;
+        Builder nullValue(double nullValue) {
+            this.nullValue.setValue(nullValue);
             return this;
         }
 
-        protected Explicit<Boolean> coerce(BuilderContext context) {
-            if (coerce != null) {
-                return new Explicit<>(coerce, true);
-            }
-            if (context.indexSettings() != null) {
-                return new Explicit<>(COERCE_SETTING.get(context.indexSettings()), false);
-            }
-            return Defaults.COERCE;
+        @Override
+        protected List<Parameter<?>> getParameters() {
+            return List.of(indexed, hasDocValues, stored, ignoreMalformed, meta, scalingFactor, coerce, nullValue);
         }
 
         @Override
         public ScaledFloatFieldMapper build(BuilderContext context) {
-            if (scalingFactorSet == false) {
-                throw new IllegalArgumentException("Field [" + name + "] misses required parameter [scaling_factor]");
-            }
-            ScaledFloatFieldType type = new ScaledFloatFieldType(buildFullName(context), indexed, hasDocValues, meta, scalingFactor);
-            return new ScaledFloatFieldMapper(name, fieldType, type, ignoreMalformed(context),
-                    coerce(context), multiFieldsBuilder.build(this, context), copyTo, nullValue);
+            ScaledFloatFieldType type = new ScaledFloatFieldType(buildFullName(context), indexed.getValue(), hasDocValues.getValue(),
+                meta.getValue(), scalingFactor.getValue());
+            return new ScaledFloatFieldMapper(name, type, multiFieldsBuilder.build(this, context), copyTo.build(), this);
         }
     }
 
-    public static class TypeParser implements Mapper.TypeParser {
-
-        @Override
-        public Mapper.Builder<?> parse(String name, Map<String, Object> node,
-                                         ParserContext parserContext) throws MapperParsingException {
-            Builder builder = new Builder(name);
-            TypeParsers.parseField(builder, name, node, parserContext);
-            for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
-                Map.Entry<String, Object> entry = iterator.next();
-                String propName = entry.getKey();
-                Object propNode = entry.getValue();
-                if (propName.equals("null_value")) {
-                    if (propNode == null) {
-                        throw new MapperParsingException("Property [null_value] cannot be null.");
-                    }
-                    builder.nullValue(ScaledFloatFieldMapper.parse(propNode));
-                    iterator.remove();
-                } else if (propName.equals("ignore_malformed")) {
-                    builder.ignoreMalformed(XContentMapValues.nodeBooleanValue(propNode, name + ".ignore_malformed"));
-                    iterator.remove();
-                } else if (propName.equals("coerce")) {
-                    builder.coerce(XContentMapValues.nodeBooleanValue(propNode, name + ".coerce"));
-                    iterator.remove();
-                } else if (propName.equals("scaling_factor")) {
-                    builder.scalingFactor(ScaledFloatFieldMapper.parse(propNode));
-                    iterator.remove();
-                }
-            }
-            return builder;
-        }
-    }
+    public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n, c.getSettings()));
 
     public static final class ScaledFloatFieldType extends SimpleMappedFieldType {
 
@@ -264,19 +217,12 @@ public class ScaledFloatFieldMapper extends FieldMapper {
         @Override
         public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName) {
             failIfNoDocValues();
-            return new IndexFieldData.Builder() {
-                @Override
-                public IndexFieldData<?> build(
-                    IndexFieldDataCache cache,
-                    CircuitBreakerService breakerService,
-                    MapperService mapperService
-                ) {
-                    final IndexNumericFieldData scaledValues = new SortedNumericIndexFieldData.Builder(
-                        name(),
-                        IndexNumericFieldData.NumericType.LONG
-                    ).build(cache, breakerService, mapperService);
-                    return new ScaledFloatIndexFieldData(scaledValues, scalingFactor);
-                }
+            return (cache, breakerService, mapperService) -> {
+                final IndexNumericFieldData scaledValues = new SortedNumericIndexFieldData.Builder(
+                    name(),
+                    IndexNumericFieldData.NumericType.LONG
+                ).build(cache, breakerService, mapperService);
+                return new ScaledFloatIndexFieldData(scaledValues, scalingFactor);
             };
         }
 
@@ -315,30 +261,33 @@ public class ScaledFloatFieldMapper extends FieldMapper {
         }
     }
 
-    private Explicit<Boolean> ignoreMalformed;
-
-    private Explicit<Boolean> coerce;
-
+    private final Explicit<Boolean> ignoreMalformed;
+    private final Explicit<Boolean> coerce;
+    private final boolean indexed;
+    private final boolean hasDocValues;
+    private final boolean stored;
     private final Double nullValue;
     private final double scalingFactor;
 
+    private final boolean ignoreMalformedByDefault;
+    private final boolean coerceByDefault;
+
     private ScaledFloatFieldMapper(
             String simpleName,
-            FieldType fieldType,
             ScaledFloatFieldType mappedFieldType,
-            Explicit<Boolean> ignoreMalformed,
-            Explicit<Boolean> coerce,
             MultiFields multiFields,
             CopyTo copyTo,
-            Double nullValue) {
-        super(simpleName, fieldType, mappedFieldType, multiFields, copyTo);
-        this.scalingFactor = mappedFieldType.scalingFactor;
-        this.nullValue = nullValue;
-        if (Double.isFinite(scalingFactor) == false || scalingFactor <= 0) {
-            throw new IllegalArgumentException("[scaling_factor] must be a positive number, got [" + scalingFactor + "]");
-        }
-        this.ignoreMalformed = ignoreMalformed;
-        this.coerce = coerce;
+            Builder builder) {
+        super(simpleName, mappedFieldType, multiFields, copyTo);
+        this.indexed = builder.indexed.getValue();
+        this.hasDocValues = builder.hasDocValues.getValue();
+        this.stored = builder.stored.getValue();
+        this.scalingFactor = builder.scalingFactor.getValue();
+        this.nullValue = builder.nullValue.getValue();
+        this.ignoreMalformed = builder.ignoreMalformed.getValue();
+        this.coerce = builder.coerce.getValue();
+        this.ignoreMalformedByDefault = builder.ignoreMalformed.getDefaultValue().value();
+        this.coerceByDefault = builder.coerce.getDefaultValue().value();
     }
 
     @Override
@@ -351,6 +300,11 @@ public class ScaledFloatFieldMapper extends FieldMapper {
         return CONTENT_TYPE;
     }
 
+    @Override
+    public ParametrizedFieldMapper.Builder getMergeBuilder() {
+        return new Builder(simpleName(), ignoreMalformedByDefault, coerceByDefault).init(this);
+    }
+
     @Override
     protected ScaledFloatFieldMapper clone() {
         return (ScaledFloatFieldMapper) super.clone();
@@ -406,49 +360,15 @@ public class ScaledFloatFieldMapper extends FieldMapper {
         }
         long scaledValue = Math.round(doubleValue * scalingFactor);
 
-        boolean indexed = fieldType().isSearchable();
-        boolean docValued = fieldType().hasDocValues();
-        boolean stored = fieldType.stored();
-        List<Field> fields = NumberFieldMapper.NumberType.LONG.createFields(fieldType().name(), scaledValue, indexed, docValued, stored);
+        List<Field> fields
+            = NumberFieldMapper.NumberType.LONG.createFields(fieldType().name(), scaledValue, indexed, hasDocValues, stored);
         context.doc().addAll(fields);
 
-        if (docValued == false && (indexed || stored)) {
+        if (hasDocValues == false && (indexed || stored)) {
             createFieldNamesField(context);
         }
     }
 
-    @Override
-    protected void mergeOptions(FieldMapper other, List<String> conflicts) {
-        ScaledFloatFieldMapper mergeWith = (ScaledFloatFieldMapper) other;
-        ScaledFloatFieldType ft = (ScaledFloatFieldType) other.fieldType();
-        if (fieldType().scalingFactor != ft.scalingFactor) {
-            conflicts.add("mapper [" + name() + "] has different [scaling_factor] values");
-        }
-        if (mergeWith.ignoreMalformed.explicit()) {
-            this.ignoreMalformed = mergeWith.ignoreMalformed;
-        }
-        if (mergeWith.coerce.explicit()) {
-            this.coerce = mergeWith.coerce;
-        }
-    }
-
-    @Override
-    protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
-        super.doXContentBody(builder, includeDefaults, params);
-
-        builder.field("scaling_factor", scalingFactor);
-
-        if (includeDefaults || ignoreMalformed.explicit()) {
-            builder.field("ignore_malformed", ignoreMalformed.value());
-        }
-        if (includeDefaults || coerce.explicit()) {
-            builder.field("coerce", coerce.value());
-        }
-        if (nullValue != null) {
-            builder.field("null_value", nullValue);
-        }
-    }
-
     static Double parse(Object value) {
         return objectToDouble(value);
     }

+ 9 - 27
modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapperTests.java

@@ -30,42 +30,23 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.plugins.Plugin;
-import org.junit.Before;
 
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 
 import static java.util.Collections.singletonList;
 import static org.elasticsearch.index.mapper.FieldMapperTestCase.fetchSourceValue;
 import static org.hamcrest.Matchers.containsString;
 
-public class ScaledFloatFieldMapperTests extends FieldMapperTestCase2<ScaledFloatFieldMapper.Builder> {
-    @Before
-    public void setup() {
-        addModifier("scaling_factor", false, (a, b) -> {
-            a.scalingFactor(10);
-            b.scalingFactor(100);
-        });
-    }
-
-    @Override
-    protected Set<String> unsupportedProperties() {
-        return Set.of("analyzer", "similarity");
-    }
+public class ScaledFloatFieldMapperTests extends MapperTestCase {
 
     @Override
     protected Collection<? extends Plugin> getPlugins() {
         return singletonList(new MapperExtrasPlugin());
     }
 
-    @Override
-    protected ScaledFloatFieldMapper.Builder newBuilder() {
-        return new ScaledFloatFieldMapper.Builder("scaled-float").scalingFactor(1);
-    }
-
     @Override
     protected void minimalMapping(XContentBuilder b) throws IOException {
         b.field("type", "scaled_float").field("scaling_factor", 10.0);
@@ -95,15 +76,15 @@ public class ScaledFloatFieldMapperTests extends FieldMapperTestCase2<ScaledFloa
         assertFalse(dvField.fieldType().stored());
     }
 
-    public void testMissingScalingFactor() throws IOException {
+    public void testMissingScalingFactor() {
         Exception e = expectThrows(
             MapperParsingException.class,
             () -> createMapperService(fieldMapping(b -> b.field("type", "scaled_float")))
         );
-        assertThat(e.getMessage(), containsString("Field [field] misses required parameter [scaling_factor]"));
+        assertThat(e.getMessage(), containsString("Failed to parse mapping: Field [scaling_factor] is required"));
     }
 
-    public void testIllegalScalingFactor() throws IOException {
+    public void testIllegalScalingFactor() {
         Exception e = expectThrows(
             MapperParsingException.class,
             () -> createMapperService(fieldMapping(b -> b.field("type", "scaled_float").field("scaling_factor", -1)))
@@ -269,26 +250,27 @@ public class ScaledFloatFieldMapperTests extends FieldMapperTestCase2<ScaledFloa
     /**
      * `index_options` was deprecated and is rejected as of 7.0
      */
-    public void testRejectIndexOptions() throws IOException {
+    public void testRejectIndexOptions() {
         MapperParsingException e = expectThrows(
             MapperParsingException.class,
             () -> createMapperService(fieldMapping(b -> b.field("type", "scaled_float").field("index_options", randomIndexOptions())))
         );
-        assertThat(e.getMessage(), containsString("index_options not allowed in field [field] of type [scaled_float]"));
+        assertThat(e.getMessage(),
+            containsString("Failed to parse mapping: unknown parameter [index_options] on mapper [field] of type [scaled_float]"));
     }
 
     public void testFetchSourceValue() {
         Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT.id).build();
         Mapper.BuilderContext context = new Mapper.BuilderContext(settings, new ContentPath());
 
-        ScaledFloatFieldMapper mapper = new ScaledFloatFieldMapper.Builder("field")
+        ScaledFloatFieldMapper mapper = new ScaledFloatFieldMapper.Builder("field", false, false)
             .scalingFactor(100)
             .build(context);
         assertEquals(List.of(3.14), fetchSourceValue(mapper, 3.1415926));
         assertEquals(List.of(3.14), fetchSourceValue(mapper, "3.1415"));
         assertEquals(List.of(), fetchSourceValue(mapper, ""));
 
-        ScaledFloatFieldMapper nullValueMapper = new ScaledFloatFieldMapper.Builder("field")
+        ScaledFloatFieldMapper nullValueMapper = new ScaledFloatFieldMapper.Builder("field", false, false)
             .scalingFactor(100)
             .nullValue(2.71)
             .build(context);

+ 1 - 4
modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorFieldMapper.java

@@ -169,10 +169,7 @@ public class PercolatorFieldMapper extends FieldMapper {
 
         static NumberFieldMapper createMinimumShouldMatchField(BuilderContext context) {
             NumberFieldMapper.Builder builder =
-                    new NumberFieldMapper.Builder(MINIMUM_SHOULD_MATCH_FIELD_NAME, NumberFieldMapper.NumberType.INTEGER);
-            builder.index(false);
-            builder.store(false);
-            builder.docValues(true);
+                    NumberFieldMapper.Builder.docValuesOnly(MINIMUM_SHOULD_MATCH_FIELD_NAME, NumberFieldMapper.NumberType.INTEGER);
             return builder.build(context);
         }
 

+ 8 - 12
server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java

@@ -622,16 +622,12 @@ final class DocumentParser {
         }
     }
 
-    private static Mapper.Builder<?> newLongBuilder(String name) {
-        return new NumberFieldMapper.Builder(name, NumberFieldMapper.NumberType.LONG);
+    private static Mapper.Builder<?> newLongBuilder(String name, Settings settings) {
+        return new NumberFieldMapper.Builder(name, NumberFieldMapper.NumberType.LONG, settings);
     }
 
-    private static Mapper.Builder<?> newFloatBuilder(String name) {
-        return new NumberFieldMapper.Builder(name, NumberFieldMapper.NumberType.FLOAT);
-    }
-
-    private static Mapper.Builder<?> newDateBuilder(String name, DateFormatter dateTimeFormatter, boolean ignoreMalformedByDefault) {
-        return new DateFieldMapper.Builder(name, DateFieldMapper.Resolution.MILLISECONDS, dateTimeFormatter, ignoreMalformedByDefault);
+    private static Mapper.Builder<?> newFloatBuilder(String name, Settings settings) {
+        return new NumberFieldMapper.Builder(name, NumberFieldMapper.NumberType.FLOAT, settings);
     }
 
     private static Mapper.Builder<?> createBuilderFromDynamicValue(final ParseContext context,
@@ -659,13 +655,13 @@ final class DocumentParser {
             if (parseableAsLong && context.root().numericDetection()) {
                 Mapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, XContentFieldType.LONG);
                 if (builder == null) {
-                    builder = newLongBuilder(currentFieldName);
+                    builder = newLongBuilder(currentFieldName, context.indexSettings().getSettings());
                 }
                 return builder;
             } else if (parseableAsDouble && context.root().numericDetection()) {
                 Mapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, XContentFieldType.DOUBLE);
                 if (builder == null) {
-                    builder = newFloatBuilder(currentFieldName);
+                    builder = newFloatBuilder(currentFieldName, context.indexSettings().getSettings());
                 }
                 return builder;
             } else if (parseableAsLong == false && parseableAsDouble == false && context.root().dateDetection()) {
@@ -704,7 +700,7 @@ final class DocumentParser {
                     || numberType == XContentParser.NumberType.BIG_INTEGER) {
                 Mapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, XContentFieldType.LONG);
                 if (builder == null) {
-                    builder = newLongBuilder(currentFieldName);
+                    builder = newLongBuilder(currentFieldName, context.indexSettings().getSettings());
                 }
                 return builder;
             } else if (numberType == XContentParser.NumberType.FLOAT
@@ -715,7 +711,7 @@ final class DocumentParser {
                     // no templates are defined, we use float by default instead of double
                     // since this is much more space-efficient and should be enough most of
                     // the time
-                    builder = newFloatBuilder(currentFieldName);
+                    builder = newFloatBuilder(currentFieldName, context.indexSettings().getSettings());
                 }
                 return builder;
             }

+ 72 - 134
server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java

@@ -23,14 +23,12 @@ import com.fasterxml.jackson.core.JsonParseException;
 import com.fasterxml.jackson.core.exc.InputCoercionException;
 import org.apache.lucene.document.DoublePoint;
 import org.apache.lucene.document.Field;
-import org.apache.lucene.document.FieldType;
 import org.apache.lucene.document.FloatPoint;
 import org.apache.lucene.document.HalfFloatPoint;
 import org.apache.lucene.document.IntPoint;
 import org.apache.lucene.document.LongPoint;
 import org.apache.lucene.document.SortedNumericDocValuesField;
 import org.apache.lucene.document.StoredField;
-import org.apache.lucene.index.IndexOptions;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BoostQuery;
 import org.apache.lucene.search.DocValuesFieldExistsQuery;
@@ -47,10 +45,9 @@ import org.elasticsearch.common.lucene.Lucene;
 import org.elasticsearch.common.lucene.search.Queries;
 import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Setting.Property;
-import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentParser.Token;
-import org.elasticsearch.common.xcontent.support.XContentMapValues;
 import org.elasticsearch.index.fielddata.IndexFieldData;
 import org.elasticsearch.index.fielddata.IndexNumericFieldData.NumericType;
 import org.elasticsearch.index.fielddata.plain.SortedNumericIndexFieldData;
@@ -62,123 +59,77 @@ import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.function.Function;
 
 /** A {@link FieldMapper} for numeric types: byte, short, int, long, float and double. */
-public class NumberFieldMapper extends FieldMapper {
+public class NumberFieldMapper extends ParametrizedFieldMapper {
 
     public static final Setting<Boolean> COERCE_SETTING =
             Setting.boolSetting("index.mapping.coerce", true, Property.IndexScope);
 
-    public static class Defaults {
-        public static final Explicit<Boolean> IGNORE_MALFORMED = new Explicit<>(false, false);
-        public static final Explicit<Boolean> COERCE = new Explicit<>(true, false);
-        public static final FieldType FIELD_TYPE = new FieldType();
-        static {
-            FIELD_TYPE.setStored(false);
-            FIELD_TYPE.setIndexOptions(IndexOptions.DOCS);
-            FIELD_TYPE.freeze();
-        }
+    private static NumberFieldMapper toType(FieldMapper in) {
+        return (NumberFieldMapper) in;
     }
 
-    public static class Builder extends FieldMapper.Builder<Builder> {
+    public static class Builder extends ParametrizedFieldMapper.Builder {
 
-        private Boolean ignoreMalformed;
-        private Boolean coerce;
-        private Number nullValue;
-        private final NumberType type;
+        private final Parameter<Boolean> indexed = Parameter.indexParam(m -> toType(m).indexed, true);
+        private final Parameter<Boolean> hasDocValues = Parameter.docValuesParam(m -> toType(m).hasDocValues, true);
+        private final Parameter<Boolean> stored = Parameter.storeParam(m -> toType(m).stored, false);
 
-        public Builder(String name, NumberType type) {
-            super(name, Defaults.FIELD_TYPE);
-            this.type = type;
-            builder = this;
-        }
+        private final Parameter<Explicit<Boolean>> ignoreMalformed;
+        private final Parameter<Explicit<Boolean>> coerce;
 
-        public Builder ignoreMalformed(boolean ignoreMalformed) {
-            this.ignoreMalformed = ignoreMalformed;
-            return builder;
-        }
+        private final Parameter<Number> nullValue;
 
-        public Builder nullValue(Number nullValue) {
-            this.nullValue = nullValue;
-            return builder;
-        }
+        private final Parameter<Map<String, String>> meta = Parameter.metaParam();
 
-        @Override
-        public Builder indexOptions(IndexOptions indexOptions) {
-            throw new MapperParsingException(
-                    "index_options not allowed in field [" + name + "] of type [" + type.typeName() + "]");
-        }
+        private final NumberType type;
 
-        protected Explicit<Boolean> ignoreMalformed(BuilderContext context) {
-            if (ignoreMalformed != null) {
-                return new Explicit<>(ignoreMalformed, true);
-            }
-            if (context.indexSettings() != null) {
-                return new Explicit<>(IGNORE_MALFORMED_SETTING.get(context.indexSettings()), false);
-            }
-            return Defaults.IGNORE_MALFORMED;
+        public Builder(String name, NumberType type, Settings settings) {
+            this(name, type, IGNORE_MALFORMED_SETTING.get(settings), COERCE_SETTING.get(settings));
         }
 
-        public Builder coerce(boolean coerce) {
-            this.coerce = coerce;
+        public static Builder docValuesOnly(String name, NumberType type) {
+            Builder builder = new Builder(name, type, false, false);
+            builder.indexed.setValue(false);
             return builder;
         }
 
-        protected Explicit<Boolean> coerce(BuilderContext context) {
-            if (coerce != null) {
-                return new Explicit<>(coerce, true);
-            }
-            if (context.indexSettings() != null) {
-                return new Explicit<>(COERCE_SETTING.get(context.indexSettings()), false);
-            }
-            return Defaults.COERCE;
+        public Builder(String name, NumberType type, boolean ignoreMalformedByDefault, boolean coerceByDefault) {
+            super(name);
+            this.type = type;
+            this.ignoreMalformed
+                = Parameter.explicitBoolParam("ignore_malformed", true, m -> toType(m).ignoreMalformed, ignoreMalformedByDefault);
+            this.coerce
+                = Parameter.explicitBoolParam("coerce", true, m -> toType(m).coerce, coerceByDefault);
+            this.nullValue = new Parameter<>("null_value", false, () -> null,
+                (n, c, o) -> type.parse(o, false), m -> toType(m).nullValue);
         }
 
-        @Override
-        public NumberFieldMapper build(BuilderContext context) {
-            return new NumberFieldMapper(name, fieldType, new NumberFieldType(buildFullName(context), type, indexed, hasDocValues, meta),
-                ignoreMalformed(context), coerce(context), nullValue,
-                multiFieldsBuilder.build(this, context), copyTo);
+        Builder nullValue(Number number) {
+            this.nullValue.setValue(number);
+            return this;
         }
-    }
-
-    public static class TypeParser implements Mapper.TypeParser {
 
-        final NumberType type;
+        public Builder docValues(boolean hasDocValues) {
+            this.hasDocValues.setValue(hasDocValues);
+            return this;
+        }
 
-        public TypeParser(NumberType type) {
-            this.type = type;
+        @Override
+        protected List<Parameter<?>> getParameters() {
+            return List.of(indexed, hasDocValues, stored, ignoreMalformed, coerce, nullValue, meta);
         }
 
         @Override
-        public Mapper.Builder<?> parse(String name, Map<String, Object> node,
-                                         ParserContext parserContext) throws MapperParsingException {
-            Builder builder = new Builder(name, type);
-            TypeParsers.parseField(builder, name, node, parserContext);
-            for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
-                Map.Entry<String, Object> entry = iterator.next();
-                String propName = entry.getKey();
-                Object propNode = entry.getValue();
-                if (propName.equals("null_value")) {
-                    if (propNode == null) {
-                        throw new MapperParsingException("Property [null_value] cannot be null.");
-                    }
-                    builder.nullValue(type.parse(propNode, false));
-                    iterator.remove();
-                } else if (propName.equals("ignore_malformed")) {
-                    builder.ignoreMalformed(XContentMapValues.nodeBooleanValue(propNode, name + ".ignore_malformed"));
-                    iterator.remove();
-                } else if (propName.equals("coerce")) {
-                    builder.coerce(XContentMapValues.nodeBooleanValue(propNode, name + ".coerce"));
-                    iterator.remove();
-                }
-            }
-            return builder;
+        public NumberFieldMapper build(BuilderContext context) {
+            return new NumberFieldMapper(name,
+                new NumberFieldType(buildFullName(context), type, indexed.getValue(), hasDocValues.getValue(), meta.getValue()),
+                multiFieldsBuilder.build(this, context), copyTo.build(), this);
         }
     }
 
@@ -823,10 +774,12 @@ public class NumberFieldMapper extends FieldMapper {
 
         private final String name;
         private final NumericType numericType;
+        private final TypeParser parser;
 
         NumberType(String name, NumericType numericType) {
             this.name = name;
             this.numericType = numericType;
+            this.parser = new TypeParser((n, c) -> new Builder(n, this, c.getSettings()));
         }
 
         /** Get the associated type name. */
@@ -837,6 +790,9 @@ public class NumberFieldMapper extends FieldMapper {
         public final NumericType numericType() {
             return numericType;
         }
+        public final TypeParser parser() {
+            return parser;
+        }
         public abstract Query termQuery(String field, Object value);
         public abstract Query termsQuery(String field, List<Object> values);
         public abstract Query rangeQuery(String field, Object lowerTerm, Object upperTerm,
@@ -1002,23 +958,34 @@ public class NumberFieldMapper extends FieldMapper {
         }
     }
 
-    private Explicit<Boolean> ignoreMalformed;
-    private Explicit<Boolean> coerce;
+    private final NumberType type;
+
+    private final boolean indexed;
+    private final boolean hasDocValues;
+    private final boolean stored;
+    private final Explicit<Boolean> ignoreMalformed;
+    private final Explicit<Boolean> coerce;
     private final Number nullValue;
 
+    private final boolean ignoreMalformedByDefault;
+    private final boolean coerceByDefault;
+
     private NumberFieldMapper(
             String simpleName,
-            FieldType fieldType,
             MappedFieldType mappedFieldType,
-            Explicit<Boolean> ignoreMalformed,
-            Explicit<Boolean> coerce,
-            Number nullValue,
             MultiFields multiFields,
-            CopyTo copyTo) {
-        super(simpleName, fieldType, mappedFieldType, multiFields, copyTo);
-        this.ignoreMalformed = ignoreMalformed;
-        this.coerce = coerce;
-        this.nullValue = nullValue;
+            CopyTo copyTo,
+            Builder builder) {
+        super(simpleName, mappedFieldType, multiFields, copyTo);
+        this.type = builder.type;
+        this.indexed = builder.indexed.getValue();
+        this.hasDocValues = builder.hasDocValues.getValue();
+        this.stored = builder.stored.getValue();
+        this.ignoreMalformed = builder.ignoreMalformed.getValue();
+        this.coerce = builder.coerce.getValue();
+        this.nullValue = builder.nullValue.getValue();
+        this.ignoreMalformedByDefault = builder.ignoreMalformed.getDefaultValue().value();
+        this.coerceByDefault = builder.coerce.getDefaultValue().value();
     }
 
     @Override
@@ -1075,12 +1042,10 @@ public class NumberFieldMapper extends FieldMapper {
             numericValue = fieldType().type.parse(value, coerce.value());
         }
 
-        boolean docValued = fieldType().hasDocValues();
-        boolean stored = fieldType.stored();
         context.doc().addAll(fieldType().type.createFields(fieldType().name(), numericValue,
-            fieldType().isSearchable(), docValued, stored));
+            indexed, hasDocValues, stored));
 
-        if (docValued == false && (stored || fieldType().isSearchable())) {
+        if (hasDocValues == false && (stored || indexed)) {
             createFieldNamesField(context);
         }
     }
@@ -1103,34 +1068,7 @@ public class NumberFieldMapper extends FieldMapper {
     }
 
     @Override
-    protected void mergeOptions(FieldMapper other, List<String> conflicts) {
-        NumberFieldMapper m = (NumberFieldMapper) other;
-        if (fieldType().type != m.fieldType().type) {
-            conflicts.add("mapper [" + name() + "] cannot be changed from type [" + fieldType().type.name +
-                "] to [" + m.fieldType().type.name + "]");
-        } else {
-            if (m.ignoreMalformed.explicit()) {
-                this.ignoreMalformed = m.ignoreMalformed;
-            }
-            if (m.coerce.explicit()) {
-                this.coerce = m.coerce;
-            }
-        }
-    }
-
-    @Override
-    protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
-        super.doXContentBody(builder, includeDefaults, params);
-
-        if (includeDefaults || ignoreMalformed.explicit()) {
-            builder.field("ignore_malformed", ignoreMalformed.value());
-        }
-        if (includeDefaults || coerce.explicit()) {
-            builder.field("coerce", coerce.value());
-        }
-
-        if (nullValue != null) {
-            builder.field("null_value", nullValue);
-        }
+    public ParametrizedFieldMapper.Builder getMergeBuilder() {
+        return new Builder(simpleName(), type, ignoreMalformedByDefault, coerceByDefault).init(this);
     }
 }

+ 36 - 1
server/src/main/java/org/elasticsearch/index/mapper/ParametrizedFieldMapper.java

@@ -21,6 +21,7 @@ package org.elasticsearch.index.mapper;
 
 import org.apache.lucene.document.FieldType;
 import org.elasticsearch.Version;
+import org.elasticsearch.common.Explicit;
 import org.elasticsearch.common.TriFunction;
 import org.elasticsearch.common.logging.DeprecationLogger;
 import org.elasticsearch.common.settings.Settings;
@@ -81,9 +82,14 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
             throw new IllegalArgumentException("mapper [" + name() + "] cannot be changed from type ["
                 + contentType() + "] to [" + mergeWith.getClass().getSimpleName() + "]");
         }
+        String mergeWithContentType = ((FieldMapper)mergeWith).contentType();
         if (Objects.equals(this.getClass(), mergeWith.getClass()) == false) {
             throw new IllegalArgumentException("mapper [" + name() + "] cannot be changed from type ["
-                + contentType() + "] to [" + ((FieldMapper) mergeWith).contentType() + "]");
+                + contentType() + "] to [" + mergeWithContentType + "]");
+        }
+        if (Objects.equals(contentType(), mergeWithContentType) == false) {
+            throw new IllegalArgumentException("mapper [" + name() + "] cannot be changed from type ["
+                + contentType() + "] to [" + mergeWithContentType + "]");
         }
 
         ParametrizedFieldMapper.Builder builder = getMergeBuilder();
@@ -265,6 +271,35 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
             return new Parameter<>(name, updateable, () -> defaultValue, (n, c, o) -> XContentMapValues.nodeBooleanValue(o), initializer);
         }
 
+        /**
+         * Defines a parameter that takes the values {@code true} or {@code false}, and will always serialize
+         * its value if configured.
+         * @param name          the parameter name
+         * @param updateable    whether the parameter can be changed by a mapping update
+         * @param initializer   a function that reads the parameter value from an existing mapper
+         * @param defaultValue  the default value, to be used if the parameter is undefined in a mapping
+         */
+        public static Parameter<Explicit<Boolean>> explicitBoolParam(String name, boolean updateable,
+                                                                     Function<FieldMapper, Explicit<Boolean>> initializer,
+                                                                     boolean defaultValue) {
+            Explicit<Boolean> defaultExplicit = new Explicit<>(defaultValue, false);
+            return new Parameter<>(name, updateable, () -> defaultExplicit,
+                (n, c, o) -> new Explicit<>(XContentMapValues.nodeBooleanValue(o), true), initializer)
+                .setSerializer((b, n, v) -> b.field(n, v.value()), v -> Boolean.toString(v.value()));
+        }
+
+        /**
+         * Defines a parameter that takes a double value
+         * @param name          the parameter name
+         * @param updateable    whether the parameter can be changed by a mapping update
+         * @param initializer   a function that reads the parameter value from an existing mapper
+         * @param defaultValue  the default value, to be used if the parameter is undefined in a mapping
+         */
+        public static Parameter<Double> doubleParam(String name, boolean updateable,
+                                                  Function<FieldMapper, Double> initializer, double defaultValue) {
+            return new Parameter<>(name, updateable, () -> defaultValue, (n, c, o) -> XContentMapValues.nodeDoubleValue(o), initializer);
+        }
+
         /**
          * Defines a parameter that takes a float value
          * @param name          the parameter name

+ 6 - 6
server/src/main/java/org/elasticsearch/index/mapper/TypeParsers.java

@@ -34,9 +34,8 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.Collectors;
 
 import static org.elasticsearch.common.xcontent.support.XContentMapValues.isArray;
 import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeFloatValue;
@@ -214,10 +213,11 @@ public class TypeParsers {
                         value.getClass().getSimpleName() + "[" + value + "] for field [" + name + "]");
             }
         }
-        final Function<Map.Entry<String, ?>, Object> entryValueFunction = Map.Entry::getValue;
-        final Function<Object, String> stringCast = String.class::cast;
-        return meta.entrySet().stream()
-                .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, entryValueFunction.andThen(stringCast)));
+        Map<String, String> sortedMeta = new TreeMap<>();
+        for (Map.Entry<String, ?> entry : meta.entrySet()) {
+            sortedMeta.put(entry.getKey(), (String) entry.getValue());
+        }
+        return Collections.unmodifiableMap(sortedMeta);
     }
 
 

+ 1 - 1
server/src/main/java/org/elasticsearch/indices/IndicesModule.java

@@ -105,7 +105,7 @@ public class IndicesModule extends AbstractModule {
 
         // builtin mappers
         for (NumberFieldMapper.NumberType type : NumberFieldMapper.NumberType.values()) {
-            mappers.put(type.typeName(), new NumberFieldMapper.TypeParser(type));
+            mappers.put(type.typeName(), type.parser());
         }
         for (RangeType type : RangeType.values()) {
             mappers.put(type.typeName(), new RangeFieldMapper.TypeParser(type));

+ 6 - 6
server/src/test/java/org/elasticsearch/index/fielddata/AbstractFieldDataTestCase.java

@@ -102,22 +102,22 @@ public abstract class AbstractFieldDataTestCase extends ESSingleNodeTestCase {
                 fieldType = new TextFieldMapper.Builder(fieldName).fielddata(true).build(context).fieldType();
             }
         } else if (type.equals("float")) {
-            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.FLOAT)
+            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.FLOAT, false, true)
                     .docValues(docValues).build(context).fieldType();
         } else if (type.equals("double")) {
-            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.DOUBLE)
+            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.DOUBLE, false, true)
                     .docValues(docValues).build(context).fieldType();
         } else if (type.equals("long")) {
-            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.LONG)
+            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.LONG, false, true)
                     .docValues(docValues).build(context).fieldType();
         } else if (type.equals("int")) {
-            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.INTEGER)
+            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.INTEGER, false, true)
                     .docValues(docValues).build(context).fieldType();
         } else if (type.equals("short")) {
-            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.SHORT)
+            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.SHORT, false, true)
                     .docValues(docValues).build(context).fieldType();
         } else if (type.equals("byte")) {
-            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.BYTE)
+            fieldType = new NumberFieldMapper.Builder(fieldName, NumberFieldMapper.NumberType.BYTE, false, true)
                     .docValues(docValues).build(context).fieldType();
         } else if (type.equals("geo_point")) {
             fieldType = new GeoPointFieldMapper.Builder(fieldName).docValues(docValues).build(context).fieldType();

+ 6 - 6
server/src/test/java/org/elasticsearch/index/fielddata/IndexFieldDataServiceTests.java

@@ -78,23 +78,23 @@ public class IndexFieldDataServiceTests extends ESSingleNodeTestCase {
         assertTrue(fd instanceof SortedSetOrdinalsIndexFieldData);
 
         for (MappedFieldType mapper : Arrays.asList(
-                new NumberFieldMapper.Builder("int", NumberFieldMapper.NumberType.BYTE).build(ctx).fieldType(),
-                new NumberFieldMapper.Builder("int", NumberFieldMapper.NumberType.SHORT).build(ctx).fieldType(),
-                new NumberFieldMapper.Builder("int", NumberFieldMapper.NumberType.INTEGER).build(ctx).fieldType(),
-                new NumberFieldMapper.Builder("long", NumberFieldMapper.NumberType.LONG).build(ctx).fieldType()
+                new NumberFieldMapper.Builder("int", NumberFieldMapper.NumberType.BYTE, false, true).build(ctx).fieldType(),
+                new NumberFieldMapper.Builder("int", NumberFieldMapper.NumberType.SHORT, false, true).build(ctx).fieldType(),
+                new NumberFieldMapper.Builder("int", NumberFieldMapper.NumberType.INTEGER, false, true).build(ctx).fieldType(),
+                new NumberFieldMapper.Builder("long", NumberFieldMapper.NumberType.LONG, false, true).build(ctx).fieldType()
                 )) {
             ifdService.clear();
             fd = ifdService.getForField(mapper);
             assertTrue(fd instanceof SortedNumericIndexFieldData);
         }
 
-        final MappedFieldType floatMapper = new NumberFieldMapper.Builder("float", NumberFieldMapper.NumberType.FLOAT)
+        final MappedFieldType floatMapper = new NumberFieldMapper.Builder("float", NumberFieldMapper.NumberType.FLOAT, false, true)
                 .build(ctx).fieldType();
         ifdService.clear();
         fd = ifdService.getForField(floatMapper);
         assertTrue(fd instanceof SortedNumericIndexFieldData);
 
-        final MappedFieldType doubleMapper = new NumberFieldMapper.Builder("double", NumberFieldMapper.NumberType.DOUBLE)
+        final MappedFieldType doubleMapper = new NumberFieldMapper.Builder("double", NumberFieldMapper.NumberType.DOUBLE, false, true)
                 .build(ctx).fieldType();
         ifdService.clear();
         fd = ifdService.getForField(doubleMapper);

+ 8 - 69
server/src/test/java/org/elasticsearch/index/mapper/NumberFieldMapperTests.java

@@ -31,7 +31,6 @@ import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.compress.CompressedXContent;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.ToXContentObject;
-import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType;
@@ -42,15 +41,16 @@ import org.elasticsearch.rest.RestStatus;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+import static org.elasticsearch.index.mapper.FieldMapperTestCase.fetchSourceValue;
 import static org.hamcrest.Matchers.containsString;
 
-public class NumberFieldMapperTests extends AbstractNumericFieldMapperTestCase<NumberFieldMapper.Builder> {
+public class NumberFieldMapperTests extends AbstractNumericFieldMapperTestCase {
 
     @Override
     protected void setTypeList() {
@@ -58,11 +58,6 @@ public class NumberFieldMapperTests extends AbstractNumericFieldMapperTestCase<N
         WHOLE_TYPES = new HashSet<>(Arrays.asList("byte", "short", "integer", "long"));
     }
 
-    @Override
-    protected Set<String> unsupportedProperties() {
-        return Set.of("analyzer", "similarity");
-    }
-
     @Override
     public void doTestDefaults(String type) throws Exception {
         String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
@@ -272,12 +267,7 @@ public class NumberFieldMapperTests extends AbstractNumericFieldMapperTestCase<N
      */
     public void testIgnoreMalformedWithObject() throws Exception {
         for (String type : TYPES) {
-            Object malformedValue = new ToXContentObject() {
-                @Override
-                public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
-                    return builder.startObject().field("foo", "bar").endObject();
-                }
-            };
+            Object malformedValue = (ToXContentObject) (builder, params) -> builder.startObject().field("foo", "bar").endObject();
             for (Boolean ignoreMalformed : new Boolean[] { true, false }) {
                 String mapping = Strings.toString(
                         jsonBuilder().startObject().startObject("type").startObject("properties").startObject("field").field("type", type)
@@ -295,42 +285,6 @@ public class NumberFieldMapperTests extends AbstractNumericFieldMapperTestCase<N
         }
     }
 
-    public void testRejectNorms() throws IOException {
-        // not supported as of 5.0
-        for (String type : TYPES) {
-            DocumentMapperParser parser = createIndex("index-" + type).mapperService().documentMapperParser();
-            String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
-                .startObject("properties")
-                    .startObject("foo")
-                        .field("type", type)
-                        .field("norms", random().nextBoolean())
-                    .endObject()
-                .endObject().endObject().endObject());
-            MapperParsingException e = expectThrows(MapperParsingException.class,
-                    () -> parser.parse("type", new CompressedXContent(mapping)));
-            assertThat(e.getMessage(), containsString("Mapping definition for [foo] has unsupported parameters:  [norms"));
-        }
-    }
-
-    /**
-     * `index_options` was deprecated and is rejected as of 7.0
-     */
-    public void testRejectIndexOptions() throws IOException {
-        for (String type : TYPES) {
-            DocumentMapperParser parser = createIndex("index-" + type).mapperService().documentMapperParser();
-            String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
-                .startObject("properties")
-                    .startObject("foo")
-                        .field("type", type)
-                    .field("index_options", randomFrom(new String[] { "docs", "freqs", "positions", "offsets" }))
-                    .endObject()
-                .endObject().endObject().endObject());
-            MapperParsingException e = expectThrows(MapperParsingException.class,
-                    () -> parser.parse("type", new CompressedXContent(mapping)));
-            assertThat(e.getMessage(), containsString("index_options not allowed in field [foo] of type [" + type +"]"));
-        }
-    }
-
     @Override
     protected void doTestNullValue(String type) throws IOException {
         String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject()
@@ -408,11 +362,11 @@ public class NumberFieldMapperTests extends AbstractNumericFieldMapperTestCase<N
         Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT.id).build();
         Mapper.BuilderContext context = new Mapper.BuilderContext(settings, new ContentPath());
 
-        NumberFieldMapper mapper = new NumberFieldMapper.Builder("field", NumberType.INTEGER).build(context);
+        NumberFieldMapper mapper = new NumberFieldMapper.Builder("field", NumberType.INTEGER, false, true).build(context);
         assertEquals(List.of(3), fetchSourceValue(mapper, 3.14));
         assertEquals(List.of(42), fetchSourceValue(mapper, "42.9"));
 
-        NumberFieldMapper nullValueMapper = new NumberFieldMapper.Builder("field", NumberType.FLOAT)
+        NumberFieldMapper nullValueMapper = new NumberFieldMapper.Builder("field", NumberType.FLOAT, false, true)
             .nullValue(2.71f)
             .build(context);
         assertEquals(List.of(2.71f), fetchSourceValue(nullValueMapper, ""));
@@ -493,18 +447,7 @@ public class NumberFieldMapperTests extends AbstractNumericFieldMapperTestCase<N
         client().admin().indices().preparePutMapping("test57287").setSource(mapping, XContentType.JSON).get();
         String doc = "{\"number\" : 9223372036854775808}";
         IndexResponse response = client().index(new IndexRequest("test57287").source(doc, XContentType.JSON)).get();
-        assertTrue(response.status() == RestStatus.CREATED);
-    }
-
-    public void testDeprecatedSimilarityParameter() throws Exception {
-        String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties")
-            .startObject("field")
-            .field("type", "long")
-            .field("similarity", "bm25")
-            .endObject().endObject().endObject().endObject());
-
-        parser.parse("type", new CompressedXContent(mapping));
-        assertWarnings("The [similarity] parameter has no effect on field [field] and will be removed in 8.0");
+        assertSame(response.status(), RestStatus.CREATED);
     }
 
     private void parseRequest(NumberType type, BytesReference content) throws IOException {
@@ -531,15 +474,11 @@ public class NumberFieldMapperTests extends AbstractNumericFieldMapperTestCase<N
         if (value instanceof BigInteger) {
             return BytesReference.bytes(XContentFactory.jsonBuilder()
                 .startObject()
-                    .rawField("field", new ByteArrayInputStream(value.toString().getBytes("UTF-8")), XContentType.JSON)
+                    .rawField("field", new ByteArrayInputStream(value.toString().getBytes(StandardCharsets.UTF_8)), XContentType.JSON)
                 .endObject());
         } else {
             return BytesReference.bytes(XContentFactory.jsonBuilder().startObject().field("field", value).endObject());
         }
     }
 
-    @Override
-    protected NumberFieldMapper.Builder newBuilder() {
-        return new NumberFieldMapper.Builder("number", NumberType.LONG);
-    }
 }

+ 2 - 26
server/src/test/java/org/elasticsearch/index/mapper/RangeFieldMapperTests.java

@@ -35,7 +35,6 @@ import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.test.InternalSettingsPlugin;
-import org.junit.Before;
 
 import java.io.IOException;
 import java.net.InetAddress;
@@ -45,8 +44,8 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Set;
 
+import static org.elasticsearch.index.mapper.FieldMapperTestCase.fetchSourceValue;
 import static org.elasticsearch.index.query.RangeQueryBuilder.GTE_FIELD;
 import static org.elasticsearch.index.query.RangeQueryBuilder.GT_FIELD;
 import static org.elasticsearch.index.query.RangeQueryBuilder.LTE_FIELD;
@@ -55,30 +54,7 @@ import static org.hamcrest.Matchers.anyOf;
 import static org.hamcrest.Matchers.containsString;
 
 
-public class RangeFieldMapperTests extends AbstractNumericFieldMapperTestCase<RangeFieldMapper.Builder> {
-
-    @Override
-    protected RangeFieldMapper.Builder newBuilder() {
-        return new RangeFieldMapper.Builder("range", RangeType.DATE)
-            .format("iso8601");
-    }
-
-    @Override
-    protected Set<String> unsupportedProperties() {
-        return Set.of("analyzer", "similarity");
-    }
-
-    @Before
-    public void addModifiers() {
-        addModifier("format", true, (a, b) -> {
-            a.format("basic_week_date");
-            b.format("strict_week_date");
-        });
-        addModifier("locale", true, (a, b) -> {
-            a.locale(Locale.CANADA);
-            b.locale(Locale.JAPAN);
-        });
-    }
+public class RangeFieldMapperTests extends AbstractNumericFieldMapperTestCase {
 
     @Override
     protected Collection<Class<? extends Plugin>> getPlugins() {

+ 2 - 1
test/framework/src/main/java/org/elasticsearch/index/mapper/AbstractNumericFieldMapperTestCase.java

@@ -24,6 +24,7 @@ import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.index.IndexService;
 import org.elasticsearch.index.mapper.MapperService.MergeReason;
 import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.test.ESSingleNodeTestCase;
 import org.elasticsearch.test.InternalSettingsPlugin;
 import org.junit.Before;
 
@@ -34,7 +35,7 @@ import java.util.Set;
 
 import static org.hamcrest.Matchers.containsString;
 
-public abstract class AbstractNumericFieldMapperTestCase<T extends FieldMapper.Builder<?>> extends FieldMapperTestCase<T> {
+public abstract class AbstractNumericFieldMapperTestCase extends ESSingleNodeTestCase {
     protected Set<String> TYPES;
     protected Set<String> WHOLE_TYPES;
     protected IndexService indexService;

+ 2 - 2
x-pack/plugin/rollup/src/test/java/org/elasticsearch/xpack/rollup/job/RollupIndexerIndexingTests.java

@@ -535,7 +535,7 @@ public class RollupIndexerIndexingTests extends AggregatorTestCase {
 
         if (job.getGroupConfig().getHistogram() != null) {
             for (String field : job.getGroupConfig().getHistogram().getFields()) {
-                MappedFieldType ft = new NumberFieldMapper.Builder(field, NumberFieldMapper.NumberType.LONG)
+                MappedFieldType ft = new NumberFieldMapper.Builder(field, NumberFieldMapper.NumberType.LONG, false, false)
                         .build(new Mapper.BuilderContext(settings.getSettings(), new ContentPath(0)))
                         .fieldType();
                 fieldTypes.put(ft.name(), ft);
@@ -553,7 +553,7 @@ public class RollupIndexerIndexingTests extends AggregatorTestCase {
 
         if (job.getMetricsConfig() != null) {
             for (MetricConfig metric : job.getMetricsConfig()) {
-                MappedFieldType ft = new NumberFieldMapper.Builder(metric.getField(), NumberFieldMapper.NumberType.LONG)
+                MappedFieldType ft = new NumberFieldMapper.Builder(metric.getField(), NumberFieldMapper.NumberType.LONG, false, false)
                         .build(new Mapper.BuilderContext(settings.getSettings(), new ContentPath(0)))
                         .fieldType();
                 fieldTypes.put(ft.name(), ft);