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 2021/03/13 07:11:39 UTC
[calcite] 04/04: [CALCITE-4477] In Interpreter,
support table-valued functions
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 084d608c6adbbb82bcbdc2778439dfbeb6d6afdd
Author: Julian Hyde <jh...@apache.org>
AuthorDate: Mon Jan 25 00:10:08 2021 -0800
[CALCITE-4477] In Interpreter, support table-valued functions
Fix RexCallBinding, so that we can access constant arguments
to table function scans when created via RelBuilder.
Add Fibonacci table function, as a test.
---
.../java/org/apache/calcite/interpreter/Nodes.java | 5 ++
.../calcite/interpreter/TableFunctionScanNode.java | 75 ++++++++++++++++
.../calcite/schema/impl/TableFunctionImpl.java | 14 +--
.../org/apache/calcite/sql/SqlCallBinding.java | 18 ----
.../org/apache/calcite/sql/SqlOperatorBinding.java | 18 +++-
.../org/apache/calcite/test/InterpreterTest.java | 51 +++++++++++
.../apache/calcite/test/MockSqlOperatorTable.java | 19 +++++
.../org/apache/calcite/test/RelBuilderTest.java | 99 ++++++++++++++++++++++
.../org/apache/calcite/test/TableFunctionTest.java | 2 +-
.../test/java/org/apache/calcite/util/Smalls.java | 64 +++++++++++++-
10 files changed, 336 insertions(+), 29 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/interpreter/Nodes.java b/core/src/main/java/org/apache/calcite/interpreter/Nodes.java
index 84224d0..511eeaa 100644
--- a/core/src/main/java/org/apache/calcite/interpreter/Nodes.java
+++ b/core/src/main/java/org/apache/calcite/interpreter/Nodes.java
@@ -25,6 +25,7 @@ import org.apache.calcite.rel.core.Match;
import org.apache.calcite.rel.core.Project;
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.Uncollect;
import org.apache.calcite.rel.core.Values;
@@ -74,6 +75,10 @@ public class Nodes {
node = TableScanNode.create(this, scan, scan.filters, scan.projects);
}
+ public void visit(TableFunctionScan functionScan) {
+ node = TableFunctionScanNode.create(this, functionScan);
+ }
+
public void visit(Sort sort) {
node = new SortNode(this, sort);
}
diff --git a/core/src/main/java/org/apache/calcite/interpreter/TableFunctionScanNode.java b/core/src/main/java/org/apache/calcite/interpreter/TableFunctionScanNode.java
new file mode 100644
index 0000000..395cd57
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/interpreter/TableFunctionScanNode.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.interpreter;
+
+import org.apache.calcite.linq4j.Enumerable;
+import org.apache.calcite.linq4j.Enumerator;
+import org.apache.calcite.rel.core.TableFunctionScan;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.runtime.Enumerables;
+import org.apache.calcite.schema.Function;
+import org.apache.calcite.schema.impl.TableFunctionImpl;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.validate.SqlUserDefinedTableFunction;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Interpreter node that implements a
+ * {@link TableFunctionScan}.
+ */
+public class TableFunctionScanNode implements Node {
+ private final Scalar scalar;
+ private final Context context;
+ private final Sink sink;
+
+ private TableFunctionScanNode(Compiler compiler, TableFunctionScan rel) {
+ this.scalar =
+ compiler.compile(ImmutableList.of(rel.getCall()), rel.getRowType());
+ this.context = compiler.createContext();
+ this.sink = compiler.sink(rel);
+ }
+
+ @Override public void run() throws InterruptedException {
+ final Object o = scalar.execute(context);
+ if (o instanceof Enumerable) {
+ @SuppressWarnings("unchecked") final Enumerable<Row> rowEnumerable =
+ Enumerables.toRow((Enumerable) o);
+ final Enumerator<Row> enumerator = rowEnumerable.enumerator();
+ while (enumerator.moveNext()) {
+ sink.send(enumerator.current());
+ }
+ }
+ }
+
+ /** Creates a TableFunctionScanNode. */
+ static TableFunctionScanNode create(Compiler compiler, TableFunctionScan rel) {
+ RexNode call = rel.getCall();
+ if (call instanceof RexCall) {
+ SqlOperator operator = ((RexCall) call).getOperator();
+ if (operator instanceof SqlUserDefinedTableFunction) {
+ Function function = ((SqlUserDefinedTableFunction) operator).function;
+ if (function instanceof TableFunctionImpl) {
+ return new TableFunctionScanNode(compiler, rel);
+ }
+ }
+ }
+ throw new AssertionError("cannot convert table function scan "
+ + rel.getCall() + " to enumerable");
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/schema/impl/TableFunctionImpl.java b/core/src/main/java/org/apache/calcite/schema/impl/TableFunctionImpl.java
index 36d4edf..fa4d699 100644
--- a/core/src/main/java/org/apache/calcite/schema/impl/TableFunctionImpl.java
+++ b/core/src/main/java/org/apache/calcite/schema/impl/TableFunctionImpl.java
@@ -16,7 +16,6 @@
*/
package org.apache.calcite.schema.impl;
-import org.apache.calcite.DataContext;
import org.apache.calcite.adapter.enumerable.CallImplementor;
import org.apache.calcite.adapter.enumerable.NullPolicy;
import org.apache.calcite.adapter.enumerable.ReflectiveCallNotNullImplementor;
@@ -53,8 +52,8 @@ import static java.util.Objects.requireNonNull;
* Implementation of {@link org.apache.calcite.schema.TableFunction} based on a
* method.
*/
-public class TableFunctionImpl extends ReflectiveFunctionBase implements
- TableFunction, ImplementableFunction {
+public class TableFunctionImpl extends ReflectiveFunctionBase
+ implements TableFunction, ImplementableFunction {
private final CallImplementor implementor;
/** Private constructor; use {@link #create}. */
@@ -129,7 +128,7 @@ public class TableFunctionImpl extends ReflectiveFunctionBase implements
Expression queryable = Expressions.call(
Expressions.convert_(expr, QueryableTable.class),
BuiltInMethod.QUERYABLE_TABLE_AS_QUERYABLE.method,
- Expressions.call(DataContext.ROOT,
+ Expressions.call(translator.getRoot(),
BuiltInMethod.DATA_CONTEXT_GET_QUERY_PROVIDER.method),
Expressions.constant(null, SchemaPlus.class),
Expressions.constant(call.getOperator().getName(), String.class));
@@ -137,7 +136,8 @@ public class TableFunctionImpl extends ReflectiveFunctionBase implements
BuiltInMethod.QUERYABLE_AS_ENUMERABLE.method);
} else {
expr = Expressions.call(expr,
- BuiltInMethod.SCANNABLE_TABLE_SCAN.method, DataContext.ROOT);
+ BuiltInMethod.SCANNABLE_TABLE_SCAN.method,
+ translator.getRoot());
}
return expr;
}
@@ -152,8 +152,8 @@ public class TableFunctionImpl extends ReflectiveFunctionBase implements
method.getDeclaringClass().getConstructor();
o = constructor.newInstance();
}
- return (Table) requireNonNull(
- method.invoke(o, arguments.toArray()),
+ final Object table = method.invoke(o, arguments.toArray());
+ return (Table) requireNonNull(table,
() -> "got null from " + method + " with arguments " + arguments);
} catch (IllegalArgumentException e) {
throw RESOURCE.illegalArgumentForTableFunctionCall(
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java b/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java
index 068f9af..8852a0d 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java
@@ -16,9 +16,7 @@
*/
package org.apache.calcite.sql;
-import org.apache.calcite.adapter.enumerable.EnumUtils;
import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
import org.apache.calcite.runtime.CalciteException;
import org.apache.calcite.runtime.Resources;
import org.apache.calcite.sql.fun.SqlLiteralChainOperator;
@@ -272,22 +270,6 @@ public class SqlCallBinding extends SqlOperatorBinding {
return valueAs(node, clazz);
}
- @Override public @Nullable Object getOperandLiteralValue(int ordinal, RelDataType type) {
- if (!(type instanceof RelDataTypeFactoryImpl.JavaType)) {
- return null;
- }
- final Class<?> clazz = ((RelDataTypeFactoryImpl.JavaType) type).getJavaClass();
- final Object o = getOperandLiteralValue(ordinal, Object.class);
- if (o == null) {
- return null;
- }
- if (clazz.isInstance(o)) {
- return clazz.cast(o);
- }
- final Object o2 = o instanceof NlsString ? ((NlsString) o).getValue() : o;
- return EnumUtils.evaluate(o2, clazz);
- }
-
private static <T extends Object> @Nullable T valueAs(SqlNode node, Class<T> clazz) {
final SqlLiteral literal;
switch (node.getKind()) {
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlOperatorBinding.java b/core/src/main/java/org/apache/calcite/sql/SqlOperatorBinding.java
index 1693e89..9711385 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlOperatorBinding.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlOperatorBinding.java
@@ -16,12 +16,15 @@
*/
package org.apache.calcite.sql;
+import org.apache.calcite.adapter.enumerable.EnumUtils;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
import org.apache.calcite.runtime.CalciteException;
import org.apache.calcite.runtime.Resources;
import org.apache.calcite.sql.validate.SqlMonotonicity;
import org.apache.calcite.sql.validate.SqlValidatorException;
+import org.apache.calcite.util.NlsString;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -146,9 +149,22 @@ public abstract class SqlOperatorBinding {
* @return value of operand
*/
public @Nullable Object getOperandLiteralValue(int ordinal, RelDataType type) {
- throw new UnsupportedOperationException();
+ if (!(type instanceof RelDataTypeFactoryImpl.JavaType)) {
+ return null;
+ }
+ final Class<?> clazz = ((RelDataTypeFactoryImpl.JavaType) type).getJavaClass();
+ final Object o = getOperandLiteralValue(ordinal, Object.class);
+ if (o == null) {
+ return null;
+ }
+ if (clazz.isInstance(o)) {
+ return clazz.cast(o);
+ }
+ final Object o2 = o instanceof NlsString ? ((NlsString) o).getValue() : o;
+ return EnumUtils.evaluate(o2, clazz);
}
+
@Deprecated // to be removed before 2.0
public @Nullable Comparable getOperandLiteralValue(int ordinal) {
return getOperandLiteralValue(ordinal, Comparable.class);
diff --git a/core/src/test/java/org/apache/calcite/test/InterpreterTest.java b/core/src/test/java/org/apache/calcite/test/InterpreterTest.java
index f24659b..1b95cc4 100644
--- a/core/src/test/java/org/apache/calcite/test/InterpreterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/InterpreterTest.java
@@ -31,6 +31,9 @@ import org.apache.calcite.rel.rules.CoreRules;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.schema.TableFunction;
+import org.apache.calcite.schema.impl.AbstractSchema;
+import org.apache.calcite.schema.impl.TableFunctionImpl;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
@@ -39,6 +42,7 @@ import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.Planner;
import org.apache.calcite.tools.RelConversionException;
import org.apache.calcite.tools.ValidationException;
+import org.apache.calcite.util.Smalls;
import org.apache.calcite.util.Util;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -539,4 +543,51 @@ class InterpreterTest {
"[7839, 1981-11-17]", "[7844, 1981-09-08]", "[7876, 1987-05-23]",
"[7900, 1981-12-03]", "[7902, 1981-12-03]", "[7934, 1982-01-23]");
}
+
+ /** Tests a table function. */
+ @Test void testInterpretTableFunction() {
+ SchemaPlus schema = rootSchema.add("s", new AbstractSchema());
+ final TableFunction table1 = TableFunctionImpl.create(Smalls.MAZE_METHOD);
+ schema.add("Maze", table1);
+ final String sql = "select *\n"
+ + "from table(\"s\".\"Maze\"(5, 3, 1))";
+ String[] rows = {"[abcde]", "[xyz]", "[generate(w=5, h=3, s=1)]"};
+ sql(sql).returnsRows(rows);
+ }
+
+ /** Tests a table function that takes zero arguments.
+ *
+ * <p>Note that we use {@link Smalls#FIBONACCI_LIMIT_100_TABLE_METHOD}; if we
+ * used {@link Smalls#FIBONACCI_TABLE_METHOD}, even with {@code LIMIT 6},
+ * we would run out of memory, due to
+ * <a href="https://issues.apache.org/jira/browse/CALCITE-4478">[CALCITE-4478]
+ * In interpreter, support infinite relations</a>. */
+ @Test void testInterpretNilaryTableFunction() {
+ SchemaPlus schema = rootSchema.add("s", new AbstractSchema());
+ final TableFunction table1 =
+ TableFunctionImpl.create(Smalls.FIBONACCI_LIMIT_100_TABLE_METHOD);
+ schema.add("fibonacciLimit100", table1);
+ final String sql = "select *\n"
+ + "from table(\"s\".\"fibonacciLimit100\"())\n"
+ + "limit 6";
+ String[] rows = {"[1]", "[1]", "[2]", "[3]", "[5]", "[8]"};
+ sql(sql).returnsRows(rows);
+ }
+
+ /** Tests a table function whose row type is determined by parsing a JSON
+ * argument. */
+ @Test void testInterpretTableFunctionWithDynamicType() {
+ SchemaPlus schema = rootSchema.add("s", new AbstractSchema());
+ final TableFunction table1 =
+ TableFunctionImpl.create(Smalls.DYNAMIC_ROW_TYPE_TABLE_METHOD);
+ schema.add("dynamicRowTypeTable", table1);
+ final String sql = "select *\n"
+ + "from table(\"s\".\"dynamicRowTypeTable\"('"
+ + "{\"nullable\":false,\"fields\":["
+ + " {\"name\":\"i\",\"type\":\"INTEGER\",\"nullable\":false},"
+ + " {\"name\":\"d\",\"type\":\"DATE\",\"nullable\":true}"
+ + "]}', 0))\n"
+ + "where \"i\" < 0 and \"d\" is not null";
+ sql(sql).returnsRows();
+ }
}
diff --git a/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java b/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java
index cb42bff..81523de 100644
--- a/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java
+++ b/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java
@@ -92,6 +92,25 @@ public class MockSqlOperatorTable extends ChainedSqlOperatorTable {
}
}
+ /** "DYNTYPE" user-defined table function. */
+ public static class DynamicTypeFunction extends SqlFunction
+ implements SqlTableFunction {
+ public DynamicTypeFunction() {
+ super("RAMP",
+ SqlKind.OTHER_FUNCTION,
+ ReturnTypes.CURSOR,
+ null,
+ OperandTypes.NUMERIC,
+ SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION);
+ }
+
+ @Override public SqlReturnTypeInference getRowTypeInference() {
+ return opBinding -> opBinding.getTypeFactory().builder()
+ .add("I", SqlTypeName.INTEGER)
+ .build();
+ }
+ }
+
/** Not valid as a table function, even though it returns CURSOR, because
* it does not implement {@link SqlTableFunction}. */
public static class NotATableFunction extends SqlFunction {
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 b083332..aee5b9d 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -49,14 +49,25 @@ import org.apache.calcite.rex.RexOver;
import org.apache.calcite.rex.RexWindowBounds;
import org.apache.calcite.runtime.CalciteException;
import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.schema.TableFunction;
+import org.apache.calcite.schema.impl.TableFunctionImpl;
import org.apache.calcite.schema.impl.ViewTable;
import org.apache.calcite.schema.impl.ViewTableMacro;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlMatchRecognize;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.type.InferTypes;
+import org.apache.calcite.sql.type.OperandTypes;
+import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.sql.type.SqlOperandMetadata;
+import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.validate.SqlUserDefinedTableFunction;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.Programs;
import org.apache.calcite.tools.RelBuilder;
@@ -65,6 +76,7 @@ import org.apache.calcite.tools.RelRunners;
import org.apache.calcite.util.Holder;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Pair;
+import org.apache.calcite.util.Smalls;
import org.apache.calcite.util.TimestampString;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.mapping.Mappings;
@@ -78,6 +90,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.Test;
+import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
@@ -94,6 +107,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
import static org.apache.calcite.test.Matchers.hasHints;
import static org.apache.calcite.test.Matchers.hasTree;
@@ -384,6 +398,52 @@ public class RelBuilderTest {
}
}
+ /** Tests scanning a table function whose row type is determined by parsing a
+ * JSON argument. The arguments must therefore be available at prepare
+ * time. */
+ @Test void testTableFunctionScanDynamicType() {
+ // Equivalent SQL:
+ // SELECT *
+ // FROM TABLE("dynamicRowType"('{nullable:true,fields:[...]}', 3))
+ final RelBuilder builder = RelBuilder.create(config().build());
+ final Method m = Smalls.DYNAMIC_ROW_TYPE_TABLE_METHOD;
+ final TableFunction tableFunction =
+ TableFunctionImpl.create(m.getDeclaringClass(), m.getName());
+ final SqlOperator operator =
+ new SqlUserDefinedTableFunction(
+ new SqlIdentifier("dynamicRowType", SqlParserPos.ZERO),
+ SqlKind.OTHER_FUNCTION, ReturnTypes.CURSOR, InferTypes.ANY_NULLABLE,
+ Arg.metadata(
+ Arg.of("count", f -> f.createSqlType(SqlTypeName.INTEGER),
+ SqlTypeFamily.INTEGER, false),
+ Arg.of("typeJson", f -> f.createSqlType(SqlTypeName.VARCHAR),
+ SqlTypeFamily.STRING, false)),
+ tableFunction);
+
+ final String jsonRowType = "{\"nullable\":false,\"fields\":["
+ + " {\"name\":\"i\",\"type\":\"INTEGER\",\"nullable\":false},"
+ + " {\"name\":\"d\",\"type\":\"DATE\",\"nullable\":true}"
+ + "]}";
+ final int rowCount = 3;
+ RelNode root = builder.functionScan(operator, 0,
+ builder.literal(jsonRowType), builder.literal(rowCount))
+ .build();
+ final String expected = "LogicalTableFunctionScan("
+ + "invocation=[dynamicRowType('{\"nullable\":false,\"fields\":["
+ + " {\"name\":\"i\",\"type\":\"INTEGER\",\"nullable\":false},"
+ + " {\"name\":\"d\",\"type\":\"DATE\",\"nullable\":true}]}', 3)], "
+ + "rowType=[RecordType(INTEGER i, DATE d)])\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 void testJoinTemporalTable() {
// Equivalent SQL:
// SELECT *
@@ -4118,4 +4178,43 @@ public class RelBuilderTest {
"empid=110; name=Theodore",
"empid=150; name=Sebastian");
}
+
+ /** Operand to a user-defined function. */
+ private interface Arg {
+ String name();
+ RelDataType type(RelDataTypeFactory typeFactory);
+ SqlTypeFamily family();
+ boolean optional();
+
+ static SqlOperandMetadata metadata(Arg... args) {
+ return OperandTypes.operandMetadata(
+ Arrays.stream(args).map(Arg::family).collect(Collectors.toList()),
+ typeFactory ->
+ Arrays.stream(args).map(arg -> arg.type(typeFactory))
+ .collect(Collectors.toList()),
+ i -> args[i].name(), i -> args[i].optional());
+ }
+
+ static Arg of(String name,
+ Function<RelDataTypeFactory, RelDataType> protoType,
+ SqlTypeFamily family, boolean optional) {
+ return new Arg() {
+ @Override public String name() {
+ return name;
+ }
+
+ @Override public RelDataType type(RelDataTypeFactory typeFactory) {
+ return protoType.apply(typeFactory);
+ }
+
+ @Override public SqlTypeFamily family() {
+ return family;
+ }
+
+ @Override public boolean optional() {
+ return optional;
+ }
+ };
+ }
+ }
}
diff --git a/core/src/test/java/org/apache/calcite/test/TableFunctionTest.java b/core/src/test/java/org/apache/calcite/test/TableFunctionTest.java
index f0f0148..8a4ac9d 100644
--- a/core/src/test/java/org/apache/calcite/test/TableFunctionTest.java
+++ b/core/src/test/java/org/apache/calcite/test/TableFunctionTest.java
@@ -57,7 +57,7 @@ class TableFunctionTest {
final String c = Smalls.class.getName();
final String m = Smalls.MULTIPLICATION_TABLE_METHOD.getName();
final String m2 = Smalls.FIBONACCI_TABLE_METHOD.getName();
- final String m3 = Smalls.FIBONACCI2_TABLE_METHOD.getName();
+ final String m3 = Smalls.FIBONACCI_LIMIT_TABLE_METHOD.getName();
return CalciteAssert.model("{\n"
+ " version: '1.0',\n"
+ " schemas: [\n"
diff --git a/core/src/test/java/org/apache/calcite/util/Smalls.java b/core/src/test/java/org/apache/calcite/util/Smalls.java
index 03527b7..6dab428 100644
--- a/core/src/test/java/org/apache/calcite/util/Smalls.java
+++ b/core/src/test/java/org/apache/calcite/util/Smalls.java
@@ -30,6 +30,7 @@ import org.apache.calcite.linq4j.function.Deterministic;
import org.apache.calcite.linq4j.function.Parameter;
import org.apache.calcite.linq4j.function.SemiStrict;
import org.apache.calcite.linq4j.tree.Types;
+import org.apache.calcite.rel.externalize.RelJsonReader;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexLiteral;
@@ -92,8 +93,15 @@ public class Smalls {
int.class, Integer.class);
public static final Method FIBONACCI_TABLE_METHOD =
Types.lookupMethod(Smalls.class, "fibonacciTable");
- public static final Method FIBONACCI2_TABLE_METHOD =
+ public static final Method FIBONACCI_LIMIT_100_TABLE_METHOD =
+ Types.lookupMethod(Smalls.class, "fibonacciTableWithLimit100");
+ public static final Method FIBONACCI_LIMIT_TABLE_METHOD =
Types.lookupMethod(Smalls.class, "fibonacciTableWithLimit", long.class);
+ public static final Method FIBONACCI_INSTANCE_TABLE_METHOD =
+ Types.lookupMethod(Smalls.FibonacciTableFunction.class, "eval");
+ public static final Method DYNAMIC_ROW_TYPE_TABLE_METHOD =
+ Types.lookupMethod(Smalls.class, "dynamicRowTypeTable", String.class,
+ int.class);
public static final Method VIEW_METHOD =
Types.lookupMethod(Smalls.class, "view", String.class);
public static final Method STR_METHOD =
@@ -234,11 +242,20 @@ public class Smalls {
}
/** A function that generates the Fibonacci sequence.
- * Interesting because it has one column and no arguments. */
+ *
+ * <p>Interesting because it has one column and no arguments,
+ * and because it is infinite. */
public static ScannableTable fibonacciTable() {
return fibonacciTableWithLimit(-1L);
}
+ /** A function that generates the first 100 terms of the Fibonacci sequence.
+ *
+ * <p>Interesting because it has one column and no arguments. */
+ public static ScannableTable fibonacciTableWithLimit100() {
+ return fibonacciTableWithLimit(100L);
+ }
+
/** A function that generates the Fibonacci sequence.
* Interesting because it has one column and no arguments. */
public static ScannableTable fibonacciTableWithLimit(final long limit) {
@@ -299,6 +316,33 @@ public class Smalls {
};
}
+ public static ScannableTable dynamicRowTypeTable(String jsonRowType,
+ int rowCount) {
+ return new DynamicRowTypeTable(jsonRowType, rowCount);
+ }
+
+ /** A table whose row type is determined by parsing a JSON argument. */
+ private static class DynamicRowTypeTable extends AbstractTable
+ implements ScannableTable {
+ private final String jsonRowType;
+
+ DynamicRowTypeTable(String jsonRowType, int count) {
+ this.jsonRowType = jsonRowType;
+ }
+
+ @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+ try {
+ return RelJsonReader.readType(typeFactory, jsonRowType);
+ } catch (IOException e) {
+ throw Util.throwAsRuntime(e);
+ }
+ }
+
+ @Override public Enumerable<@Nullable Object[]> scan(DataContext root) {
+ return Linq4j.emptyEnumerable();
+ }
+ }
+
/** Table function that adds a number to the first column of input cursor. */
public static QueryableTable processCursor(final int offset,
final Enumerable<Object[]> a) {
@@ -479,6 +523,22 @@ public class Smalls {
}
}
+ /** Example of a UDF with non-default constructor.
+ *
+ * <p>Not used; we do not currently have a way to instantiate function
+ * objects other than via their default constructor. */
+ public static class FibonacciTableFunction {
+ private final int limit;
+
+ public FibonacciTableFunction(int limit) {
+ this.limit = limit;
+ }
+
+ public ScannableTable eval() {
+ return fibonacciTableWithLimit(limit);
+ }
+ }
+
/** User-defined function with two arguments. */
public static class MyIncrement {
public float eval(int x, int y) {