Index: modules/join/src/test/org/apache/lucene/search/TestBlockJoin.java =================================================================== --- modules/join/src/test/org/apache/lucene/search/TestBlockJoin.java (revision 1183333) +++ modules/join/src/test/org/apache/lucene/search/TestBlockJoin.java (working copy) @@ -57,6 +57,14 @@ return job; } + // ... has multiple qualifications + private Document makeQualification(String qualification, int year) { + Document job = new Document(); + job.add(newField("qualification", qualification, StringField.TYPE_STORED)); + job.add(new NumericField("year").setIntValue(year)); + return job; + } + public void testSimple() throws Exception { final Directory dir = newDirectory(); @@ -492,4 +500,94 @@ } } } + + public void testMultiChildTypes() throws Exception { + + final Directory dir = newDirectory(); + final RandomIndexWriter w = new RandomIndexWriter(random, dir); + + final List docs = new ArrayList(); + + docs.add(makeJob("java", 2007)); + docs.add(makeJob("python", 2010)); + docs.add(makeQualification("maths", 1999)); + docs.add(makeResume("Lisa", "United Kingdom")); + w.addDocuments(docs); + + IndexReader r = w.getReader(); + w.close(); + IndexSearcher s = new IndexSearcher(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")))); + + // Define child document criteria (finds an example of relevant work experience) + BooleanQuery childJobQuery = new BooleanQuery(); + childJobQuery.add(new BooleanClause(new TermQuery(new Term("skill", "java")), Occur.MUST)); + childJobQuery.add(new BooleanClause(NumericRangeQuery.newIntRange("year", 2006, 2011, true, true), Occur.MUST)); + + BooleanQuery childQualificationQuery = new BooleanQuery(); + childQualificationQuery.add(new BooleanClause(new TermQuery(new Term("qualification", "maths")), Occur.MUST)); + childQualificationQuery.add(new BooleanClause(NumericRangeQuery.newIntRange("year", 1980, 2000, true, true), Occur.MUST)); + + + // Define parent document criteria (find a resident in the UK) + Query parentQuery = new TermQuery(new Term("country", "United Kingdom")); + + // Wrap the child document query to 'join' any matches + // up to corresponding parent: + BlockJoinQuery childJobJoinQuery = new BlockJoinQuery(childJobQuery, parentsFilter, BlockJoinQuery.ScoreMode.Avg); + BlockJoinQuery childQualificationJoinQuery = new BlockJoinQuery(childQualificationQuery, parentsFilter, BlockJoinQuery.ScoreMode.Avg); + + // Combine the parent and nested child queries into a single query for a candidate + BooleanQuery fullQuery = new BooleanQuery(); + fullQuery.add(new BooleanClause(parentQuery, Occur.MUST)); + fullQuery.add(new BooleanClause(childJobJoinQuery, Occur.MUST)); + fullQuery.add(new BooleanClause(childQualificationJoinQuery, Occur.MUST)); + + //????? How do I control volume of jobs vs qualifications per parent? + BlockJoinCollector c = new BlockJoinCollector(Sort.RELEVANCE, 10, true, false); + + s.search(fullQuery, c); + + //Examine "Job" children + boolean showNullPointerIssue=true; + if (showNullPointerIssue) { + TopGroups jobResults = c.getTopGroups(childJobJoinQuery, null, 0, 10, 0, true); + + //assertEquals(1, results.totalHitCount); + assertEquals(1, jobResults.totalGroupedHitCount); + assertEquals(1, jobResults.groups.length); + + final GroupDocs group = jobResults.groups[0]; + assertEquals(1, group.totalHits); + + Document childJobDoc = s.doc(group.scoreDocs[0].doc); + //System.out.println(" doc=" + group.scoreDocs[0].doc); + assertEquals("java", childJobDoc.get("skill")); + assertNotNull(group.groupValue); + Document parentDoc = s.doc(group.groupValue); + assertEquals("Lisa", parentDoc.get("name")); + } + + //Now Examine qualification children + TopGroups qualificationResults = c.getTopGroups(childQualificationJoinQuery, null, 0, 10, 0, true); + + //!!!!! This next line can null pointer - but only if prior "jobs" section called first + assertEquals(1, qualificationResults.totalGroupedHitCount); + assertEquals(1, qualificationResults.groups.length); + + final GroupDocs qGroup = qualificationResults.groups[0]; + assertEquals(1, qGroup.totalHits); + + Document childQualificationDoc = s.doc(qGroup.scoreDocs[0].doc); + assertEquals("maths", childQualificationDoc.get("qualification")); + assertNotNull(qGroup.groupValue); + Document parentDoc = s.doc(qGroup.groupValue); + assertEquals("Lisa", parentDoc.get("name")); + + + r.close(); + dir.close(); + } } Index: modules/join/src/java/org/apache/lucene/search/join/BlockJoinCollector.java =================================================================== --- modules/join/src/java/org/apache/lucene/search/join/BlockJoinCollector.java (revision 1183333) +++ modules/join/src/java/org/apache/lucene/search/join/BlockJoinCollector.java (working copy) @@ -387,15 +387,17 @@ // unbox once final int slot = _slot; - if (offset >= queue.size()) { + if (sortedGroups == null) { + if (offset >= queue.size()) { + return null; + } + sortQueue(); + } else if (offset > sortedGroups.length) { return null; } + int totalGroupedHitCount = 0; - if (sortedGroups == null) { - sortQueue(); - } - final FakeScorer fakeScorer = new FakeScorer(); final GroupDocs[] groups = new GroupDocs[sortedGroups.length - offset]; Index: lucene/contrib/CHANGES.txt =================================================================== --- lucene/contrib/CHANGES.txt (revision 1183333) +++ lucene/contrib/CHANGES.txt (working copy) @@ -121,6 +121,10 @@ * LUCENE-3495: Fix BlockJoinQuery to properly implement getBoost()/setBoost(). (Robert Muir) + * LUCENE-3519: BlockJoinCollector always returned null when you tried + to retrieve top groups for any BlockJoinQuery after the first (Mark + Harwood, Mike McCandless) + API Changes * LUCENE-3436: Add SuggestMode to the spellchecker, so you can specify the strategy