Skip to content

Commit 3056ce9

Browse files
authored
BoolQParserPlugin: add percentage and threshold based minimum match functionality (#4406)
1 parent 3039f30 commit 3056ce9

6 files changed

Lines changed: 88 additions & 31 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
title: add percentage and threshold based minimum match functionality, as we know it from ExtendedDismaxQParser, to BoolQParserPlugin
2+
type: changed
3+
authors:
4+
- name: Renato Haeberli
5+
links:
6+
- name: PR#4406
7+
url: https://github.com/apache/solr/pull/4406

solr/core/src/java/org/apache/solr/search/BoolQParserPlugin.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
import org.apache.lucene.search.BooleanClause;
2323
import org.apache.lucene.search.BooleanQuery;
2424
import org.apache.lucene.search.Query;
25+
import org.apache.solr.common.params.DisMaxParams;
2526
import org.apache.solr.common.params.SolrParams;
2627
import org.apache.solr.query.FilterQuery;
2728
import org.apache.solr.request.SolrQueryRequest;
2829
import org.apache.solr.search.join.FiltersQParser;
30+
import org.apache.solr.util.SolrPluginUtils;
2931

3032
/**
3133
* Create a boolean query from sub queries. Sub queries can be marked as {@code must}, {@code
@@ -46,10 +48,12 @@ public Query parse() throws SyntaxError {
4648
}
4749

4850
@Override
49-
protected BooleanQuery.Builder createBuilder() {
50-
BooleanQuery.Builder builder = super.createBuilder();
51-
builder.setMinimumNumberShouldMatch(localParams.getInt("mm", 0));
52-
return builder;
51+
protected BooleanQuery parseImpl() throws SyntaxError {
52+
BooleanQuery query = super.parseImpl();
53+
SolrParams solrParams = SolrParams.wrapDefaults(localParams, params);
54+
String minShouldMatch = SolrPluginUtils.parseMinShouldMatch(req.getSchema(), solrParams);
55+
boolean mmAutoRelax = params.getBool(DisMaxParams.MM_AUTORELAX, false);
56+
return SolrPluginUtils.setMinShouldMatch(query, minShouldMatch, mmAutoRelax);
5357
}
5458

5559
@Override

solr/core/src/java/org/apache/solr/search/DisMaxQParser.java

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.apache.solr.common.params.SolrParams;
2929
import org.apache.solr.common.util.NamedList;
3030
import org.apache.solr.common.util.StrUtils;
31-
import org.apache.solr.parser.QueryParser;
3231
import org.apache.solr.request.SolrQueryRequest;
3332
import org.apache.solr.schema.IndexSchema;
3433
import org.apache.solr.util.SolrPluginUtils;
@@ -47,19 +46,6 @@ public class DisMaxQParser extends QParser {
4746
*/
4847
private static String IMPOSSIBLE_FIELD_NAME = "\uFFFC\uFFFC\uFFFC";
4948

50-
/**
51-
* Applies the appropriate default rules for the "mm" param based on the effective value of the
52-
* "q.op" param
53-
*
54-
* @see QueryParsing#OP
55-
* @see DisMaxParams#MM
56-
*/
57-
public static String parseMinShouldMatch(final IndexSchema schema, final SolrParams params) {
58-
QueryParser.Operator op = QueryParsing.parseOP(params.get(QueryParsing.OP));
59-
60-
return params.get(DisMaxParams.MM, op.equals(QueryParser.Operator.AND) ? "100%" : "0%");
61-
}
62-
6349
/**
6450
* Uses {@link SolrPluginUtils#parseFieldBoosts(String)} with the 'qf' parameter. Falls back to
6551
* the 'df' parameter
@@ -248,7 +234,7 @@ protected Query getUserQuery(
248234
String userQuery, SolrPluginUtils.DisjunctionMaxQueryParser up, SolrParams solrParams)
249235
throws SyntaxError {
250236

251-
String minShouldMatch = parseMinShouldMatch(req.getSchema(), solrParams);
237+
String minShouldMatch = SolrPluginUtils.parseMinShouldMatch(req.getSchema(), solrParams);
252238
Query dis = up.parse(userQuery);
253239
Query query = dis;
254240

solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1723,7 +1723,7 @@ public ExtendedDismaxConfiguration(
17231723
solrParams = SolrParams.wrapDefaults(localParams, params);
17241724
schema = req.getSchema();
17251725
// req.getSearcher() here causes searcher refcount imbalance
1726-
minShouldMatch = DisMaxQParser.parseMinShouldMatch(schema, solrParams);
1726+
minShouldMatch = SolrPluginUtils.parseMinShouldMatch(schema, solrParams);
17271727
userFields = new UserFields(U.parseFieldBoosts(solrParams.getParams(DMP.UF)));
17281728
try {
17291729
// req.getSearcher() here causes searcher refcount imbalance

solr/core/src/java/org/apache/solr/util/SolrPluginUtils.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.apache.lucene.search.Sort;
5151
import org.apache.solr.common.SolrException;
5252
import org.apache.solr.common.params.CommonParams;
53+
import org.apache.solr.common.params.DisMaxParams;
5354
import org.apache.solr.common.params.MapSolrParams;
5455
import org.apache.solr.common.params.SolrParams;
5556
import org.apache.solr.common.util.CollectionUtil;
@@ -605,6 +606,19 @@ public static void setMinShouldMatch(BooleanQuery.Builder q, String spec, boolea
605606
}
606607
}
607608

609+
/**
610+
* Applies the appropriate default rules for the "mm" param based on the effective value of the
611+
* "q.op" param
612+
*
613+
* @see QueryParsing#OP
614+
* @see DisMaxParams#MM
615+
*/
616+
public static String parseMinShouldMatch(final IndexSchema schema, final SolrParams params) {
617+
QueryParser.Operator op = QueryParsing.parseOP(params.get(QueryParsing.OP));
618+
619+
return params.get(DisMaxParams.MM, op.equals(QueryParser.Operator.AND) ? "100%" : "0%");
620+
}
621+
608622
public static void setMinShouldMatch(BooleanQuery.Builder q, String spec) {
609623
setMinShouldMatch(q, spec, false);
610624
}

solr/core/src/test/org/apache/solr/search/TestMmBoolQParserPlugin.java

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@
3030

3131
public class TestMmBoolQParserPlugin extends SolrTestCaseJ4 {
3232

33+
private static BooleanQuery.Builder shouldBuilder(String... terms) {
34+
BooleanQuery.Builder builder = new BooleanQuery.Builder();
35+
for (String term : terms) {
36+
builder.add(new TermQuery(new Term("name", term)), BooleanClause.Occur.SHOULD);
37+
}
38+
return builder;
39+
}
40+
3341
@BeforeClass
3442
public static void beforeClass() throws Exception {
3543
initCore("solrconfig.xml", "schema.xml");
@@ -61,12 +69,55 @@ public void testMinShouldMatch() throws Exception {
6169
parseQuery(req("q", "{!bool should=name:foo should=name:bar should=name:qux mm=2}"));
6270

6371
BooleanQuery expected =
64-
new BooleanQuery.Builder()
65-
.add(new TermQuery(new Term("name", "foo")), BooleanClause.Occur.SHOULD)
66-
.add(new TermQuery(new Term("name", "bar")), BooleanClause.Occur.SHOULD)
67-
.add(new TermQuery(new Term("name", "qux")), BooleanClause.Occur.SHOULD)
68-
.setMinimumNumberShouldMatch(2)
69-
.build();
72+
shouldBuilder("foo", "bar", "qux").setMinimumNumberShouldMatch(2).build();
73+
74+
assertEquals(expected, actual);
75+
}
76+
77+
@Test
78+
public void testMinShouldMatchPercentage75() throws Exception {
79+
Query actual =
80+
parseQuery(req("q", "{!bool should=name:foo should=name:bar should=name:qux mm=75%}"));
81+
82+
BooleanQuery expected =
83+
shouldBuilder("foo", "bar", "qux").setMinimumNumberShouldMatch(2).build();
84+
85+
assertEquals(expected, actual);
86+
}
87+
88+
@Test
89+
public void testMinShouldMatchPercentage50() throws Exception {
90+
Query actual =
91+
parseQuery(req("q", "{!bool should=name:foo should=name:bar should=name:qux mm=50%}"));
92+
93+
BooleanQuery expected =
94+
shouldBuilder("foo", "bar", "qux").setMinimumNumberShouldMatch(1).build();
95+
96+
assertEquals(expected, actual);
97+
}
98+
99+
@Test
100+
public void testMinShouldMatchThresholdsLower() throws Exception {
101+
Query actual =
102+
parseQuery(
103+
req("q", "{!bool should=name:foo should=name:bar should=name:qux mm='2<-1 5<-2'}"));
104+
105+
BooleanQuery expected =
106+
shouldBuilder("foo", "bar", "qux").setMinimumNumberShouldMatch(2).build();
107+
108+
assertEquals(expected, actual);
109+
}
110+
111+
@Test
112+
public void testMinShouldMatchThresholdsUpper() throws Exception {
113+
Query actual =
114+
parseQuery(
115+
req(
116+
"q",
117+
"{!bool should=name:foo should=name:bar should=name:qux should=name:n1 should=name:n2 should=name:n3 mm='2<-1 5<-2'}"));
118+
119+
BooleanQuery expected =
120+
shouldBuilder("foo", "bar", "qux", "n1", "n2", "n3").setMinimumNumberShouldMatch(4).build();
70121

71122
assertEquals(expected, actual);
72123
}
@@ -102,11 +153,6 @@ public void testExcludeTags() throws Exception {
102153

103154
@Test
104155
public void testInvalidMinShouldMatchThrowsException() {
105-
expectThrows(
106-
SolrException.class,
107-
NumberFormatException.class,
108-
() -> parseQuery(req("q", "{!bool should=name:foo mm=20%}")));
109-
110156
expectThrows(
111157
SolrException.class,
112158
NumberFormatException.class,

0 commit comments

Comments
 (0)