This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit f20b7c2a4a4b34347b1f37b851e41cb28e0a649f
Author: kkasa <[email protected]>
AuthorDate: Thu Aug 1 15:25:00 2024 +0200

    [CALCITE-6513] FilterProjectTransposeRule may cause OOM when Project 
expressions are complex
    
    Close apache/calcite#3900
---
 .../java/org/apache/calcite/plan/RelOptUtil.java   | 22 +++++++++++++++
 .../rel/rules/FilterProjectTransposeRule.java      | 14 +++++++++-
 .../apache/calcite/rel/rules/ProjectMergeRule.java | 11 +++++---
 .../java/org/apache/calcite/tools/RelBuilder.java  |  2 +-
 .../org/apache/calcite/test/RelOptRulesTest.java   | 31 ++++++++++++++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 26 ++++++++++++++++++
 6 files changed, 100 insertions(+), 6 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index 5387acd8cf..ffd1252e56 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -53,7 +53,9 @@ import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.rules.CoreRules;
+import org.apache.calcite.rel.rules.FilterProjectTransposeRule;
 import org.apache.calcite.rel.rules.MultiJoin;
+import org.apache.calcite.rel.rules.ProjectMergeRule;
 import org.apache.calcite.rel.stream.StreamRules;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -121,6 +123,7 @@ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.Deque;
 import java.util.HashMap;
@@ -148,6 +151,15 @@ public abstract class RelOptUtil {
 
   public static final double EPSILON = 1.0e-5;
 
+  /** Default amount by which the complexity of a {@link Project} or
+   * {@link Filter} may increase when applying a rule. (Complexity is,
+   * roughly, the number of {@link RexNode}s in all expressions.)
+   *
+   * @see ProjectMergeRule.Config#bloat()
+   * @see FilterProjectTransposeRule.Config#bloat()
+   * @see RelBuilder.Config#bloat() */
+  public static final int DEFAULT_BLOAT = 100;
+
   @SuppressWarnings("Guava")
   @Deprecated // to be removed before 2.0
   public static final com.google.common.base.Predicate<Filter>
@@ -3168,6 +3180,16 @@ public abstract class RelOptUtil {
     return pushShuttle(project).visitList(nodes);
   }
 
+  public static @Nullable RexNode pushPastProjectUnlessBloat(RexNode node,
+      Project project, int bloat) {
+    List<RexNode> newConditions =
+        pushPastProjectUnlessBloat(Collections.singletonList(node), project, 
bloat);
+    if (newConditions == null || newConditions.size() != 1) {
+      return null;
+    }
+    return newConditions.get(0);
+  }
+
   /** As {@link #pushPastProject}, but returns null if the resulting 
expressions
    * are significantly more complex.
    *
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
index 5be607f821..726c07bb14 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
@@ -164,7 +164,10 @@ public class FilterProjectTransposeRule
     }
     // convert the filter to one that references the child of the project
     RexNode newCondition =
-        RelOptUtil.pushPastProject(filter.getCondition(), project);
+        RelOptUtil.pushPastProjectUnlessBloat(filter.getCondition(), project, 
config.bloat());
+    if (newCondition == null) {
+      return;
+    }
 
     final RelBuilder relBuilder = call.builder();
     RelNode newFilterRel;
@@ -258,5 +261,14 @@ public class FilterProjectTransposeRule
                   b2.operand(relClass).anyInputs())))
           .as(Config.class);
     }
+
+    /** Limit how much complexity can increase during merging.
+     * Default is {@link RelOptUtil#DEFAULT_BLOAT}. */
+    @Value.Default default int bloat() {
+      return RelOptUtil.DEFAULT_BLOAT;
+    }
+
+    /** Sets {@link #bloat()}. */
+    Config withBloat(int bloat);
   }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
index 31f7e182a8..4608a8c8bc 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectMergeRule.java
@@ -45,8 +45,11 @@ public class ProjectMergeRule
     implements TransformationRule {
   /** Default amount by which complexity is allowed to increase.
    *
-   * @see Config#bloat() */
-  public static final int DEFAULT_BLOAT = 100;
+   * @see Config#bloat()
+   * @deprecated please use {@link RelOptUtil#DEFAULT_BLOAT}
+   */
+  @Deprecated // to be removed before 2.0
+  public static final int DEFAULT_BLOAT = RelOptUtil.DEFAULT_BLOAT;
 
   /** Creates a ProjectMergeRule. */
   protected ProjectMergeRule(Config config) {
@@ -155,9 +158,9 @@ public class ProjectMergeRule
     }
 
     /** Limit how much complexity can increase during merging.
-     * Default is {@link #DEFAULT_BLOAT} (100). */
+     * Default is {@link RelOptUtil#DEFAULT_BLOAT} (100). */
     @Value.Default default int bloat() {
-      return ProjectMergeRule.DEFAULT_BLOAT;
+      return RelOptUtil.DEFAULT_BLOAT;
     }
 
     /** Sets {@link #bloat()}. */
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java 
b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 048012e57f..908915e48b 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -4905,7 +4905,7 @@ public class RelBuilder {
      * gather common sub-expressions and compute them only once.
      */
     @Value.Default default int bloat() {
-      return 100;
+      return RelOptUtil.DEFAULT_BLOAT;
     }
 
     /** Sets {@link #bloat}. */
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java 
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index be44def3b6..f9eb6dad23 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -2699,6 +2699,37 @@ class RelOptRulesTest extends RelOptTestBase {
         .check();
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6513";>[CALCITE-6513]
+   * FilterProjectTransposeRule may cause OOM when Project expressions are
+   * complex</a>. */
+  @Test void testFilterProjectTransposeBloat() {
+    String sql =
+        "SELECT x1 from\n"
+        + "    (SELECT 'L1' || x0  || x0 || x0 || x0 as x1 from\n"
+        + "        (SELECT 'L0' || ENAME || ENAME || ENAME || ENAME as x0 from 
emp) t1) t2\n"
+        + "WHERE x1 = 'Something'";
+
+    final FilterProjectTransposeRule filterProjectTransposeRule =
+        CoreRules.FILTER_PROJECT_TRANSPOSE.config
+            .withOperandSupplier(b0 ->
+                b0.operand(Filter.class).predicate(filter -> true)
+                    .oneInput(b1 ->
+                        b1.operand(Project.class).predicate(project -> true)
+                            .anyInputs()))
+            .as(FilterProjectTransposeRule.Config.class)
+            .withCopyFilter(true)
+            .withCopyProject(true)
+            .withBloat(3)
+            .toRule();
+    sql(sql)
+        .withRelBuilderConfig(config -> config.withBloat(3))
+        .withDecorrelate(false)
+        .withExpand(true)
+        .withRule(filterProjectTransposeRule)
+        .check();
+  }
+
   private static final String NOT_STRONG_EXPR =
       "case when e.sal < 11 then 11 else -1 * e.sal end";
 
diff --git 
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml 
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index b9b4e85aa6..a849461992 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -4607,6 +4607,32 @@ LogicalProject(EMPNO=[$0])
           LogicalProject(TWICEDEPTNO=[*($0, 2)])
             LogicalFilter(condition=[=($cor0.DEPTNO, *($0, 2))])
               LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testFilterProjectTransposeBloat">
+    <Resource name="sql">
+      <![CDATA[SELECT x1 from
+    (SELECT 'L1' || x0  || x0 || x0 || x0 as x1 from
+        (SELECT 'L0' || ENAME || ENAME || ENAME || ENAME as x0 from emp) t1) t2
+WHERE x1 = 'Something']]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(X1=[$0])
+  LogicalFilter(condition=[=($0, 'Something')])
+    LogicalProject(X1=[||(||(||(||('L1', $0), $0), $0), $0)])
+      LogicalProject(X0=[||(||(||(||('L0', $1), $1), $1), $1)])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(X1=[$0])
+  LogicalProject(X1=[||(||(||(||('L1', $0), $0), $0), $0)])
+    LogicalFilter(condition=[=(||(||(||(||('L1', $0), $0), $0), $0), 
'Something')])
+      LogicalProject(X0=[||(||(||(||('L0', $1), $1), $1), $1)])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
   </TestCase>

Reply via email to