This is an automated email from the ASF dual-hosted git repository.

dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git

commit a511387c55149a1e02f03a3bfca0f20f91f23628
Author: Renato Haeberli <[email protected]>
AuthorDate: Thu May 14 15:05:03 2026 +0200

    BoolQParserPlugin: add percentage and threshold based minimum match 
functionality (#4406)
    
    (cherry picked from commit 3056ce9a8b2b65a947f6cb7e82a6f6b4399131a9)
---
 ...enhance-minimum-match-for-BoolQParserPlugin.yml |  7 +++
 .../org/apache/solr/search/BoolQParserPlugin.java  | 12 ++--
 .../java/org/apache/solr/search/DisMaxQParser.java | 16 +----
 .../apache/solr/search/ExtendedDismaxQParser.java  |  2 +-
 .../java/org/apache/solr/util/SolrPluginUtils.java | 14 +++++
 .../solr/search/TestMmBoolQParserPlugin.java       | 68 ++++++++++++++++++----
 6 files changed, 88 insertions(+), 31 deletions(-)

diff --git 
a/changelog/unreleased/PR#4406-enhance-minimum-match-for-BoolQParserPlugin.yml 
b/changelog/unreleased/PR#4406-enhance-minimum-match-for-BoolQParserPlugin.yml
new file mode 100644
index 00000000000..52c118049cd
--- /dev/null
+++ 
b/changelog/unreleased/PR#4406-enhance-minimum-match-for-BoolQParserPlugin.yml
@@ -0,0 +1,7 @@
+title: add percentage and threshold based minimum match functionality, as we 
know it from ExtendedDismaxQParser, to BoolQParserPlugin
+type: changed
+authors:
+- name: Renato Haeberli
+links:
+- name: PR#4406
+  url: https://github.com/apache/solr/pull/4406
diff --git a/solr/core/src/java/org/apache/solr/search/BoolQParserPlugin.java 
b/solr/core/src/java/org/apache/solr/search/BoolQParserPlugin.java
index 9d37fa590c8..8a0a8eb96db 100644
--- a/solr/core/src/java/org/apache/solr/search/BoolQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/BoolQParserPlugin.java
@@ -22,10 +22,12 @@ import java.util.Map;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.Query;
+import org.apache.solr.common.params.DisMaxParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.query.FilterQuery;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.search.join.FiltersQParser;
+import org.apache.solr.util.SolrPluginUtils;
 
 /**
  * Create a boolean query from sub queries. Sub queries can be marked as 
{@code must}, {@code
@@ -46,10 +48,12 @@ public class BoolQParserPlugin extends QParserPlugin {
       }
 
       @Override
-      protected BooleanQuery.Builder createBuilder() {
-        BooleanQuery.Builder builder = super.createBuilder();
-        builder.setMinimumNumberShouldMatch(localParams.getInt("mm", 0));
-        return builder;
+      protected BooleanQuery parseImpl() throws SyntaxError {
+        BooleanQuery query = super.parseImpl();
+        SolrParams solrParams = SolrParams.wrapDefaults(localParams, params);
+        String minShouldMatch = 
SolrPluginUtils.parseMinShouldMatch(req.getSchema(), solrParams);
+        boolean mmAutoRelax = params.getBool(DisMaxParams.MM_AUTORELAX, false);
+        return SolrPluginUtils.setMinShouldMatch(query, minShouldMatch, 
mmAutoRelax);
       }
 
       @Override
diff --git a/solr/core/src/java/org/apache/solr/search/DisMaxQParser.java 
b/solr/core/src/java/org/apache/solr/search/DisMaxQParser.java
index 79756bc1090..25e93592d35 100644
--- a/solr/core/src/java/org/apache/solr/search/DisMaxQParser.java
+++ b/solr/core/src/java/org/apache/solr/search/DisMaxQParser.java
@@ -28,7 +28,6 @@ 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.common.util.StrUtils;
-import org.apache.solr.parser.QueryParser;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.util.SolrPluginUtils;
@@ -47,19 +46,6 @@ public class DisMaxQParser extends QParser {
    */
   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) {
-    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
@@ -248,7 +234,7 @@ public class DisMaxQParser extends QParser {
       String userQuery, SolrPluginUtils.DisjunctionMaxQueryParser up, 
SolrParams solrParams)
       throws SyntaxError {
 
-    String minShouldMatch = parseMinShouldMatch(req.getSchema(), solrParams);
+    String minShouldMatch = 
SolrPluginUtils.parseMinShouldMatch(req.getSchema(), solrParams);
     Query dis = up.parse(userQuery);
     Query query = dis;
 
diff --git 
a/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java 
b/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java
index b1dcb910a9d..4dd2745bf1e 100644
--- a/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java
+++ b/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java
@@ -1723,7 +1723,7 @@ public class ExtendedDismaxQParser extends QParser {
       solrParams = SolrParams.wrapDefaults(localParams, params);
       schema = req.getSchema();
       // req.getSearcher() here causes searcher refcount imbalance
-      minShouldMatch = DisMaxQParser.parseMinShouldMatch(schema, solrParams);
+      minShouldMatch = SolrPluginUtils.parseMinShouldMatch(schema, solrParams);
       userFields = new 
UserFields(U.parseFieldBoosts(solrParams.getParams(DMP.UF)));
       try {
         // req.getSearcher() here causes searcher refcount imbalance
diff --git a/solr/core/src/java/org/apache/solr/util/SolrPluginUtils.java 
b/solr/core/src/java/org/apache/solr/util/SolrPluginUtils.java
index b32b77846d2..ed6178be4f9 100644
--- a/solr/core/src/java/org/apache/solr/util/SolrPluginUtils.java
+++ b/solr/core/src/java/org/apache/solr/util/SolrPluginUtils.java
@@ -50,6 +50,7 @@ import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Sort;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.params.DisMaxParams;
 import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.CollectionUtil;
@@ -605,6 +606,19 @@ public class SolrPluginUtils {
     }
   }
 
+  /**
+   * 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) {
+    QueryParser.Operator op = 
QueryParsing.parseOP(params.get(QueryParsing.OP));
+
+    return params.get(DisMaxParams.MM, op.equals(QueryParser.Operator.AND) ? 
"100%" : "0%");
+  }
+
   public static void setMinShouldMatch(BooleanQuery.Builder q, String spec) {
     setMinShouldMatch(q, spec, false);
   }
diff --git 
a/solr/core/src/test/org/apache/solr/search/TestMmBoolQParserPlugin.java 
b/solr/core/src/test/org/apache/solr/search/TestMmBoolQParserPlugin.java
index 57790c330d7..988fa6767d4 100644
--- a/solr/core/src/test/org/apache/solr/search/TestMmBoolQParserPlugin.java
+++ b/solr/core/src/test/org/apache/solr/search/TestMmBoolQParserPlugin.java
@@ -30,6 +30,14 @@ import org.junit.Test;
 
 public class TestMmBoolQParserPlugin extends SolrTestCaseJ4 {
 
+  private static BooleanQuery.Builder shouldBuilder(String... terms) {
+    BooleanQuery.Builder builder = new BooleanQuery.Builder();
+    for (String term : terms) {
+      builder.add(new TermQuery(new Term("name", term)), 
BooleanClause.Occur.SHOULD);
+    }
+    return builder;
+  }
+
   @BeforeClass
   public static void beforeClass() throws Exception {
     initCore("solrconfig.xml", "schema.xml");
@@ -61,12 +69,55 @@ public class TestMmBoolQParserPlugin extends SolrTestCaseJ4 
{
         parseQuery(req("q", "{!bool should=name:foo should=name:bar 
should=name:qux mm=2}"));
 
     BooleanQuery expected =
-        new BooleanQuery.Builder()
-            .add(new TermQuery(new Term("name", "foo")), 
BooleanClause.Occur.SHOULD)
-            .add(new TermQuery(new Term("name", "bar")), 
BooleanClause.Occur.SHOULD)
-            .add(new TermQuery(new Term("name", "qux")), 
BooleanClause.Occur.SHOULD)
-            .setMinimumNumberShouldMatch(2)
-            .build();
+        shouldBuilder("foo", "bar", 
"qux").setMinimumNumberShouldMatch(2).build();
+
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testMinShouldMatchPercentage75() throws Exception {
+    Query actual =
+        parseQuery(req("q", "{!bool should=name:foo should=name:bar 
should=name:qux mm=75%}"));
+
+    BooleanQuery expected =
+        shouldBuilder("foo", "bar", 
"qux").setMinimumNumberShouldMatch(2).build();
+
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testMinShouldMatchPercentage50() throws Exception {
+    Query actual =
+        parseQuery(req("q", "{!bool should=name:foo should=name:bar 
should=name:qux mm=50%}"));
+
+    BooleanQuery expected =
+        shouldBuilder("foo", "bar", 
"qux").setMinimumNumberShouldMatch(1).build();
+
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testMinShouldMatchThresholdsLower() throws Exception {
+    Query actual =
+        parseQuery(
+            req("q", "{!bool should=name:foo should=name:bar should=name:qux 
mm='2<-1 5<-2'}"));
+
+    BooleanQuery expected =
+        shouldBuilder("foo", "bar", 
"qux").setMinimumNumberShouldMatch(2).build();
+
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testMinShouldMatchThresholdsUpper() throws Exception {
+    Query actual =
+        parseQuery(
+            req(
+                "q",
+                "{!bool should=name:foo should=name:bar should=name:qux 
should=name:n1 should=name:n2 should=name:n3 mm='2<-1 5<-2'}"));
+
+    BooleanQuery expected =
+        shouldBuilder("foo", "bar", "qux", "n1", "n2", 
"n3").setMinimumNumberShouldMatch(4).build();
 
     assertEquals(expected, actual);
   }
@@ -102,11 +153,6 @@ public class TestMmBoolQParserPlugin extends 
SolrTestCaseJ4 {
 
   @Test
   public void testInvalidMinShouldMatchThrowsException() {
-    expectThrows(
-        SolrException.class,
-        NumberFormatException.class,
-        () -> parseQuery(req("q", "{!bool should=name:foo mm=20%}")));
-
     expectThrows(
         SolrException.class,
         NumberFormatException.class,

Reply via email to