Index: lucene/contrib/memory/src/test/org/apache/lucene/index/memory/MemoryIndexTest.java =================================================================== --- lucene/contrib/memory/src/test/org/apache/lucene/index/memory/MemoryIndexTest.java (revision 1174729) +++ lucene/contrib/memory/src/test/org/apache/lucene/index/memory/MemoryIndexTest.java (working copy) @@ -143,11 +143,9 @@ * Return a random analyzer (Simple, Stop, Standard) to analyze the terms. */ private Analyzer randomAnalyzer() { - switch(random.nextInt(3)) { - case 0: return new MockAnalyzer(random, MockTokenizer.SIMPLE, true); - case 1: return new MockAnalyzer(random, MockTokenizer.SIMPLE, true, MockTokenFilter.ENGLISH_STOPSET, true); - default: return new MockAnalyzer(random, MockTokenizer.WHITESPACE, false); - } + return random.nextBoolean() ? + new MockAnalyzer(random, MockTokenizer.SIMPLE, true) : + new MockAnalyzer(random, MockTokenizer.WHITESPACE, false); } /** 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) @@ -167,9 +167,9 @@ */ protected class BooleanWeight extends Weight { /** The Similarity implementation. */ - protected SimilarityProvider similarityProvider; - protected ArrayList weights; - protected int maxCoord; // num optional + num required + protected final SimilarityProvider similarityProvider; + protected final ArrayList weights; + protected final int maxCoord; // num optional + num required private final boolean disableCoord; private final boolean termConjunction; @@ -179,16 +179,27 @@ this.disableCoord = disableCoord; weights = new ArrayList(clauses.size()); boolean termConjunction = clauses.isEmpty() || minNrShouldMatch != 0 ? false : true; - for (int i = 0 ; i < clauses.size(); i++) { - BooleanClause c = clauses.get(i); - Weight w = c.getQuery().createWeight(searcher); + int maxCoord = 0; + for (final BooleanClause c : clauses) { + final Weight w = c.getQuery().createWeight(searcher); if (!(c.isRequired() && (w instanceof TermWeight))) { termConjunction = false; } + if (!c.isProhibited()) { + maxCoord++; + } weights.add(w); - if (!c.isProhibited()) maxCoord++; } + + // check for only prohibited clauses + if (maxCoord == 0 && clauses.size() > 0) { + throw new UnsupportedOperationException( + "A BooleanQuery must have at least one SHOULD or MUST clause, if MUST_NOT clauses are used."+BooleanQuery.this.toString() + ); + } + this.termConjunction = termConjunction; + this.maxCoord = maxCoord; } @Override Index: lucene/src/test/org/apache/lucene/search/TestBoolean2.java =================================================================== --- lucene/src/test/org/apache/lucene/search/TestBoolean2.java (revision 1174729) +++ lucene/src/test/org/apache/lucene/search/TestBoolean2.java (working copy) @@ -195,7 +195,11 @@ query.add(new TermQuery(new Term(field, "xx")), BooleanClause.Occur.MUST_NOT); query.add(new TermQuery(new Term(field, "w5")), BooleanClause.Occur.MUST_NOT); int[] expDocNrs = {}; - queriesTest(query, expDocNrs); + try { + queriesTest(query, expDocNrs); + fail("BQ with only prohibited clauses should throw UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + } } @Test @@ -249,48 +253,39 @@ int tot=0; BooleanQuery q1 = null; - try { - // increase number of iterations for more complete testing - int num = atLeast(10); - for (int i=0; i0) { qType = rnd.nextInt(10); @@ -322,6 +318,7 @@ BooleanClause.Occur occur; if (r<2) { occur=BooleanClause.Occur.MUST_NOT; + containsProhibitedClause = true; } else if (r<5) { if (allowMust) { @@ -329,8 +326,10 @@ } else { occur=BooleanClause.Occur.SHOULD; } + containsPositiveClause = true; } else { occur=BooleanClause.Occur.SHOULD; + containsPositiveClause = true; } current.add(q, occur); Index: lucene/src/test/org/apache/lucene/search/TestComplexExplanations.java =================================================================== --- lucene/src/test/org/apache/lucene/search/TestComplexExplanations.java (revision 1174729) +++ lucene/src/test/org/apache/lucene/search/TestComplexExplanations.java (working copy) @@ -94,12 +94,6 @@ dm.add(xxYYZZ); - BooleanQuery xxW1 = new BooleanQuery(); - xxW1.add(new TermQuery(new Term(FIELD, "xx")), Occur.MUST_NOT); - xxW1.add(new TermQuery(new Term(FIELD, "w1")), Occur.MUST_NOT); - - dm.add(xxW1); - DisjunctionMaxQuery dm2 = new DisjunctionMaxQuery(0.5f); dm2.add(new TermQuery(new Term(FIELD, "w1"))); dm2.add(new TermQuery(new Term(FIELD, "w2"))); @@ -157,12 +151,6 @@ dm.add(xxYYZZ); - BooleanQuery xxW1 = new BooleanQuery(); - xxW1.add(new TermQuery(new Term(FIELD, "xx")), Occur.MUST_NOT); - xxW1.add(new TermQuery(new Term(FIELD, "w1")), Occur.MUST_NOT); - - dm.add(xxW1); - DisjunctionMaxQuery dm2 = new DisjunctionMaxQuery(0.5f); dm2.add(new TermQuery(new Term(FIELD, "w1"))); dm2.add(new TermQuery(new Term(FIELD, "w2"))); 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) { + } } }