diff --git a/modules/join/src/java/org/apache/lucene/search/join/BlockJoinQuery.java b/modules/join/src/java/org/apache/lucene/search/join/BlockJoinQuery.java index b897889303e..93329758003 100644 --- a/modules/join/src/java/org/apache/lucene/search/join/BlockJoinQuery.java +++ b/modules/join/src/java/org/apache/lucene/search/join/BlockJoinQuery.java @@ -26,7 +26,6 @@ import org.apache.lucene.index.IndexReader.AtomicReaderContext; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; // javadocs import org.apache.lucene.index.Term; -import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.DocIdSet; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.Explanation; @@ -194,7 +193,7 @@ public class BlockJoinQuery extends Query { private final Scorer childScorer; private final FixedBitSet parentBits; private final ScoreMode scoreMode; - private int parentDoc; + private int parentDoc = -1; private float parentScore; private int nextChildDoc; @@ -324,8 +323,15 @@ public class BlockJoinQuery extends Query { return parentDoc = NO_MORE_DOCS; } - // Every parent must have at least one child: - assert parentTarget != 0; + if (parentTarget == 0) { + // Callers should only be passing in a docID from + // the parent space, so this means this parent + // has no children (it got docID 0), so it cannot + // possibly match. We must handle this case + // separately otherwise we pass invalid -1 to + // prevSetBit below: + return nextDoc(); + } final int prevParentDoc = parentBits.prevSetBit(parentTarget-1); diff --git a/modules/join/src/test/org/apache/lucene/search/TestBlockJoin.java b/modules/join/src/test/org/apache/lucene/search/TestBlockJoin.java index 69da7c337a1..044c444b3c9 100644 --- a/modules/join/src/test/org/apache/lucene/search/TestBlockJoin.java +++ b/modules/join/src/test/org/apache/lucene/search/TestBlockJoin.java @@ -21,11 +21,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.NumericField; import org.apache.lucene.document.StringField; import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.LogDocMergePolicy; import org.apache.lucene.index.RandomIndexWriter; import org.apache.lucene.index.Term; import org.apache.lucene.search.BooleanClause.Occur; @@ -36,6 +38,7 @@ import org.apache.lucene.search.join.BlockJoinQuery; import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.util.ReaderUtil; import org.apache.lucene.util._TestUtil; public class TestBlockJoin extends LuceneTestCase { @@ -85,7 +88,7 @@ public class TestBlockJoin extends LuceneTestCase { IndexReader r = w.getReader(); w.close(); - IndexSearcher s = new IndexSearcher(r); + IndexSearcher s = newSearcher(r); // Create a filter that defines "parent" documents in the index - in this case resumes Filter parentsFilter = new CachingWrapperFilter(new QueryWrapperFilter(new TermQuery(new Term("docType", "resume")))); @@ -282,10 +285,10 @@ public class TestBlockJoin extends LuceneTestCase { } } - final IndexSearcher s = new IndexSearcher(r); + final IndexSearcher s = newSearcher(r); s.setDefaultFieldSortScoring(true, true); - final IndexSearcher joinS = new IndexSearcher(joinR); + final IndexSearcher joinS = newSearcher(joinR); final Filter parentsFilter = new CachingWrapperFilter(new QueryWrapperFilter(new TermQuery(new Term("isParent", "x")))); @@ -516,7 +519,7 @@ public class TestBlockJoin extends LuceneTestCase { IndexReader r = w.getReader(); w.close(); - IndexSearcher s = new IndexSearcher(r); + IndexSearcher s = newSearcher(r); // Create a filter that defines "parent" documents in the index - in this case resumes Filter parentsFilter = new CachingWrapperFilter(new QueryWrapperFilter(new TermQuery(new Term("docType", "resume")))); @@ -590,4 +593,62 @@ public class TestBlockJoin extends LuceneTestCase { r.close(); dir.close(); } + + public void testAdvanceSingleParentSingleChild() throws Exception { + Directory dir = newDirectory(); + RandomIndexWriter w = new RandomIndexWriter(random, dir); + Document childDoc = new Document(); + childDoc.add(newField("child", "1", StringField.TYPE_UNSTORED)); + Document parentDoc = new Document(); + parentDoc.add(newField("parent", "1", StringField.TYPE_UNSTORED)); + w.addDocuments(Arrays.asList(new Document[] {childDoc, parentDoc})); + IndexReader r = w.getReader(); + w.close(); + IndexSearcher s = newSearcher(r); + Query tq = new TermQuery(new Term("child", "1")); + Filter parentFilter = new CachingWrapperFilter( + new QueryWrapperFilter( + new TermQuery(new Term("parent", "1")))); + + BlockJoinQuery q = new BlockJoinQuery(tq, parentFilter, BlockJoinQuery.ScoreMode.Avg); + Weight weight = s.createNormalizedWeight(q); + DocIdSetIterator disi = weight.scorer(ReaderUtil.leaves(s.getIndexReader().getTopReaderContext())[0], true, true, null); + assertEquals(1, disi.advance(1)); + r.close(); + dir.close(); + } + + public void testAdvanceSingleParentNoChild() throws Exception { + Directory dir = newDirectory(); + RandomIndexWriter w = new RandomIndexWriter(random, dir, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).setMergePolicy(new LogDocMergePolicy())); + Document parentDoc = new Document(); + parentDoc.add(newField("parent", "1", StringField.TYPE_UNSTORED)); + parentDoc.add(newField("isparent", "yes", StringField.TYPE_UNSTORED)); + w.addDocuments(Arrays.asList(new Document[] {parentDoc})); + + // Add another doc so scorer is not null + parentDoc = new Document(); + parentDoc.add(newField("parent", "2", StringField.TYPE_UNSTORED)); + parentDoc.add(newField("isparent", "yes", StringField.TYPE_UNSTORED)); + Document childDoc = new Document(); + childDoc.add(newField("child", "2", StringField.TYPE_UNSTORED)); + w.addDocuments(Arrays.asList(new Document[] {childDoc, parentDoc})); + + // Need single seg: + w.forceMerge(1); + IndexReader r = w.getReader(); + w.close(); + IndexSearcher s = newSearcher(r); + Query tq = new TermQuery(new Term("child", "2")); + Filter parentFilter = new CachingWrapperFilter( + new QueryWrapperFilter( + new TermQuery(new Term("isparent", "yes")))); + + BlockJoinQuery q = new BlockJoinQuery(tq, parentFilter, BlockJoinQuery.ScoreMode.Avg); + Weight weight = s.createNormalizedWeight(q); + DocIdSetIterator disi = weight.scorer(ReaderUtil.leaves(s.getIndexReader().getTopReaderContext())[0], true, true, null); + assertEquals(2, disi.advance(0)); + r.close(); + dir.close(); + } }