enables multi-param for several dismax params (SOLR-174)

git-svn-id: https://svn.apache.org/repos/asf/lucene/solr/trunk@514851 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Mike Klaas 2007-03-05 20:22:10 +00:00
parent cfb9f5f2f2
commit 25227a299d
7 changed files with 176 additions and 46 deletions

View File

@ -137,6 +137,12 @@ Changes in runtime behavior
IndexSchema have also been clarified.
(Erik Hatcher and hossman)
4. DisMaxRequestHandler's bq, bf, qf, and pf parameters can now accept
multiple values (klaas).
5. Query are re-written before highlighting is performed. This enables
proper highlighting of prefix and wildcard queries (klaas).
Optimizations
1. SOLR-114: HashDocSet specific implementations of union() and andNot()
for a 20x performance improvement for those set operations, and a new

View File

@ -73,6 +73,8 @@ import org.apache.solr.util.SolrPluginUtils;
* <li> qf - (Query Fields) fields and boosts to use when building
* DisjunctionMaxQueries from the users query. Format is:
* "<code>fieldA^1.0 fieldB^2.2</code>".
* This param can be specified multiple times, and the fields
* are additive.
* </li>
* <li> mm - (Minimum Match) this supports a wide variety of
* complex expressions.
@ -81,6 +83,8 @@ import org.apache.solr.util.SolrPluginUtils;
* <li> pf - (Phrase Fields) fields/boosts to make phrase queries out
* of, to boost the users query for exact matches on the specified fields.
* Format is: "<code>fieldA^1.0 fieldB^2.2</code>".
* This param can be specified multiple times, and the fields
* are additive.
* </li>
* <li> ps - (Phrase Slop) amount of slop on phrase queries built for pf
* fields.
@ -93,12 +97,19 @@ import org.apache.solr.util.SolrPluginUtils;
* with a default boost (1.0f), then the individual clauses will be
* added directly to the main query. Otherwise, the query will be
* included as is.
* This param can be specified multiple times, and the boosts are
* are additive. NOTE: the behaviour listed above is only in effect
* if a single <code>bq</code> paramter is specified. Hence you can
* disable it by specifying an additional, blank, <code>bq</code>
* parameter.
* </li>
* <li> bf - (Boost Functions) functions (with optional boosts) that will be
* included in the users query to influence the score.
* Format is: "<code>funcA(arg1,arg2)^1.2
* funcB(arg3,arg4)^2.2</code>". NOTE: Whitespace is not allowed
* in the function arguments.
* This param can be specified multiple times, and the functions
* are additive.
* </li>
* <li> fq - (Filter Query) a raw lucene query that can be used
* to restrict the super set of products we are interested in - more
@ -122,7 +133,6 @@ import org.apache.solr.util.SolrPluginUtils;
* <pre>
* :TODO: document facet param support
*
* :TODO: make bf,pf,qf multival params now that SolrParams supports them
* </pre>
*/
public class DisMaxRequestHandler extends RequestHandlerBase {
@ -173,8 +183,8 @@ public class DisMaxRequestHandler extends RequestHandlerBase {
SolrIndexSearcher s = req.getSearcher();
IndexSchema schema = req.getSchema();
Map<String,Float> queryFields = U.parseFieldBoosts(params.get(DMP.QF));
Map<String,Float> phraseFields = U.parseFieldBoosts(params.get(DMP.PF));
Map<String,Float> queryFields = U.parseFieldBoosts(params.getParams(DMP.QF));
Map<String,Float> phraseFields = U.parseFieldBoosts(params.getParams(DMP.PF));
float tiebreaker = params.getFloat(DMP.TIE, 0.0f);
@ -255,29 +265,39 @@ public class DisMaxRequestHandler extends RequestHandlerBase {
/* * * Boosting Query * * */
String boostQuery = params.get(DMP.BQ);
if (null != boostQuery && !boostQuery.equals("")) {
Query tmp = p.parse(boostQuery);
/* if the default boost was used, and we've got a BooleanQuery
* extract the subqueries out and use them directly
*/
if (1.0f == tmp.getBoost() && tmp instanceof BooleanQuery) {
for (BooleanClause c : ((BooleanQuery)tmp).getClauses()) {
query.add(c);
String[] boostParams = params.getParams(DMP.BQ);
List<Query> boostQueries = U.parseQueryStrings(req, boostParams);
if (null != boostQueries) {
if(1 == boostQueries.size() && 1 == boostParams.length) {
/* legacy logic */
Query f = boostQueries.get(0);
if (1.0f == f.getBoost() && f instanceof BooleanQuery) {
/* if the default boost was used, and we've got a BooleanQuery
* extract the subqueries out and use them directly
*/
for (BooleanClause c : ((BooleanQuery)f).getClauses()) {
query.add(c);
}
} else {
query.add(f, BooleanClause.Occur.SHOULD);
}
} else {
query.add(tmp, BooleanClause.Occur.SHOULD);
for(Query f : boostQueries) {
query.add(f, BooleanClause.Occur.SHOULD);
}
}
}
/* * * Boosting Functions * * */
String boostFunc = params.get(DMP.BF);
if (null != boostFunc && !boostFunc.equals("")) {
List<Query> funcs = U.parseFuncs(schema, boostFunc);
for (Query f : funcs) {
query.add(f, Occur.SHOULD);
String[] boostFuncs = params.getParams(DMP.BF);
if (null != boostFuncs && 0 != boostFuncs.length) {
for (String boostFunc : boostFuncs) {
if(null == boostFunc || "".equals(boostFunc)) continue;
List<Query> funcs = U.parseFuncs(schema, boostFunc);
for (Query f : funcs) {
query.add(f, Occur.SHOULD);
}
}
}
@ -318,22 +338,23 @@ public class DisMaxRequestHandler extends RequestHandlerBase {
NamedList debug = U.doStandardDebug(req, userQuery, query, results.docList);
if (null != debug) {
debug.add("altquerystring", altUserQuery);
debug.add("boostquery", boostQuery);
debug.add("boostfunc", boostFunc);
if (null != boostQueries) {
debug.add("boost_queries", boostParams);
debug.add("parsed_boost_queries",
QueryParsing.toString(boostQueries, req.getSchema()));
}
debug.add("boostfuncs", params.getParams(DMP.BF));
if (null != restrictions) {
debug.add("filter_queries", params.getParams(FQ));
List<String> fqs = new ArrayList<String>(restrictions.size());
for (Query fq : restrictions) {
fqs.add(QueryParsing.toString(fq, req.getSchema()));
}
debug.add("parsed_filter_queries",fqs);
debug.add("parsed_filter_queries",
QueryParsing.toString(restrictions, req.getSchema()));
}
rsp.add("debug", debug);
}
} catch (Exception e) {
SolrException.logOnce(SolrCore.log,
"Exception durring debug", e);
"Exception during debug", e);
rsp.add("exception_during_debug", SolrException.toStr(e));
}
@ -341,8 +362,11 @@ public class DisMaxRequestHandler extends RequestHandlerBase {
if(HighlightingUtils.isHighlightingEnabled(req) && parsedUserQuery != null) {
String[] highFields = queryFields.keySet().toArray(new String[0]);
NamedList sumData =
HighlightingUtils.doHighlighting(results.docList, parsedUserQuery,
req, highFields);
HighlightingUtils.doHighlighting(
results.docList,
parsedUserQuery.rewrite(req.getSearcher().getReader()),
req,
highFields);
if(sumData != null)
rsp.add("highlighting", sumData);
}

View File

@ -138,9 +138,9 @@ public class StandardRequestHandler extends RequestHandlerBase {
SolrException.logOnce(SolrCore.log, "Exception during debug", e);
rsp.add("exception_during_debug", SolrException.toStr(e));
}
NamedList sumData = HighlightingUtils.doHighlighting(
results.docList, query, req, new String[]{defaultField});
results.docList, query.rewrite(req.getSearcher().getReader()), req, new String[]{defaultField});
if(sumData != null)
rsp.add("highlighting", sumData);
}

View File

@ -31,6 +31,7 @@ import org.apache.solr.schema.FieldType;
import org.apache.solr.request.SolrParams;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.logging.Level;
import java.io.IOException;
@ -469,6 +470,16 @@ public class QueryParsing {
}
/**
* Builds a list of String which are stringified versions of a list of Queries
*/
public static List<String> toString(List<Query> queries, IndexSchema schema) {
List<String> out = new ArrayList<String>(queries.size());
for (Query q : queries) {
out.add(QueryParsing.toString(q, schema));
}
return out;
}
private static ValueSource parseValSource(StrParser sp, IndexSchema schema) throws ParseException {
String id = sp.getId();

View File

@ -483,20 +483,31 @@ public class SolrPluginUtils {
* @return Map of fieldOne =&gt; 2.3, fieldTwo =&gt; null, fieldThree =&gt; -0.4
*/
public static Map<String,Float> parseFieldBoosts(String in) {
if (null == in || "".equals(in.trim())) {
return parseFieldBoosts(new String[]{in});
}
/**
* Like <code>parseFieldBoosts(String)</code>, but parses all the strings
* in the provided array (which may be null).
*
* @param fieldList an array of Strings eg. <code>{"fieldOne^2.3", "fieldTwo"}</code>
* @return Map of fieldOne =&gt; 2.3, fieldThree =&gt; -0.4
*/
public static Map<String,Float> parseFieldBoosts(String[] fieldLists) {
if (null == fieldLists || 0 == fieldLists.length) {
return new HashMap<String,Float>();
}
String[] bb = in.trim().split("\\s+");
Map<String, Float> out = new HashMap<String,Float>(7);
for (String s : bb) {
String[] bbb = s.split("\\^");
out.put(bbb[0], 1 == bbb.length ? null : Float.valueOf(bbb[1]));
for (String in : fieldLists) {
if (null == in || "".equals(in.trim()))
continue;
String[] bb = in.trim().split("\\s+");
for (String s : bb) {
String[] bbb = s.split("\\^");
out.put(bbb[0], 1 == bbb.length ? null : Float.valueOf(bbb[1]));
}
}
return out;
}
/**
* Given a string containing functions with optional boosts, returns
* an array of Queries representing those functions with the specified
@ -804,10 +815,16 @@ public class SolrPluginUtils {
* @return null if no filter queries
*/
public static List<Query> parseFilterQueries(SolrQueryRequest req) throws ParseException {
String[] in = req.getParams().getParams(SolrParams.FQ);
if (null == in || 0 == in.length) return null;
return parseQueryStrings(req, req.getParams().getParams(SolrParams.FQ));
}
/** Turns an array of query strings into a List of Query objects.
*
* @return null if no queries are generated
*/
public static List<Query> parseQueryStrings(SolrQueryRequest req,
String[] queries) throws ParseException {
if (null == queries || 0 == queries.length) return null;
List<Query> out = new LinkedList<Query>();
SolrIndexSearcher s = req.getSearcher();
/* Ignore SolrParams.DF - could have init param FQs assuming the
@ -815,7 +832,7 @@ public class SolrPluginUtils {
* If user doesn't want schema default, they should be explicit in the FQ.
*/
SolrQueryParser qp = new SolrQueryParser(s.getSchema(), null);
for (String q : in) {
for (String q : queries) {
if (null != q && 0 != q.trim().length()) {
out.add(qp.parse(q));
}

View File

@ -29,6 +29,7 @@ import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.HashMap;
import java.util.regex.Pattern;
/**
* Tests some basic functionality of the DisMaxRequestHandler
@ -46,8 +47,8 @@ public class DisMaxRequestHandlerTest extends AbstractSolrTestCase {
"facet.field","t_s"
);
}
public void testSomeStuff() throws Exception {
/** Add some documents to the index */
protected void populate() {
assertU(adoc("id", "666",
"features_t", "cool and scary stuff",
"subject", "traveling in hell",
@ -77,7 +78,11 @@ public class DisMaxRequestHandlerTest extends AbstractSolrTestCase {
"weight", "97.3",
"iind", "8675309"));
assertU(commit());
}
public void testSomeStuff() throws Exception {
populate();
assertQ("basic match",
req("guide")
,"//*[@numFound='2']"
@ -94,6 +99,42 @@ public class DisMaxRequestHandlerTest extends AbstractSolrTestCase {
,"//result/doc[2]/int[@name='id'][.='666']"
,"//result/doc[3]/int[@name='id'][.='8675309']"
);
assertQ("multi qf",
req("q", "cool"
,"qt", "dismax"
,"version", "2.0"
,"qf", "subject"
,"qf", "features_t"
)
,"//*[@numFound='3']"
);
assertQ("boost query",
req("q", "cool stuff"
,"qt", "dismax"
,"version", "2.0"
,"bq", "subject:hell^400"
)
,"//*[@numFound='3']"
,"//result/doc[1]/int[@name='id'][.='666']"
,"//result/doc[2]/int[@name='id'][.='42']"
,"//result/doc[3]/int[@name='id'][.='8675309']"
);
assertQ("multi boost query",
req("q", "cool stuff"
,"qt", "dismax"
,"version", "2.0"
,"bq", "subject:hell^400"
,"bq", "subject:cool^4"
,"debugQuery", "true"
)
,"//*[@numFound='3']"
,"//result/doc[1]/int[@name='id'][.='666']"
,"//result/doc[2]/int[@name='id'][.='8675309']"
,"//result/doc[3]/int[@name='id'][.='42']"
);
assertQ("minimum mm is three",
req("cool stuff traveling")
@ -135,6 +176,33 @@ public class DisMaxRequestHandlerTest extends AbstractSolrTestCase {
);
}
public void testExtraBlankBQ() throws Exception {
populate();
// if the boost queries are in their own boolean query, the clauses will be
// surrounded by ()'s in the debug output
Pattern p = Pattern.compile("subject:hell\\s*subject:cool");
Pattern p_bool = Pattern.compile("\\(subject:hell\\s*subject:cool\\)");
String resp = h.query(req("q", "cool stuff"
,"qt", "dismax"
,"version", "2.0"
,"bq", "subject:hell OR subject:cool"
,"debugQuery", "true"
));
assertTrue(p.matcher(resp).find());
assertFalse(p_bool.matcher(resp).find());
resp = h.query(req("q", "cool stuff"
,"qt", "dismax"
,"version", "2.0"
,"bq", "subject:hell OR subject:cool"
,"bq",""
,"debugQuery", "true"
));
assertTrue(p.matcher(resp).find());
assertTrue(p_bool.matcher(resp).find());
}
public void testOldStyleDefaults() throws Exception {
lrf = h.getRequestFactory

View File

@ -92,6 +92,10 @@ public class SolrPluginUtilsTest extends AbstractSolrTestCase {
(" fieldOne^2.3 fieldTwo fieldThree^-0.4 "));
assertEquals("really spacey e1", e1, SolrPluginUtils.parseFieldBoosts
(" \t fieldOne^2.3 \n fieldTwo fieldThree^-0.4 "));
assertEquals("really spacey e1", e1, SolrPluginUtils.parseFieldBoosts
(new String[]{" \t fieldOne^2.3 \n",
" fieldTwo fieldThree^-0.4 ",
" "}));
Map<String,Float> e2 = new HashMap<String,Float>();
assertEquals("empty e2", e2, SolrPluginUtils.parseFieldBoosts