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