From fa3e1ad71fd97200b98f9e7d1e461ddfa78e357c Mon Sep 17 00:00:00 2001 From: Bar Rotstein Date: Wed, 14 Oct 2020 08:25:52 -0400 Subject: [PATCH] SOLR-14869: ChildDocTransformer should have omitted deleted child documents. Closes #1970 --- solr/CHANGES.txt | 2 + .../transform/ChildDocTransformer.java | 11 ++++- .../TestChildDocTransformerHierarchy.java | 47 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 2017a992da1..cf5a759c59a 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -219,6 +219,8 @@ Optimizations Bug Fixes --------------------- +* SOLR-14869: ChildDocTransformer should have omitted deleted child documents. (Bar Rotstein, David Smiley) + * SOLR-11656: TLOG replication doesn't work properly after rebalancing leaders. (Yuki Yano via Erick Erickson) diff --git a/solr/core/src/java/org/apache/solr/response/transform/ChildDocTransformer.java b/solr/core/src/java/org/apache/solr/response/transform/ChildDocTransformer.java index 7339bf6a8af..de00b8278ca 100644 --- a/solr/core/src/java/org/apache/solr/response/transform/ChildDocTransformer.java +++ b/solr/core/src/java/org/apache/solr/response/transform/ChildDocTransformer.java @@ -32,6 +32,7 @@ import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.ReaderUtil; import org.apache.lucene.index.SortedDocValues; import org.apache.lucene.search.join.BitSetProducer; +import org.apache.lucene.util.Bits; import org.apache.lucene.util.BitSet; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrException; @@ -89,6 +90,7 @@ class ChildDocTransformer extends DocTransformer { final int segBaseId = leafReaderContext.docBase; final int segRootId = rootDocId - segBaseId; final BitSet segParentsBitSet = parentsFilter.getBitSet(leafReaderContext); + final Bits liveDocs = leafReaderContext.reader().getLiveDocs(); if (segParentsBitSet == null) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, @@ -117,9 +119,16 @@ class ChildDocTransformer extends DocTransformer { int matches = 0; // Loop each child ID up to the parent (exclusive). for (int docId = firstChildId; docId < rootDocId; ++docId) { + final int segDocId = docId - segBaseId; + + // check whether doc is "live" + if (liveDocs != null && !liveDocs.get(segDocId)) { + // doc is not "live"; return fast + continue; + } // get the path. (note will default to ANON_CHILD_KEY if schema is not nested or empty string if blank) - final String fullDocPath = getPathByDocId(docId - segBaseId, segPathDocValues); + final String fullDocPath = getPathByDocId(segDocId, segPathDocValues); if (isNestedSchema && !fullDocPath.startsWith(rootDocPath)) { // is not a descendant of the transformed doc; return fast. diff --git a/solr/core/src/test/org/apache/solr/response/transform/TestChildDocTransformerHierarchy.java b/solr/core/src/test/org/apache/solr/response/transform/TestChildDocTransformerHierarchy.java index bda4e7d1780..02c1acad9fc 100644 --- a/solr/core/src/test/org/apache/solr/response/transform/TestChildDocTransformerHierarchy.java +++ b/solr/core/src/test/org/apache/solr/response/transform/TestChildDocTransformerHierarchy.java @@ -135,6 +135,53 @@ public class TestChildDocTransformerHierarchy extends SolrTestCaseJ4 { ); } + @Test + public void testWithDeletedChildren() throws Exception { + // add a doc to create another segment + final String addNonTestedDoc = + "{\n" + + "\"add\": {\n" + + "\"doc\": {\n" + + "\"id\": " + -1000 + ", \n" + + "\"type_s\": \"cake\", \n" + + "}\n" + + "}\n" + + "}"; + + if (random().nextBoolean()) { + updateJ(addNonTestedDoc, null); + assertU(commit()); + } + + indexSampleData(numberOfDocsPerNestedTest); + // delete toppings path + assertU(delQ("_nest_path_:\\/toppings")); + assertU(commit()); + + try(SolrQueryRequest req = req("q", "type_s:donut", "sort", "id asc", "fl", "id, type_s, toppings, _nest_path_, [child childFilter='_nest_path_:/toppings' limit=1]", + "fq", fqToExcludeNonTestedDocs)) { + BasicResultContext res = (BasicResultContext) h.queryAndResponse("/select", req).getResponse(); + Iterator docsStreamer = res.getProcessedDocuments(); + while (docsStreamer.hasNext()) { + SolrDocument doc = docsStreamer.next(); + cleanSolrDocumentFields(doc); + assertFalse("root doc should not have anonymous child docs", doc.hasChildDocuments()); + assertNull("should not include deleted docs", doc.getFieldValues("toppings")); + } + } + + assertJQ(req("q", "type_s:donut", + "sort", "id asc", + "fl", "*, [child limit=1]", + "fq", fqToExcludeNonTestedDocs), + "/response/docs/[0]/type_s==donut", + "/response/docs/[0]/lonely/test_s==testing", + "/response/docs/[0]/lonely/lonelyGrandChild/test2_s==secondTest", + // "!" (negate): don't find toppings. The "limit" kept us from reaching these, which follow lonely. + "!/response/docs/[0]/toppings/[0]/type_s==Regular" + ); + } + @Test public void testChildFilterLimitJSON() throws Exception { indexSampleData(numberOfDocsPerNestedTest);