| /* |
| * 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)); |
| } |
| } |