You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2019/03/30 02:19:40 UTC

[calcite] 01/04: [CALCITE-1515] In RelBuilder, add functionScan method to create TableFunctionScan (Chunwei Lei)

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

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

commit a3c0abd920e65afd47abc2afffd645d6f851ad15
Author: Chunwei Lei <ch...@alibaba-inc.com>
AuthorDate: Mon Mar 11 20:27:15 2019 +0800

    [CALCITE-1515] In RelBuilder, add functionScan method to create TableFunctionScan (Chunwei Lei)
    
    Allow RelBuilder.functionScan() to have 0 relational inputs, rework
    the RexCall produced by the CURSOR function, and add an overload of
    functionScan with "(RexNode...)" arguments. (Julian Hyde)
    
    Close apache/calcite#1102
---
 .../org/apache/calcite/rel/core/RelFactories.java  | 32 ++++++++++
 .../apache/calcite/rel/core/TableFunctionScan.java |  5 +-
 .../rel/logical/LogicalTableFunctionScan.java      |  9 +--
 .../java/org/apache/calcite/tools/RelBuilder.java  | 69 ++++++++++++++++++++++
 .../org/apache/calcite/test/RelBuilderTest.java    | 54 +++++++++++++++++
 site/_docs/algebra.md                              |  3 +
 6 files changed, 166 insertions(+), 6 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
index 5980f3d..681c690 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
@@ -36,9 +36,11 @@ import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.logical.LogicalSnapshot;
 import org.apache.calcite.rel.logical.LogicalSort;
 import org.apache.calcite.rel.logical.LogicalSortExchange;
+import org.apache.calcite.rel.logical.LogicalTableFunctionScan;
 import org.apache.calcite.rel.logical.LogicalTableScan;
 import org.apache.calcite.rel.logical.LogicalUnion;
 import org.apache.calcite.rel.logical.LogicalValues;
+import org.apache.calcite.rel.metadata.RelColumnMapping;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
@@ -51,6 +53,7 @@ import org.apache.calcite.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
 
+import java.lang.reflect.Type;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -100,6 +103,9 @@ public class RelFactories {
   public static final TableScanFactory DEFAULT_TABLE_SCAN_FACTORY =
       new TableScanFactoryImpl();
 
+  public static final TableFunctionScanFactory
+      DEFAULT_TABLE_FUNCTION_SCAN_FACTORY = new TableFunctionScanFactoryImpl();
+
   public static final SnapshotFactory DEFAULT_SNAPSHOT_FACTORY =
       new SnapshotFactoryImpl();
 
@@ -499,6 +505,32 @@ public class RelFactories {
   }
 
   /**
+   * Can create a {@link TableFunctionScan}
+   * of the appropriate type for a rule's calling convention.
+   */
+  public interface TableFunctionScanFactory {
+    /** Creates a {@link TableFunctionScan}. */
+    RelNode createTableFunctionScan(RelOptCluster cluster,
+        List<RelNode> inputs, RexNode rexCall, Type elementType,
+        Set<RelColumnMapping> columnMappings);
+  }
+
+  /**
+   * Implementation of
+   * {@link TableFunctionScanFactory}
+   * that returns a {@link TableFunctionScan}.
+   */
+  private static class TableFunctionScanFactoryImpl
+      implements TableFunctionScanFactory {
+    @Override public RelNode createTableFunctionScan(RelOptCluster cluster,
+        List<RelNode> inputs, RexNode rexCall, Type elementType,
+        Set<RelColumnMapping> columnMappings) {
+      return LogicalTableFunctionScan.create(cluster, inputs, rexCall,
+          elementType, rexCall.getType(), columnMappings);
+    }
+  }
+
+  /**
    * Can create a {@link Snapshot} of
    * the appropriate type for a rule's calling convention.
    */
diff --git a/core/src/main/java/org/apache/calcite/rel/core/TableFunctionScan.java b/core/src/main/java/org/apache/calcite/rel/core/TableFunctionScan.java
index cdedbd0..0951609 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/TableFunctionScan.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/TableFunctionScan.java
@@ -64,6 +64,7 @@ public abstract class TableFunctionScan extends AbstractRelNode {
    *
    * @param cluster        Cluster that this relational expression belongs to
    * @param inputs         0 or more relational inputs
+   * @param traitSet       Trait set
    * @param rexCall        Function invocation expression
    * @param elementType    Element type of the collection that will implement
    *                       this table
@@ -72,13 +73,13 @@ public abstract class TableFunctionScan extends AbstractRelNode {
    */
   protected TableFunctionScan(
       RelOptCluster cluster,
-      RelTraitSet traits,
+      RelTraitSet traitSet,
       List<RelNode> inputs,
       RexNode rexCall,
       Type elementType,
       RelDataType rowType,
       Set<RelColumnMapping> columnMappings) {
-    super(cluster, traits);
+    super(cluster, traitSet);
     this.rexCall = rexCall;
     this.elementType = elementType;
     this.rowType = rowType;
diff --git a/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableFunctionScan.java b/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableFunctionScan.java
index ec18685..2852652 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableFunctionScan.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableFunctionScan.java
@@ -45,11 +45,12 @@ public class LogicalTableFunctionScan extends TableFunctionScan {
    *
    * @param cluster        Cluster that this relational expression belongs to
    * @param inputs         0 or more relational inputs
-   * @param rexCall        function invocation expression
-   * @param elementType    element type of the collection that will implement
+   * @param traitSet       Trait set
+   * @param rexCall        Function invocation expression
+   * @param elementType    Element type of the collection that will implement
    *                       this table
-   * @param rowType        row type produced by function
-   * @param columnMappings column mappings associated with this function
+   * @param rowType        Row type produced by function
+   * @param columnMappings Column mappings associated with this function
    */
   public LogicalTableFunctionScan(
       RelOptCluster cluster,
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 462d6cb..0d2972b 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -44,11 +44,13 @@ import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.core.SemiJoin;
 import org.apache.calcite.rel.core.Snapshot;
 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;
 import org.apache.calcite.rel.logical.LogicalFilter;
 import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.metadata.RelColumnMapping;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -72,7 +74,9 @@ import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlReturnTypeInference;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.TableFunctionReturnTypeInference;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
 import org.apache.calcite.util.Holder;
 import org.apache.calcite.util.ImmutableBitSet;
@@ -143,6 +147,7 @@ public class RelBuilder {
   private final RelFactories.CorrelateFactory correlateFactory;
   private final RelFactories.ValuesFactory valuesFactory;
   private final RelFactories.TableScanFactory scanFactory;
+  private final RelFactories.TableFunctionScanFactory tableFunctionScanFactory;
   private final RelFactories.SnapshotFactory snapshotFactory;
   private final RelFactories.MatchFactory matchFactory;
   private final Deque<Frame> stack = new ArrayDeque<>();
@@ -193,6 +198,9 @@ public class RelBuilder {
     this.scanFactory =
         Util.first(context.unwrap(RelFactories.TableScanFactory.class),
             RelFactories.DEFAULT_TABLE_SCAN_FACTORY);
+    this.tableFunctionScanFactory =
+        Util.first(context.unwrap(RelFactories.TableFunctionScanFactory.class),
+            RelFactories.DEFAULT_TABLE_FUNCTION_SCAN_FACTORY);
     this.snapshotFactory =
         Util.first(context.unwrap(RelFactories.SnapshotFactory.class),
             RelFactories.DEFAULT_SNAPSHOT_FACTORY);
@@ -1045,6 +1053,67 @@ public class RelBuilder {
     return this;
   }
 
+
+  /**
+   * Gets column mappings of the operator.
+   *
+   * @param op operator instance
+   * @return column mappings associated with this function
+   */
+  private Set<RelColumnMapping> getColumnMappings(SqlOperator op) {
+    SqlReturnTypeInference inference = op.getReturnTypeInference();
+    if (inference instanceof TableFunctionReturnTypeInference) {
+      return ((TableFunctionReturnTypeInference) inference).getColumnMappings();
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Creates a RexCall to the {@code CURSOR} function by ordinal.
+   *
+   * @param inputCount Number of inputs
+   * @param ordinal The reference to the relational input
+   * @return RexCall to CURSOR function
+   */
+  public RexNode cursor(int inputCount, int ordinal) {
+    if (inputCount <= ordinal || ordinal < 0) {
+      throw new IllegalArgumentException("bad input count or ordinal");
+    }
+    // Refer to the "ordinal"th input as if it were a field
+    // (because that's how things are laid out inside a TableFunctionScan)
+    final RelNode input = peek(inputCount, ordinal);
+    return call(SqlStdOperatorTable.CURSOR,
+        getRexBuilder().makeInputRef(input.getRowType(), ordinal));
+  }
+
+  /** Creates a {@link TableFunctionScan}. */
+  public RelBuilder functionScan(SqlOperator operator,
+      int inputCount, RexNode... operands) {
+    return functionScan(operator, inputCount, ImmutableList.copyOf(operands));
+  }
+
+  /** Creates a {@link TableFunctionScan}. */
+  public RelBuilder functionScan(SqlOperator operator,
+      int inputCount, Iterable<? extends RexNode> operands) {
+    if (inputCount < 0 || inputCount > stack.size()) {
+      throw new IllegalArgumentException("bad input count");
+    }
+
+    // Gets inputs.
+    final List<RelNode> inputs = new LinkedList<>();
+    for (int i = 0; i < inputCount; i++) {
+      inputs.add(0, build());
+    }
+
+    final RexNode call = call(operator, ImmutableList.copyOf(operands));
+    final RelNode functionScan =
+        tableFunctionScanFactory.createTableFunctionScan(cluster, inputs,
+            call, null, getColumnMappings(operator));
+    push(functionScan);
+    return this;
+  }
+
   /** Creates a {@link Filter} of an array of
    * predicates.
    *
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index 3ec2859..75feb03 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -43,6 +43,7 @@ import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.impl.ViewTable;
 import org.apache.calcite.schema.impl.ViewTableMacro;
 import org.apache.calcite.sql.SqlMatchRecognize;
+import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParser;
 import org.apache.calcite.sql.type.SqlTypeName;
@@ -75,12 +76,14 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.NoSuchElementException;
 import java.util.TreeSet;
 
 import static org.apache.calcite.test.Matchers.hasTree;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -298,6 +301,57 @@ public class RelBuilderTest {
     assertThat(root, hasTree(expected));
   }
 
+  @Test public void testTableFunctionScan() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM TABLE(
+    //       DEDUP(CURSOR(select * from emp),
+    //             CURSOR(select * from DEPT), 'NAME'))
+    final RelBuilder builder = RelBuilder.create(config().build());
+    final SqlOperator dedupFunction =
+        new MockSqlOperatorTable.DedupFunction();
+    RelNode root = builder.scan("EMP")
+        .scan("DEPT")
+        .functionScan(dedupFunction, 2, builder.cursor(2, 0),
+            builder.cursor(2, 1))
+        .build();
+    final String expected = "LogicalTableFunctionScan("
+        + "invocation=[DEDUP(CURSOR($0), CURSOR($1))], "
+        + "rowType=[RecordType(VARCHAR(1024) NAME)])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n"
+        + "  LogicalTableScan(table=[[scott, DEPT]])\n";
+    assertThat(root, hasTree(expected));
+
+    // Make sure that the builder's stack is empty.
+    try {
+      RelNode node = builder.build();
+      fail("expected error, got " + node);
+    } catch (NoSuchElementException e) {
+      assertNull(e.getMessage());
+    }
+  }
+
+  @Test public void testTableFunctionScanZeroInputs() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM TABLE(RAMP(3))
+    final RelBuilder builder = RelBuilder.create(config().build());
+    final SqlOperator rampFunction = new MockSqlOperatorTable.RampFunction();
+    RelNode root = builder.functionScan(rampFunction, 0, builder.literal(3))
+        .build();
+    final String expected = "LogicalTableFunctionScan(invocation=[RAMP(3)], "
+        + "rowType=[RecordType(INTEGER I)])\n";
+    assertThat(root, hasTree(expected));
+
+    // Make sure that the builder's stack is empty.
+    try {
+      RelNode node = builder.build();
+      fail("expected error, got " + node);
+    } catch (NoSuchElementException e) {
+      assertNull(e.getMessage());
+    }
+  }
+
   @Test public void testJoinTemporalTable() {
     // Equivalent SQL:
     //   SELECT *
diff --git a/site/_docs/algebra.md b/site/_docs/algebra.md
index fc51905..a68bb89 100644
--- a/site/_docs/algebra.md
+++ b/site/_docs/algebra.md
@@ -259,6 +259,7 @@ return the `RelBuilder`.
 | Method              | Description
 |:------------------- |:-----------
 | `scan(tableName)` | Creates a [TableScan]({{ site.apiRoot }}/org/apache/calcite/rel/core/TableScan.html).
+| `functionScan(operator, n, expr...)`<br/>`functionScan(operator, n, exprList)` | Creates a [TableFunctionScan]({{ site.apiRoot }}/org/apache/calcite/rel/core/TableFunctionScan.html) of the `n` most recent relational expressions.
 | `values(fieldNames, value...)`<br/>`values(rowType, tupleList)` | Creates a [Values]({{ site.apiRoot }}/org/apache/calcite/rel/core/Values.html).
 | `filter(expr...)`<br/>`filter(exprList)` | Creates a [Filter]({{ site.apiRoot }}/org/apache/calcite/rel/core/Filter.html) over the AND of the given predicates.
 | `project(expr...)`<br/>`project(exprList [, fieldNames])` | Creates a [Project]({{ site.apiRoot }}/org/apache/calcite/rel/core/Project.html). To override the default name, wrap expressions using `alias`, or specify the `fieldNames` argument.
@@ -304,6 +305,7 @@ Argument types:
 * `subsets` Map whose key is String, value is a sorted set of String
 * `distribution` [RelDistribution]({{ site.apiRoot }}/org/apache/calcite/rel/RelDistribution.html)
 * `collation` [RelCollation]({{ site.apiRoot }}/org/apache/calcite/rel/RelCollation.html)
+* `operator` [SqlOperator]({{ site.apiRoot }}/org/apache/calcite/sql/SqlOperator.html)
 
 The builder methods perform various optimizations, including:
 
@@ -364,6 +366,7 @@ added to the stack.
 | `desc(expr)` | Changes sort direction to descending (only valid as an argument to `sort` or `sortLimit`)
 | `nullsFirst(expr)` | Changes sort order to nulls first (only valid as an argument to `sort` or `sortLimit`)
 | `nullsLast(expr)` | Changes sort order to nulls last (only valid as an argument to `sort` or `sortLimit`)
+| `cursor(n, input)` | Reference to `input`th (0-based) relational input of a `TableFunctionScan` with `n` inputs (see `functionScan`)
 
 #### Pattern methods