diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 02113d05d85b..97b3eee74c83 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -323,6 +323,8 @@ Optimizations --------------------- * GITHUG#16280: Single-pass writeString fast path for short strings in ByteBuffersDataOutput (neoremind) +* GITHUB#16220: Implement WANDScorer.advanceShallow() to enable block-max optimizations when nested in conjunctions. (Prithvi S) + * GITHUB#16146: Speed up no-score filtered-optional Boolean queries by routing dense conjunctions with cached FixedBitSet filters through ConstantScoreBulkScorer, which batches matches in bulk via DocIdStream. (Costin Leau) diff --git a/lucene/core/src/java/org/apache/lucene/search/WANDScorer.java b/lucene/core/src/java/org/apache/lucene/search/WANDScorer.java index 94c82a92d8a4..46a7369f65ce 100644 --- a/lucene/core/src/java/org/apache/lucene/search/WANDScorer.java +++ b/lucene/core/src/java/org/apache/lucene/search/WANDScorer.java @@ -566,17 +566,18 @@ public float score() throws IOException { @Override public int advanceShallow(int target) throws IOException { - // Propagate to improve score bounds + // Propagate to sub-scorers. Scorers past target constrain the boundary to docID - 1. + int newUpTo = DocIdSetIterator.NO_MORE_DOCS; for (Scorer scorer : allScorers) { - if (scorer.docID() < target) { - scorer.advanceShallow(target); + // Use <= instead of < so we still propagate and use the block boundary when already at + // target. + if (scorer.docID() <= target) { + newUpTo = Math.min(newUpTo, scorer.advanceShallow(target)); + } else if (scorer.docID() != DocIdSetIterator.NO_MORE_DOCS) { + newUpTo = Math.min(newUpTo, scorer.docID() - 1); } } - if (target <= upTo) { - return upTo; - } - // TODO: implement - return DocIdSetIterator.NO_MORE_DOCS; + return newUpTo; } @Override diff --git a/lucene/core/src/test/org/apache/lucene/search/TestWANDScorer.java b/lucene/core/src/test/org/apache/lucene/search/TestWANDScorer.java index 32d11daa44e8..5dd0fe6b2f23 100644 --- a/lucene/core/src/test/org/apache/lucene/search/TestWANDScorer.java +++ b/lucene/core/src/test/org/apache/lucene/search/TestWANDScorer.java @@ -895,6 +895,53 @@ private void doTestRandomSpecialMaxScore(float maxScore) throws IOException { dir.close(); } + /** Test advanceShallow when WAND is nested inside a conjunction with BlockScoreQueryWrapper. */ + public void testRandomNestedWAND() throws IOException { + Directory dir = newDirectory(); + IndexWriter w = new IndexWriter(dir, newIndexWriterConfig()); + int numDocs = atLeast(1000); + for (int i = 0; i < numDocs; ++i) { + Document doc = new Document(); + int numValues = random().nextInt(1 << random().nextInt(5)); + int start = random().nextInt(10); + for (int j = 0; j < numValues; ++j) { + doc.add(new StringField("foo", Integer.toString(start + j), Store.NO)); + } + w.addDocument(doc); + } + IndexReader reader = DirectoryReader.open(w); + w.close(); + // turn off concurrent search to avoid Random object used across threads resulting into + // RuntimeException, as WANDScorerQuery#createWeight has reference to this searcher, + // but will be called during searching + IndexSearcher searcher = newSearcher(reader, true, true, false); + + for (int iter = 0; iter < 10; ++iter) { + int start = random().nextInt(10); + int numClauses = random().nextInt(1 << random().nextInt(5)); + BooleanQuery.Builder builder = new BooleanQuery.Builder(); + for (int i = 0; i < numClauses; ++i) { + builder.add( + maybeWrap(new TermQuery(new Term("foo", Integer.toString(start + i)))), Occur.SHOULD); + } + Query wandQuery = new WANDScorerQuery(builder.build(), random().nextBoolean()); + + int blockLength = TestUtil.nextInt(random(), 2, 16); + Query wrappedWand = new BlockScoreQueryWrapper(wandQuery, blockLength); + + int filterTerm = random().nextInt(30); + Query nestedQuery = + new BooleanQuery.Builder() + .add(wrappedWand, Occur.MUST) + .add(new TermQuery(new Term("foo", Integer.toString(filterTerm))), Occur.MUST) + .build(); + + CheckHits.checkTopScores(random(), nestedQuery, searcher); + } + reader.close(); + dir.close(); + } + private static class MaxScoreWrapperScorer extends FilterScorer { private final int maxRange;