Migrate CompletionFieldMapper to parametrized format (#59691)
This adds a number of new optional parameters to Parameter, including: * custom serialization (to handle analyzers) * deprecated parameter names * parameter validation * allowing default values to be based on the values of other parameters We preserve the previous serialization format of CompletionFieldMapper, always emitting most fields, in order to meet mapping checks in mixed version clusters, where the mapper service will check that mappings have been correctly parsed and updated by checking their serialized outputs.
This commit is contained in:
parent
4089cbd767
commit
10be10c99b
|
@ -59,8 +59,7 @@ public class BinaryFieldMapper extends ParametrizedFieldMapper {
|
||||||
|
|
||||||
private final Parameter<Boolean> stored = Parameter.boolParam("store", false, m -> toType(m).stored, false);
|
private final Parameter<Boolean> stored = Parameter.boolParam("store", false, m -> toType(m).stored, false);
|
||||||
private final Parameter<Boolean> hasDocValues = Parameter.boolParam("doc_values", false, m -> toType(m).hasDocValues, false);
|
private final Parameter<Boolean> hasDocValues = Parameter.boolParam("doc_values", false, m -> toType(m).hasDocValues, false);
|
||||||
private final Parameter<Map<String, String>> meta
|
private final Parameter<Map<String, String>> meta = Parameter.metaParam();
|
||||||
= new Parameter<>("meta", true, Collections.emptyMap(), TypeParsers::parseMeta, m -> m.fieldType().meta());
|
|
||||||
|
|
||||||
public Builder(String name) {
|
public Builder(String name) {
|
||||||
this(name, false);
|
this(name, false);
|
||||||
|
|
|
@ -79,12 +79,11 @@ public class BooleanFieldMapper extends ParametrizedFieldMapper {
|
||||||
private final Parameter<Boolean> indexed = Parameter.boolParam("index", false, m -> toType(m).indexed, true);
|
private final Parameter<Boolean> indexed = Parameter.boolParam("index", false, m -> toType(m).indexed, true);
|
||||||
private final Parameter<Boolean> stored = Parameter.boolParam("store", false, m -> toType(m).stored, false);
|
private final Parameter<Boolean> stored = Parameter.boolParam("store", false, m -> toType(m).stored, false);
|
||||||
|
|
||||||
private final Parameter<Boolean> nullValue
|
private final Parameter<Boolean> nullValue = new Parameter<>("null_value", false, () -> null,
|
||||||
= new Parameter<>("null_value", false, null, (n, o) -> XContentMapValues.nodeBooleanValue(o), m -> toType(m).nullValue);
|
(n, c, o) -> XContentMapValues.nodeBooleanValue(o), m -> toType(m).nullValue);
|
||||||
|
|
||||||
private final Parameter<Float> boost = Parameter.floatParam("boost", true, m -> m.fieldType().boost(), 1.0f);
|
private final Parameter<Float> boost = Parameter.floatParam("boost", true, m -> m.fieldType().boost(), 1.0f);
|
||||||
private final Parameter<Map<String, String>> meta
|
private final Parameter<Map<String, String>> meta = Parameter.metaParam();
|
||||||
= new Parameter<>("meta", true, Collections.emptyMap(), TypeParsers::parseMeta, m -> m.fieldType().meta());
|
|
||||||
|
|
||||||
public Builder(String name) {
|
public Builder(String name) {
|
||||||
super(name);
|
super(name);
|
||||||
|
|
|
@ -34,13 +34,11 @@ import org.apache.lucene.search.suggest.document.RegexCompletionQuery;
|
||||||
import org.apache.lucene.search.suggest.document.SuggestField;
|
import org.apache.lucene.search.suggest.document.SuggestField;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.cluster.metadata.IndexMetadata;
|
import org.elasticsearch.cluster.metadata.IndexMetadata;
|
||||||
import org.elasticsearch.common.ParseField;
|
|
||||||
import org.elasticsearch.common.ParsingException;
|
import org.elasticsearch.common.ParsingException;
|
||||||
import org.elasticsearch.common.logging.DeprecationLogger;
|
import org.elasticsearch.common.logging.DeprecationLogger;
|
||||||
import org.elasticsearch.common.lucene.Lucene;
|
|
||||||
import org.elasticsearch.common.unit.Fuzziness;
|
import org.elasticsearch.common.unit.Fuzziness;
|
||||||
import org.elasticsearch.common.util.set.Sets;
|
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.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser.NumberType;
|
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 org.elasticsearch.search.suggest.completion.context.ContextMappings;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
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
|
* Mapper for completion field. The field values are indexed as a weighted FST for
|
||||||
* fast auto-completion/search-as-you-type functionality.<br>
|
* fast auto-completion/search-as-you-type functionality.<br>
|
||||||
|
@ -84,7 +81,7 @@ import static org.elasticsearch.index.mapper.TypeParsers.parseMultiField;
|
||||||
* This field can also be extended to add search criteria to suggestions
|
* This field can also be extended to add search criteria to suggestions
|
||||||
* for query-time filtering and boosting (see {@link ContextMappings}
|
* 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";
|
public static final String CONTENT_TYPE = "completion";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,6 +89,11 @@ public class CompletionFieldMapper extends FieldMapper {
|
||||||
*/
|
*/
|
||||||
static final int COMPLETION_CONTEXTS_LIMIT = 10;
|
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 class Defaults {
|
||||||
public static final FieldType FIELD_TYPE = new FieldType();
|
public static final FieldType FIELD_TYPE = new FieldType();
|
||||||
static {
|
static {
|
||||||
|
@ -108,20 +110,113 @@ public class CompletionFieldMapper extends FieldMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Fields {
|
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
|
// Content field names
|
||||||
public static final String CONTENT_FIELD_NAME_INPUT = "input";
|
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_WEIGHT = "weight";
|
||||||
public static final String CONTENT_FIELD_NAME_CONTEXTS = "contexts";
|
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<NamedAnalyzer> analyzer;
|
||||||
|
private final Parameter<NamedAnalyzer> searchAnalyzer;
|
||||||
|
private final Parameter<Boolean> preserveSeparators = Parameter.boolParam("preserve_separators", false,
|
||||||
|
m -> toType(m).preserveSeparators, Defaults.DEFAULT_PRESERVE_SEPARATORS);
|
||||||
|
private final Parameter<Boolean> preservePosInc = Parameter.boolParam("preserve_position_increments", false,
|
||||||
|
m -> toType(m).preservePosInc, Defaults.DEFAULT_POSITION_INCREMENTS);
|
||||||
|
private final Parameter<ContextMappings> 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<Integer> 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<Map<String, String>> 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<Parameter<?>> 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<String> ALLOWED_CONTENT_FIELD_NAMES = Sets.newHashSet(Fields.CONTENT_FIELD_NAME_INPUT,
|
public static final Set<String> ALLOWED_CONTENT_FIELD_NAMES = Sets.newHashSet(Fields.CONTENT_FIELD_NAME_INPUT,
|
||||||
Fields.CONTENT_FIELD_NAME_WEIGHT, Fields.CONTENT_FIELD_NAME_CONTEXTS);
|
Fields.CONTENT_FIELD_NAME_WEIGHT, Fields.CONTENT_FIELD_NAME_CONTEXTS);
|
||||||
|
|
||||||
|
@ -130,60 +225,11 @@ public class CompletionFieldMapper extends FieldMapper {
|
||||||
@Override
|
@Override
|
||||||
public Mapper.Builder<?> parse(String name, Map<String, Object> node, ParserContext parserContext)
|
public Mapper.Builder<?> parse(String name, Map<String, Object> node, ParserContext parserContext)
|
||||||
throws MapperParsingException {
|
throws MapperParsingException {
|
||||||
CompletionFieldMapper.Builder builder = new CompletionFieldMapper.Builder(name);
|
CompletionFieldMapper.Builder builder
|
||||||
NamedAnalyzer indexAnalyzer = null;
|
= new CompletionFieldMapper.Builder(name, parserContext.getIndexAnalyzers().get("simple"));
|
||||||
NamedAnalyzer searchAnalyzer = null;
|
builder.parse(name, parserContext, node);
|
||||||
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
|
|
||||||
Map.Entry<String, Object> 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);
|
|
||||||
return builder;
|
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 {
|
public static final class CompletionFieldType extends TermBasedFieldType {
|
||||||
|
@ -194,17 +240,9 @@ public class CompletionFieldMapper extends FieldMapper {
|
||||||
private boolean preservePositionIncrements = Defaults.DEFAULT_POSITION_INCREMENTS;
|
private boolean preservePositionIncrements = Defaults.DEFAULT_POSITION_INCREMENTS;
|
||||||
private ContextMappings contextMappings = null;
|
private ContextMappings contextMappings = null;
|
||||||
|
|
||||||
public CompletionFieldType(String name, FieldType luceneFieldType,
|
public CompletionFieldType(String name, NamedAnalyzer searchAnalyzer, Map<String, String> meta) {
|
||||||
NamedAnalyzer searchAnalyzer, NamedAnalyzer searchQuoteAnalyzer, Map<String, String> meta) {
|
|
||||||
super(name, true, false,
|
super(name, true, false,
|
||||||
new TextSearchInfo(luceneFieldType, null, searchAnalyzer, searchQuoteAnalyzer), meta);
|
new TextSearchInfo(Defaults.FIELD_TYPE, null, searchAnalyzer, searchAnalyzer), 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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPreserveSep(boolean preserveSep) {
|
public void setPreserveSep(boolean preserveSep) {
|
||||||
|
@ -245,14 +283,6 @@ public class CompletionFieldMapper extends FieldMapper {
|
||||||
return contextMappings;
|
return contextMappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean preserveSep() {
|
|
||||||
return preserveSep;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean preservePositionIncrements() {
|
|
||||||
return preservePositionIncrements;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return postings format to use for this field-type
|
* @return postings format to use for this field-type
|
||||||
*/
|
*/
|
||||||
|
@ -302,100 +332,24 @@ public class CompletionFieldMapper extends FieldMapper {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private final int maxInputLength;
|
||||||
* Builder for {@link CompletionFieldMapper}
|
private final boolean preserveSeparators;
|
||||||
*/
|
private final boolean preservePosInc;
|
||||||
public static class Builder extends FieldMapper.Builder<Builder> {
|
private final NamedAnalyzer defaultAnalyzer;
|
||||||
|
private final NamedAnalyzer analyzer;
|
||||||
|
private final NamedAnalyzer searchAnalyzer;
|
||||||
|
private final ContextMappings contexts;
|
||||||
|
|
||||||
private int maxInputLength = Defaults.DEFAULT_MAX_INPUT_LENGTH;
|
public CompletionFieldMapper(String simpleName, MappedFieldType mappedFieldType, NamedAnalyzer defaultAnalyzer,
|
||||||
private ContextMappings contextMappings = null;
|
MultiFields multiFields, CopyTo copyTo, Builder builder) {
|
||||||
private boolean preserveSeparators = Defaults.DEFAULT_PRESERVE_SEPARATORS;
|
super(simpleName, mappedFieldType, multiFields, copyTo);
|
||||||
private boolean preservePositionIncrements = Defaults.DEFAULT_POSITION_INCREMENTS;
|
this.defaultAnalyzer = defaultAnalyzer;
|
||||||
|
this.maxInputLength = builder.maxInputLength.getValue();
|
||||||
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(Builder.class));
|
this.preserveSeparators = builder.preserveSeparators.getValue();
|
||||||
|
this.preservePosInc = builder.preservePosInc.getValue();
|
||||||
/**
|
this.analyzer = builder.analyzer.getValue();
|
||||||
* @param name of the completion field to build
|
this.searchAnalyzer = builder.searchAnalyzer.getValue();
|
||||||
*/
|
this.contexts = builder.contexts.getValue();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
@Override
|
||||||
protected void parseCreateField(ParseContext context) throws IOException {
|
protected void parseCreateField(ParseContext context) throws IOException {
|
||||||
// no-op
|
// no-op
|
||||||
|
@ -633,23 +565,6 @@ public class CompletionFieldMapper extends FieldMapper {
|
||||||
return CONTENT_TYPE;
|
return CONTENT_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void mergeOptions(FieldMapper other, List<String> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,18 @@ package org.elasticsearch.index.mapper;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.lucene.document.FieldType;
|
import org.apache.lucene.document.FieldType;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
|
import org.elasticsearch.common.TriFunction;
|
||||||
import org.elasticsearch.common.logging.DeprecationLogger;
|
import org.elasticsearch.common.logging.DeprecationLogger;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
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.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -37,8 +41,9 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines how a particular field should be indexed and searched
|
* Defines how a particular field should be indexed and searched
|
||||||
|
@ -113,6 +118,13 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
|
||||||
return builder.endObject();
|
return builder.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes a parameter
|
||||||
|
*/
|
||||||
|
protected interface Serializer<T> {
|
||||||
|
void serialize(XContentBuilder builder, String name, T value) throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A configurable parameter for a field mapper
|
* A configurable parameter for a field mapper
|
||||||
* @param <T> the type of the value the parameter holds
|
* @param <T> the type of the value the parameter holds
|
||||||
|
@ -120,12 +132,16 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
|
||||||
public static final class Parameter<T> {
|
public static final class Parameter<T> {
|
||||||
|
|
||||||
public final String name;
|
public final String name;
|
||||||
private final T defaultValue;
|
private final List<String> deprecatedNames = new ArrayList<>();
|
||||||
private final BiFunction<String, Object, T> parser;
|
private final Supplier<T> defaultValue;
|
||||||
|
private final TriFunction<String, ParserContext, Object, T> parser;
|
||||||
private final Function<FieldMapper, T> initializer;
|
private final Function<FieldMapper, T> initializer;
|
||||||
private final boolean updateable;
|
private final boolean updateable;
|
||||||
private boolean acceptsNull = false;
|
private boolean acceptsNull = false;
|
||||||
|
private Consumer<T> validator = null;
|
||||||
|
private Serializer<T> serializer = XContentBuilder::field;
|
||||||
private T value;
|
private T value;
|
||||||
|
private boolean isSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Parameter
|
* 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 parser a function that converts an object to a parameter value
|
||||||
* @param initializer a function that reads a parameter value from an existing mapper
|
* @param initializer a function that reads a parameter value from an existing mapper
|
||||||
*/
|
*/
|
||||||
public Parameter(String name, boolean updateable, T defaultValue,
|
public Parameter(String name, boolean updateable, Supplier<T> defaultValue,
|
||||||
BiFunction<String, Object, T> parser, Function<FieldMapper, T> initializer) {
|
TriFunction<String, ParserContext, Object, T> parser, Function<FieldMapper, T> initializer) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.defaultValue = defaultValue;
|
this.defaultValue = Objects.requireNonNull(defaultValue);
|
||||||
this.value = defaultValue;
|
this.value = null;
|
||||||
this.parser = parser;
|
this.parser = parser;
|
||||||
this.initializer = initializer;
|
this.initializer = initializer;
|
||||||
this.updateable = updateable;
|
this.updateable = updateable;
|
||||||
|
@ -149,13 +165,14 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
|
||||||
* Returns the current value of the parameter
|
* Returns the current value of the parameter
|
||||||
*/
|
*/
|
||||||
public T getValue() {
|
public T getValue() {
|
||||||
return value;
|
return isSet ? value : defaultValue.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current value of the parameter
|
* Sets the current value of the parameter
|
||||||
*/
|
*/
|
||||||
public void setValue(T value) {
|
public void setValue(T value) {
|
||||||
|
this.isSet = true;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,26 +184,59 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
|
||||||
return this;
|
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<T> 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<T> setValidator(Consumer<T> validator) {
|
||||||
|
this.validator = validator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a custom serializer for this parameter
|
||||||
|
*/
|
||||||
|
public Parameter<T> setSerializer(Serializer<T> 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) {
|
private void merge(FieldMapper toMerge, Conflicts conflicts) {
|
||||||
T value = initializer.apply(toMerge);
|
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());
|
conflicts.addConflict(name, this.value.toString(), value.toString());
|
||||||
} else {
|
} else {
|
||||||
this.value = value;
|
setValue(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException {
|
private void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException {
|
||||||
if (includeDefaults || (Objects.equals(defaultValue, value) == false)) {
|
if (includeDefaults || Objects.equals(getValue(), defaultValue.get()) == false) {
|
||||||
builder.field(name, value);
|
serializer.serialize(builder, name, getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +249,7 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
|
||||||
*/
|
*/
|
||||||
public static Parameter<Boolean> boolParam(String name, boolean updateable,
|
public static Parameter<Boolean> boolParam(String name, boolean updateable,
|
||||||
Function<FieldMapper, Boolean> initializer, boolean defaultValue) {
|
Function<FieldMapper, Boolean> 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<Float> floatParam(String name, boolean updateable,
|
public static Parameter<Float> floatParam(String name, boolean updateable,
|
||||||
Function<FieldMapper, Float> initializer, float defaultValue) {
|
Function<FieldMapper, Float> 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<Integer> intParam(String name, boolean updateable,
|
||||||
|
Function<FieldMapper, Integer> 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<String> stringParam(String name, boolean updateable,
|
public static Parameter<String> stringParam(String name, boolean updateable,
|
||||||
Function<FieldMapper, String> initializer, String defaultValue) {
|
Function<FieldMapper, String> initializer, String defaultValue) {
|
||||||
return new Parameter<>(name, updateable, defaultValue,
|
return new Parameter<>(name, updateable, () -> defaultValue,
|
||||||
(n, o) -> XContentMapValues.nodeStringValue(o), initializer);
|
(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<NamedAnalyzer> analyzerParam(String name, boolean updateable,
|
||||||
|
Function<FieldMapper, NamedAnalyzer> initializer,
|
||||||
|
Supplier<NamedAnalyzer> 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<Map<String, String>> metaParam() {
|
||||||
|
return new Parameter<>("meta", true, Collections::emptyMap,
|
||||||
|
(n, c, o) -> TypeParsers.parseMeta(n, o), m -> m.fieldType().meta());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class Conflicts {
|
private static final class Conflicts {
|
||||||
|
@ -288,6 +379,13 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
|
||||||
multiFieldsBuilder.update(newSubField, parentPath(newSubField.name()));
|
multiFieldsBuilder.update(newSubField, parentPath(newSubField.name()));
|
||||||
}
|
}
|
||||||
this.copyTo.reset(in.copyTo);
|
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);
|
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()) {
|
for (Parameter<?> parameter : getParameters()) {
|
||||||
parameter.toXContent(builder, includeDefaults);
|
parameter.toXContent(builder, includeDefaults);
|
||||||
}
|
}
|
||||||
|
@ -317,10 +418,14 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
|
||||||
* @param parserContext the parser context
|
* @param parserContext the parser context
|
||||||
* @param fieldNode the root node of the map of mappings for this field
|
* @param fieldNode the root node of the map of mappings for this field
|
||||||
*/
|
*/
|
||||||
public final void parse(String name, TypeParser.ParserContext parserContext, Map<String, Object> fieldNode) {
|
public final void parse(String name, ParserContext parserContext, Map<String, Object> fieldNode) {
|
||||||
Map<String, Parameter<?>> paramsMap = new HashMap<>();
|
Map<String, Parameter<?>> paramsMap = new HashMap<>();
|
||||||
|
Map<String, Parameter<?>> deprecatedParamsMap = new HashMap<>();
|
||||||
for (Parameter<?> param : getParameters()) {
|
for (Parameter<?> param : getParameters()) {
|
||||||
paramsMap.put(param.name, param);
|
paramsMap.put(param.name, param);
|
||||||
|
for (String deprecatedName : param.deprecatedNames) {
|
||||||
|
deprecatedParamsMap.put(deprecatedName, param);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
String type = (String) fieldNode.remove("type");
|
String type = (String) fieldNode.remove("type");
|
||||||
for (Iterator<Map.Entry<String, Object>> iterator = fieldNode.entrySet().iterator(); iterator.hasNext();) {
|
for (Iterator<Map.Entry<String, Object>> iterator = fieldNode.entrySet().iterator(); iterator.hasNext();) {
|
||||||
|
@ -337,7 +442,13 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
continue;
|
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 (parameter == null) {
|
||||||
if (isDeprecatedParameter(propName, parserContext.indexVersionCreated())) {
|
if (isDeprecatedParameter(propName, parserContext.indexVersionCreated())) {
|
||||||
deprecationLogger.deprecatedAndMaybeLog(propName,
|
deprecationLogger.deprecatedAndMaybeLog(propName,
|
||||||
|
@ -352,9 +463,10 @@ public abstract class ParametrizedFieldMapper extends FieldMapper {
|
||||||
throw new MapperParsingException("[" + propName + "] on mapper [" + name
|
throw new MapperParsingException("[" + propName + "] on mapper [" + name
|
||||||
+ "] of type [" + type + "] must not have a [null] value");
|
+ "] of type [" + type + "] must not have a [null] value");
|
||||||
}
|
}
|
||||||
parameter.parse(name, propNode);
|
parameter.parse(name, parserContext, propNode);
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
|
validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// These parameters were previously *always* parsed by TypeParsers#parseField(), even if they
|
// These parameters were previously *always* parsed by TypeParsers#parseField(), even if they
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.index.mapper;
|
package org.elasticsearch.index.mapper;
|
||||||
|
|
||||||
import org.apache.lucene.analysis.standard.StandardAnalyzer;
|
|
||||||
import org.apache.lucene.document.SortedSetDocValuesField;
|
import org.apache.lucene.document.SortedSetDocValuesField;
|
||||||
import org.apache.lucene.index.IndexableField;
|
import org.apache.lucene.index.IndexableField;
|
||||||
import org.apache.lucene.search.Query;
|
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.XContentType;
|
||||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
import org.elasticsearch.index.IndexService;
|
import org.elasticsearch.index.IndexService;
|
||||||
import org.elasticsearch.index.analysis.AnalyzerScope;
|
|
||||||
import org.elasticsearch.index.analysis.NamedAnalyzer;
|
import org.elasticsearch.index.analysis.NamedAnalyzer;
|
||||||
import org.elasticsearch.search.suggest.completion.context.ContextBuilder;
|
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||||
import org.elasticsearch.search.suggest.completion.context.ContextMappings;
|
|
||||||
import org.hamcrest.FeatureMatcher;
|
import org.hamcrest.FeatureMatcher;
|
||||||
import org.hamcrest.Matcher;
|
import org.hamcrest.Matcher;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.hamcrest.core.CombinableMatcher;
|
import org.hamcrest.core.CombinableMatcher;
|
||||||
import org.junit.Before;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
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.instanceOf;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
public class CompletionFieldMapperTests extends FieldMapperTestCase<CompletionFieldMapper.Builder> {
|
public class CompletionFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
|
|
||||||
@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<String> 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 void testDefaultConfiguration() throws IOException {
|
public void testDefaultConfiguration() throws IOException {
|
||||||
String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1")
|
String mapping = Strings.toString(jsonBuilder().startObject().startObject("type1")
|
||||||
|
@ -155,6 +126,8 @@ public class CompletionFieldMapperTests extends FieldMapperTestCase<CompletionFi
|
||||||
assertThat(analyzer.preservePositionIncrements(), equalTo(true));
|
assertThat(analyzer.preservePositionIncrements(), equalTo(true));
|
||||||
assertThat(analyzer.preserveSep(), equalTo(false));
|
assertThat(analyzer.preserveSep(), equalTo(false));
|
||||||
|
|
||||||
|
assertEquals("{\"completion\":{\"type\":\"completion\",\"analyzer\":\"simple\",\"search_analyzer\":\"standard\"," +
|
||||||
|
"\"preserve_separators\":false,\"preserve_position_increments\":true,\"max_input_length\":50}}", Strings.toString(fieldMapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testTypeParsing() throws Exception {
|
public void testTypeParsing() throws Exception {
|
||||||
|
@ -176,7 +149,8 @@ public class CompletionFieldMapperTests extends FieldMapperTestCase<CompletionFi
|
||||||
assertThat(fieldMapper, instanceOf(CompletionFieldMapper.class));
|
assertThat(fieldMapper, instanceOf(CompletionFieldMapper.class));
|
||||||
|
|
||||||
XContentBuilder builder = jsonBuilder().startObject();
|
XContentBuilder builder = jsonBuilder().startObject();
|
||||||
fieldMapper.toXContent(builder, ToXContent.EMPTY_PARAMS).endObject();
|
fieldMapper.toXContent(builder,
|
||||||
|
new ToXContent.MapParams(Collections.singletonMap("include_defaults", "true"))).endObject();
|
||||||
builder.close();
|
builder.close();
|
||||||
Map<String, Object> serializedMap = createParser(JsonXContent.jsonXContent, BytesReference.bytes(builder)).map();
|
Map<String, Object> serializedMap = createParser(JsonXContent.jsonXContent, BytesReference.bytes(builder)).map();
|
||||||
Map<String, Object> configMap = (Map<String, Object>) serializedMap.get("completion");
|
Map<String, Object> configMap = (Map<String, Object>) serializedMap.get("completion");
|
||||||
|
|
|
@ -19,15 +19,20 @@
|
||||||
|
|
||||||
package org.elasticsearch.index.mapper;
|
package org.elasticsearch.index.mapper;
|
||||||
|
|
||||||
|
import org.apache.lucene.analysis.standard.StandardAnalyzer;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.compress.CompressedXContent;
|
import org.elasticsearch.common.compress.CompressedXContent;
|
||||||
|
import org.elasticsearch.common.lucene.Lucene;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
import org.elasticsearch.index.IndexService;
|
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.index.mapper.ParametrizedFieldMapper.Parameter;
|
||||||
import org.elasticsearch.plugins.MapperPlugin;
|
import org.elasticsearch.plugins.MapperPlugin;
|
||||||
import org.elasticsearch.plugins.Plugin;
|
import org.elasticsearch.plugins.Plugin;
|
||||||
|
@ -41,6 +46,10 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
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 class ParametrizedMapperTests extends ESSingleNodeTestCase {
|
||||||
|
|
||||||
public static class TestPlugin extends Plugin implements MapperPlugin {
|
public static class TestPlugin extends Plugin implements MapperPlugin {
|
||||||
|
@ -55,6 +64,27 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
|
||||||
return Collections.singletonList(TestPlugin.class);
|
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) {
|
private static TestMapper toType(Mapper in) {
|
||||||
return (TestMapper) in;
|
return (TestMapper) in;
|
||||||
}
|
}
|
||||||
|
@ -64,9 +94,28 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
|
||||||
final Parameter<Boolean> fixed
|
final Parameter<Boolean> fixed
|
||||||
= Parameter.boolParam("fixed", false, m -> toType(m).fixed, true);
|
= Parameter.boolParam("fixed", false, m -> toType(m).fixed, true);
|
||||||
final Parameter<Boolean> fixed2
|
final Parameter<Boolean> fixed2
|
||||||
= Parameter.boolParam("fixed2", false, m -> toType(m).fixed2, false);
|
= Parameter.boolParam("fixed2", false, m -> toType(m).fixed2, false)
|
||||||
|
.addDeprecatedName("fixed2_old");
|
||||||
final Parameter<String> variable
|
final Parameter<String> variable
|
||||||
= Parameter.stringParam("variable", true, m -> toType(m).variable, "default").acceptsNull();
|
= Parameter.stringParam("variable", true, m -> toType(m).variable, "default").acceptsNull();
|
||||||
|
final Parameter<StringWrapper> 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<Integer> 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<NamedAnalyzer> analyzer
|
||||||
|
= Parameter.analyzerParam("analyzer", true, m -> toType(m).analyzer, () -> Lucene.KEYWORD_ANALYZER);
|
||||||
|
final Parameter<NamedAnalyzer> searchAnalyzer
|
||||||
|
= Parameter.analyzerParam("search_analyzer", true, m -> toType(m).searchAnalyzer, analyzer::getValue);
|
||||||
|
|
||||||
final Parameter<Boolean> index = Parameter.boolParam("index", false, m -> toType(m).index, true);
|
final Parameter<Boolean> index = Parameter.boolParam("index", false, m -> toType(m).index, true);
|
||||||
|
|
||||||
protected Builder(String name) {
|
protected Builder(String name) {
|
||||||
|
@ -75,7 +124,7 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Parameter<?>> getParameters() {
|
protected List<Parameter<?>> getParameters() {
|
||||||
return Arrays.asList(fixed, fixed2, variable, index);
|
return Arrays.asList(fixed, fixed2, variable, index, wrapper, intValue, analyzer, searchAnalyzer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -100,6 +149,10 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
|
||||||
private final boolean fixed;
|
private final boolean fixed;
|
||||||
private final boolean fixed2;
|
private final boolean fixed2;
|
||||||
private final String variable;
|
private final String variable;
|
||||||
|
private final StringWrapper wrapper;
|
||||||
|
private final int intValue;
|
||||||
|
private final NamedAnalyzer analyzer;
|
||||||
|
private final NamedAnalyzer searchAnalyzer;
|
||||||
private final boolean index;
|
private final boolean index;
|
||||||
|
|
||||||
protected TestMapper(String simpleName, String fullName, MultiFields multiFields, CopyTo copyTo,
|
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.fixed = builder.fixed.getValue();
|
||||||
this.fixed2 = builder.fixed2.getValue();
|
this.fixed2 = builder.fixed2.getValue();
|
||||||
this.variable = builder.variable.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();
|
this.index = builder.index.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +185,15 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TestMapper fromMapping(String mapping, Version version) {
|
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)) {
|
if (Objects.equals("keyword", s)) {
|
||||||
return new KeywordFieldMapper.TypeParser();
|
return new KeywordFieldMapper.TypeParser();
|
||||||
}
|
}
|
||||||
|
@ -162,7 +227,8 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
|
||||||
mapper.toXContent(builder, params);
|
mapper.toXContent(builder, params);
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
assertEquals("{\"field\":{\"type\":\"test_mapper\",\"fixed\":true," +
|
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));
|
Strings.toString(builder));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,6 +321,53 @@ public class ParametrizedMapperTests extends ESSingleNodeTestCase {
|
||||||
assertEquals(mapping, Strings.toString(indexService.mapperService().documentMapper()));
|
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 {
|
public void testDeprecatedParameters() throws IOException {
|
||||||
// 'index' is declared explicitly, 'store' is not, but is one of the previously always-accepted params
|
// '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}";
|
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));
|
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));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import org.elasticsearch.common.unit.Fuzziness;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.index.analysis.AnalyzerScope;
|
import org.elasticsearch.index.analysis.AnalyzerScope;
|
||||||
import org.elasticsearch.index.analysis.NamedAnalyzer;
|
import org.elasticsearch.index.analysis.NamedAnalyzer;
|
||||||
import org.elasticsearch.index.mapper.CompletionFieldMapper;
|
|
||||||
import org.elasticsearch.index.mapper.CompletionFieldMapper.CompletionFieldType;
|
import org.elasticsearch.index.mapper.CompletionFieldMapper.CompletionFieldType;
|
||||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||||
import org.elasticsearch.search.suggest.AbstractSuggestionBuilderTestCase;
|
import org.elasticsearch.search.suggest.AbstractSuggestionBuilderTestCase;
|
||||||
|
@ -169,14 +168,11 @@ public class CompletionSuggesterBuilderTests extends AbstractSuggestionBuilderTe
|
||||||
@Override
|
@Override
|
||||||
protected MappedFieldType mockFieldType(String fieldName, boolean analyzerSet) {
|
protected MappedFieldType mockFieldType(String fieldName, boolean analyzerSet) {
|
||||||
if (analyzerSet == false) {
|
if (analyzerSet == false) {
|
||||||
CompletionFieldType completionFieldType = new CompletionFieldType(fieldName,
|
CompletionFieldType completionFieldType = new CompletionFieldType(fieldName, null, Collections.emptyMap());
|
||||||
CompletionFieldMapper.Defaults.FIELD_TYPE, null, null, Collections.emptyMap());
|
|
||||||
completionFieldType.setContextMappings(new ContextMappings(contextMappings));
|
completionFieldType.setContextMappings(new ContextMappings(contextMappings));
|
||||||
return completionFieldType;
|
return completionFieldType;
|
||||||
}
|
}
|
||||||
CompletionFieldType completionFieldType = new CompletionFieldType(fieldName,
|
CompletionFieldType completionFieldType = new CompletionFieldType(fieldName,
|
||||||
CompletionFieldMapper.Defaults.FIELD_TYPE,
|
|
||||||
new NamedAnalyzer("fieldSearchAnalyzer", AnalyzerScope.INDEX, new SimpleAnalyzer()),
|
|
||||||
new NamedAnalyzer("fieldSearchAnalyzer", AnalyzerScope.INDEX, new SimpleAnalyzer()),
|
new NamedAnalyzer("fieldSearchAnalyzer", AnalyzerScope.INDEX, new SimpleAnalyzer()),
|
||||||
Collections.emptyMap());
|
Collections.emptyMap());
|
||||||
completionFieldType.setContextMappings(new ContextMappings(contextMappings));
|
completionFieldType.setContextMappings(new ContextMappings(contextMappings));
|
||||||
|
|
Loading…
Reference in New Issue