diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToChildBlockJoinQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/ToChildBlockJoinQuery.java index 272458d..60d7c98 100644 --- a/lucene/join/src/java/org/apache/lucene/search/join/ToChildBlockJoinQuery.java +++ b/lucene/join/src/java/org/apache/lucene/search/join/ToChildBlockJoinQuery.java @@ -24,7 +24,6 @@ import java.util.Set; import org.apache.lucene.index.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.DocIdSet; import org.apache.lucene.search.Explanation; @@ -32,7 +31,6 @@ import org.apache.lucene.search.Filter; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.Scorer; -import org.apache.lucene.search.Scorer.ChildScorer; import org.apache.lucene.search.Weight; import org.apache.lucene.util.Bits; import org.apache.lucene.util.FixedBitSet; @@ -48,6 +46,8 @@ import org.apache.lucene.util.FixedBitSet; public class ToChildBlockJoinQuery extends Query { + public static final String INVALID_QUERY_MESSAGE = "Parent query yields document which is not matched by parents filter, docID="; + private final Filter parentsFilter; private final Query parentQuery; @@ -203,6 +203,7 @@ public class ToChildBlockJoinQuery extends Query { // children: while (true) { parentDoc = parentScorer.nextDoc(); + validateParentDoc(); if (parentDoc == 0) { // Degenerate but allowed: parent has no children @@ -211,6 +212,7 @@ public class ToChildBlockJoinQuery extends Query { // tricky because scorer must return -1 for // .doc() on init... parentDoc = parentScorer.nextDoc(); + validateParentDoc(); } if (parentDoc == NO_MORE_DOCS) { @@ -248,6 +250,12 @@ public class ToChildBlockJoinQuery extends Query { } } + private void validateParentDoc() { + if ( parentDoc != NO_MORE_DOCS && !parentBits.get(parentDoc)) { + throw new IllegalStateException(INVALID_QUERY_MESSAGE + parentDoc); + } + } + @Override public int docID() { return childDoc; @@ -277,6 +285,7 @@ public class ToChildBlockJoinQuery extends Query { if (childDoc == -1 || childTarget > parentDoc) { // Advance to new parent: parentDoc = parentScorer.advance(childTarget); + validateParentDoc(); //System.out.println(" advance to parentDoc=" + parentDoc); assert parentDoc > childTarget; if (parentDoc == NO_MORE_DOCS) { diff --git a/solr/core/src/test/org/apache/solr/search/join/BJQChildQueryValidationTest.java b/solr/core/src/test/org/apache/solr/search/join/BJQChildQueryValidationTest.java new file mode 100644 index 0000000..6c1cb3a --- /dev/null +++ b/solr/core/src/test/org/apache/solr/search/join/BJQChildQueryValidationTest.java @@ -0,0 +1,126 @@ +package org.apache.solr.search.join; + +import org.apache.lucene.search.join.ToChildBlockJoinQuery; +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.SolrInputDocument; +import org.apache.solr.request.SolrQueryRequest; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class BJQChildQueryValidationTest extends SolrTestCaseJ4 { + + public static final int AMOUNT_OF_PARENT_DOCS = 20; + public static final int AMOUNT_OF_CHILD_DOCS = 10; + public static final int DOC_NUM = AMOUNT_OF_PARENT_DOCS + AMOUNT_OF_PARENT_DOCS * AMOUNT_OF_CHILD_DOCS; + + @Rule + public ExpectedException thrown= ExpectedException.none(); + + @BeforeClass + public static void beforeClass() throws Exception { + initCore("solrconfig.xml", "schema15.xml"); + createIndex(); + } + + private static void createIndex() { + List parents = createParentDocs(); + Collections.shuffle(parents, random()); + for (SolrInputDocument parent : parents) { + assertU(adoc(parent)); + } + assertU(commit()); + assertQ(req("q", "*:*"), "//*[@numFound='" + DOC_NUM + "']"); + } + + private static List createParentDocs() { + List result = new ArrayList<>(AMOUNT_OF_PARENT_DOCS); + for ( int i = 0; i < AMOUNT_OF_PARENT_DOCS; i++) { + result.add( createParentDoc(i)) ; + } + return result; + } + + private static SolrInputDocument createParentDoc(int parentNumber) { + SolrInputDocument result = new SolrInputDocument(); + result.addField( "id", createFieldValue(parentNumber) ); + result.addField( "parent_s", createFieldValue(parentNumber)); + List childDocuments = createChildren(parentNumber); + Collections.shuffle(childDocuments, random()); + result.addChildDocuments(childDocuments); + return result; + } + + private static List createChildren(int parentNumber) { + List result = new ArrayList<>(AMOUNT_OF_CHILD_DOCS); + for ( int i = 0; i < AMOUNT_OF_CHILD_DOCS; i++) { + result.add(createChildDoc(parentNumber, i)) ; + } + return result; + } + + private static SolrInputDocument createChildDoc(int parentNumber, int childNumber) { + SolrInputDocument result = new SolrInputDocument(); + result.addField( "id", createFieldValue(parentNumber,childNumber) ); + result.addField( "child_s", createFieldValue(childNumber)); + return result; + } + + private static String createFieldValue( int... documentNumbers) { + StringBuilder stringBuilder = new StringBuilder(); + for (int documentNumber : documentNumbers) { + if (stringBuilder.length() > 0) { + stringBuilder.append("_"); + } + stringBuilder.append( documentNumber); + } + return stringBuilder.toString(); + } + + @Test + public void testNextDocValidationForToParentBjq() throws Exception { + SolrQueryRequest req = req( "q", "{!parent which=\"parent_s:*\" v=$parentq}", + "parentq", "parent_s:* " + randomChildQuery() ); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("child query must only match non-parent docs"); + h.query(req); + } + + @Test + public void testAdvanceValidationForToParentBjq() throws Exception { + SolrQueryRequest req = req( "q", "+parent_s:* {!parent which=\"parent_s:*\" v=$parentq}", + "parentq", "parent_s:* " + randomChildQuery() ); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("child query must only match non-parent docs"); + h.query(req); + } + + @Test + public void testNextDocValidationForToChildBjq() throws Exception { + SolrQueryRequest req = req( "q", "{!child of=\"parent_s:*\" v=$parentq}", + "parentq", "parent_s:* " + randomChildQuery() ); + thrown.expect(IllegalStateException.class); + thrown.expectMessage(ToChildBlockJoinQuery.INVALID_QUERY_MESSAGE); + h.query(req); + } + + @Test + public void testAdvanceValidationForToChildBjq() throws Exception { + SolrQueryRequest req = req( "q", "+child_s:* {!child of=\"parent_s:*\" v=$parentq}", + "parentq", "parent_s:* " + randomChildQuery() ); + thrown.expect(IllegalStateException.class); + thrown.expectMessage(ToChildBlockJoinQuery.INVALID_QUERY_MESSAGE); + h.query(req); + } + + private String randomChildQuery() { + int randomParentNumber = random().nextInt(AMOUNT_OF_PARENT_DOCS); + int randomChildNumber = random().nextInt(AMOUNT_OF_CHILD_DOCS); + return "id:" + createFieldValue(randomParentNumber, randomChildNumber); + } +}