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 <[email protected]> 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
