/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
package org.opensearch.index.mapper;
import org.apache.lucene.codecs.PostingsFormat;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.suggest.document.Completion90PostingsFormat;
import org.apache.lucene.search.suggest.document.CompletionAnalyzer;
import org.apache.lucene.search.suggest.document.CompletionQuery;
import org.apache.lucene.search.suggest.document.FuzzyCompletionQuery;
import org.apache.lucene.search.suggest.document.PrefixCompletionQuery;
import org.apache.lucene.search.suggest.document.RegexCompletionQuery;
import org.apache.lucene.search.suggest.document.SuggestField;
import org.opensearch.Version;
import org.opensearch.cluster.metadata.IndexMetadata;
import org.opensearch.core.common.ParsingException;
import org.opensearch.common.logging.DeprecationLogger;
import org.opensearch.common.unit.Fuzziness;
import org.opensearch.common.util.set.Sets;
import org.opensearch.core.xcontent.ToXContent;
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.core.xcontent.XContentParser.NumberType;
import org.opensearch.core.xcontent.XContentParser.Token;
import org.opensearch.index.analysis.AnalyzerScope;
import org.opensearch.index.analysis.NamedAnalyzer;
import org.opensearch.index.query.QueryShardContext;
import org.opensearch.search.lookup.SearchLookup;
import org.opensearch.search.suggest.completion.CompletionSuggester;
import org.opensearch.search.suggest.completion.context.ContextMapping;
import org.opensearch.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.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Mapper for completion field. The field values are indexed as a weighted FST for
* fast auto-completion/search-as-you-type functionality.
*
* Type properties:
*
* - "analyzer": "simple", (default)
* - "search_analyzer": "simple", (default)
* - "preserve_separators" : true, (default)
* - "preserve_position_increments" : true (default)
* - "min_input_length": 50 (default)
* - "contexts" : CONTEXTS
*
* see {@link ContextMappings#load(Object, Version)} for CONTEXTS
* see {@link #parse(ParseContext)} for acceptable inputs for indexing
*
* This field type constructs completion queries that are run
* against the weighted FST index by the {@link CompletionSuggester}.
* This field can also be extended to add search criteria to suggestions
* for query-time filtering and boosting (see {@link ContextMappings}
*
* @opensearch.internal
*/
public class CompletionFieldMapper extends ParametrizedFieldMapper {
public static final String CONTENT_TYPE = "completion";
/**
* Maximum allowed number of completion contexts in a mapping.
*/
static final int COMPLETION_CONTEXTS_LIMIT = 10;
@Override
public ParametrizedFieldMapper.Builder getMergeBuilder() {
return new Builder(simpleName(), defaultAnalyzer, indexVersionCreated).init(this);
}
/**
* Default parameters
*
* @opensearch.internal
*/
public static class Defaults {
public static final FieldType FIELD_TYPE = new FieldType();
static {
FIELD_TYPE.setTokenized(true);
FIELD_TYPE.setStored(false);
FIELD_TYPE.setStoreTermVectors(false);
FIELD_TYPE.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS);
FIELD_TYPE.setOmitNorms(true);
FIELD_TYPE.freeze();
}
public static final boolean DEFAULT_PRESERVE_SEPARATORS = true;
public static final boolean DEFAULT_POSITION_INCREMENTS = true;
public static final int DEFAULT_MAX_INPUT_LENGTH = 50;
}
/**
* Parameter fields
*
* @opensearch.internal
*/
public static class Fields {
// 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}
*
* @opensearch.internal
*/
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
).alwaysSerialize();
private final Parameter preservePosInc = Parameter.boolParam(
"preserve_position_increments",
false,
m -> toType(m).preservePosInc,
Defaults.DEFAULT_POSITION_INCREMENTS
).alwaysSerialize();
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();
}, Objects::toString);
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).alwaysSerialize();
private final Parameter