Skip to content

Commit

Permalink
fix up
Browse files Browse the repository at this point in the history
RelBuilder.functionScan() can have 0 relational inputs (Julian Hyde)

Close apache#1102
  • Loading branch information
julianhyde committed Mar 28, 2019
1 parent f06cf50 commit bd116f4
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 70 deletions.
60 changes: 29 additions & 31 deletions core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ public class RelFactories {
public static final TableScanFactory DEFAULT_TABLE_SCAN_FACTORY =
new TableScanFactoryImpl();

public static final SnapshotFactory DEFAULT_SNAPSHOT_FACTORY =
new SnapshotFactoryImpl();

public static final TableFunctionScanFactory
DEFAULT_TABLE_FUNCTION_SCAN_FACTORY = new TableFunctionScanFactoryImpl();

public static final SnapshotFactory DEFAULT_SNAPSHOT_FACTORY =
new SnapshotFactoryImpl();

/** A {@link RelBuilderFactory} that creates a {@link RelBuilder} that will
* create logical relational expressions for everything. */
public static final RelBuilderFactory LOGICAL_BUILDER =
Expand Down Expand Up @@ -504,6 +504,32 @@ public RelNode createScan(RelOptCluster cluster, RelOptTable table) {
};
}

/**
* 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.
Expand Down Expand Up @@ -555,34 +581,6 @@ public RelNode createMatch(RelNode input, RexNode pattern,
partitionKeys, orderKeys, interval);
}
}

/**
* 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);
}
}
}

// End RelFactories.java
54 changes: 26 additions & 28 deletions core/src/main/java/org/apache/calcite/tools/RelBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ 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 RelFactories.TableFunctionScanFactory tableFunctionScanFactory;
private final Deque<Frame> stack = new ArrayDeque<>();
private final boolean simplify;
private final RexSimplify simplifier;
Expand Down Expand Up @@ -198,15 +198,15 @@ protected RelBuilder(Context context, RelOptCluster cluster,
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);
this.matchFactory =
Util.first(context.unwrap(RelFactories.MatchFactory.class),
RelFactories.DEFAULT_MATCH_FACTORY);
this.tableFunctionScanFactory =
Util.first(context.unwrap(RelFactories.TableFunctionScanFactory.class),
RelFactories.DEFAULT_TABLE_FUNCTION_SCAN_FACTORY);
final RexExecutor executor =
Util.first(context.unwrap(RexExecutor.class),
Util.first(cluster.getPlanner().getExecutor(), RexUtil.EXECUTOR));
Expand Down Expand Up @@ -1070,48 +1070,46 @@ private Set<RelColumnMapping> getColumnMappings(SqlOperator op) {
}

/**
* Create a RexCall to the CURSOR function by ordinal.
* 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 || inputCount < 1) {
throw new IllegalArgumentException(
"bad input count or ordinal");
if (inputCount <= ordinal || ordinal < 0) {
throw new IllegalArgumentException("bad input count or ordinal");
}
return getRexBuilder().makeCall(SqlStdOperatorTable.CURSOR,
getRexBuilder().makeLiteral(inputCount, getTypeFactory()
.createSqlType(SqlTypeName.INTEGER), false),
getRexBuilder()
.makeInputRef(peek(inputCount, ordinal).getRowType(), 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}.
*
*/
/** 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 < 1 || inputCount > stack.size()) {
throw new IllegalArgumentException(
"bad input count");
if (inputCount < 0 || inputCount > stack.size()) {
throw new IllegalArgumentException("bad input count");
}

// Gets inputs.
List<RelNode> inputs = new LinkedList<>();
final List<RelNode> inputs = new LinkedList<>();
for (int i = 0; i < inputCount; i++) {
inputs.add(0, build());
}

RelNode functionScan =
tableFunctionScanFactory.createTableFunctionScan(cluster,
inputs,
getRexBuilder()
.makeCall(operator, ImmutableList.copyOf(operands)),
null,
getColumnMappings(operator));
final RexNode call = call(operator, ImmutableList.copyOf(operands));
final RelNode functionScan =
tableFunctionScanFactory.createTableFunctionScan(cluster, inputs,
call, null, getColumnMappings(operator));
push(functionScan);
return this;
}
Expand Down
48 changes: 38 additions & 10 deletions core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
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;
Expand Down Expand Up @@ -82,6 +83,7 @@

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;
Expand Down Expand Up @@ -302,25 +304,51 @@ static Frameworks.ConfigBuilder expandingConfig(Connection connection)
@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());
RelNode root = builder.scan("EMP").scan("DEPT")
.functionScan(new MockSqlOperatorTable.DedupFunction(), 2,
Lists.newArrayList(builder.cursor(2, 0),
builder.cursor(2, 1))).build();
final String expected = "LogicalTableFunctionScan(invocation="
+ "[DEDUP(CURSOR(2, $0), CURSOR(2, $1))], "
// 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));

// Test whether builder is empty now.
// 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) {
assertTrue(e.getMessage() == null);
assertNull(e.getMessage());
}
}

Expand Down
3 changes: 2 additions & 1 deletion site/_docs/algebra.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -279,7 +280,6 @@ return the `RelBuilder`.
| `minus(all)` | Creates a [Minus]({{ site.apiRoot }}/org/apache/calcite/rel/core/Minus.html) of the two most recent relational expressions.
| `snapshot(period)` | Creates a [Snapshot]({{ site.apiRoot }}/org/apache/calcite/rel/core/Snapshot.html) of the given snapshot period.
| `match(pattern, strictStart,` `strictEnd, patterns, measures,` `after, subsets, allRows,` `partitionKeys, orderKeys,` `interval)` | Creates a [Match]({{ site.apiRoot }}/org/apache/calcite/rel/core/Match.html).
| `functionScan(operator, n, exprList)` | Creates a [TableFunctionScan]({{ site.apiRoot }}/org/apache/calcite/rel/core/TableFunctionScan.html) of the `n` (default two) most recent relational expressions.

Argument types:

Expand Down Expand Up @@ -366,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

Expand Down

1 comment on commit bd116f4

@chunweilei
Copy link

@chunweilei chunweilei commented on bd116f4 Mar 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, thank you~

Please sign in to comment.