From 1ea996ebce7294d781284f3a950755ce97222cdd Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Fri, 5 Jun 2026 14:35:57 +0200 Subject: [PATCH 1/5] Prepare SimpleCollector in QueryUtils for driver replacement Create outerWeight/shadowWeight before the search call so they are ready for the loop, eliminating the per-leaf weight creation inside collect(). Replace leafPtr with a currentLeafContext field set in doSetNextReader. Move opidx to an instance field. Rename the outer Scorable field to outerScorer to avoid a future name collision with the shadow Scorer. No behaviour change. --- .../lucene/tests/search/QueryUtils.java | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java b/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java index f2f995152d1d..7b11138c6316 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java +++ b/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java @@ -320,8 +320,6 @@ public CacheHelper getReaderCacheHelper() { */ public static void checkSkipTo(final Query q, final IndexSearcher s) throws IOException { // System.out.println("Checking "+q); - final List readerContextArray = s.getTopReaderContext().leaves(); - final int skip_op = 0; final int next_op = 1; final int[][] orders = { @@ -339,7 +337,6 @@ public static void checkSkipTo(final Query q, final IndexSearcher s) throws IOEx // System.out.print("Order:");for (int i = 0; i < order.length; i++) // System.out.print(order[i]==skip_op ? " skip()":" next()"); // System.out.println(); - final int[] opidx = {0}; final int[] lastDoc = {-1}; // FUTURE: ensure scorer.doc()==-1 @@ -347,33 +344,34 @@ public static void checkSkipTo(final Query q, final IndexSearcher s) throws IOEx final float maxDiff = 1e-5f; final LeafReader[] lastReader = {null}; + Query rewritten = s.rewrite(q); + Weight shadowWeight = s.createWeight(rewritten, ScoreMode.COMPLETE, 1); + s.search( q, new SimpleCollector() { - private Scorable sc; + private Scorable outerScorer; private Scorer scorer; private DocIdSetIterator iterator; - private int leafPtr; + private LeafReaderContext currentLeafContext; + private int opidx; @Override public void setScorer(Scorable scorer) { - this.sc = scorer; + this.outerScorer = scorer; } @Override public void collect(int doc) throws IOException { - float score = sc.score(); + float score = outerScorer.score(); lastDoc[0] = doc; try { if (scorer == null) { - Query rewritten = s.rewrite(q); - Weight w = s.createWeight(rewritten, ScoreMode.COMPLETE, 1); - LeafReaderContext context = readerContextArray.get(leafPtr); - scorer = w.scorer(context); + scorer = shadowWeight.scorer(currentLeafContext); iterator = scorer.iterator(); } - int op = order[(opidx[0]++) % order.length]; + int op = order[(opidx++) % order.length]; // System.out.println(op==skip_op ? // "skip("+(sdoc[0]+1)+")":"next()"); boolean more = @@ -398,10 +396,10 @@ protected void doSetNextReader(LeafReaderContext context) throws IOException { if (lastReader[0] != null) { assertNoPastSegmentEnd( q, s, lastReader[0], lastDoc[0], context.reader().getLiveDocs()); - leafPtr++; } + currentLeafContext = context; lastReader[0] = context.reader(); - assert readerContextArray.get(leafPtr).reader() == context.reader(); + assert currentLeafContext.reader() == context.reader(); this.scorer = null; lastDoc[0] = -1; } @@ -419,29 +417,29 @@ public static void checkFirstSkipTo(final Query q, final IndexSearcher s) throws final float maxDiff = 1e-3f; final int[] lastDoc = {-1}; final LeafReader[] lastReader = {null}; - final List context = s.getTopReaderContext().leaves(); Query rewritten = s.rewrite(q); + Weight w = s.createWeight(rewritten, ScoreMode.COMPLETE, 1); + s.search( q, new SimpleCollector() { - private final Weight w = s.createWeight(rewritten, ScoreMode.COMPLETE, 1); - private Scorable scorer; - private int leafPtr; + private Scorable outerScorer; + private LeafReaderContext currentLeafContext; private long intervalTimes32 = 1 * 32; @Override public void setScorer(Scorable scorer) { - this.scorer = scorer; + this.outerScorer = scorer; } @Override public void collect(int doc) throws IOException { - float score = scorer.score(); + float score = outerScorer.score(); try { // The intervalTimes32 trick helps contain the runtime of this check: first we check // every single doc in the interval, then after 32 docs we check every 2 docs, etc. for (int i = lastDoc[0] + 1; i <= doc; i += intervalTimes32++ / 1024) { - assertAdvanceTo(doc, i, score, w, context.get(leafPtr), maxDiff); + assertAdvanceTo(doc, i, score, w, currentLeafContext, maxDiff); } lastDoc[0] = doc; } catch (IOException e) { @@ -461,9 +459,8 @@ protected void doSetNextReader(LeafReaderContext context) throws IOException { if (lastReader[0] != null) { assertNoPastSegmentEnd( q, s, lastReader[0], lastDoc[0], context.reader().getLiveDocs()); - leafPtr++; } - + currentLeafContext = context; lastReader[0] = context.reader(); lastDoc[0] = -1; } From 8e7a86e934d7fe5fb449b8a01c623417f65135a9 Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Fri, 5 Jun 2026 14:57:15 +0200 Subject: [PATCH 2/5] Replace SimpleCollector driver with explicit leaf iteration in QueryUtils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the deprecated IndexSearcher#search(Query, Collector) call. The SimpleCollector wrapper is gone; a for-loop over leaf contexts and a while-loop over matching documents are now the explicit driver. The only content changes are the array-wrapper removals (lastDoc[0] → lastDoc, lastReader[0] → lastReader) and the outer try-catch, both unavoidable consequences of removing the anonymous class. --- .../lucene/tests/search/QueryUtils.java | 179 +++++++----------- 1 file changed, 70 insertions(+), 109 deletions(-) diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java b/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java index 7b11138c6316..23966ffc7a3e 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java +++ b/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java @@ -55,7 +55,6 @@ import org.apache.lucene.search.ScoreMode; import org.apache.lucene.search.Scorer; import org.apache.lucene.search.ScorerSupplier; -import org.apache.lucene.search.SimpleCollector; import org.apache.lucene.search.Weight; import org.apache.lucene.tests.util.LuceneTestCase; import org.apache.lucene.util.Bits; @@ -320,6 +319,8 @@ public CacheHelper getReaderCacheHelper() { */ public static void checkSkipTo(final Query q, final IndexSearcher s) throws IOException { // System.out.println("Checking "+q); + final List readerContextArray = s.getTopReaderContext().leaves(); + final int skip_op = 0; final int next_op = 1; final int[][] orders = { @@ -337,76 +338,55 @@ public static void checkSkipTo(final Query q, final IndexSearcher s) throws IOEx // System.out.print("Order:");for (int i = 0; i < order.length; i++) // System.out.print(order[i]==skip_op ? " skip()":" next()"); // System.out.println(); - final int[] lastDoc = {-1}; + int lastDoc = -1; // FUTURE: ensure scorer.doc()==-1 final float maxDiff = 1e-5f; - final LeafReader[] lastReader = {null}; + LeafReader lastReader = null; Query rewritten = s.rewrite(q); + Weight outerWeight = s.createWeight(rewritten, ScoreMode.COMPLETE, 1); Weight shadowWeight = s.createWeight(rewritten, ScoreMode.COMPLETE, 1); - s.search( - q, - new SimpleCollector() { - private Scorable outerScorer; - private Scorer scorer; - private DocIdSetIterator iterator; - private LeafReaderContext currentLeafContext; - private int opidx; - - @Override - public void setScorer(Scorable scorer) { - this.outerScorer = scorer; - } - - @Override - public void collect(int doc) throws IOException { - float score = outerScorer.score(); - lastDoc[0] = doc; - try { - if (scorer == null) { - scorer = shadowWeight.scorer(currentLeafContext); - iterator = scorer.iterator(); - } - - int op = order[(opidx++) % order.length]; - // System.out.println(op==skip_op ? - // "skip("+(sdoc[0]+1)+")":"next()"); - boolean more = - op == skip_op - ? iterator.advance(scorer.docID() + 1) != DocIdSetIterator.NO_MORE_DOCS - : iterator.nextDoc() != DocIdSetIterator.NO_MORE_DOCS; - assertDocAndScore(doc, score, more, scorer, maxDiff, order, op, skip_op, q, s); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public ScoreMode scoreMode() { - return ScoreMode.COMPLETE; - } + int opidx = 0; + for (LeafReaderContext leafContext : readerContextArray) { + if (lastReader != null) { + assertNoPastSegmentEnd(q, s, lastReader, lastDoc, leafContext.reader().getLiveDocs()); + } + lastReader = leafContext.reader(); + lastDoc = -1; + Scorer scorer = null; + DocIdSetIterator iterator = null; + + Scorer outerScorer = outerWeight.scorer(leafContext); + if (outerScorer == null) { + continue; + } + DocIdSetIterator outerIterator = outerScorer.iterator(); + int doc; + while ((doc = outerIterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) { + float score = outerScorer.score(); + lastDoc = doc; + + if (scorer == null) { + scorer = shadowWeight.scorer(leafContext); + iterator = scorer.iterator(); + } - @Override - protected void doSetNextReader(LeafReaderContext context) throws IOException { - // confirm that skipping beyond the last doc, on the - // previous reader, hits NO_MORE_DOCS - if (lastReader[0] != null) { - assertNoPastSegmentEnd( - q, s, lastReader[0], lastDoc[0], context.reader().getLiveDocs()); - } - currentLeafContext = context; - lastReader[0] = context.reader(); - assert currentLeafContext.reader() == context.reader(); - this.scorer = null; - lastDoc[0] = -1; - } - }); + int op = order[(opidx++) % order.length]; + // System.out.println(op==skip_op ? + // "skip("+(sdoc[0]+1)+")":"next()"); + boolean more = + op == skip_op + ? iterator.advance(scorer.docID() + 1) != DocIdSetIterator.NO_MORE_DOCS + : iterator.nextDoc() != DocIdSetIterator.NO_MORE_DOCS; + assertDocAndScore(doc, score, more, scorer, maxDiff, order, op, skip_op, q, s); + } + } - if (lastReader[0] != null) { - assertNoPastSegmentEnd(q, s, lastReader[0], lastDoc[0], lastReader[0].getLiveDocs()); + if (lastReader != null) { + assertNoPastSegmentEnd(q, s, lastReader, lastDoc, lastReader.getLiveDocs()); } } } @@ -415,59 +395,40 @@ protected void doSetNextReader(LeafReaderContext context) throws IOException { public static void checkFirstSkipTo(final Query q, final IndexSearcher s) throws IOException { // System.out.println("checkFirstSkipTo: "+q); final float maxDiff = 1e-3f; - final int[] lastDoc = {-1}; - final LeafReader[] lastReader = {null}; + int lastDoc = -1; + LeafReader lastReader = null; + final List context = s.getTopReaderContext().leaves(); Query rewritten = s.rewrite(q); + Weight outerWeight = s.createWeight(rewritten, ScoreMode.COMPLETE, 1); Weight w = s.createWeight(rewritten, ScoreMode.COMPLETE, 1); + long intervalTimes32 = 1 * 32; - s.search( - q, - new SimpleCollector() { - private Scorable outerScorer; - private LeafReaderContext currentLeafContext; - private long intervalTimes32 = 1 * 32; - - @Override - public void setScorer(Scorable scorer) { - this.outerScorer = scorer; - } - - @Override - public void collect(int doc) throws IOException { - float score = outerScorer.score(); - try { - // The intervalTimes32 trick helps contain the runtime of this check: first we check - // every single doc in the interval, then after 32 docs we check every 2 docs, etc. - for (int i = lastDoc[0] + 1; i <= doc; i += intervalTimes32++ / 1024) { - assertAdvanceTo(doc, i, score, w, currentLeafContext, maxDiff); - } - lastDoc[0] = doc; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public ScoreMode scoreMode() { - return ScoreMode.COMPLETE; - } + for (LeafReaderContext leafContext : context) { + if (lastReader != null) { + assertNoPastSegmentEnd(q, s, lastReader, lastDoc, leafContext.reader().getLiveDocs()); + } + lastReader = leafContext.reader(); + lastDoc = -1; - @Override - protected void doSetNextReader(LeafReaderContext context) throws IOException { - // confirm that skipping beyond the last doc, on the - // previous reader, hits NO_MORE_DOCS - if (lastReader[0] != null) { - assertNoPastSegmentEnd( - q, s, lastReader[0], lastDoc[0], context.reader().getLiveDocs()); - } - currentLeafContext = context; - lastReader[0] = context.reader(); - lastDoc[0] = -1; - } - }); + Scorer outerScorer = outerWeight.scorer(leafContext); + if (outerScorer == null) { + continue; + } + DocIdSetIterator outerIterator = outerScorer.iterator(); + int doc; + while ((doc = outerIterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) { + float score = outerScorer.score(); + // The intervalTimes32 trick helps contain the runtime of this check: first we check + // every single doc in the interval, then after 32 docs we check every 2 docs, etc. + for (int i = lastDoc + 1; i <= doc; i += intervalTimes32++ / 1024) { + assertAdvanceTo(doc, i, score, w, leafContext, maxDiff); + } + lastDoc = doc; + } + } - if (lastReader[0] != null) { - assertNoPastSegmentEnd(q, s, lastReader[0], lastDoc[0], lastReader[0].getLiveDocs()); + if (lastReader != null) { + assertNoPastSegmentEnd(q, s, lastReader, lastDoc, lastReader.getLiveDocs()); } } From 692af3ed044204479f319f781e759ab62d63df9f Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Wed, 17 Jun 2026 16:19:34 +0200 Subject: [PATCH 3/5] changes --- lucene/CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 250c3d1d04b6..45fb9925e0cd 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -656,6 +656,9 @@ Other * GITHUB#16258: Extract per-doc assertion helpers in QueryUtils (Luca Cavanna) +* GITHUB#16266: Remove deprecated search(Query, Collector) calls in QueryUtils by replacing + SimpleCollector-based iteration with explicit leaf-level Weight.scorer() calls. (Luca Cavanna) + ======================= Lucene 10.4.0 ======================= API Changes From 6c512be36dd7dc87fef2661be6b4dc7d45ba349a Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Tue, 23 Jun 2026 14:01:17 +0200 Subject: [PATCH 4/5] move changes entry --- lucene/CHANGES.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 7f946a509d28..0c2ae2aa62c9 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -299,7 +299,8 @@ Bug Fixes Other --------------------- -(No changes) +* GITHUB#16266: Remove deprecated search(Query, Collector) calls in QueryUtils by replacing + SimpleCollector-based iteration with explicit leaf-level Weight.scorer() calls. (Luca Cavanna) ======================= Lucene 10.5.0 ======================= @@ -691,9 +692,6 @@ Other * GITHUB#16258: Extract per-doc assertion helpers in QueryUtils (Luca Cavanna) -* GITHUB#16266: Remove deprecated search(Query, Collector) calls in QueryUtils by replacing - SimpleCollector-based iteration with explicit leaf-level Weight.scorer() calls. (Luca Cavanna) - ======================= Lucene 10.4.0 ======================= API Changes From dab1ba3843f2d7619c55f497bb46f20e1a06f416 Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Fri, 26 Jun 2026 15:22:03 +0200 Subject: [PATCH 5/5] iter --- .../src/java/org/apache/lucene/tests/search/QueryUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java b/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java index 23966ffc7a3e..737e4184386a 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java +++ b/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java @@ -364,8 +364,10 @@ public static void checkSkipTo(final Query q, final IndexSearcher s) throws IOEx continue; } DocIdSetIterator outerIterator = outerScorer.iterator(); + Bits liveDocs = leafContext.reader().getLiveDocs(); int doc; while ((doc = outerIterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) { + if (liveDocs != null && !liveDocs.get(doc)) continue; float score = outerScorer.score(); lastDoc = doc; @@ -415,8 +417,10 @@ public static void checkFirstSkipTo(final Query q, final IndexSearcher s) throws continue; } DocIdSetIterator outerIterator = outerScorer.iterator(); + Bits liveDocs = leafContext.reader().getLiveDocs(); int doc; while ((doc = outerIterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) { + if (liveDocs != null && !liveDocs.get(doc)) continue; float score = outerScorer.score(); // The intervalTimes32 trick helps contain the runtime of this check: first we check // every single doc in the interval, then after 32 docs we check every 2 docs, etc.