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 3c36335346c..dd528689ac7 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/BinaryFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/BinaryFieldMapper.java @@ -59,8 +59,7 @@ public class BinaryFieldMapper extends ParametrizedFieldMapper { private final Parameter stored = Parameter.boolParam("store", false, m -> toType(m).stored, false); private final Parameter hasDocValues = Parameter.boolParam("doc_values", false, m -> toType(m).hasDocValues, false); - private final Parameter> meta - = new Parameter<>("meta", true, Collections.emptyMap(), TypeParsers::parseMeta, m -> m.fieldType().meta()); + private final Parameter> meta = Parameter.metaParam(); public Builder(String name) { this(name, false); 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 6926aa2403e..2e1eea8d339 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java @@ -79,12 +79,11 @@ public class BooleanFieldMapper extends ParametrizedFieldMapper { private final Parameter indexed = Parameter.boolParam("index", false, m -> toType(m).indexed, true); private final Parameter stored = Parameter.boolParam("store", false, m -> toType(m).stored, false); - private final Parameter nullValue - = new Parameter<>("null_value", false, null, (n, o) -> XContentMapValues.nodeBooleanValue(o), m -> toType(m).nullValue); + private final Parameter nullValue = new Parameter<>("null_value", false, () -> null, + (n, c, o) -> XContentMapValues.nodeBooleanValue(o), m -> toType(m).nullValue); private final Parameter boost = Parameter.floatParam("boost", true, m -> m.fieldType().boost(), 1.0f); - private final Parameter> meta - = new Parameter<>("meta", true, Collections.emptyMap(), TypeParsers::parseMeta, m -> m.fieldType().meta()); + private final Parameter> meta = Parameter.metaParam(); public Builder(String name) { super(name); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java index efb3a546a8c..5f59e0d9052 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java @@ -34,13 +34,11 @@ import org.apache.lucene.search.suggest.document.RegexCompletionQuery; import org.apache.lucene.search.suggest.document.SuggestField; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.logging.DeprecationLogger; -import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.util.set.Sets; -import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser.NumberType; @@ -53,16 +51,15 @@ import org.elasticsearch.search.suggest.completion.context.ContextMapping; import org.elasticsearch.search.suggest.completion.context.ContextMappings; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; -import static org.elasticsearch.index.mapper.TypeParsers.parseMultiField; - /** * Mapper for completion field. The field values are indexed as a weighted FST for * fast auto-completion/search-as-you-type functionality.
@@ -84,7 +81,7 @@ import static org.elasticsearch.index.mapper.TypeParsers.parseMultiField; * This field can also be extended to add search criteria to suggestions * for query-time filtering and boosting (see {@link ContextMappings} */ -public class CompletionFieldMapper extends FieldMapper { +public class CompletionFieldMapper extends ParametrizedFieldMapper { public static final String CONTENT_TYPE = "completion"; /** @@ -92,6 +89,11 @@ public class CompletionFieldMapper extends FieldMapper { */ static final int COMPLETION_CONTEXTS_LIMIT = 10; + @Override + public ParametrizedFieldMapper.Builder getMergeBuilder() { + return new Builder(simpleName(), defaultAnalyzer).init(this); + } + public static class Defaults { public static final FieldType FIELD_TYPE = new FieldType(); static { @@ -108,20 +110,113 @@ public class CompletionFieldMapper extends FieldMapper { } public static class Fields { - // Mapping field names - public static final ParseField ANALYZER = new ParseField("analyzer"); - public static final ParseField SEARCH_ANALYZER = new ParseField("search_analyzer"); - public static final ParseField PRESERVE_SEPARATORS = new ParseField("preserve_separators"); - public static final ParseField PRESERVE_POSITION_INCREMENTS = new ParseField("preserve_position_increments"); - public static final ParseField TYPE = new ParseField("type"); - public static final ParseField CONTEXTS = new ParseField("contexts"); - public static final ParseField MAX_INPUT_LENGTH = new ParseField("max_input_length", "max_input_len"); // Content field names public static final String CONTENT_FIELD_NAME_INPUT = "input"; public static final String CONTENT_FIELD_NAME_WEIGHT = "weight"; public static final String CONTENT_FIELD_NAME_CONTEXTS = "contexts"; } + private static CompletionFieldMapper toType(FieldMapper in) { + return (CompletionFieldMapper) in; + } + + /** + * Builder for {@link CompletionFieldMapper} + */ + public static class Builder extends ParametrizedFieldMapper.Builder { + + private final Parameter analyzer; + private final Parameter searchAnalyzer; + private final Parameter preserveSeparators = Parameter.boolParam("preserve_separators", false, + m -> toType(m).preserveSeparators, Defaults.DEFAULT_PRESERVE_SEPARATORS); + private final Parameter preservePosInc = Parameter.boolParam("preserve_position_increments", false, + m -> toType(m).preservePosInc, Defaults.DEFAULT_POSITION_INCREMENTS); + private final Parameter contexts = new Parameter<>("contexts", false, () -> null, + (n, c, o) -> ContextMappings.load(o, c.indexVersionCreated()), m -> toType(m).contexts) + .setSerializer((b, n, c) -> { + if (c == null) { + return; + } + b.startArray(n); + c.toXContent(b, ToXContent.EMPTY_PARAMS); + b.endArray(); + }); + private final Parameter maxInputLength = Parameter.intParam("max_input_length", true, + m -> toType(m).maxInputLength, Defaults.DEFAULT_MAX_INPUT_LENGTH) + .addDeprecatedName("max_input_len") + .setValidator(Builder::validateInputLength); + private final Parameter> meta = Parameter.metaParam(); + + private final NamedAnalyzer defaultAnalyzer; + + private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(Builder.class)); + + /** + * @param name of the completion field to build + */ + public Builder(String name, NamedAnalyzer defaultAnalyzer) { + super(name); + this.defaultAnalyzer = defaultAnalyzer; + this.analyzer = Parameter.analyzerParam("analyzer", false, m -> toType(m).analyzer, () -> defaultAnalyzer); + this.searchAnalyzer + = Parameter.analyzerParam("search_analyzer", true, m -> toType(m).searchAnalyzer, analyzer::getValue); + } + + private static void validateInputLength(int maxInputLength) { + if (maxInputLength <= 0) { + throw new IllegalArgumentException("[max_input_length] must be > 0 but was [" + maxInputLength + "]"); + } + } + + @Override + protected List> getParameters() { + return Arrays.asList(analyzer, searchAnalyzer, preserveSeparators, preservePosInc, contexts, maxInputLength, meta); + } + + @Override + protected void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException { + builder.field("analyzer", this.analyzer.getValue().name()); + if (Objects.equals(this.analyzer.getValue().name(), this.searchAnalyzer.getValue().name()) == false) { + builder.field("search_analyzer", this.searchAnalyzer.getValue().name()); + } + builder.field(this.preserveSeparators.name, this.preserveSeparators.getValue()); + builder.field(this.preservePosInc.name, this.preservePosInc.getValue()); + builder.field(this.maxInputLength.name, this.maxInputLength.getValue()); + if (this.contexts.getValue() != null) { + builder.startArray(this.contexts.name); + this.contexts.getValue().toXContent(builder, ToXContent.EMPTY_PARAMS); + builder.endArray(); + } + } + + @Override + public CompletionFieldMapper build(BuilderContext context) { + checkCompletionContextsLimit(context); + NamedAnalyzer completionAnalyzer = new NamedAnalyzer(this.searchAnalyzer.getValue().name(), AnalyzerScope.INDEX, + new CompletionAnalyzer(this.searchAnalyzer.getValue(), preserveSeparators.getValue(), preservePosInc.getValue())); + + CompletionFieldType ft + = new CompletionFieldType(buildFullName(context), completionAnalyzer, meta.getValue()); + ft.setContextMappings(contexts.getValue()); + ft.setPreservePositionIncrements(preservePosInc.getValue()); + ft.setPreserveSep(preserveSeparators.getValue()); + ft.setIndexAnalyzer(analyzer.getValue()); + return new CompletionFieldMapper(name, ft, defaultAnalyzer, + multiFieldsBuilder.build(this, context), copyTo.build(), this); + } + + private void checkCompletionContextsLimit(BuilderContext context) { + if (this.contexts.getValue() != null && this.contexts.getValue().size() > COMPLETION_CONTEXTS_LIMIT) { + deprecationLogger.deprecatedAndMaybeLog("excessive_completion_contexts", + "You have defined more than [" + COMPLETION_CONTEXTS_LIMIT + "] completion contexts" + + " in the mapping for index [" + context.indexSettings().get(IndexMetadata.SETTING_INDEX_PROVIDED_NAME) + "]. " + + "The maximum allowed number of completion contexts in a mapping will be limited to " + + "[" + COMPLETION_CONTEXTS_LIMIT + "] starting in version [8.0]."); + } + } + + } + public static final Set ALLOWED_CONTENT_FIELD_NAMES = Sets.newHashSet(Fields.CONTENT_FIELD_NAME_INPUT, Fields.CONTENT_FIELD_NAME_WEIGHT, Fields.CONTENT_FIELD_NAME_CONTEXTS); @@ -130,60 +225,11 @@ public class CompletionFieldMapper extends FieldMapper { @Override public Mapper.Builder parse(String name, Map node, ParserContext parserContext) throws MapperParsingException { - CompletionFieldMapper.Builder builder = new CompletionFieldMapper.Builder(name); - NamedAnalyzer indexAnalyzer = null; - NamedAnalyzer searchAnalyzer = null; - for (Iterator> iterator = node.entrySet().iterator(); iterator.hasNext();) { - Map.Entry entry = iterator.next(); - String fieldName = entry.getKey(); - Object fieldNode = entry.getValue(); - if (fieldName.equals("type")) { - continue; - } - if (Fields.ANALYZER.match(fieldName, LoggingDeprecationHandler.INSTANCE)) { - indexAnalyzer = getNamedAnalyzer(parserContext, fieldNode.toString()); - iterator.remove(); - } else if (Fields.SEARCH_ANALYZER.match(fieldName, LoggingDeprecationHandler.INSTANCE)) { - searchAnalyzer = getNamedAnalyzer(parserContext, fieldNode.toString()); - iterator.remove(); - } else if (Fields.PRESERVE_SEPARATORS.match(fieldName, LoggingDeprecationHandler.INSTANCE)) { - builder.preserveSeparators(Boolean.parseBoolean(fieldNode.toString())); - iterator.remove(); - } else if (Fields.PRESERVE_POSITION_INCREMENTS.match(fieldName, LoggingDeprecationHandler.INSTANCE)) { - builder.preservePositionIncrements(Boolean.parseBoolean(fieldNode.toString())); - iterator.remove(); - } else if (Fields.MAX_INPUT_LENGTH.match(fieldName, LoggingDeprecationHandler.INSTANCE)) { - builder.maxInputLength(Integer.parseInt(fieldNode.toString())); - iterator.remove(); - } else if (Fields.CONTEXTS.match(fieldName, LoggingDeprecationHandler.INSTANCE)) { - builder.contextMappings(ContextMappings.load(fieldNode, parserContext.indexVersionCreated())); - iterator.remove(); - } else if (parseMultiField(builder::addMultiField, name, parserContext, fieldName, fieldNode)) { - iterator.remove(); - } - } - - if (indexAnalyzer == null) { - if (searchAnalyzer != null) { - throw new MapperParsingException("analyzer on completion field [" + name + "] must be set when search_analyzer is set"); - } - indexAnalyzer = searchAnalyzer = parserContext.getIndexAnalyzers().get("simple"); - } else if (searchAnalyzer == null) { - searchAnalyzer = indexAnalyzer; - } - - builder.indexAnalyzer(indexAnalyzer); - builder.searchAnalyzer(searchAnalyzer); + CompletionFieldMapper.Builder builder + = new CompletionFieldMapper.Builder(name, parserContext.getIndexAnalyzers().get("simple")); + builder.parse(name, parserContext, node); return builder; } - - private NamedAnalyzer getNamedAnalyzer(ParserContext parserContext, String name) { - NamedAnalyzer analyzer = parserContext.getIndexAnalyzers().get(name); - if (analyzer == null) { - throw new IllegalArgumentException("Can't find default or mapped analyzer with name [" + name + "]"); - } - return analyzer; - } } public static final class CompletionFieldType extends TermBasedFieldType { @@ -194,17 +240,9 @@ public class CompletionFieldMapper extends FieldMapper { private boolean preservePositionIncrements = Defaults.DEFAULT_POSITION_INCREMENTS; private ContextMappings contextMappings = null; - public CompletionFieldType(String name, FieldType luceneFieldType, - NamedAnalyzer searchAnalyzer, NamedAnalyzer searchQuoteAnalyzer, Map meta) { + public CompletionFieldType(String name, NamedAnalyzer searchAnalyzer, Map meta) { super(name, true, false, - new TextSearchInfo(luceneFieldType, null, searchAnalyzer, searchQuoteAnalyzer), meta); - } - - public CompletionFieldType(String name) { - this(name, Defaults.FIELD_TYPE, - new NamedAnalyzer("completion", AnalyzerScope.INDEX, new CompletionAnalyzer(Lucene.STANDARD_ANALYZER)), - new NamedAnalyzer("completion", AnalyzerScope.INDEX, new CompletionAnalyzer(Lucene.STANDARD_ANALYZER)), - Collections.emptyMap()); + new TextSearchInfo(Defaults.FIELD_TYPE, null, searchAnalyzer, searchAnalyzer), meta); } public void setPreserveSep(boolean preserveSep) { @@ -245,14 +283,6 @@ public class CompletionFieldMapper extends FieldMapper { return contextMappings; } - public boolean preserveSep() { - return preserveSep; - } - - public boolean preservePositionIncrements() { - return preservePositionIncrements; - } - /** * @return postings format to use for this field-type */ @@ -302,100 +332,24 @@ public class CompletionFieldMapper extends FieldMapper { } - /** - * Builder for {@link CompletionFieldMapper} - */ - public static class Builder extends FieldMapper.Builder { + private final int maxInputLength; + private final boolean preserveSeparators; + private final boolean preservePosInc; + private final NamedAnalyzer defaultAnalyzer; + private final NamedAnalyzer analyzer; + private final NamedAnalyzer searchAnalyzer; + private final ContextMappings contexts; - private int maxInputLength = Defaults.DEFAULT_MAX_INPUT_LENGTH; - private ContextMappings contextMappings = null; - private boolean preserveSeparators = Defaults.DEFAULT_PRESERVE_SEPARATORS; - private boolean preservePositionIncrements = Defaults.DEFAULT_POSITION_INCREMENTS; - - private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(Builder.class)); - - /** - * @param name of the completion field to build - */ - public Builder(String name) { - super(name, Defaults.FIELD_TYPE); - builder = this; - } - - /** - * @param maxInputLength maximum expected prefix length - * NOTE: prefixes longer than this will - * be truncated - */ - public Builder maxInputLength(int maxInputLength) { - if (maxInputLength <= 0) { - throw new IllegalArgumentException(Fields.MAX_INPUT_LENGTH.getPreferredName() - + " must be > 0 but was [" + maxInputLength + "]"); - } - this.maxInputLength = maxInputLength; - return this; - } - - /** - * Add context mapping to this field - * @param contextMappings see {@link ContextMappings#load(Object, Version)} - */ - public Builder contextMappings(ContextMappings contextMappings) { - this.contextMappings = contextMappings; - return this; - } - - public Builder preserveSeparators(boolean preserveSeparators) { - this.preserveSeparators = preserveSeparators; - return this; - } - - public Builder preservePositionIncrements(boolean preservePositionIncrements) { - this.preservePositionIncrements = preservePositionIncrements; - return this; - } - - @Override - public CompletionFieldMapper build(BuilderContext context) { - checkCompletionContextsLimit(context); - NamedAnalyzer searchAnalyzer = new NamedAnalyzer(this.searchAnalyzer.name(), AnalyzerScope.INDEX, - new CompletionAnalyzer(this.searchAnalyzer, preserveSeparators, preservePositionIncrements)); - - CompletionFieldType ft - = new CompletionFieldType(buildFullName(context), this.fieldType, searchAnalyzer, searchAnalyzer, meta); - ft.setContextMappings(contextMappings); - ft.setPreservePositionIncrements(preservePositionIncrements); - ft.setPreserveSep(preserveSeparators); - ft.setIndexAnalyzer(indexAnalyzer); - return new CompletionFieldMapper(name, this.fieldType, ft, - multiFieldsBuilder.build(this, context), copyTo, maxInputLength); - } - - private void checkCompletionContextsLimit(BuilderContext context) { - if (this.contextMappings != null && this.contextMappings.size() > COMPLETION_CONTEXTS_LIMIT) { - deprecationLogger.deprecatedAndMaybeLog("excessive_completion_contexts", - "You have defined more than [" + COMPLETION_CONTEXTS_LIMIT + "] completion contexts" + - " in the mapping for index [" + context.indexSettings().get(IndexMetadata.SETTING_INDEX_PROVIDED_NAME) + "]. " + - "The maximum allowed number of completion contexts in a mapping will be limited to " + - "[" + COMPLETION_CONTEXTS_LIMIT + "] starting in version [8.0]."); - } - } - - @Override - public Builder index(boolean index) { - if (index == false) { - throw new MapperParsingException("Completion field type must be indexed"); - } - return builder; - } - } - - private int maxInputLength; - - public CompletionFieldMapper(String simpleName, FieldType fieldType, MappedFieldType mappedFieldType, - MultiFields multiFields, CopyTo copyTo, int maxInputLength) { - super(simpleName, fieldType, mappedFieldType, multiFields, copyTo); - this.maxInputLength = maxInputLength; + public CompletionFieldMapper(String simpleName, MappedFieldType mappedFieldType, NamedAnalyzer defaultAnalyzer, + MultiFields multiFields, CopyTo copyTo, Builder builder) { + super(simpleName, mappedFieldType, multiFields, copyTo); + this.defaultAnalyzer = defaultAnalyzer; + this.maxInputLength = builder.maxInputLength.getValue(); + this.preserveSeparators = builder.preserveSeparators.getValue(); + this.preservePosInc = builder.preservePosInc.getValue(); + this.analyzer = builder.analyzer.getValue(); + this.searchAnalyzer = builder.searchAnalyzer.getValue(); + this.contexts = builder.contexts.getValue(); } @Override @@ -601,28 +555,6 @@ public class CompletionFieldMapper extends FieldMapper { } } - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(simpleName()) - .field(Fields.TYPE.getPreferredName(), CONTENT_TYPE); - builder.field(Fields.ANALYZER.getPreferredName(), fieldType().indexAnalyzer().name()); - if (fieldType().indexAnalyzer().name().equals(fieldType().getTextSearchInfo().getSearchAnalyzer().name()) == false) { - builder.field(Fields.SEARCH_ANALYZER.getPreferredName(), fieldType().getTextSearchInfo().getSearchAnalyzer().name()); - } - builder.field(Fields.PRESERVE_SEPARATORS.getPreferredName(), fieldType().preserveSep()); - builder.field(Fields.PRESERVE_POSITION_INCREMENTS.getPreferredName(), fieldType().preservePositionIncrements()); - builder.field(Fields.MAX_INPUT_LENGTH.getPreferredName(), this.maxInputLength); - - if (fieldType().hasContextMappings()) { - builder.startArray(Fields.CONTEXTS.getPreferredName()); - fieldType().getContextMappings().toXContent(builder, params); - builder.endArray(); - } - - multiFields.toXContent(builder, params); - return builder.endObject(); - } - @Override protected void parseCreateField(ParseContext context) throws IOException { // no-op @@ -633,23 +565,6 @@ public class CompletionFieldMapper extends FieldMapper { return CONTENT_TYPE; } - @Override - protected void mergeOptions(FieldMapper other, List conflicts) { - CompletionFieldType c = (CompletionFieldType)other.fieldType(); - if (fieldType().preservePositionIncrements != c.preservePositionIncrements) { - conflicts.add("mapper [" + name() + "] has different [preserve_position_increments] values"); - } - if (fieldType().preserveSep != c.preserveSep) { - conflicts.add("mapper [" + name() + "] has different [preserve_separators] values"); - } - if (fieldType().hasContextMappings() != c.hasContextMappings()) { - conflicts.add("mapper [" + name() + "] has different [context_mappings] values"); - } else if (fieldType().hasContextMappings() && fieldType().contextMappings.equals(c.contextMappings) == false) { - conflicts.add("mapper [" + name() + "] has different [context_mappings] values"); - } - - this.maxInputLength = ((CompletionFieldMapper)other).maxInputLength; - } } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/ParametrizedFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/ParametrizedFieldMapper.java index 3437db86eec..02cdadad75a 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/ParametrizedFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/ParametrizedFieldMapper.java @@ -22,14 +22,18 @@ package org.elasticsearch.index.mapper; import org.apache.logging.log4j.LogManager; import org.apache.lucene.document.FieldType; import org.elasticsearch.Version; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.support.XContentMapValues; +import org.elasticsearch.index.analysis.NamedAnalyzer; +import org.elasticsearch.index.mapper.Mapper.TypeParser.ParserContext; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -37,8 +41,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; /** * Defines how a particular field should be indexed and searched @@ -113,6 +118,13 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { return builder.endObject(); } + /** + * Serializes a parameter + */ + protected interface Serializer { + void serialize(XContentBuilder builder, String name, T value) throws IOException; + } + /** * A configurable parameter for a field mapper * @param the type of the value the parameter holds @@ -120,12 +132,16 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { public static final class Parameter { public final String name; - private final T defaultValue; - private final BiFunction parser; + private final List deprecatedNames = new ArrayList<>(); + private final Supplier defaultValue; + private final TriFunction parser; private final Function initializer; private final boolean updateable; private boolean acceptsNull = false; + private Consumer validator = null; + private Serializer serializer = XContentBuilder::field; private T value; + private boolean isSet; /** * Creates a new Parameter @@ -135,11 +151,11 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { * @param parser a function that converts an object to a parameter value * @param initializer a function that reads a parameter value from an existing mapper */ - public Parameter(String name, boolean updateable, T defaultValue, - BiFunction parser, Function initializer) { + public Parameter(String name, boolean updateable, Supplier defaultValue, + TriFunction parser, Function initializer) { this.name = name; - this.defaultValue = defaultValue; - this.value = defaultValue; + this.defaultValue = Objects.requireNonNull(defaultValue); + this.value = null; this.parser = parser; this.initializer = initializer; this.updateable = updateable; @@ -149,13 +165,14 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { * Returns the current value of the parameter */ public T getValue() { - return value; + return isSet ? value : defaultValue.get(); } /** * Sets the current value of the parameter */ public void setValue(T value) { + this.isSet = true; this.value = value; } @@ -167,26 +184,59 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { return this; } - private void init(FieldMapper toInit) { - this.value = initializer.apply(toInit); + /** + * Adds a deprecated parameter name. + * + * If this parameter name is encountered during parsing, a deprecation warning will + * be emitted. The parameter will be serialized with its main name. + */ + public Parameter addDeprecatedName(String deprecatedName) { + this.deprecatedNames.add(deprecatedName); + return this; } - private void parse(String field, Object in) { - this.value = parser.apply(field, in); + /** + * Adds validation to a parameter, called after parsing and merging + */ + public Parameter setValidator(Consumer validator) { + this.validator = validator; + return this; + } + + /** + * Configure a custom serializer for this parameter + */ + public Parameter setSerializer(Serializer serializer) { + this.serializer = serializer; + return this; + } + + private void validate() { + if (validator != null && isSet) { + validator.accept(value); + } + } + + private void init(FieldMapper toInit) { + setValue(initializer.apply(toInit)); + } + + private void parse(String field, ParserContext context, Object in) { + setValue(parser.apply(field, context, in)); } private void merge(FieldMapper toMerge, Conflicts conflicts) { T value = initializer.apply(toMerge); - if (updateable == false && Objects.equals(this.value, value) == false) { + if (updateable == false && isSet && Objects.equals(this.value, value) == false) { conflicts.addConflict(name, this.value.toString(), value.toString()); } else { - this.value = value; + setValue(value); } } private void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException { - if (includeDefaults || (Objects.equals(defaultValue, value) == false)) { - builder.field(name, value); + if (includeDefaults || Objects.equals(getValue(), defaultValue.get()) == false) { + serializer.serialize(builder, name, getValue()); } } @@ -199,7 +249,7 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { */ public static Parameter boolParam(String name, boolean updateable, Function initializer, boolean defaultValue) { - return new Parameter<>(name, updateable, defaultValue, (n, o) -> XContentMapValues.nodeBooleanValue(o), initializer); + return new Parameter<>(name, updateable, () -> defaultValue, (n, c, o) -> XContentMapValues.nodeBooleanValue(o), initializer); } /** @@ -211,7 +261,19 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { */ public static Parameter floatParam(String name, boolean updateable, Function initializer, float defaultValue) { - return new Parameter<>(name, updateable, defaultValue, (n, o) -> XContentMapValues.nodeFloatValue(o), initializer); + return new Parameter<>(name, updateable, () -> defaultValue, (n, c, o) -> XContentMapValues.nodeFloatValue(o), initializer); + } + + /** + * Defines a parameter that takes an integer 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 intParam(String name, boolean updateable, + Function initializer, int defaultValue) { + return new Parameter<>(name, updateable, () -> defaultValue, (n, c, o) -> XContentMapValues.nodeIntegerValue(o), initializer); } /** @@ -223,9 +285,38 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { */ public static Parameter stringParam(String name, boolean updateable, Function initializer, String defaultValue) { - return new Parameter<>(name, updateable, defaultValue, - (n, o) -> XContentMapValues.nodeStringValue(o), initializer); + return new Parameter<>(name, updateable, () -> defaultValue, + (n, c, o) -> XContentMapValues.nodeStringValue(o), initializer); } + + /** + * Defines a parameter that takes an analyzer name + * @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 defaultAnalyzer the default value, to be used if the parameter is undefined in a mapping + */ + public static Parameter analyzerParam(String name, boolean updateable, + Function initializer, + Supplier defaultAnalyzer) { + return new Parameter<>(name, updateable, defaultAnalyzer, (n, c, o) -> { + String analyzerName = o.toString(); + NamedAnalyzer a = c.getIndexAnalyzers().get(analyzerName); + if (a == null) { + throw new IllegalArgumentException("analyzer [" + analyzerName + "] has not been configured in mappings"); + } + return a; + }, initializer).setSerializer((b, n, v) -> b.field(n, v.name())); + } + + /** + * Declares a metadata parameter + */ + public static Parameter> metaParam() { + return new Parameter<>("meta", true, Collections::emptyMap, + (n, c, o) -> TypeParsers.parseMeta(n, o), m -> m.fieldType().meta()); + } + } private static final class Conflicts { @@ -288,6 +379,13 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { multiFieldsBuilder.update(newSubField, parentPath(newSubField.name())); } this.copyTo.reset(in.copyTo); + validate(); + } + + private void validate() { + for (Parameter param : getParameters()) { + param.validate(); + } } /** @@ -305,7 +403,10 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { return context.path().pathAsText(name); } - private void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException { + /** + * Writes the current builder parameter values as XContent + */ + protected void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException { for (Parameter parameter : getParameters()) { parameter.toXContent(builder, includeDefaults); } @@ -317,10 +418,14 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { * @param parserContext the parser context * @param fieldNode the root node of the map of mappings for this field */ - public final void parse(String name, TypeParser.ParserContext parserContext, Map fieldNode) { + public final void parse(String name, ParserContext parserContext, Map fieldNode) { Map> paramsMap = new HashMap<>(); + Map> deprecatedParamsMap = new HashMap<>(); for (Parameter param : getParameters()) { paramsMap.put(param.name, param); + for (String deprecatedName : param.deprecatedNames) { + deprecatedParamsMap.put(deprecatedName, param); + } } String type = (String) fieldNode.remove("type"); for (Iterator> iterator = fieldNode.entrySet().iterator(); iterator.hasNext();) { @@ -337,7 +442,13 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { iterator.remove(); continue; } - Parameter parameter = paramsMap.get(propName); + Parameter parameter = deprecatedParamsMap.get(propName); + if (parameter != null) { + deprecationLogger.deprecatedAndMaybeLog(propName, "Parameter [{}] on mapper [{}] is deprecated, use [{}]", + propName, name, parameter.name); + } else { + parameter = paramsMap.get(propName); + } if (parameter == null) { if (isDeprecatedParameter(propName, parserContext.indexVersionCreated())) { deprecationLogger.deprecatedAndMaybeLog(propName, @@ -352,9 +463,10 @@ public abstract class ParametrizedFieldMapper extends FieldMapper { throw new MapperParsingException("[" + propName + "] on mapper [" + name + "] of type [" + type + "] must not have a [null] value"); } - parameter.parse(name, propNode); + parameter.parse(name, parserContext, propNode); iterator.remove(); } + validate(); } // These parameters were previously *always* parsed by TypeParsers#parseField(), even if they diff --git a/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java index b2aea6002d9..9a9bc0c0eae 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java @@ -18,7 +18,6 @@ */ package org.elasticsearch.index.mapper; -import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.SortedSetDocValuesField; import org.apache.lucene.index.IndexableField; import org.apache.lucene.search.Query; @@ -43,20 +42,16 @@ import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.IndexService; -import org.elasticsearch.index.analysis.AnalyzerScope; import org.elasticsearch.index.analysis.NamedAnalyzer; -import org.elasticsearch.search.suggest.completion.context.ContextBuilder; -import org.elasticsearch.search.suggest.completion.context.ContextMappings; +import org.elasticsearch.test.ESSingleNodeTestCase; import org.hamcrest.FeatureMatcher; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.hamcrest.core.CombinableMatcher; -import org.junit.Before; import java.io.IOException; -import java.util.Arrays; +import java.util.Collections; import java.util.Map; -import java.util.Set; import java.util.function.Function; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; @@ -68,31 +63,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -public class CompletionFieldMapperTests extends FieldMapperTestCase { - - @Before - public void addModifiers() { - addBooleanModifier("preserve_separators", false, CompletionFieldMapper.Builder::preserveSeparators); - addBooleanModifier("preserve_position_increments", false, CompletionFieldMapper.Builder::preservePositionIncrements); - addModifier("context_mappings", false, (a, b) -> { - ContextMappings contextMappings = new ContextMappings(Arrays.asList(ContextBuilder.category("foo").build(), - ContextBuilder.geo("geo").build())); - a.contextMappings(contextMappings); - }); - } - - @Override - protected Set unsupportedProperties() { - return org.elasticsearch.common.collect.Set.of("doc_values", "index"); - } - - @Override - protected CompletionFieldMapper.Builder newBuilder() { - CompletionFieldMapper.Builder builder = new CompletionFieldMapper.Builder("completion"); - builder.indexAnalyzer(new NamedAnalyzer("standard", AnalyzerScope.INDEX, new StandardAnalyzer())); - builder.searchAnalyzer(new NamedAnalyzer("standard", AnalyzerScope.INDEX, new StandardAnalyzer())); - return builder; - } +public class CompletionFieldMapperTests extends ESSingleNodeTestCase { public void testDefaultConfiguration() throws IOException { String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1") @@ -155,6 +126,8 @@ public class CompletionFieldMapperTests extends FieldMapperTestCase serializedMap = createParser(JsonXContent.jsonXContent, BytesReference.bytes(builder)).map(); Map configMap = (Map) serializedMap.get("completion"); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/ParametrizedMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/ParametrizedMapperTests.java index 777c41d60e1..ab845e55648 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/ParametrizedMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/ParametrizedMapperTests.java @@ -19,15 +19,20 @@ package org.elasticsearch.index.mapper; +import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.elasticsearch.Version; import org.elasticsearch.common.Strings; import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.IndexService; +import org.elasticsearch.index.analysis.AnalyzerScope; +import org.elasticsearch.index.analysis.IndexAnalyzers; +import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.index.mapper.ParametrizedFieldMapper.Parameter; import org.elasticsearch.plugins.MapperPlugin; import org.elasticsearch.plugins.Plugin; @@ -41,6 +46,10 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import static org.hamcrest.Matchers.instanceOf; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class ParametrizedMapperTests extends ESSingleNodeTestCase { public static class TestPlugin extends Plugin implements MapperPlugin { @@ -55,6 +64,27 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase { return Collections.singletonList(TestPlugin.class); } + private static class StringWrapper { + final String name; + + private StringWrapper(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StringWrapper that = (StringWrapper) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + } + private static TestMapper toType(Mapper in) { return (TestMapper) in; } @@ -64,9 +94,28 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase { final Parameter fixed = Parameter.boolParam("fixed", false, m -> toType(m).fixed, true); final Parameter fixed2 - = Parameter.boolParam("fixed2", false, m -> toType(m).fixed2, false); + = Parameter.boolParam("fixed2", false, m -> toType(m).fixed2, false) + .addDeprecatedName("fixed2_old"); final Parameter variable = Parameter.stringParam("variable", true, m -> toType(m).variable, "default").acceptsNull(); + final Parameter wrapper + = new Parameter<>("wrapper", true, () -> new StringWrapper("default"), + (n, c, o) -> { + if (o == null) return null; + return new StringWrapper(o.toString()); + }, + m -> toType(m).wrapper).setSerializer((b, n, v) -> b.field(n, v.name)); + final Parameter intValue = Parameter.intParam("int_value", true, m -> toType(m).intValue, 5) + .setValidator(n -> { + if (n > 50) { + throw new IllegalArgumentException("Value of [n] cannot be greater than 50"); + } + }); + final Parameter analyzer + = Parameter.analyzerParam("analyzer", true, m -> toType(m).analyzer, () -> Lucene.KEYWORD_ANALYZER); + final Parameter searchAnalyzer + = Parameter.analyzerParam("search_analyzer", true, m -> toType(m).searchAnalyzer, analyzer::getValue); + final Parameter index = Parameter.boolParam("index", false, m -> toType(m).index, true); protected Builder(String name) { @@ -75,7 +124,7 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase { @Override protected List> getParameters() { - return Arrays.asList(fixed, fixed2, variable, index); + return Arrays.asList(fixed, fixed2, variable, index, wrapper, intValue, analyzer, searchAnalyzer); } @Override @@ -100,6 +149,10 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase { private final boolean fixed; private final boolean fixed2; private final String variable; + private final StringWrapper wrapper; + private final int intValue; + private final NamedAnalyzer analyzer; + private final NamedAnalyzer searchAnalyzer; private final boolean index; protected TestMapper(String simpleName, String fullName, MultiFields multiFields, CopyTo copyTo, @@ -108,6 +161,10 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase { this.fixed = builder.fixed.getValue(); this.fixed2 = builder.fixed2.getValue(); this.variable = builder.variable.getValue(); + this.wrapper = builder.wrapper.getValue(); + this.intValue = builder.intValue.getValue(); + this.analyzer = builder.analyzer.getValue(); + this.searchAnalyzer = builder.searchAnalyzer.getValue(); this.index = builder.index.getValue(); } @@ -128,7 +185,15 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase { } private static TestMapper fromMapping(String mapping, Version version) { - Mapper.TypeParser.ParserContext pc = new Mapper.TypeParser.ParserContext(s -> null, null, s -> { + MapperService mapperService = mock(MapperService.class); + IndexAnalyzers indexAnalyzers = new IndexAnalyzers( + org.elasticsearch.common.collect.Map.of( + "_standard", Lucene.STANDARD_ANALYZER, + "_keyword", Lucene.KEYWORD_ANALYZER, + "default", new NamedAnalyzer("default", AnalyzerScope.INDEX, new StandardAnalyzer())), + Collections.emptyMap(), Collections.emptyMap()); + when(mapperService.getIndexAnalyzers()).thenReturn(indexAnalyzers); + Mapper.TypeParser.ParserContext pc = new Mapper.TypeParser.ParserContext(s -> null, mapperService, s -> { if (Objects.equals("keyword", s)) { return new KeywordFieldMapper.TypeParser(); } @@ -162,7 +227,8 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase { mapper.toXContent(builder, params); builder.endObject(); assertEquals("{\"field\":{\"type\":\"test_mapper\",\"fixed\":true," + - "\"fixed2\":false,\"variable\":\"default\",\"index\":true}}", + "\"fixed2\":false,\"variable\":\"default\",\"index\":true," + + "\"wrapper\":\"default\",\"int_value\":5,\"analyzer\":\"_keyword\",\"search_analyzer\":\"_keyword\"}}", Strings.toString(builder)); } @@ -255,6 +321,53 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase { assertEquals(mapping, Strings.toString(indexService.mapperService().documentMapper())); } + // test custom serializer + public void testCustomSerialization() { + String mapping = "{\"type\":\"test_mapper\",\"wrapper\":\"wrapped value\"}"; + TestMapper mapper = fromMapping(mapping); + assertEquals("wrapped value", mapper.wrapper.name); + assertEquals("{\"field\":" + mapping + "}", Strings.toString(mapper)); + } + + // test validator + public void testParameterValidation() { + String mapping = "{\"type\":\"test_mapper\",\"int_value\":10}"; + TestMapper mapper = fromMapping(mapping); + assertEquals(10, mapper.intValue); + assertEquals("{\"field\":" + mapping + "}", Strings.toString(mapper)); + + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> fromMapping("{\"type\":\"test_mapper\",\"int_value\":60}")); + assertEquals("Value of [n] cannot be greater than 50", e.getMessage()); + + } + + // test deprecations + public void testDeprecatedParameterName() { + String mapping = "{\"type\":\"test_mapper\",\"fixed2_old\":true}"; + TestMapper mapper = fromMapping(mapping); + assertTrue(mapper.fixed2); + assertWarnings("Parameter [fixed2_old] on mapper [field] is deprecated, use [fixed2]"); + assertEquals("{\"field\":{\"type\":\"test_mapper\",\"fixed2\":true}}", Strings.toString(mapper)); + } + + public void testAnalyzers() { + String mapping = "{\"type\":\"test_mapper\",\"analyzer\":\"_standard\"}"; + TestMapper mapper = fromMapping(mapping); + assertEquals(mapper.analyzer, Lucene.STANDARD_ANALYZER); + assertEquals("{\"field\":" + mapping + "}", Strings.toString(mapper)); + + String withDef = "{\"type\":\"test_mapper\",\"analyzer\":\"default\"}"; + mapper = fromMapping(withDef); + assertEquals(mapper.analyzer.name(), "default"); + assertThat(mapper.analyzer.analyzer(), instanceOf(StandardAnalyzer.class)); + assertEquals("{\"field\":" + withDef + "}", Strings.toString(mapper)); + + String badAnalyzer = "{\"type\":\"test_mapper\",\"analyzer\":\"wibble\"}"; + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> fromMapping(badAnalyzer)); + assertEquals("analyzer [wibble] has not been configured in mappings", e.getMessage()); + } + public void testDeprecatedParameters() throws IOException { // 'index' is declared explicitly, 'store' is not, but is one of the previously always-accepted params String mapping = "{\"type\":\"test_mapper\",\"index\":false,\"store\":true}"; @@ -264,4 +377,25 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase { assertEquals("{\"field\":{\"type\":\"test_mapper\",\"index\":false}}", Strings.toString(mapper)); } + public void testLinkedAnalyzers() { + String mapping = "{\"type\":\"test_mapper\",\"analyzer\":\"_standard\"}"; + TestMapper mapper = fromMapping(mapping); + assertEquals("_standard", mapper.analyzer.name()); + assertEquals("_standard", mapper.searchAnalyzer.name()); + assertEquals("{\"field\":" + mapping + "}", Strings.toString(mapper)); + + String mappingWithSA = "{\"type\":\"test_mapper\",\"search_analyzer\":\"_standard\"}"; + mapper = fromMapping(mappingWithSA); + assertEquals("_keyword", mapper.analyzer.name()); + assertEquals("_standard", mapper.searchAnalyzer.name()); + assertEquals("{\"field\":" + mappingWithSA + "}", Strings.toString(mapper)); + + String mappingWithBoth = "{\"type\":\"test_mapper\",\"analyzer\":\"default\",\"search_analyzer\":\"_standard\"}"; + mapper = fromMapping(mappingWithBoth); + assertEquals("default", mapper.analyzer.name()); + assertEquals("_standard", mapper.searchAnalyzer.name()); + assertEquals("{\"field\":" + mappingWithBoth + "}", Strings.toString(mapper)); + + } + } diff --git a/server/src/test/java/org/elasticsearch/search/suggest/completion/CompletionSuggesterBuilderTests.java b/server/src/test/java/org/elasticsearch/search/suggest/completion/CompletionSuggesterBuilderTests.java index 556adb0813f..cdc45eebec0 100644 --- a/server/src/test/java/org/elasticsearch/search/suggest/completion/CompletionSuggesterBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/search/suggest/completion/CompletionSuggesterBuilderTests.java @@ -25,7 +25,6 @@ import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.index.analysis.AnalyzerScope; import org.elasticsearch.index.analysis.NamedAnalyzer; -import org.elasticsearch.index.mapper.CompletionFieldMapper; import org.elasticsearch.index.mapper.CompletionFieldMapper.CompletionFieldType; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.search.suggest.AbstractSuggestionBuilderTestCase; @@ -169,14 +168,11 @@ public class CompletionSuggesterBuilderTests extends AbstractSuggestionBuilderTe @Override protected MappedFieldType mockFieldType(String fieldName, boolean analyzerSet) { if (analyzerSet == false) { - CompletionFieldType completionFieldType = new CompletionFieldType(fieldName, - CompletionFieldMapper.Defaults.FIELD_TYPE, null, null, Collections.emptyMap()); + CompletionFieldType completionFieldType = new CompletionFieldType(fieldName, null, Collections.emptyMap()); completionFieldType.setContextMappings(new ContextMappings(contextMappings)); return completionFieldType; } CompletionFieldType completionFieldType = new CompletionFieldType(fieldName, - CompletionFieldMapper.Defaults.FIELD_TYPE, - new NamedAnalyzer("fieldSearchAnalyzer", AnalyzerScope.INDEX, new SimpleAnalyzer()), new NamedAnalyzer("fieldSearchAnalyzer", AnalyzerScope.INDEX, new SimpleAnalyzer()), Collections.emptyMap()); completionFieldType.setContextMappings(new ContextMappings(contextMappings));