javanna commented on code in PR #12072:
URL: https://github.com/apache/lucene/pull/12072#discussion_r1067144911


##########
lucene/core/src/test/org/apache/lucene/search/TestBooleanRewrites.java:
##########
@@ -322,6 +323,45 @@ public void testMatchAllMustNot() throws IOException {
     assertEquals(new MatchNoDocsQuery(), searcher.rewrite(bq2));
   }
 
+  public void testDeeplyNestedBooleanRewrite() throws IOException {
+    IndexSearcher searcher = newSearcher(new MultiReader());
+
+    Directory dir = newDirectory();
+    (new RandomIndexWriter(random(), dir)).close();
+    IndexReader r = DirectoryReader.open(dir);
+    Function<Integer, TermQuery> termQueryFunction =
+        (i) -> new TermQuery(new Term("layer[" + i + "]", "foo"));
+    int depth = TestUtil.nextInt(random(), 10, 30);
+    TermQuery tq = termQueryFunction.apply(depth);
+    Query expectedQuery = new BooleanQuery.Builder().add(tq, 
Occur.FILTER).build();

Review Comment:
   Would it make sense to write a similar test for boolean queries that do need 
scoring? I am worried that the wrapping we do in the main rewrite has some 
impact too but we don't exercise it as much with this test.



##########
lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java:
##########
@@ -203,9 +203,17 @@ BooleanQuery rewriteNoScoring(IndexSearcher indexSearcher) 
throws IOException {
 
     for (BooleanClause clause : clauses) {
       Query query = clause.getQuery();
-      Query rewritten = new ConstantScoreQuery(query).rewrite(indexSearcher);
-      if (rewritten instanceof ConstantScoreQuery) {
-        rewritten = ((ConstantScoreQuery) rewritten).getQuery();
+      Query rewritten;
+      // If the clause query is a boolean query, we shouldn't call 
ConstantScoreQuery#rewrite as
+      // this causes
+      // exponential growth of runtime.
+      if (query instanceof BooleanQuery booleanQuery) {
+        rewritten = booleanQuery.rewriteNoScoring(indexSearcher);

Review Comment:
   I would make the same change to the main rewrite method. That will reduce 
further the amount of time we spend on needlessly rewriting boolean queries.



##########
lucene/core/src/test/org/apache/lucene/search/TestBooleanRewrites.java:
##########
@@ -322,6 +323,45 @@ public void testMatchAllMustNot() throws IOException {
     assertEquals(new MatchNoDocsQuery(), searcher.rewrite(bq2));
   }
 
+  public void testDeeplyNestedBooleanRewrite() throws IOException {
+    IndexSearcher searcher = newSearcher(new MultiReader());
+
+    Directory dir = newDirectory();
+    (new RandomIndexWriter(random(), dir)).close();
+    IndexReader r = DirectoryReader.open(dir);
+    Function<Integer, TermQuery> termQueryFunction =
+        (i) -> new TermQuery(new Term("layer[" + i + "]", "foo"));
+    int depth = TestUtil.nextInt(random(), 10, 30);
+    TermQuery tq = termQueryFunction.apply(depth);
+    Query expectedQuery = new BooleanQuery.Builder().add(tq, 
Occur.FILTER).build();
+    Query deepBuilder = new BooleanQuery.Builder().add(tq, Occur.MUST).build();
+    for (int i = depth; i > 0; i--) {
+      tq = termQueryFunction.apply(i);
+      // Do this to accurately set setMinimumNumberShouldMatch to the number 
of should clauses.
+      // This makes setting expectation for rewrite much easier.
+      boolean useShoulds = random().nextBoolean();

Review Comment:
   I wonder if we should have a separate test with should clauses, just to 
simplify the test a bit.



##########
lucene/core/src/test/org/apache/lucene/search/TestBooleanRewrites.java:
##########
@@ -322,6 +323,45 @@ public void testMatchAllMustNot() throws IOException {
     assertEquals(new MatchNoDocsQuery(), searcher.rewrite(bq2));
   }
 
+  public void testDeeplyNestedBooleanRewrite() throws IOException {
+    IndexSearcher searcher = newSearcher(new MultiReader());
+
+    Directory dir = newDirectory();
+    (new RandomIndexWriter(random(), dir)).close();
+    IndexReader r = DirectoryReader.open(dir);
+    Function<Integer, TermQuery> termQueryFunction =
+        (i) -> new TermQuery(new Term("layer[" + i + "]", "foo"));
+    int depth = TestUtil.nextInt(random(), 10, 30);
+    TermQuery tq = termQueryFunction.apply(depth);
+    Query expectedQuery = new BooleanQuery.Builder().add(tq, 
Occur.FILTER).build();
+    Query deepBuilder = new BooleanQuery.Builder().add(tq, Occur.MUST).build();
+    for (int i = depth; i > 0; i--) {
+      tq = termQueryFunction.apply(i);
+      // Do this to accurately set setMinimumNumberShouldMatch to the number 
of should clauses.
+      // This makes setting expectation for rewrite much easier.
+      boolean useShoulds = random().nextBoolean();
+      BooleanQuery.Builder bq =
+          new BooleanQuery.Builder()
+              .setMinimumNumberShouldMatch(useShoulds ? 2 : 0)
+              .add(tq, useShoulds ? Occur.SHOULD : Occur.MUST)
+              .add(deepBuilder, useShoulds ? Occur.SHOULD : Occur.MUST);
+      deepBuilder = bq.build();
+      BooleanQuery.Builder expectedBq = new BooleanQuery.Builder().add(tq, 
Occur.FILTER);
+      if (i == depth - 1) {
+        expectedBq.add(termQueryFunction.apply(depth), Occur.FILTER);
+      } else {
+        expectedBq.add(expectedQuery, Occur.FILTER);
+      }
+      expectedQuery = expectedBq.build();
+    }
+    BooleanQuery bq = new BooleanQuery.Builder().add(deepBuilder, 
Occur.FILTER).build();
+    expectedQuery = new BoostQuery(new ConstantScoreQuery(expectedQuery), 
0.0f);
+    Query rewritten = searcher.rewrite(bq);
+    r.close();
+    dir.close();
+    assertEquals(expectedQuery, rewritten);

Review Comment:
   Does this test actually verifies that we are not doing any needless 
rewriting? I was under the impression that how long the test takes depending on 
the depth of the boolean query is the only way to catch a regression. Would it 
be possible to write specific tests that verify the intermediate steps by 
calling the query rewrite method against some of the intermediate 
representations of the query and checking what is returned? I have not given 
this a lot of thought, it may be hard... but I am curious to hear what you 
think.



##########
lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java:
##########
@@ -203,9 +203,17 @@ BooleanQuery rewriteNoScoring(IndexSearcher indexSearcher) 
throws IOException {
 
     for (BooleanClause clause : clauses) {
       Query query = clause.getQuery();
-      Query rewritten = new ConstantScoreQuery(query).rewrite(indexSearcher);
-      if (rewritten instanceof ConstantScoreQuery) {
-        rewritten = ((ConstantScoreQuery) rewritten).getQuery();
+      Query rewritten;
+      // If the clause query is a boolean query, we shouldn't call 
ConstantScoreQuery#rewrite as
+      // this causes
+      // exponential growth of runtime.
+      if (query instanceof BooleanQuery booleanQuery) {
+        rewritten = booleanQuery.rewriteNoScoring(indexSearcher);
+      } else {
+        rewritten = new ConstantScoreQuery(query).rewrite(indexSearcher);
+        if (rewritten instanceof ConstantScoreQuery constantScoreQuery) {
+          rewritten = constantScoreQuery.getQuery();
+        }

Review Comment:
   I agree. I am tempted to go and simplify the logic further (do we need the 
wrapping at all?) but I will leave that for a follow-up. I like how your fix is 
contained.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to