blob: 28bd3d1f9f529b106d5c0059173bcbbb072a584f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.search;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.Query;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.parser.QueryParser;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.common.params.DisMaxParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.util.SolrPluginUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
/**
* Query parser for dismax queries
* <p>
* <b>Note: This API is experimental and may change in non backward-compatible ways in the future</b>
*
*
*/
public class DisMaxQParser extends QParser {
/**
* A field we can't ever find in any schema, so we can safely tell DisjunctionMaxQueryParser to use it as our
* defaultField, and map aliases from it to any field in our schema.
*/
private static String IMPOSSIBLE_FIELD_NAME = "\uFFFC\uFFFC\uFFFC";
/**
* Applies the appropriate default rules for the "mm" param based on the
* effective value of the "q.op" param
*
* @see QueryParsing#OP
* @see DisMaxParams#MM
*/
public static String parseMinShouldMatch(final IndexSchema schema,
final SolrParams params) {
org.apache.solr.parser.QueryParser.Operator op = QueryParsing.parseOP(params.get(QueryParsing.OP));
return params.get(DisMaxParams.MM,
op.equals(QueryParser.Operator.AND) ? "100%" : "0%");
}
/**
* Uses {@link SolrPluginUtils#parseFieldBoosts(String)} with the 'qf' parameter. Falls back to the 'df' parameter
*/
public static Map<String, Float> parseQueryFields(final IndexSchema indexSchema, final SolrParams solrParams)
throws SyntaxError {
Map<String, Float> queryFields = SolrPluginUtils.parseFieldBoosts(solrParams.getParams(DisMaxParams.QF));
if (queryFields.isEmpty()) {
String df = solrParams.get(CommonParams.DF);
if (df == null) {
throw new SyntaxError("Neither "+DisMaxParams.QF+" nor "+CommonParams.DF +" are present.");
}
queryFields.put(df, 1.0f);
}
return queryFields;
}
public DisMaxQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
super(qstr, localParams, params, req);
}
protected Map<String, Float> queryFields;
protected Query parsedUserQuery;
protected String[] boostParams;
protected List<Query> boostQueries;
protected Query altUserQuery;
private boolean parsed = false;
@Override
public Query parse() throws SyntaxError {
parsed = true;
SolrParams solrParams = SolrParams.wrapDefaults(localParams, params);
queryFields = parseQueryFields(req.getSchema(), solrParams);
/* the main query we will execute. we disable the coord because
* this query is an artificial construct
*/
BooleanQuery.Builder query = new BooleanQuery.Builder();
boolean notBlank = addMainQuery(query, solrParams);
if (!notBlank)
return null;
addBoostQuery(query, solrParams);
addBoostFunctions(query, solrParams);
return QueryUtils.build(query, this);
}
protected void addBoostFunctions(BooleanQuery.Builder query, SolrParams solrParams) throws SyntaxError {
String[] boostFuncs = solrParams.getParams(DisMaxParams.BF);
if (null != boostFuncs && 0 != boostFuncs.length) {
for (String boostFunc : boostFuncs) {
if (null == boostFunc || "".equals(boostFunc)) continue;
Map<String, Float> ff = SolrPluginUtils.parseFieldBoosts(boostFunc);
for (Map.Entry<String, Float> entry : ff.entrySet()) {
Query fq = subQuery(entry.getKey(), FunctionQParserPlugin.NAME).getQuery();
Float b = entry.getValue();
if (null != b) {
fq = new BoostQuery(fq, b);
}
query.add(fq, BooleanClause.Occur.SHOULD);
}
}
}
}
protected void addBoostQuery(BooleanQuery.Builder query, SolrParams solrParams) throws SyntaxError {
boostParams = solrParams.getParams(DisMaxParams.BQ);
//List<Query> boostQueries = SolrPluginUtils.parseQueryStrings(req, boostParams);
boostQueries = null;
if (boostParams != null && boostParams.length > 0) {
boostQueries = new ArrayList<>();
for (String qs : boostParams) {
if (qs.trim().length() == 0) continue;
Query q = subQuery(qs, null).getQuery();
boostQueries.add(q);
}
}
if (null != boostQueries) {
if (1 == boostQueries.size() && 1 == boostParams.length) {
/* legacy logic */
Query f = boostQueries.get(0);
while (f instanceof BoostQuery) {
BoostQuery bq = (BoostQuery) f;
if (bq .getBoost() == 1f) {
f = bq.getQuery();
} else {
break;
}
}
if (f instanceof BooleanQuery) {
/* if the default boost was used, and we've got a BooleanQuery
* extract the subqueries out and use them directly
*/
for (Object c : ((BooleanQuery) f).clauses()) {
query.add((BooleanClause) c);
}
} else {
query.add(f, BooleanClause.Occur.SHOULD);
}
} else {
for (Query f : boostQueries) {
query.add(f, BooleanClause.Occur.SHOULD);
}
}
}
}
/** Adds the main query to the query argument. If it's blank then false is returned. */
protected boolean addMainQuery(BooleanQuery.Builder query, SolrParams solrParams) throws SyntaxError {
Map<String, Float> phraseFields = SolrPluginUtils.parseFieldBoosts(solrParams.getParams(DisMaxParams.PF));
float tiebreaker = solrParams.getFloat(DisMaxParams.TIE, 0.0f);
/* a parser for dealing with user input, which will convert
* things to DisjunctionMaxQueries
*/
SolrPluginUtils.DisjunctionMaxQueryParser up = getParser(queryFields, DisMaxParams.QS, solrParams, tiebreaker);
/* for parsing sloppy phrases using DisjunctionMaxQueries */
SolrPluginUtils.DisjunctionMaxQueryParser pp = getParser(phraseFields, DisMaxParams.PS, solrParams, tiebreaker);
/* * * Main User Query * * */
parsedUserQuery = null;
String userQuery = getString();
altUserQuery = null;
if (StringUtils.isBlank(userQuery)) {
// If no query is specified, we may have an alternate
altUserQuery = getAlternateUserQuery(solrParams);
if (altUserQuery == null)
return false;
query.add(altUserQuery, BooleanClause.Occur.MUST);
} else {
// There is a valid query string
userQuery = SolrPluginUtils.partialEscape(SolrPluginUtils.stripUnbalancedQuotes(userQuery)).toString();
userQuery = SolrPluginUtils.stripIllegalOperators(userQuery).toString();
parsedUserQuery = getUserQuery(userQuery, up, solrParams);
query.add(parsedUserQuery, BooleanClause.Occur.MUST);
Query phrase = getPhraseQuery(userQuery, pp);
if (null != phrase) {
query.add(phrase, BooleanClause.Occur.SHOULD);
}
}
return true;
}
protected Query getAlternateUserQuery(SolrParams solrParams) throws SyntaxError {
String altQ = solrParams.get(DisMaxParams.ALTQ);
if (altQ != null) {
QParser altQParser = subQuery(altQ, null);
return altQParser.getQuery();
} else {
return null;
}
}
protected Query getPhraseQuery(String userQuery, SolrPluginUtils.DisjunctionMaxQueryParser pp) throws SyntaxError {
/* * * Add on Phrases for the Query * * */
/* build up phrase boosting queries */
/* if the userQuery already has some quotes, strip them out.
* we've already done the phrases they asked for in the main
* part of the query, this is to boost docs that may not have
* matched those phrases but do match looser phrases.
*/
String userPhraseQuery = userQuery.replace("\"", "");
return pp.parse("\"" + userPhraseQuery + "\"");
}
protected Query getUserQuery(String userQuery, SolrPluginUtils.DisjunctionMaxQueryParser up, SolrParams solrParams)
throws SyntaxError {
String minShouldMatch = parseMinShouldMatch(req.getSchema(), solrParams);
Query dis = up.parse(userQuery);
Query query = dis;
if (dis instanceof BooleanQuery) {
BooleanQuery.Builder t = new BooleanQuery.Builder();
SolrPluginUtils.flattenBooleanQuery(t, (BooleanQuery) dis);
boolean mmAutoRelax = params.getBool(DisMaxParams.MM_AUTORELAX, false);
SolrPluginUtils.setMinShouldMatch(t, minShouldMatch, mmAutoRelax);
query = QueryUtils.build(t, this);
}
return query;
}
protected SolrPluginUtils.DisjunctionMaxQueryParser getParser(Map<String, Float> fields, String paramName,
SolrParams solrParams, float tiebreaker) {
int slop = solrParams.getInt(paramName, 0);
SolrPluginUtils.DisjunctionMaxQueryParser parser = new SolrPluginUtils.DisjunctionMaxQueryParser(this,
IMPOSSIBLE_FIELD_NAME);
parser.addAlias(IMPOSSIBLE_FIELD_NAME, tiebreaker, fields);
parser.setPhraseSlop(slop);
parser.setSplitOnWhitespace(true);
return parser;
}
@Override
public String[] getDefaultHighlightFields() {
return queryFields.keySet().toArray(new String[queryFields.keySet().size()]);
}
@Override
public Query getHighlightQuery() throws SyntaxError {
if (!parsed)
parse();
return parsedUserQuery == null ? altUserQuery : parsedUserQuery;
}
@Override
public void addDebugInfo(NamedList<Object> debugInfo) {
super.addDebugInfo(debugInfo);
debugInfo.add("altquerystring", altUserQuery);
if (null != boostQueries) {
debugInfo.add("boost_queries", boostParams);
debugInfo.add("parsed_boost_queries",
QueryParsing.toString(boostQueries, req.getSchema()));
}
debugInfo.add("boostfuncs", req.getParams().getParams(DisMaxParams.BF));
}
}