/* * 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. */ package org.elasticsearch.index.query; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.index.IndexReader; import org.apache.lucene.queryparser.classic.MapperQueryParser; import org.apache.lucene.queryparser.classic.QueryParserSettings; import org.apache.lucene.search.Query; import org.apache.lucene.search.join.BitSetProducer; import org.apache.lucene.search.similarities.Similarity; import org.apache.lucene.util.SetOnce; import org.elasticsearch.Version; import org.elasticsearch.client.Client; import org.elasticsearch.common.CheckedFunction; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.analysis.IndexAnalyzers; import org.elasticsearch.index.cache.bitset.BitsetFilterCache; import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexFieldDataService; import org.elasticsearch.index.mapper.ContentPath; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.Mapper; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.ObjectMapper; import org.elasticsearch.index.mapper.TextFieldMapper; import org.elasticsearch.index.query.support.NestedScope; import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.script.CompiledScript; import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.SearchScript; import org.elasticsearch.search.lookup.SearchLookup; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.function.Function; import java.util.function.LongSupplier; import static java.util.Collections.unmodifiableMap; /** * Context object used to create lucene queries on the shard level. */ public class QueryShardContext extends QueryRewriteContext { private final MapperService mapperService; private final SimilarityService similarityService; private final BitsetFilterCache bitsetFilterCache; private final IndexFieldDataService indexFieldDataService; private final IndexSettings indexSettings; private final int shardId; private String[] types = Strings.EMPTY_ARRAY; private boolean cachable = true; private final SetOnce<Boolean> frozen = new SetOnce<>(); public void setTypes(String... types) { this.types = types; } public String[] getTypes() { return types; } private final Map<String, Query> namedQueries = new HashMap<>(); private final MapperQueryParser queryParser = new MapperQueryParser(this); private boolean allowUnmappedFields; private boolean mapUnmappedFieldAsString; private NestedScope nestedScope; private boolean isFilter; public QueryShardContext(int shardId, IndexSettings indexSettings, BitsetFilterCache bitsetFilterCache, IndexFieldDataService indexFieldDataService, MapperService mapperService, SimilarityService similarityService, ScriptService scriptService, NamedXContentRegistry xContentRegistry, Client client, IndexReader reader, LongSupplier nowInMillis) { super(indexSettings, mapperService, scriptService, xContentRegistry, client, reader, nowInMillis); this.shardId = shardId; this.indexSettings = indexSettings; this.similarityService = similarityService; this.mapperService = mapperService; this.bitsetFilterCache = bitsetFilterCache; this.indexFieldDataService = indexFieldDataService; this.allowUnmappedFields = indexSettings.isDefaultAllowUnmappedFields(); this.nestedScope = new NestedScope(); } public QueryShardContext(QueryShardContext source) { this(source.shardId, source.indexSettings, source.bitsetFilterCache, source.indexFieldDataService, source.mapperService, source.similarityService, source.scriptService, source.getXContentRegistry(), source.client, source.reader, source.nowInMillis); this.types = source.getTypes(); } private void reset() { allowUnmappedFields = indexSettings.isDefaultAllowUnmappedFields(); this.lookup = null; this.namedQueries.clear(); this.nestedScope = new NestedScope(); this.isFilter = false; } public IndexAnalyzers getIndexAnalyzers() { return mapperService.getIndexAnalyzers(); } public Similarity getSearchSimilarity() { return similarityService != null ? similarityService.similarity(mapperService) : null; } public String defaultField() { return indexSettings.getDefaultField(); } public boolean queryStringLenient() { return indexSettings.isQueryStringLenient(); } public boolean queryStringAnalyzeWildcard() { return indexSettings.isQueryStringAnalyzeWildcard(); } public boolean queryStringAllowLeadingWildcard() { return indexSettings.isQueryStringAllowLeadingWildcard(); } public MapperQueryParser queryParser(QueryParserSettings settings) { queryParser.reset(settings); return queryParser; } public BitSetProducer bitsetFilter(Query filter) { return bitsetFilterCache.getBitSetProducer(filter); } public <IFD extends IndexFieldData<?>> IFD getForField(MappedFieldType mapper) { return indexFieldDataService.getForField(mapper); } public void addNamedQuery(String name, Query query) { if (query != null) { namedQueries.put(name, query); } } public Map<String, Query> copyNamedQueries() { // This might be a good use case for CopyOnWriteHashMap return unmodifiableMap(new HashMap<>(namedQueries)); } /** * Return whether we are currently parsing a filter or a query. */ public boolean isFilter() { return isFilter; } /** * Public for testing only! * * Sets whether we are currently parsing a filter or a query */ public void setIsFilter(boolean isFilter) { this.isFilter = isFilter; } /** * Returns all the fields that match a given pattern. If prefixed with a * type then the fields will be returned with a type prefix. */ public Collection<String> simpleMatchToIndexNames(String pattern) { return mapperService.simpleMatchToIndexNames(pattern); } public MappedFieldType fieldMapper(String name) { return failIfFieldMappingNotFound(name, mapperService.fullName(name)); } public ObjectMapper getObjectMapper(String name) { return mapperService.getObjectMapper(name); } /** * Returns s {@link DocumentMapper} instance for the given type. * Delegates to {@link MapperService#documentMapper(String)} */ public DocumentMapper documentMapper(String type) { return mapperService.documentMapper(type); } /** * Gets the search analyzer for the given field, or the default if there is none present for the field * TODO: remove this by moving defaults into mappers themselves */ public Analyzer getSearchAnalyzer(MappedFieldType fieldType) { if (fieldType.searchAnalyzer() != null) { return fieldType.searchAnalyzer(); } return getMapperService().searchAnalyzer(); } /** * Gets the search quote analyzer for the given field, or the default if there is none present for the field * TODO: remove this by moving defaults into mappers themselves */ public Analyzer getSearchQuoteAnalyzer(MappedFieldType fieldType) { if (fieldType.searchQuoteAnalyzer() != null) { return fieldType.searchQuoteAnalyzer(); } return getMapperService().searchQuoteAnalyzer(); } public void setAllowUnmappedFields(boolean allowUnmappedFields) { this.allowUnmappedFields = allowUnmappedFields; } public void setMapUnmappedFieldAsString(boolean mapUnmappedFieldAsString) { this.mapUnmappedFieldAsString = mapUnmappedFieldAsString; } MappedFieldType failIfFieldMappingNotFound(String name, MappedFieldType fieldMapping) { if (fieldMapping != null || allowUnmappedFields) { return fieldMapping; } else if (mapUnmappedFieldAsString) { TextFieldMapper.Builder builder = new TextFieldMapper.Builder(name); return builder.build(new Mapper.BuilderContext(indexSettings.getSettings(), new ContentPath(1))).fieldType(); } else { throw new QueryShardException(this, "No field mapping can be found for the field with name [{}]", name); } } /** * Returns the narrowed down explicit types, or, if not set, all types. */ public Collection<String> queryTypes() { String[] types = getTypes(); if (types == null || types.length == 0) { return getMapperService().types(); } if (types.length == 1 && types[0].equals("_all")) { return getMapperService().types(); } return Arrays.asList(types); } private SearchLookup lookup = null; public SearchLookup lookup() { if (lookup == null) { lookup = new SearchLookup(getMapperService(), indexFieldDataService, types); } return lookup; } public NestedScope nestedScope() { return nestedScope; } public Version indexVersionCreated() { return indexSettings.getIndexVersionCreated(); } public ParsedQuery toFilter(QueryBuilder queryBuilder) { return toQuery(queryBuilder, q -> { Query filter = q.toFilter(this); if (filter == null) { return null; } return filter; }); } public ParsedQuery toQuery(QueryBuilder queryBuilder) { return toQuery(queryBuilder, q -> { Query query = q.toQuery(this); if (query == null) { query = Queries.newMatchNoDocsQuery("No query left after rewrite."); } return query; }); } private ParsedQuery toQuery(QueryBuilder queryBuilder, CheckedFunction<QueryBuilder, Query, IOException> filterOrQuery) { reset(); try { QueryBuilder rewriteQuery = QueryBuilder.rewriteQuery(queryBuilder, this); return new ParsedQuery(filterOrQuery.apply(rewriteQuery), copyNamedQueries()); } catch(QueryShardException | ParsingException e ) { throw e; } catch(Exception e) { throw new QueryShardException(this, "failed to create query: {}", e, queryBuilder); } finally { reset(); } } public final Index index() { return indexSettings.getIndex(); } /** * Compiles (or retrieves from cache) and binds the parameters to the * provided script */ public final SearchScript getSearchScript(Script script, ScriptContext context) { failIfFrozen(); return scriptService.search(lookup(), script, context); } /** * Returns a lazily created {@link SearchScript} that is compiled immediately but can be pulled later once all * parameters are available. */ public final Function<Map<String, Object>, SearchScript> getLazySearchScript(Script script, ScriptContext context) { failIfFrozen(); CompiledScript compile = scriptService.compile(script, context); return (p) -> scriptService.search(lookup(), compile, p); } /** * Compiles (or retrieves from cache) and binds the parameters to the * provided script */ public final ExecutableScript getExecutableScript(Script script, ScriptContext context) { failIfFrozen(); CompiledScript compiledScript = scriptService.compile(script, context); return scriptService.executable(compiledScript, script.getParams()); } /** * Returns a lazily created {@link ExecutableScript} that is compiled immediately but can be pulled later once all * parameters are available. */ public final Function<Map<String, Object>, ExecutableScript> getLazyExecutableScript(Script script, ScriptContext context) { failIfFrozen(); CompiledScript executable = scriptService.compile(script, context); return (p) -> scriptService.executable(executable, p); } /** * if this method is called the query context will throw exception if methods are accessed * that could yield different results across executions like {@link #getTemplateBytes(Script)} */ public final void freezeContext() { this.frozen.set(Boolean.TRUE); } /** * This method fails if {@link #freezeContext()} is called before on this * context. This is used to <i>seal</i>. * * This methods and all methods that call it should be final to ensure that * setting the request as not cacheable and the freezing behaviour of this * class cannot be bypassed. This is important so we can trust when this * class says a request can be cached. */ protected final void failIfFrozen() { this.cachable = false; if (frozen.get() == Boolean.TRUE) { throw new IllegalArgumentException("features that prevent cachability are disabled on this context"); } else { assert frozen.get() == null : frozen.get(); } } @Override public final BytesReference getTemplateBytes(Script template) { failIfFrozen(); return super.getTemplateBytes(template); } /** * Returns <code>true</code> iff the result of the processed search request is cachable. Otherwise <code>false</code> */ public final boolean isCachable() { return cachable; } /** * Returns the shard ID this context was created for. */ public int getShardId() { return shardId; } @Override public final long nowInMillis() { failIfFrozen(); return super.nowInMillis(); } @Override public Client getClient() { failIfFrozen(); // we somebody uses a terms filter with lookup for instance can't be cached... return super.getClient(); } }