All Downloads are FREE. Search and download functionalities are using the official Maven repository.

querqy.elasticsearch.DismaxSearchEngineRequestAdapter Maven / Gradle / Ivy

There is a newer version: 1.7.es8150.0
Show newest version
package querqy.elasticsearch;

import static querqy.lucene.PhraseBoosting.makePhraseFieldsBoostQuery;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.DelegatingAnalyzerWrapper;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentType;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.SearchExecutionContext;
import querqy.elasticsearch.infologging.ESInfoLoggingContext;
import querqy.elasticsearch.infologging.InfoLoggingSpecProvider;
import querqy.elasticsearch.query.BoostingQueries;
import querqy.elasticsearch.query.Generated;
import querqy.elasticsearch.query.InfoLoggingSpec;
import querqy.elasticsearch.query.PhraseBoosts;
import querqy.elasticsearch.query.QuerqyQueryBuilder;
import querqy.elasticsearch.query.QueryBuilderRawQuery;
import querqy.elasticsearch.query.Rewriter;
import querqy.elasticsearch.query.RewrittenQueries;
import querqy.infologging.InfoLogging;
import querqy.infologging.InfoLoggingContext;
import querqy.lucene.LuceneSearchEngineRequestAdapter;
import querqy.lucene.PhraseBoosting.PhraseBoostFieldParams;
import querqy.lucene.QuerySimilarityScoring;
import querqy.lucene.rewrite.SearchFieldsAndBoosting;
import querqy.lucene.rewrite.cache.TermQueryCache;
import querqy.model.QuerqyQuery;
import querqy.model.RawQuery;
import querqy.model.StringRawQuery;
import querqy.parser.QuerqyParser;
import querqy.rewrite.ContextAwareQueryRewriter;
import querqy.rewrite.QueryRewriter;
import querqy.rewrite.RewriteChain;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;


/**
 *  Rewriters will access params using prefix 'querqy.<rewriter id>....
 */
public class DismaxSearchEngineRequestAdapter implements LuceneSearchEngineRequestAdapter, InfoLoggingSpecProvider {

    private final RewriteChain rewriteChain;
    private final SearchExecutionContext shardContext;
    final ESInfoLoggingContext infoLoggingContext;
    private final QuerqyQueryBuilder queryBuilder;
    private final Map context = new HashMap<>();

    public DismaxSearchEngineRequestAdapter(final QuerqyQueryBuilder queryBuilder,
                                            final RewriteChain rewriteChain,
                                            final SearchExecutionContext shardContext,
                                            final InfoLogging infoLogging) {
        this.shardContext = shardContext;
        this.rewriteChain = rewriteChain;
        this.queryBuilder = queryBuilder;
        this.infoLoggingContext = (infoLogging != null) ? new ESInfoLoggingContext(infoLogging, this) : null;
    }

    /**
     * 

Get the query string that should be parsed into the main query.

* *

Must be neither null or nor empty.

* * @return The query string. */ @Override public String getQueryString() { return queryBuilder.getMatchingQuery().getQueryString(); } /** *

Does this query string mean 'match all documents'?

* * @param queryString The query string. * @return true if the query string means 'match all documents' and false otherwise */ @Override public boolean isMatchAllQuery(final String queryString) { // TODO: do we want this? return false; } /** *

Should the query results be scored?

* *

This should return false for filter queries. If this method returns false, no boost queries will be used * (neither those from Querqy query rewriting nor those that were passed in request parameters).

* * @return true if the query results should be scored and false otherwise */ @Override public boolean needsScores() { // TODO: should we allow to switch this off? return true; } /** *

Get the analyzer for applying text analysis to query terms.

* *

This will normally be an {@link Analyzer} that delegates to other Analyzers based on the given query fields.

* * @return The query analyzer. */ @Override public Analyzer getQueryAnalyzer() { return new MapperAnalyzerWrapper(mappedFieldType -> mappedFieldType.getTextSearchInfo().getSearchAnalyzer()); } /** * Get an optional {@link TermQueryCache} * * @return The optional TermQueryCache */ @Override public Optional getTermQueryCache() { // TODO return Optional.empty(); } /** *

Should Querqy boost queries be added to the main query? - yes, as we will not have control over * re-rank queries from within the query builder.

* * @return Always true */ @Override public boolean addQuerqyBoostQueriesToMainQuery() { return true; } @Override public Optional getUserQuerySimilarityScoring() { return queryBuilder.getMatchingQuery().getSimilarityScoring(); } @Override public Optional getBoostQuerySimilarityScoring() { final BoostingQueries boostingQueries = queryBuilder.getBoostingQueries(); if (boostingQueries == null) { return Optional.empty(); } return boostingQueries.getRewrittenQueries().map(RewrittenQueries::getSimilarityScoring); } /** *

Get the query fields and their weights for the query entered by the user.

* * @return A map of field names and field boost factors. * @see #getGeneratedQueryFieldsAndBoostings() */ @Override public Map getQueryFieldsAndBoostings() { return queryBuilder.getQueryFieldsAndBoostings(); } /** *

Get the query fields and their weights for queries that were generated during query rewriting.

* *

If this method returns an empty map, the map returned by {@link #getQueryFieldsAndBoostings()} will also be * used for generated queries.

* * @return A map of field names and field boost factors, or an empty map. * @see #useFieldBoostingInQuerqyBoostQueries() */ @Override public Map getGeneratedQueryFieldsAndBoostings() { return queryBuilder.getGenerated().map(Generated::getQueryFieldsAndBoostings).orElse(Collections.emptyMap()); } @Override public Optional createQuerqyParser() { // TODO return Optional.empty(); } @Override public boolean useFieldBoostingInQuerqyBoostQueries() { final BoostingQueries boostingQueries = queryBuilder.getBoostingQueries(); if (boostingQueries == null) { return true; } return boostingQueries.getRewrittenQueries().map(RewrittenQueries::isUseFieldBoosts).orElse(true); } @Override public Optional getTiebreaker() { return queryBuilder.getTieBreaker(); } /** *

Apply the 'minimum should match' setting of the request.

*

It will be the responsibility of the LuceneSearchEngineRequestAdapter implementation to derive the * 'minimum should match' setting from request parameters or other configuration.

*

The query parameter is the rewritten user query. {@link QueryRewriter}s shall guarantee to * preserve the number of top-level query clauses at query rewriting.

* * @param query The parsed and rewritten user query. * @return The query after application of 'minimum should match' * @see BooleanQuery#getMinimumNumberShouldMatch() */ @Override public Query applyMinimumShouldMatch(final BooleanQuery query) { return Queries.applyMinimumShouldMatch(query, queryBuilder.getMinimumShouldMatch()); } /** * Get the weight to be multiplied with the main Querqy query (the query entered by the user). * * @return An optional weight for the main query */ @Override public Optional getUserQueryWeight() { return queryBuilder.getMatchingQuery().getWeight(); } @Override public Optional getGeneratedFieldBoost() { final Optional generated = queryBuilder.getGenerated(); if (generated.isPresent()) { return generated.get().getFieldBoostFactor(); } else { return Optional.empty(); } } @Override public Optional getPositiveQuerqyBoostWeight() { final BoostingQueries boostingQueries = queryBuilder.getBoostingQueries(); if (boostingQueries == null) { return Optional.empty(); } return boostingQueries.getRewrittenQueries().map(RewrittenQueries::getPositiveWeight); } @Override public Optional getNegativeQuerqyBoostWeight() { final BoostingQueries boostingQueries = queryBuilder.getBoostingQueries(); if (boostingQueries == null) { return Optional.empty(); } return boostingQueries.getRewrittenQueries().map(RewrittenQueries::getNegativeWeight); } /** *

Get the list of boost queries whose scores should be added to the score of the main query.

*

The queries are not a result of query rewriting but queries that may have been added as request parameters * (like 'bq' in Solr's Dismax query parser).

* * @param userQuery The user query parsed into a {@link QuerqyQuery} * @return The list of additive boost queries or an empty list if no such query exists. * @throws SyntaxException if a multiplicative boost query could not be parsed */ @Override public List getAdditiveBoosts(final QuerqyQuery userQuery) throws SyntaxException { //final PhraseBoosts phraseBoosts = queryBuilder.getPhraseBoosts(); final BoostingQueries boostingQueries = queryBuilder.getBoostingQueries(); if (boostingQueries != null) { final PhraseBoosts phraseBoosts = boostingQueries.getPhraseBoosts(); if (phraseBoosts != null) { final List boosts = new ArrayList<>(1); final List phraseBoostFieldParams = phraseBoosts.toPhraseBoostFieldParams(); if (phraseBoostFieldParams == null || phraseBoostFieldParams.isEmpty()) { return boosts; } makePhraseFieldsBoostQuery(userQuery, phraseBoostFieldParams, phraseBoosts.getTieBreaker(), getQueryAnalyzer()).ifPresent(boosts::add); return boosts; } } return null; } /** *

Get the list of boost queries whose scores should be multiplied to the score of the main query.

*

The queries are * not a result of query rewriting but queries that may have been added as request parameters (like 'boost' * in Solr's Extended Dismax query parser).

* * @param userQuery The user query parsed into a {@link QuerqyQuery} * @return The list of multiplicative boost queries or an empty list if no such query exists. * @throws SyntaxException if a multiplicative boost query could not be parsed */ @Override public List getMultiplicativeBoosts(final QuerqyQuery userQuery) throws SyntaxException { return null; } @Override public Optional parseRankQuery() throws SyntaxException { return Optional.empty(); } /** *

Parse a {@link RawQuery}. The RawQuery must be of type {@link QueryBuilderRawQuery} or {@link StringRawQuery}.

* * @param rawQuery The raw query. * @return The Query parsed from the RawQuery. * @throws SyntaxException @throws SyntaxException if the raw query query could not be parsed */ @Override public Query parseRawQuery(final RawQuery rawQuery) throws SyntaxException { try { if (rawQuery instanceof QueryBuilderRawQuery) { return ((QueryBuilderRawQuery) rawQuery).getQueryBuilder().toQuery(shardContext); } if (rawQuery instanceof StringRawQuery) { final XContentParser parser = XContentHelper.createParser(shardContext.getXContentRegistry(), null, new BytesArray(((StringRawQuery) rawQuery).getQueryString()), XContentType.JSON); return shardContext.parseInnerQueryBuilder(parser).toQuery(shardContext); } throw new IllegalArgumentException("Cannot handle RawQuery of type "+ rawQuery.getClass().getName()); } catch (final IOException e) { throw new SyntaxException("Error parsing raw query", e); } } @Override public Optional getFieldBoostModel() { return queryBuilder.getFieldBoostModel(); } /** *

Get the rewrite chain to be applied to the user query.

* * @return The rewrite chain. */ @Override public RewriteChain getRewriteChain() { return rewriteChain; } /** *

Get a map to hold context information while rewriting the query.

* * @return A non-null context map. * @see ContextAwareQueryRewriter */ @Override public Map getContext() { return context; } /** * Get request parameter as String * * @param name the parameter name * @return the optional parameter value */ @Override public Optional getRequestParam(final String name) { return getParam(name); } Optional getParam(final String name) { final String[] parts = name.split("\\."); if (parts.length < 3 || !"querqy".equals(parts[0])) { return Optional.empty(); } final String rewriterId = parts[1]; for (final Rewriter rewriter : queryBuilder.getRewriters()) { if (rewriterId.equals(rewriter.getName())) { return getRewriterParam(rewriter, Arrays.copyOfRange(parts, 2, parts.length)); } } return Optional.empty(); } Optional getRewriterParam(final Rewriter rewriter, final String[] path) { final Map params = rewriter.getParams(); if (params == null) { return Optional.empty(); } Map current = params; final int len = path.length - 1; for (int i = 0; i < len; i++) { current = (Map) current.get(path[i]); if (current == null) { return Optional.empty(); } } return Optional.ofNullable((T) current.get(path[len])); } /** * Get request parameter as an array of Strings * * @param name the parameter name * @return the parameter value String array (String[0] if not set) */ @Override public String[] getRequestParams(final String name) { final String[] parts = name.split("\\."); if (parts.length < 3 || !"querqy".equals(parts[0])) { return new String[0]; } final String rewriterId = parts[1]; for (final Rewriter rewriter : queryBuilder.getRewriters()) { if (rewriterId.equals(rewriter.getName())) { final Map params = rewriter.getParams(); if (params == null) { return new String[0]; } Map current = params; final int len = parts.length - 1; for (int i = 2; i < len; i++) { current = ( Map) current.get(parts[i]); if (current == null) { return new String[0]; } } final Object obj = current.get(parts[len]); if (obj == null) { return new String[0]; } if (obj instanceof String) { return new String[] {obj.toString()}; } else { return (String[]) obj; } } } return new String[0]; } /** * Get request parameter as Boolean * * @param name the parameter name * @return the optional parameter value */ @Override public Optional getBooleanRequestParam(final String name) { return getParam(name); } /** * Get request parameter as Integer * * @param name the parameter name * @return the optional parameter value */ @Override public Optional getIntegerRequestParam(final String name) { return getParam(name); } /** * Get request parameter as Float * * @param name the parameter name * @return the optional parameter value */ @Override public Optional getFloatRequestParam(final String name) { return getParam(name); } /** * Get request parameter as Double * * @param name the parameter name * @return the optional parameter value */ @Override public Optional getDoubleRequestParam(final String name) { return getParam(name); } /** *

Get the per-request info logging. Return an empty option if logging hasn't been configured or was disabled * for this request.

* * @return the InfoLoggingContext object */ @Override public Optional getInfoLoggingContext() { return Optional.ofNullable(infoLoggingContext); } /** *

Should debug information be collected while rewriting the query?

*

Debug information will be kept in the context map under the * {@link querqy.rewrite.AbstractLoggingRewriter#CONTEXT_KEY_DEBUG_DATA} key.

* * @return true if debug information shall be collected, false otherwise * @see #getContext() */ @Override public boolean isDebugQuery() { return false; } public SearchExecutionContext getSearchExecutionContext() { return shardContext; } @Override public Optional getInfoLoggingSpec() { return infoLoggingContext != null ? Optional.ofNullable(queryBuilder.getInfoLoggingSpec()) : Optional.empty(); } class MapperAnalyzerWrapper extends DelegatingAnalyzerWrapper { private final Function analyzerProvider; MapperAnalyzerWrapper(final Function analyzerProvider) { super(Analyzer.PER_FIELD_REUSE_STRATEGY); this.analyzerProvider = analyzerProvider; } @Override protected Analyzer getWrappedAnalyzer(final String fieldName) { final MappedFieldType fieldType = shardContext.getFieldType(fieldName); if (fieldType != null) { final Analyzer analyzer = analyzerProvider.apply(fieldType); if (analyzer != null) { return analyzer; } } return shardContext.getIndexAnalyzers().getDefaultSearchAnalyzer(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy