This is an automated email from the ASF dual-hosted git repository. volodymyr pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push: new d1cf4a38b [CALCITE-4448] Use TableMacro user-defined table functions with QueryableTable d1cf4a38b is described below commit d1cf4a38b71b514b1d208b5ffa3220b92693464f Author: Volodymyr Vysotskyi <vvo...@gmail.com> AuthorDate: Sun Dec 27 23:01:49 2020 +0200 [CALCITE-4448] Use TableMacro user-defined table functions with QueryableTable --- .../apache/calcite/prepare/RelOptTableImpl.java | 79 ++++++------- .../java/org/apache/calcite/schema/Schemas.java | 29 +++++ .../apache/calcite/sql2rel/SqlToRelConverter.java | 12 +- .../java/org/apache/calcite/test/JdbcTest.java | 4 +- .../calcite/test/RelMdColumnOriginsTest.java | 3 +- .../org/apache/calcite/test/TableFunctionTest.java | 22 ++++ .../apache/calcite/test/TableInRootSchemaTest.java | 111 +---------------- .../linq4j/tree/TableExpressionFactory.java | 33 ++++++ .../java/org/apache/calcite/piglet/PigTable.java | 2 +- .../main/java/org/apache/calcite/util/Smalls.java | 131 +++++++++++++++++++++ 10 files changed, 269 insertions(+), 157 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java index 087829249..29a6f07c0 100644 --- a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java +++ b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java @@ -18,6 +18,7 @@ package org.apache.calcite.prepare; import org.apache.calcite.jdbc.CalciteSchema; import org.apache.calcite.linq4j.tree.Expression; +import org.apache.calcite.linq4j.tree.TableExpressionFactory; import org.apache.calcite.materialize.Lattice; import org.apache.calcite.plan.RelOptSchema; import org.apache.calcite.plan.RelOptTable; @@ -34,11 +35,8 @@ import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rel.type.RelProtoDataType; import org.apache.calcite.rel.type.RelRecordType; import org.apache.calcite.schema.ColumnStrategy; -import org.apache.calcite.schema.FilterableTable; import org.apache.calcite.schema.ModifiableTable; import org.apache.calcite.schema.Path; -import org.apache.calcite.schema.ProjectableFilterableTable; -import org.apache.calcite.schema.QueryableTable; import org.apache.calcite.schema.ScannableTable; import org.apache.calcite.schema.Schema; import org.apache.calcite.schema.SchemaPlus; @@ -66,7 +64,6 @@ import java.util.AbstractList; import java.util.Collection; import java.util.List; import java.util.Set; -import java.util.function.Function; import static java.util.Objects.requireNonNull; @@ -77,7 +74,7 @@ public class RelOptTableImpl extends Prepare.AbstractPreparingTable { private final @Nullable RelOptSchema schema; private final RelDataType rowType; private final @Nullable Table table; - private final @Nullable Function<Class, Expression> expressionFunction; + private final @Nullable TableExpressionFactory tableExpressionFactory; private final ImmutableList<String> names; /** Estimate for the row count, or null. @@ -94,13 +91,13 @@ public class RelOptTableImpl extends Prepare.AbstractPreparingTable { RelDataType rowType, List<String> names, @Nullable Table table, - @Nullable Function<Class, Expression> expressionFunction, + @Nullable TableExpressionFactory tableExpressionFactory, @Nullable Double rowCount) { this.schema = schema; this.rowType = requireNonNull(rowType, "rowType"); this.names = ImmutableList.copyOf(names); this.table = table; // may be null - this.expressionFunction = expressionFunction; // may be null + this.tableExpressionFactory = tableExpressionFactory; // may be null this.rowCount = rowCount; // may be null } @@ -113,29 +110,53 @@ public class RelOptTableImpl extends Prepare.AbstractPreparingTable { c -> expression, null); } + @Deprecated // to be removed before 2.0 public static RelOptTableImpl create( @Nullable RelOptSchema schema, RelDataType rowType, List<String> names, Table table, Expression expression) { + return create(schema, rowType, names, table, c -> expression); + } + + /** + * Creates {@link RelOptTableImpl} instance with specified arguments + * and row count obtained from table statistic. + * + * @param schema table schema + * @param rowType table row type + * @param names full table path + * @param table table + * @param expressionFactory expression function for accessing table data + * in the generated code + * + * @return {@link RelOptTableImpl} instance + */ + public static RelOptTableImpl create( + @Nullable RelOptSchema schema, + RelDataType rowType, + List<String> names, + Table table, + TableExpressionFactory expressionFactory) { return new RelOptTableImpl(schema, rowType, names, table, - c -> expression, table.getStatistic().getRowCount()); + expressionFactory, table.getStatistic().getRowCount()); } public static RelOptTableImpl create(@Nullable RelOptSchema schema, RelDataType rowType, Table table, Path path) { final SchemaPlus schemaPlus = MySchemaPlus.create(path); return new RelOptTableImpl(schema, rowType, Pair.left(path), table, - getClassExpressionFunction(schemaPlus, Util.last(path).left, table), + c -> Schemas.getTableExpression(schemaPlus, Util.last(path).left, table, c), table.getStatistic().getRowCount()); } public static RelOptTableImpl create(@Nullable RelOptSchema schema, RelDataType rowType, final CalciteSchema.TableEntry tableEntry, @Nullable Double rowCount) { final Table table = tableEntry.getTable(); - return new RelOptTableImpl(schema, rowType, tableEntry.path(), - table, getClassExpressionFunction(tableEntry, table), rowCount); + return new RelOptTableImpl(schema, rowType, tableEntry.path(), table, + c -> Schemas.getTableExpression(tableEntry.schema.plus(), tableEntry.name, table, c), + rowCount); } /** @@ -143,7 +164,7 @@ public class RelOptTableImpl extends Prepare.AbstractPreparingTable { */ public RelOptTableImpl copy(RelDataType newRowType) { return new RelOptTableImpl(this.schema, newRowType, this.names, this.table, - this.expressionFunction, this.rowCount); + this.tableExpressionFactory, this.rowCount); } @Override public String toString() { @@ -155,32 +176,6 @@ public class RelOptTableImpl extends Prepare.AbstractPreparingTable { + '}'; } - private static Function<Class, Expression> getClassExpressionFunction( - CalciteSchema.TableEntry tableEntry, Table table) { - return getClassExpressionFunction(tableEntry.schema.plus(), tableEntry.name, - table); - } - - private static Function<Class, Expression> getClassExpressionFunction( - final SchemaPlus schema, final String tableName, final Table table) { - if (table instanceof QueryableTable) { - final QueryableTable queryableTable = (QueryableTable) table; - return clazz -> queryableTable.getExpression(schema, tableName, clazz); - } else if (table instanceof ScannableTable - || table instanceof FilterableTable - || table instanceof ProjectableFilterableTable) { - return clazz -> Schemas.tableExpression(schema, Object[].class, tableName, - table.getClass()); - } else if (table instanceof StreamableTable) { - return getClassExpressionFunction(schema, tableName, - ((StreamableTable) table).stream()); - } else { - return input -> { - throw new UnsupportedOperationException(); - }; - } - } - public static RelOptTableImpl create(@Nullable RelOptSchema schema, RelDataType rowType, Table table, ImmutableList<String> names) { assert table instanceof TranslatableTable @@ -211,10 +206,10 @@ public class RelOptTableImpl extends Prepare.AbstractPreparingTable { } @Override public @Nullable Expression getExpression(Class clazz) { - if (expressionFunction == null) { + if (tableExpressionFactory == null) { return null; } - return expressionFunction.apply(clazz); + return tableExpressionFactory.create(clazz); } @Override protected RelOptTable extend(Table extendedTable) { @@ -222,7 +217,7 @@ public class RelOptTableImpl extends Prepare.AbstractPreparingTable { final RelDataType extendedRowType = extendedTable.getRowType(schema.getTypeFactory()); return new RelOptTableImpl(schema, extendedRowType, getQualifiedName(), - extendedTable, expressionFunction, getRowCount()); + extendedTable, tableExpressionFactory, getRowCount()); } @Override public boolean equals(@Nullable Object obj) { @@ -274,7 +269,7 @@ public class RelOptTableImpl extends Prepare.AbstractPreparingTable { } final RelOptTable relOptTable = new RelOptTableImpl(this.schema, b.build(), this.names, this.table, - this.expressionFunction, this.rowCount) { + this.tableExpressionFactory, this.rowCount) { @Override public <T extends Object> @Nullable T unwrap(Class<T> clazz) { if (clazz.isAssignableFrom(InitializerExpressionFactory.class)) { return clazz.cast(NullInitializerExpressionFactory.INSTANCE); diff --git a/core/src/main/java/org/apache/calcite/schema/Schemas.java b/core/src/main/java/org/apache/calcite/schema/Schemas.java index 54a82f1b8..634abda7f 100644 --- a/core/src/main/java/org/apache/calcite/schema/Schemas.java +++ b/core/src/main/java/org/apache/calcite/schema/Schemas.java @@ -184,6 +184,35 @@ public final class Schemas { return EnumUtils.convert(expression, clazz); } + /** + * Generates an expression with which table can be referenced in + * generated code. + * + * @param schema Schema + * @param tableName Table name (unique within schema) + * @param table Table to be referenced + * @param clazz Class that provides specific methods for accessing table data. + * It may differ from the {@code table} class; for example {@code clazz} may be + * {@code MongoTable.MongoQueryable}, though {@code table} is {@code MongoTable} + */ + public static Expression getTableExpression(SchemaPlus schema, String tableName, + Table table, Class<?> clazz) { + if (table instanceof QueryableTable) { + QueryableTable queryableTable = (QueryableTable) table; + return queryableTable.getExpression(schema, tableName, clazz); + } else if (table instanceof ScannableTable + || table instanceof FilterableTable + || table instanceof ProjectableFilterableTable) { + return tableExpression(schema, Object[].class, tableName, + table.getClass()); + } else if (table instanceof StreamableTable) { + return getTableExpression(schema, tableName, + ((StreamableTable) table).stream(), clazz); + } else { + throw new UnsupportedOperationException(); + } + } + public static DataContext createDataContext( Connection connection, @Nullable SchemaPlus rootSchema) { return DataContexts.of((CalciteConnection) connection, rootSchema); diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java index adc19e5d2..166b593f1 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java @@ -17,7 +17,9 @@ package org.apache.calcite.sql2rel; import org.apache.calcite.avatica.util.Spaces; +import org.apache.calcite.jdbc.CalciteSchema; import org.apache.calcite.linq4j.Ord; +import org.apache.calcite.linq4j.tree.TableExpressionFactory; import org.apache.calcite.plan.RelOptCluster; import org.apache.calcite.plan.RelOptPlanner; import org.apache.calcite.plan.RelOptSamplingParameters; @@ -90,6 +92,7 @@ import org.apache.calcite.rex.RexWindowBounds; import org.apache.calcite.schema.ColumnStrategy; import org.apache.calcite.schema.ModifiableTable; import org.apache.calcite.schema.ModifiableView; +import org.apache.calcite.schema.Schemas; import org.apache.calcite.schema.Table; import org.apache.calcite.schema.TranslatableTable; import org.apache.calcite.schema.Wrapper; @@ -2667,8 +2670,13 @@ public class SqlToRelConverter { (SqlUserDefinedTableMacro) operator; final TranslatableTable table = udf.getTable(callBinding); final RelDataType rowType = table.getRowType(typeFactory); - RelOptTable relOptTable = RelOptTableImpl.create(null, rowType, table, - udf.getNameAsId().names); + CalciteSchema schema = Schemas.subSchema( + catalogReader.getRootSchema(), udf.getNameAsId().skipLast(1).names); + TableExpressionFactory expressionFunction = + clazz -> Schemas.getTableExpression(Objects.requireNonNull(schema, "schema").plus(), + Util.last(udf.getNameAsId().names), table, clazz); + RelOptTable relOptTable = RelOptTableImpl.create(null, rowType, + udf.getNameAsId().names, table, expressionFunction); RelNode converted = toRel(relOptTable, ImmutableList.of()); bb.setRoot(converted, true); return; diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java index 2ae92cb1b..0725666ef 100644 --- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java +++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java @@ -7262,8 +7262,8 @@ public class JdbcTest { } // add tables and retrieve with various case sensitivities - final TableInRootSchemaTest.SimpleTable table = - new TableInRootSchemaTest.SimpleTable(); + final Smalls.SimpleTable table = + new Smalls.SimpleTable(); a2Schema.add("table1", table); a2Schema.add("TABLE1", table); a2Schema.add("tabLe1", table); diff --git a/core/src/test/java/org/apache/calcite/test/RelMdColumnOriginsTest.java b/core/src/test/java/org/apache/calcite/test/RelMdColumnOriginsTest.java index 4f19ebc9b..d034e9906 100644 --- a/core/src/test/java/org/apache/calcite/test/RelMdColumnOriginsTest.java +++ b/core/src/test/java/org/apache/calcite/test/RelMdColumnOriginsTest.java @@ -17,6 +17,7 @@ package org.apache.calcite.test; import org.apache.calcite.jdbc.CalciteConnection; +import org.apache.calcite.util.Smalls; import com.google.common.collect.ImmutableMultiset; @@ -43,7 +44,7 @@ class RelMdColumnOriginsTest { connection.unwrap(CalciteConnection.class); calciteConnection.getRootSchema().add("T1", - new TableInRootSchemaTest.SimpleTable()); + new Smalls.SimpleTable()); Statement statement = calciteConnection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT TABLE1.ID, TABLE2.ID FROM " 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 36d32295f..ab50be530 100644 --- a/core/src/test/java/org/apache/calcite/test/TableFunctionTest.java +++ b/core/src/test/java/org/apache/calcite/test/TableFunctionTest.java @@ -547,4 +547,26 @@ class TableFunctionTest { assertThat(CalciteAssert.toString(resultSet), equalTo(expected)); } } + + /** Test case for + * <a href="https://issues.apache.org/jira/browse/CALCITE-4448">[CALCITE-4448] + * Use TableMacro user-defined table functions with QueryableTable</a>. */ + @Test void testQueryableTableWithTableMacro() throws SQLException { + try (Connection connection = + DriverManager.getConnection("jdbc:calcite:")) { + CalciteConnection calciteConnection = + connection.unwrap(CalciteConnection.class); + SchemaPlus rootSchema = calciteConnection.getRootSchema(); + SchemaPlus schema = rootSchema.add("s", new AbstractSchema()); + schema.add("simple", new Smalls.SimpleTableMacro()); + + String sql = "select * from table(\"s\".\"simple\"())"; + ResultSet resultSet = connection.createStatement().executeQuery(sql); + String expected = "A=foo; B=5\n" + + "A=bar; B=4\n" + + "A=foo; B=3\n"; + assertThat(CalciteAssert.toString(resultSet), + equalTo(expected)); + } + } } diff --git a/core/src/test/java/org/apache/calcite/test/TableInRootSchemaTest.java b/core/src/test/java/org/apache/calcite/test/TableInRootSchemaTest.java index 5127fd87c..55b058a2f 100644 --- a/core/src/test/java/org/apache/calcite/test/TableInRootSchemaTest.java +++ b/core/src/test/java/org/apache/calcite/test/TableInRootSchemaTest.java @@ -16,21 +16,8 @@ */ package org.apache.calcite.test; -import org.apache.calcite.adapter.enumerable.EnumerableTableScan; -import org.apache.calcite.adapter.java.AbstractQueryableTable; import org.apache.calcite.jdbc.CalciteConnection; -import org.apache.calcite.linq4j.Enumerator; -import org.apache.calcite.linq4j.Linq4j; -import org.apache.calcite.linq4j.QueryProvider; -import org.apache.calcite.linq4j.Queryable; -import org.apache.calcite.plan.RelOptTable; -import org.apache.calcite.rel.RelNode; -import org.apache.calcite.rel.type.RelDataType; -import org.apache.calcite.rel.type.RelDataTypeFactory; -import org.apache.calcite.schema.SchemaPlus; -import org.apache.calcite.schema.TranslatableTable; -import org.apache.calcite.schema.impl.AbstractTableQueryable; -import org.apache.calcite.util.Pair; +import org.apache.calcite.util.Smalls; import com.google.common.collect.ImmutableMultiset; @@ -41,10 +28,6 @@ import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.Statement; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.nullValue; @@ -61,7 +44,7 @@ class TableInRootSchemaTest { CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class); - calciteConnection.getRootSchema().add("SAMPLE", new SimpleTable()); + calciteConnection.getRootSchema().add("SAMPLE", new Smalls.SimpleTable()); Statement statement = calciteConnection.createStatement(); ResultSet resultSet = statement.executeQuery("select A, SUM(B) from SAMPLE group by A"); @@ -90,94 +73,4 @@ class TableInRootSchemaTest { connection.close(); } - /** Table with columns (A, B). */ - public static class SimpleTable extends AbstractQueryableTable - implements TranslatableTable { - private String[] columnNames = { "A", "B" }; - private Class[] columnTypes = { String.class, Integer.class }; - private Object[][] rows = new Object[3][]; - - SimpleTable() { - super(Object[].class); - - rows[0] = new Object[] { "foo", 5 }; - rows[1] = new Object[] { "bar", 4 }; - rows[2] = new Object[] { "foo", 3 }; - } - - public RelDataType getRowType(RelDataTypeFactory typeFactory) { - int columnCount = columnNames.length; - final List<Pair<String, RelDataType>> columnDesc = - new ArrayList<>(columnCount); - for (int i = 0; i < columnCount; i++) { - final RelDataType colType = typeFactory - .createJavaType(columnTypes[i]); - columnDesc.add(Pair.of(columnNames[i], colType)); - } - return typeFactory.createStructType(columnDesc); - } - - public Iterator<Object[]> iterator() { - return Linq4j.enumeratorIterator(enumerator()); - } - - public Enumerator<Object[]> enumerator() { - return enumeratorImpl(null); - } - - public <T> Queryable<T> asQueryable(QueryProvider queryProvider, - SchemaPlus schema, String tableName) { - return new AbstractTableQueryable<T>(queryProvider, schema, this, - tableName) { - public Enumerator<T> enumerator() { - //noinspection unchecked - return (Enumerator<T>) enumeratorImpl(null); - } - }; - } - - private Enumerator<Object[]> enumeratorImpl(final int[] fields) { - return new Enumerator<Object[]>() { - private Object[] current; - private Iterator<Object[]> iterator = Arrays.asList(rows) - .iterator(); - - public Object[] current() { - return current; - } - - public boolean moveNext() { - if (iterator.hasNext()) { - Object[] full = iterator.next(); - current = fields != null ? convertRow(full) : full; - return true; - } else { - current = null; - return false; - } - } - - public void reset() { - throw new UnsupportedOperationException(); - } - - public void close() { - // noop - } - - private Object[] convertRow(Object[] full) { - final Object[] objects = new Object[fields.length]; - for (int i = 0; i < fields.length; i++) { - objects[i] = full[fields[i]]; - } - return objects; - } - }; - } - - public RelNode toRel(RelOptTable.ToRelContext context, - RelOptTable relOptTable) { - return EnumerableTableScan.create(context.getCluster(), relOptTable); - } - } } diff --git a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/TableExpressionFactory.java b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/TableExpressionFactory.java new file mode 100644 index 000000000..e3ea73da8 --- /dev/null +++ b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/TableExpressionFactory.java @@ -0,0 +1,33 @@ +/* + * 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.linq4j.tree; + +/** + * Factory for creating table expressions that may be used in generated code + * for accessing table data. + */ +public interface TableExpressionFactory { + + /** + * Creates {@link Expression} to be used in generated code for accessing table data. + * + * @param clazz Class that provides specific methods for accessing table data. + * + * @return {@link Expression} instance + */ + Expression create(Class clazz); +} diff --git a/piglet/src/main/java/org/apache/calcite/piglet/PigTable.java b/piglet/src/main/java/org/apache/calcite/piglet/PigTable.java index 0ab303a5a..94a8f7427 100644 --- a/piglet/src/main/java/org/apache/calcite/piglet/PigTable.java +++ b/piglet/src/main/java/org/apache/calcite/piglet/PigTable.java @@ -59,7 +59,7 @@ public class PigTable extends AbstractTable implements ScannableTable { RelDataType rowType, List<String> names) { final PigTable pigTable = new PigTable(rowType); return RelOptTableImpl.create(schema, rowType, names, pigTable, - Expressions.constant(Boolean.TRUE)); + c -> Expressions.constant(Boolean.TRUE)); } @Override public RelDataType getRowType(final RelDataTypeFactory typeFactory) { diff --git a/testkit/src/main/java/org/apache/calcite/util/Smalls.java b/testkit/src/main/java/org/apache/calcite/util/Smalls.java index fefa32491..deba6b010 100644 --- a/testkit/src/main/java/org/apache/calcite/util/Smalls.java +++ b/testkit/src/main/java/org/apache/calcite/util/Smalls.java @@ -17,6 +17,7 @@ package org.apache.calcite.util; import org.apache.calcite.DataContext; +import org.apache.calcite.adapter.enumerable.EnumerableTableScan; import org.apache.calcite.adapter.java.AbstractQueryableTable; import org.apache.calcite.config.CalciteConnectionConfig; import org.apache.calcite.linq4j.AbstractEnumerable; @@ -29,21 +30,30 @@ import org.apache.calcite.linq4j.Queryable; 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.Expression; +import org.apache.calcite.linq4j.tree.Expressions; +import org.apache.calcite.linq4j.tree.MethodCallExpression; import org.apache.calcite.linq4j.tree.Types; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.rel.RelNode; 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; import org.apache.calcite.runtime.SqlFunctions; import org.apache.calcite.schema.FunctionContext; +import org.apache.calcite.schema.FunctionParameter; import org.apache.calcite.schema.QueryableTable; import org.apache.calcite.schema.ScannableTable; import org.apache.calcite.schema.Schema; import org.apache.calcite.schema.SchemaPlus; +import org.apache.calcite.schema.Schemas; import org.apache.calcite.schema.Statistic; import org.apache.calcite.schema.Statistics; +import org.apache.calcite.schema.TableMacro; import org.apache.calcite.schema.TranslatableTable; import org.apache.calcite.schema.impl.AbstractTable; +import org.apache.calcite.schema.impl.AbstractTableQueryable; import org.apache.calcite.schema.impl.ViewTable; import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlNode; @@ -61,7 +71,10 @@ import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.util.AbstractList; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -1313,4 +1326,122 @@ public class Smalls { this.sale0 = sale; } } + + /** + * Implementation of {@link TableMacro} interface with + * {@link #apply} method that returns {@link Queryable} table. + */ + public static class SimpleTableMacro implements TableMacro { + + @Override public TranslatableTable apply(List<?> arguments) { + return new SimpleTable(); + } + + @Override public List<FunctionParameter> getParameters() { + return Collections.emptyList(); + } + } + + /** Table with columns (A, B). */ + public static class SimpleTable extends AbstractQueryableTable + implements TranslatableTable { + private final String[] columnNames = { "A", "B" }; + private final Class<?>[] columnTypes = { String.class, Integer.class }; + private final Object[][] rows = new Object[3][]; + + public SimpleTable() { + super(Object[].class); + + rows[0] = new Object[] { "foo", 5 }; + rows[1] = new Object[] { "bar", 4 }; + rows[2] = new Object[] { "foo", 3 }; + } + + @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) { + int columnCount = columnNames.length; + final List<Pair<String, RelDataType>> columnDesc = + new ArrayList<>(columnCount); + for (int i = 0; i < columnCount; i++) { + final RelDataType colType = typeFactory + .createJavaType(columnTypes[i]); + columnDesc.add(Pair.of(columnNames[i], colType)); + } + return typeFactory.createStructType(columnDesc); + } + + public Iterator<Object[]> iterator() { + return Linq4j.enumeratorIterator(enumerator()); + } + + public Enumerator<Object[]> enumerator() { + return enumeratorImpl(null); + } + + @Override public <T> Queryable<T> asQueryable(QueryProvider queryProvider, + SchemaPlus schema, String tableName) { + return new AbstractTableQueryable<T>(queryProvider, schema, this, + tableName) { + @Override public Enumerator<T> enumerator() { + //noinspection unchecked + return (Enumerator<T>) enumeratorImpl(null); + } + }; + } + + private Enumerator<Object[]> enumeratorImpl(final int[] fields) { + return new Enumerator<Object[]>() { + private Object[] current; + private final Iterator<Object[]> iterator = Arrays.asList(rows) + .iterator(); + + @Override public Object[] current() { + return current; + } + + @Override public boolean moveNext() { + if (iterator.hasNext()) { + Object[] full = iterator.next(); + current = fields != null ? convertRow(full) : full; + return true; + } else { + current = null; + return false; + } + } + + @Override public void reset() { + throw new UnsupportedOperationException(); + } + + @Override public void close() { + // noop + } + + private Object[] convertRow(Object[] full) { + final Object[] objects = new Object[fields.length]; + for (int i = 0; i < fields.length; i++) { + objects[i] = full[fields[i]]; + } + return objects; + } + }; + } + + @Override public RelNode toRel( + RelOptTable.ToRelContext context, + RelOptTable relOptTable) { + return EnumerableTableScan.create(context.getCluster(), relOptTable); + } + + @Override public Expression getExpression(SchemaPlus schema, String tableName, Class clazz) { + MethodCallExpression queryableExpression = + Expressions.call(Expressions.new_(SimpleTable.class), + BuiltInMethod.QUERYABLE_TABLE_AS_QUERYABLE.method, + Expressions.constant(null), + Schemas.expression(schema), + Expressions.constant(tableName)); + return Expressions.call(queryableExpression, + BuiltInMethod.QUERYABLE_AS_ENUMERABLE.method); + } + } }