
org.opensearch.search.suggest.completion.context.ContextMappings Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of opensearch Show documentation
Show all versions of opensearch Show documentation
OpenSearch subproject :server
/*
* 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.search.suggest.completion.context;
import org.apache.lucene.search.suggest.document.CompletionQuery;
import org.apache.lucene.search.suggest.document.ContextQuery;
import org.apache.lucene.search.suggest.document.ContextSuggestField;
import org.apache.lucene.util.CharsRef;
import org.apache.lucene.util.CharsRefBuilder;
import org.opensearch.OpenSearchParseException;
import org.opensearch.Version;
import org.opensearch.common.xcontent.ToXContent;
import org.opensearch.common.xcontent.XContentBuilder;
import org.opensearch.index.mapper.CompletionFieldMapper;
import org.opensearch.index.mapper.DocumentMapperParser;
import org.opensearch.index.mapper.ParseContext;
import org.opensearch.search.suggest.completion.context.ContextMapping.Type;
import java.io.IOException;
import java.util.ArrayList;
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 java.util.stream.Collectors;
import static org.opensearch.search.suggest.completion.context.ContextMapping.FIELD_NAME;
import static org.opensearch.search.suggest.completion.context.ContextMapping.FIELD_TYPE;
/**
* ContextMappings indexes context-enabled suggestion fields
* and creates context queries for defined {@link ContextMapping}s
* for a {@link CompletionFieldMapper}
*/
public class ContextMappings implements ToXContent, Iterable> {
private final List> contextMappings;
private final Map> contextNameMap;
public ContextMappings(List> contextMappings) {
if (contextMappings.size() > 255) {
// we can support more, but max of 255 (1 byte) unique context types per suggest field
// seems reasonable?
throw new UnsupportedOperationException("Maximum of 10 context types are supported was: " + contextMappings.size());
}
this.contextMappings = contextMappings;
contextNameMap = new HashMap<>(contextMappings.size());
for (ContextMapping> mapping : contextMappings) {
contextNameMap.put(mapping.name(), mapping);
}
}
/**
* @return number of context mappings
* held by this instance
*/
public int size() {
return contextMappings.size();
}
/**
* Returns a context mapping by its name
*/
public ContextMapping> get(String name) {
ContextMapping> contextMapping = contextNameMap.get(name);
if (contextMapping == null) {
List keys = new ArrayList<>(contextNameMap.keySet());
Collections.sort(keys);
throw new IllegalArgumentException("Unknown context name [" + name + "], must be one of " + keys.toString());
}
return contextMapping;
}
/**
* Adds a context-enabled field for all the defined mappings to document
* see {@link org.opensearch.search.suggest.completion.context.ContextMappings.TypedContextField}
*/
public void addField(ParseContext.Document document, String name, String input, int weight, Map> contexts) {
document.add(new TypedContextField(name, input, weight, contexts, document));
}
@Override
public Iterator> iterator() {
return contextMappings.iterator();
}
/**
* Field prepends context values with a suggestion
* Context values are associated with a type, denoted by
* a type id, which is prepended to the context value.
*
* Every defined context mapping yields a unique type id (index of the
* corresponding context mapping in the context mappings list)
* for all its context values
*
* The type, context and suggestion values are encoded as follows:
*
* TYPE_ID | CONTEXT_VALUE | CONTEXT_SEP | SUGGESTION_VALUE
*
*
* Field can also use values of other indexed fields as contexts
* at index time
*/
private class TypedContextField extends ContextSuggestField {
private final Map> contexts;
private final ParseContext.Document document;
TypedContextField(String name, String value, int weight, Map> contexts, ParseContext.Document document) {
super(name, value, weight);
this.contexts = contexts;
this.document = document;
}
@Override
protected Iterable contexts() {
Set typedContexts = new HashSet<>();
final CharsRefBuilder scratch = new CharsRefBuilder();
scratch.grow(1);
for (int typeId = 0; typeId < contextMappings.size(); typeId++) {
scratch.setCharAt(0, (char) typeId);
scratch.setLength(1);
ContextMapping> mapping = contextMappings.get(typeId);
Set contexts = new HashSet<>(mapping.parseContext(document));
if (this.contexts.get(mapping.name()) != null) {
contexts.addAll(this.contexts.get(mapping.name()));
}
for (String context : contexts) {
scratch.append(context);
typedContexts.add(scratch.toCharsRef());
scratch.setLength(1);
}
}
if (typedContexts.isEmpty()) {
throw new IllegalArgumentException("Contexts are mandatory in context enabled completion field [" + name + "]");
}
return new ArrayList(typedContexts);
}
}
/**
* Wraps a {@link CompletionQuery} with context queries
*
* @param query base completion query to wrap
* @param queryContexts a map of context mapping name and collected query contexts
* @return a context-enabled query
*/
public ContextQuery toContextQuery(CompletionQuery query, Map> queryContexts) {
ContextQuery typedContextQuery = new ContextQuery(query);
boolean hasContext = false;
if (queryContexts.isEmpty() == false) {
CharsRefBuilder scratch = new CharsRefBuilder();
scratch.grow(1);
for (int typeId = 0; typeId < contextMappings.size(); typeId++) {
scratch.setCharAt(0, (char) typeId);
scratch.setLength(1);
ContextMapping> mapping = contextMappings.get(typeId);
List internalQueryContext = queryContexts.get(mapping.name());
if (internalQueryContext != null) {
for (ContextMapping.InternalQueryContext context : internalQueryContext) {
scratch.append(context.context);
typedContextQuery.addContext(scratch.toCharsRef(), context.boost, !context.isPrefix);
scratch.setLength(1);
hasContext = true;
}
}
}
}
if (hasContext == false) {
throw new IllegalArgumentException("Missing mandatory contexts in context query");
}
return typedContextQuery;
}
/**
* Maps an output context list to a map of context mapping names and their values
*
* see {@link org.opensearch.search.suggest.completion.context.ContextMappings.TypedContextField}
* @return a map of context names and their values
*
*/
public Map> getNamedContexts(List contexts) {
Map> contextMap = new HashMap<>(contexts.size());
for (CharSequence typedContext : contexts) {
int typeId = typedContext.charAt(0);
assert typeId < contextMappings.size() : "Returned context has invalid type";
ContextMapping> mapping = contextMappings.get(typeId);
Set contextEntries = contextMap.get(mapping.name());
if (contextEntries == null) {
contextEntries = new HashSet<>();
contextMap.put(mapping.name(), contextEntries);
}
contextEntries.add(typedContext.subSequence(1, typedContext.length()).toString());
}
return contextMap;
}
/**
* Loads {@link ContextMappings} from configuration
*
* Expected configuration:
* List of maps representing {@link ContextMapping}
* [{"name": .., "type": .., ..}, {..}]
*
*/
public static ContextMappings load(Object configuration, Version indexVersionCreated) throws OpenSearchParseException {
final List> contextMappings;
if (configuration instanceof List) {
contextMappings = new ArrayList<>();
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy