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

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


The following commit(s) were added to refs/heads/main by this push:
     new b49a4e8348 [CALCITE-5985] FilterTableFunctionTransposeRule should not 
use "Logical" RelNodes
b49a4e8348 is described below

commit b49a4e8348e95d30a26447a08f9f2769891d3399
Author: Zhen Chen <[email protected]>
AuthorDate: Tue Apr 22 13:48:30 2025 +0800

    [CALCITE-5985] FilterTableFunctionTransposeRule should not use "Logical" 
RelNodes
---
 .../org/apache/calcite/rel/rules/CoreRules.java    | 12 ++++---
 .../rules/FilterTableFunctionTransposeRule.java    | 33 +++++++++++--------
 .../org/apache/calcite/test/RelOptRulesTest.java   | 14 ++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 23 +++++++++++++
 .../apache/calcite/test/MockSqlOperatorTable.java  | 38 ++++++++++++++++++++++
 5 files changed, 101 insertions(+), 19 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java 
b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
index f7aa685e87..fb4fdd5ea7 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
@@ -30,6 +30,7 @@
 import org.apache.calcite.rel.core.Sample;
 import org.apache.calcite.rel.core.SetOp;
 import org.apache.calcite.rel.core.Sort;
+import org.apache.calcite.rel.core.TableFunctionScan;
 import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.core.Union;
 import org.apache.calcite.rel.core.Values;
@@ -42,8 +43,8 @@
 import org.apache.calcite.rel.logical.LogicalMatch;
 import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.logical.LogicalSortExchange;
-import org.apache.calcite.rel.logical.LogicalTableFunctionScan;
 import org.apache.calcite.rel.logical.LogicalWindow;
+import org.apache.calcite.rel.rules.FilterTableFunctionTransposeRule.Config;
 import org.apache.calcite.rel.rules.materialize.MaterializedViewRules;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexUtil;
@@ -285,11 +286,12 @@ private CoreRules() {}
   public static final FilterSampleTransposeRule FILTER_SAMPLE_TRANSPOSE =
       FilterSampleTransposeRule.Config.DEFAULT.toRule();
 
-  /** Rule that pushes a {@link LogicalFilter}
-   * past a {@link LogicalTableFunctionScan}. */
+  /** Rule that pushes a {@link Filter}
+   * past a {@link TableFunctionScan}. */
   public static final FilterTableFunctionTransposeRule
-      FILTER_TABLE_FUNCTION_TRANSPOSE =
-      FilterTableFunctionTransposeRule.Config.DEFAULT.toRule();
+      FILTER_TABLE_FUNCTION_TRANSPOSE = Config.DEFAULT
+          .withOperandFor(Filter.class, TableFunctionScan.class)
+          .toRule();
 
   /** Rule that matches a {@link Filter} on a {@link TableScan}. */
   public static final FilterTableScanRule FILTER_SCAN =
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterTableFunctionTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterTableFunctionTransposeRule.java
index fbd67a0008..3b16949c5d 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterTableFunctionTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterTableFunctionTransposeRule.java
@@ -16,11 +16,12 @@
  */
 package org.apache.calcite.rel.rules;
 
-import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelRule;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rel.core.TableFunctionScan;
 import org.apache.calcite.rel.logical.LogicalFilter;
 import org.apache.calcite.rel.logical.LogicalTableFunctionScan;
 import org.apache.calcite.rel.metadata.RelColumnMapping;
@@ -55,14 +56,15 @@ protected FilterTableFunctionTransposeRule(Config config) {
   @Deprecated // to be removed before 2.0
   public FilterTableFunctionTransposeRule(RelBuilderFactory relBuilderFactory) 
{
     this(Config.DEFAULT.withRelBuilderFactory(relBuilderFactory)
-        .as(Config.class));
+        .as(Config.class)
+        .withOperandFor(LogicalFilter.class, LogicalTableFunctionScan.class));
   }
 
   //~ Methods ----------------------------------------------------------------
 
   @Override public void onMatch(RelOptRuleCall call) {
-    LogicalFilter filter = call.rel(0);
-    LogicalTableFunctionScan funcRel = call.rel(1);
+    Filter filter = call.rel(0);
+    TableFunctionScan funcRel = call.rel(1);
     Set<RelColumnMapping> columnMappings = funcRel.getColumnMappings();
     if (columnMappings == null || columnMappings.isEmpty()) {
       // No column mapping information, so no push-down
@@ -90,7 +92,6 @@ public FilterTableFunctionTransposeRule(RelBuilderFactory 
relBuilderFactory) {
       }
     }
     final List<RelNode> newFuncInputs = new ArrayList<>();
-    final RelOptCluster cluster = funcRel.getCluster();
     final RexNode condition = filter.getCondition();
 
     // create filters on top of each func input, modifying the filter
@@ -109,27 +110,31 @@ public FilterTableFunctionTransposeRule(RelBuilderFactory 
relBuilderFactory) {
                   funcInput.getRowType().getFieldList(),
                   adjustments));
       newFuncInputs.add(
-          LogicalFilter.create(funcInput, newCondition));
+          call.builder().push(funcInput)
+              .filter(newCondition)
+              .build());
     }
 
     // create a new UDX whose children are the filters created above
-    LogicalTableFunctionScan newFuncRel =
-        LogicalTableFunctionScan.create(cluster, newFuncInputs,
-            funcRel.getCall(), funcRel.getElementType(), funcRel.getRowType(),
-            columnMappings);
+    RelNode newFuncRel = funcRel.copy(funcRel.getTraitSet(), newFuncInputs);
     call.transformTo(newFuncRel);
   }
 
   /** Rule configuration. */
   @Value.Immutable
   public interface Config extends RelRule.Config {
-    Config DEFAULT = ImmutableFilterTableFunctionTransposeRule.Config.of()
-        .withOperandSupplier(b0 ->
-            b0.operand(LogicalFilter.class).oneInput(b1 ->
-                b1.operand(LogicalTableFunctionScan.class).anyInputs()));
+    Config DEFAULT = ImmutableFilterTableFunctionTransposeRule.Config.of();
 
     @Override default FilterTableFunctionTransposeRule toRule() {
       return new FilterTableFunctionTransposeRule(this);
     }
+
+    default Config withOperandFor(Class<? extends Filter> filterClass,
+        Class<? extends TableFunctionScan> tableFunctionScanClass) {
+      return withOperandSupplier(b0 ->
+          b0.operand(filterClass).oneInput(b1 ->
+              b1.operand(tableFunctionScanClass).anyInputs()))
+          .as(Config.class);
+    }
   }
 }
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 1718d2e788..90680ca528 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -2374,6 +2374,20 @@ private void 
checkSemiOrAntiJoinProjectTranspose(JoinRelType type) {
         .check();
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-5985";>[CALCITE-5985]
+   * FilterTableFunctionTransposeRule should not use "Logical" RelNodes</a>. */
+  @Test void testFilterTableFunctionScanTranspose() {
+    // "tfrt" table function is the one from MockSqlOperatorTable,
+    // that implement TableFunctionReturnTypeInference.
+    final String sql = "select * from table(tfrt(cursor(select name from 
dept)))"
+        + " where name = '1'";
+
+    sql(sql)
+        .withRule(CoreRules.FILTER_TABLE_FUNCTION_TRANSPOSE)
+        .check();
+  }
+
   @Test void testDistinctWithFilterAndGroupBy() {
     final String sql = "SELECT deptno, SUM(comm), COUNT(DISTINCT sal) FILTER 
(WHERE sal > 1000)\n"
         + "FROM emp\n"
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 c2062e20ac..180f98499c 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -5446,6 +5446,29 @@ LogicalFilter(condition=[<($0, 10)])
   LogicalSort(sort0=[$0], dir0=[ASC], offset=[1], fetch=[0])
     LogicalProject(EMPNO=[$0])
       LogicalTableScan(table=[[scott, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testFilterTableFunctionScanTranspose">
+    <Resource name="sql">
+      <![CDATA[select * from table(tfrt(cursor(select name from dept))) where 
name = '1']]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(NAME=[$0])
+  LogicalFilter(condition=[=($0, '1')])
+    LogicalTableFunctionScan(invocation=[TFRT(CAST($0):CURSOR NOT NULL)], 
rowType=[RecordType(VARCHAR(10) NAME)])
+      LogicalProject(NAME=[$1])
+        LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(NAME=[$0])
+  LogicalTableFunctionScan(invocation=[TFRT(CAST($0):CURSOR NOT NULL)], 
rowType=[RecordType(VARCHAR(10) NAME)])
+    LogicalFilter(condition=[=($0, '1')])
+      LogicalProject(NAME=[$1])
+        LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
     </Resource>
   </TestCase>
diff --git 
a/testkit/src/main/java/org/apache/calcite/test/MockSqlOperatorTable.java 
b/testkit/src/main/java/org/apache/calcite/test/MockSqlOperatorTable.java
index 242bcf8b42..a2eaae23d2 100644
--- a/testkit/src/main/java/org/apache/calcite/test/MockSqlOperatorTable.java
+++ b/testkit/src/main/java/org/apache/calcite/test/MockSqlOperatorTable.java
@@ -44,6 +44,7 @@
 import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql.type.TableFunctionReturnTypeInference;
 import org.apache.calcite.sql.util.ChainedSqlOperatorTable;
 import org.apache.calcite.sql.util.SqlOperatorTables;
 import org.apache.calcite.sql.validate.SqlValidator;
@@ -52,6 +53,9 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.List;
 import java.util.Map;
@@ -88,6 +92,7 @@ public MockSqlOperatorTable extend() {
         SqlOperatorTables.chain(parentTable,
             SqlOperatorTables.of(new RampFunction(),
                 new DedupFunction(),
+                new TableFunctionReturnTableFunction(),
                 new MyFunction(),
                 new MyAvgAggFunction(),
                 new RowFunction(),
@@ -208,6 +213,39 @@ public DedupFunction() {
     }
   }
 
+  /** "TFRT" user-defined table function. */
+  public static class TableFunctionReturnTableFunction extends SqlFunction
+      implements SqlTableFunction {
+    TableFunctionReturnTypeInference inference;
+
+    public TableFunctionReturnTableFunction() {
+      super("TFRT",
+          SqlKind.OTHER_FUNCTION,
+          null,
+          null,
+          OperandTypes.VARIADIC,
+          SqlFunctionCategory.USER_DEFINED_FUNCTION);
+    }
+
+    @Override public RelDataType inferReturnType(SqlOperatorBinding opBinding) 
{
+      inference =
+          new TableFunctionReturnTypeInference(factory -> factory.builder()
+              .add("NAME", SqlTypeName.CURSOR)
+              .build(),
+          Lists.newArrayList("NAME"), true);
+      inference.inferReturnType(opBinding);
+      return opBinding.getTypeFactory().createSqlType(SqlTypeName.CURSOR);
+    }
+
+    @Override public @Nullable SqlReturnTypeInference getReturnTypeInference() 
{
+      return inference;
+    }
+
+    @Override public SqlReturnTypeInference getRowTypeInference() {
+      return inference;
+    }
+  }
+
   /** "Score" user-defined table function. First parameter is input table
    * with row semantics. */
   public static class ScoreTableFunction extends SqlFunction

Reply via email to