diff --git a/lucene/core/src/java/org/apache/lucene/search/AbstractMultiTermQueryConstantScoreWrapper.java b/lucene/core/src/java/org/apache/lucene/search/AbstractMultiTermQueryConstantScoreWrapper.java index b6ee4db540af..e8e83f479e24 100644 --- a/lucene/core/src/java/org/apache/lucene/search/AbstractMultiTermQueryConstantScoreWrapper.java +++ b/lucene/core/src/java/org/apache/lucene/search/AbstractMultiTermQueryConstantScoreWrapper.java @@ -126,14 +126,15 @@ protected static final class WeightOrDocIdSetIterator { protected abstract static class RewritingWeight extends ConstantScoreWeight { private final MultiTermQuery q; private final ScoreMode scoreMode; - private final IndexSearcher searcher; + private final IndexSearcher nonCachingSearcher; protected RewritingWeight( MultiTermQuery q, float boost, ScoreMode scoreMode, IndexSearcher searcher) { super(q, boost); this.q = q; this.scoreMode = scoreMode; - this.searcher = searcher; + this.nonCachingSearcher = new IndexSearcher(searcher); + this.nonCachingSearcher.setQueryCache(null); } /** @@ -158,12 +159,13 @@ private WeightOrDocIdSetIterator rewriteAsBooleanQuery( LeafReaderContext context, List collectedTerms) throws IOException { BooleanQuery.Builder bq = new BooleanQuery.Builder(); for (TermAndState t : collectedTerms) { - final TermStates termStates = new TermStates(searcher.getTopReaderContext()); + final TermStates termStates = new TermStates(nonCachingSearcher.getTopReaderContext()); termStates.register(t.state, context.ord, t.docFreq, t.totalTermFreq); bq.add(new TermQuery(new Term(q.field, t.term), termStates), BooleanClause.Occur.SHOULD); } Query q = new ConstantScoreQuery(bq.build()); - final Weight weight = searcher.rewrite(q).createWeight(searcher, scoreMode, score()); + final Weight weight = + nonCachingSearcher.rewrite(q).createWeight(nonCachingSearcher, scoreMode, score()); return new WeightOrDocIdSetIterator(weight); } diff --git a/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java b/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java index d1079b69089a..837323484547 100644 --- a/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java +++ b/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java @@ -242,6 +242,15 @@ public IndexSearcher(IndexReaderContext context) { this(context, null); } + /** Reuses everything except the executor */ + public IndexSearcher(IndexSearcher searcher) { + this(searcher.getTopReaderContext()); + this.similarity = searcher.getSimilarity(); + this.queryCache = searcher.getQueryCache(); + this.queryCachingPolicy = searcher.getQueryCachingPolicy(); + this.queryTimeout = searcher.getTimeout(); + } + /** * Return the maximum number of clauses permitted, 1024 by default. Attempts to add more than the * permitted number of clauses cause {@link TooManyClauses} to be thrown. diff --git a/lucene/core/src/test/org/apache/lucene/search/TestTermInSetQuery.java b/lucene/core/src/test/org/apache/lucene/search/TestTermInSetQuery.java index 9ea2d4672bdc..313b101190ea 100644 --- a/lucene/core/src/test/org/apache/lucene/search/TestTermInSetQuery.java +++ b/lucene/core/src/test/org/apache/lucene/search/TestTermInSetQuery.java @@ -58,6 +58,71 @@ public class TestTermInSetQuery extends LuceneTestCase { + public void testCachingPolicyInteraction() throws IOException { + Directory dir = newDirectory(); + RandomIndexWriter iw = new RandomIndexWriter(random(), dir); + // Use few enough terms to trigger the BooleanQuery rewrite logic (≤ threshold) + final int numTerms = + AbstractMultiTermQueryConstantScoreWrapper.BOOLEAN_REWRITE_TERM_COUNT_THRESHOLD; + List terms = new ArrayList<>(); + for (int i = 0; i < numTerms; ++i) { + String term = "term" + i; + terms.add(newBytesRef(term)); + Document doc = new Document(); + doc.add(new StringField("field", term, Store.NO)); + iw.addDocument(doc); + } + iw.commit(); + IndexReader reader = iw.getReader(); + IndexSearcher searcher = newSearcher(reader); + iw.close(); + + final AtomicInteger onUseCount = new AtomicInteger(0); + final Set seenQueries = new HashSet<>(); + QueryCachingPolicy policy = + new QueryCachingPolicy() { + @Override + public void onUse(Query query) { + onUseCount.incrementAndGet(); + seenQueries.add(query); + } + + @Override + public boolean shouldCache(Query query) throws IOException { + return true; + } + }; + + searcher.setQueryCache(new LRUQueryCache(100, 10000)); + searcher.setQueryCachingPolicy(policy); + + TermInSetQuery query = new TermInSetQuery("field", terms); + // use count() to ensure scores are not needed, which triggers caching logic + searcher.count(query); + + // We expect only the top-level TermInSetQuery to be tracked. + // The inner rewrites (ConstantScoreQuery wrapping BooleanQuery) should + // effectively bypass the + // cache because they are executed by the non-caching private searcher. + // Verify that no BooleanQuery or ConstantScoreQuery wrapping BooleanQuery was + // tracked + assertFalse( + "Segment-specific BooleanQuery rewrites should not be tracked", + seenQueries.stream().anyMatch(q -> q instanceof BooleanQuery)); + assertFalse( + "ConstantScoreQuery wrapping BooleanQuery should not be tracked", + seenQueries.stream() + .anyMatch( + q -> + q instanceof ConstantScoreQuery + && ((ConstantScoreQuery) q).getQuery() instanceof BooleanQuery)); + // The TermInSetQuery itself should be tracked + assertTrue("TermInSetQuery should be tracked", seenQueries.contains(query)); + + reader.close(); + dir.close(); + } + public void testAllDocsInFieldTerm() throws IOException { Directory dir = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), dir);