Index: lucene/src/java/org/apache/lucene/search/BooleanQuery.java =================================================================== --- lucene/src/java/org/apache/lucene/search/BooleanQuery.java (revision 1174729) +++ lucene/src/java/org/apache/lucene/search/BooleanQuery.java (working copy) @@ -179,6 +179,8 @@ this.disableCoord = disableCoord; weights = new ArrayList(clauses.size()); boolean termConjunction = clauses.isEmpty() || minNrShouldMatch != 0 ? false : true; + int numProhibited = 0; + for (int i = 0 ; i < clauses.size(); i++) { BooleanClause c = clauses.get(i); Weight w = c.getQuery().createWeight(searcher); @@ -186,8 +188,20 @@ termConjunction = false; } weights.add(w); - if (!c.isProhibited()) maxCoord++; + if (c.isProhibited()) { + numProhibited++; + } else { + maxCoord++; + } } + + // check for only prohibited clauses + if (numProhibited > 0 && numProhibited == clauses.size()) { + throw new UnsupportedOperationException( + "A BooleanQuery must have at least one SHOULD or MUST clause, if MUST_NOT clauses are used." + ); + } + this.termConjunction = termConjunction; } Index: modules/queries/src/java/org/apache/lucene/queries/BooleanFilter.java =================================================================== --- modules/queries/src/java/org/apache/lucene/queries/BooleanFilter.java (revision 1174729) +++ modules/queries/src/java/org/apache/lucene/queries/BooleanFilter.java (working copy) @@ -35,8 +35,8 @@ * SHOULD, MUST NOT, MUST * The results Filter BitSet is constructed as follows: * SHOULD Filters are OR'd together + * The resulting Filter is AND'd with the MUST Filters * The resulting Filter is NOT'd with the NOT Filters - * The resulting Filter is AND'd with the MUST Filters */ public class BooleanFilter extends Filter { @@ -52,6 +52,18 @@ public DocIdSet getDocIdSet(AtomicReaderContext context) throws IOException { FixedBitSet res = null; final IndexReader reader = context.reader; + + int positiveClauseCount = + (shouldFilters != null ? shouldFilters.size() : 0) + + (mustFilters != null ? mustFilters.size() : 0); + int prohibitedClauseCount = + notFilters != null ? notFilters.size() : 0; + if (positiveClauseCount == 0 && prohibitedClauseCount > 0) { + throw new UnsupportedOperationException( + "A BooleanFilter must have at least one SHOULD or MUST clause, if MUST_NOT clauses are used." + ); + } + if (shouldFilters != null) { for (int i = 0; i < shouldFilters.size(); i++) { final DocIdSetIterator disi = getDISI(shouldFilters, i, context); @@ -63,19 +75,6 @@ } } - if (notFilters != null) { - for (int i = 0; i < notFilters.size(); i++) { - if (res == null) { - res = new FixedBitSet(reader.maxDoc()); - res.set(0, reader.maxDoc()); // NOTE: may set bits on deleted docs - } - final DocIdSetIterator disi = getDISI(notFilters, i, context); - if (disi != null) { - res.andNot(disi); - } - } - } - if (mustFilters != null) { for (int i = 0; i < mustFilters.size(); i++) { final DocIdSetIterator disi = getDISI(mustFilters, i, context); @@ -91,6 +90,15 @@ } } + // to apply NOT filters we need at least some matching docs (res != null) + if (res != null && notFilters != null) { + for (int i = 0; i < notFilters.size(); i++) { + final DocIdSetIterator disi = getDISI(notFilters, i, context); + if (disi == null) continue; + res.andNot(disi); + } + } + return res != null ? res : DocIdSet.EMPTY_DOCIDSET; } @@ -120,7 +128,7 @@ private DocIdSetIterator getDISI(List filters, int index, AtomicReaderContext context) throws IOException { final DocIdSet set = filters.get(index).getDocIdSet(context); - return (set == null) ? null : set.iterator(); + return (set == null || set == DocIdSet.EMPTY_DOCIDSET) ? null : set.iterator(); } @Override Index: modules/queries/src/test/org/apache/lucene/queries/BooleanFilterTest.java =================================================================== --- modules/queries/src/test/org/apache/lucene/queries/BooleanFilterTest.java (revision 1174729) +++ modules/queries/src/test/org/apache/lucene/queries/BooleanFilterTest.java (working copy) @@ -225,12 +225,20 @@ public void testJustMustNot() throws Throwable { BooleanFilter booleanFilter = new BooleanFilter(); booleanFilter.add(new FilterClause(getTermsFilter("inStock", "N"), BooleanClause.Occur.MUST_NOT)); - tstFilterCard("MUST_NOT", 4, booleanFilter); + try { + tstFilterCard("MUST_NOT", 0, booleanFilter); + fail("This must throw UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + } // same with a real DISI (no OpenBitSetIterator) booleanFilter = new BooleanFilter(); booleanFilter.add(new FilterClause(getWrappedTermQuery("inStock", "N"), BooleanClause.Occur.MUST_NOT)); - tstFilterCard("MUST_NOT", 4, booleanFilter); + try { + tstFilterCard("MUST_NOT", 0, booleanFilter); + fail("This must throw UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + } } public void testMustAndMustNot() throws Throwable { @@ -297,10 +305,18 @@ booleanFilter = new BooleanFilter(); booleanFilter.add(new FilterClause(getNullDISFilter(), BooleanClause.Occur.MUST_NOT)); - tstFilterCard("A single MUST_NOT filter that returns a null DIS should be invisible", 5, booleanFilter); + try { + tstFilterCard("MUST_NOT", 0, booleanFilter); + fail("This must throw UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + } booleanFilter = new BooleanFilter(); booleanFilter.add(new FilterClause(getNullDISIFilter(), BooleanClause.Occur.MUST_NOT)); - tstFilterCard("A single MUST_NOT filter that returns a null DIS should be invisible", 5, booleanFilter); + try { + tstFilterCard("MUST_NOT", 0, booleanFilter); + fail("This must throw UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + } } }