diff --git a/changelog/unreleased/SOLR_18256_autofix_negative_lucene_clauses.yml b/changelog/unreleased/SOLR_18256_autofix_negative_lucene_clauses.yml new file mode 100644 index 000000000000..e63ee6bc5d5a --- /dev/null +++ b/changelog/unreleased/SOLR_18256_autofix_negative_lucene_clauses.yml @@ -0,0 +1,8 @@ +type: fixed +title: Fix pure negative (NOT) sub-expression queries in lucene QParser +authors: + - name: Abhishek Umarjikar + nick: abumarjikar +links: + - name: SOLR-18256 + url: https://issues.apache.org/jira/browse/SOLR-18256 diff --git a/solr/core/src/java/org/apache/solr/search/SolrQueryParser.java b/solr/core/src/java/org/apache/solr/search/SolrQueryParser.java index f64f8c3bdaa6..35f064e9c7a1 100644 --- a/solr/core/src/java/org/apache/solr/search/SolrQueryParser.java +++ b/solr/core/src/java/org/apache/solr/search/SolrQueryParser.java @@ -16,6 +16,9 @@ */ package org.apache.solr.search; +import java.util.List; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.Query; import org.apache.solr.parser.QueryParser; /** Solr's default query parser, a schema-driven superset of the classic lucene query parser. */ @@ -24,4 +27,9 @@ public class SolrQueryParser extends QueryParser { public SolrQueryParser(QParser parser, String defaultField) { super(defaultField, parser); } + + @Override + protected Query getBooleanQuery(List clauses) throws SyntaxError { + return QueryUtils.makeQueryable(super.getBooleanQuery(clauses)); + } } diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java b/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java index 6c9cd362e416..af0b15ae9965 100644 --- a/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java +++ b/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java @@ -1901,4 +1901,32 @@ public void testFieldExistsQueries() throws SyntaxError { } } } + + @Test + public void testNestedPureNegativeQuery() throws Exception { + // Standard sample data with completely unique field values to isolate matches + assertU(adoc("id", "9414", "v_t", "pureneg foo bar")); + assertU(adoc("id", "9415", "v_t", "pureneg foo baz")); + assertU(adoc("id", "9416", "v_t", "pureneg baz")); + assertU(commit()); + + // Top-level negative query must exclude 'bar' but successfully find our other docs + // Force sort by ID so the array index expectations always line up perfectly + assertJQ( + req("q", "v_t:pureneg AND -v_t:bar", "df", "v_t", "sort", "id asc"), + "/response/docs/[0]/id=='9415'", + "/response/docs/[1]/id=='9416'"); + + // Nested pure negative query inside a parenthesized group with AND + assertJQ( + req("q", "v_t:pureneg AND v_t:foo AND (-v_t:bar)", "df", "v_t"), + "/response/numFound==1", + "/response/docs/[0]/id=='9415'"); + + // Nested pure negative query using explicit NOT syntax + assertJQ( + req("q", "v_t:pureneg AND v_t:foo AND (NOT v_t:bar)", "df", "v_t"), + "/response/numFound==1", + "/response/docs/[0]/id=='9415'"); + } }