diff --git a/build.gradle.kts b/build.gradle.kts index a9a6c0692b8..4b78f37ed76 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -605,6 +605,7 @@ allprojects { replaceRegex("Method argument list should not end in whitespace or newline", "(?[CALCITE-...] link styles: 1", "])++CALCITE-\\d+[^>]++>\\s*+\\[?(CALCITE-\\d+)\\]?", "[\$1]") // If the link was crafted manually, ensure it has [CALCITE-...] in the link text diff --git a/core/src/main/java/org/apache/calcite/config/CalciteConnectionConfig.java b/core/src/main/java/org/apache/calcite/config/CalciteConnectionConfig.java index c5e5d041e77..3999a6ce9b6 100644 --- a/core/src/main/java/org/apache/calcite/config/CalciteConnectionConfig.java +++ b/core/src/main/java/org/apache/calcite/config/CalciteConnectionConfig.java @@ -110,4 +110,16 @@ public interface CalciteConnectionConfig extends ConnectionConfig { boolean lenientOperatorLookup(); /** Returns the value of {@link CalciteConnectionProperty#TOPDOWN_OPT}. */ boolean topDownOpt(); + + /** Returns the value of {@link CalciteConnectionProperty#META_TABLE_FACTORY}, + * or a default meta table factory if not set. If + * {@code defaultMetaTableFactory} is not null, the result is never null. */ + @PolyNull T metaTableFactory(Class metaTableFactoryClass, + @PolyNull T defaultMetaTableFactory); + + /** Returns the value of {@link CalciteConnectionProperty#META_COLUMN_FACTORY}, + * or a default meta column factory if not set. If + * {@code defaultMetaColumnFactory} is not null, the result is never null. */ + @PolyNull T metaColumnFactory(Class metaColumnFactoryClass, + @PolyNull T defaultMetaColumnFactory); } diff --git a/core/src/main/java/org/apache/calcite/config/CalciteConnectionConfigImpl.java b/core/src/main/java/org/apache/calcite/config/CalciteConnectionConfigImpl.java index 49521bcb68c..7d9f9f76d1c 100644 --- a/core/src/main/java/org/apache/calcite/config/CalciteConnectionConfigImpl.java +++ b/core/src/main/java/org/apache/calcite/config/CalciteConnectionConfigImpl.java @@ -211,6 +211,21 @@ public boolean isSet(CalciteConnectionProperty property) { } @Override public boolean topDownOpt() { - return CalciteConnectionProperty.TOPDOWN_OPT.wrap(properties).getBoolean(); + return CalciteConnectionProperty.TOPDOWN_OPT.wrap(properties) + .getBoolean(); + } + + @Override public @PolyNull T metaTableFactory( + Class metaTableFactoryClass, + @PolyNull T defaultMetaTableFactory) { + return CalciteConnectionProperty.META_TABLE_FACTORY.wrap(properties) + .getPlugin(metaTableFactoryClass, defaultMetaTableFactory); + } + + @Override public @PolyNull T metaColumnFactory( + Class metaColumnFactoryClass, + @PolyNull T defaultMetaColumnFactory) { + return CalciteConnectionProperty.META_COLUMN_FACTORY.wrap(properties) + .getPlugin(metaColumnFactoryClass, defaultMetaColumnFactory); } } diff --git a/core/src/main/java/org/apache/calcite/config/CalciteConnectionProperty.java b/core/src/main/java/org/apache/calcite/config/CalciteConnectionProperty.java index 7117787c4a7..7e4b9656244 100644 --- a/core/src/main/java/org/apache/calcite/config/CalciteConnectionProperty.java +++ b/core/src/main/java/org/apache/calcite/config/CalciteConnectionProperty.java @@ -106,6 +106,12 @@ public enum CalciteConnectionProperty implements ConnectionProperty { * {@link org.apache.calcite.sql.parser.SqlParserImplFactory}. */ PARSER_FACTORY("parserFactory", Type.PLUGIN, null, false), + /** MetaTableFactory plugin. */ + META_TABLE_FACTORY("metaTableFactory", Type.PLUGIN, null, false), + + /** MetaColumnFactory plugin. */ + META_COLUMN_FACTORY("metaColumnFactory", Type.PLUGIN, null, false), + /** Name of initial schema. */ SCHEMA("schema", Type.STRING, null, false), diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaColumnFactory.java b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaColumnFactory.java new file mode 100644 index 00000000000..1f9d1a487b9 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaColumnFactory.java @@ -0,0 +1,48 @@ +/* + * 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.jdbc; + +import org.apache.calcite.avatica.MetaImpl.MetaColumn; +import org.apache.calcite.schema.Table; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; + +/** Factory for creating instances of {@link MetaColumn}. + * + * @see java.sql.DatabaseMetaData#getColumns */ +public interface CalciteMetaColumnFactory { + /** Instantiates a MetaColumn. */ + MetaColumn createColumn(Table table, String tableCat, String tableSchem, + String tableName, String columnName, int dataType, String typeName, + Integer columnSize, @Nullable Integer decimalDigits, int numPrecRadix, + int nullable, Integer charOctetLength, int ordinalPosition, + String isNullable); + + /** Returns the list of expected column names. + * + *

The default implementation returns the columns described in the JDBC + * specification. */ + default List getColumnNames() { + return CalciteMetaImpl.COLUMN_COLUMNS; + } + + /** Returns the type of object created. Must be a subclass of MetaColumn. */ + Class getMetaColumnClass(); + +} diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaColumnFactoryImpl.java b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaColumnFactoryImpl.java new file mode 100644 index 00000000000..be320626cf1 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaColumnFactoryImpl.java @@ -0,0 +1,69 @@ +/* + * 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.jdbc; + +import org.apache.calcite.avatica.MetaImpl.MetaColumn; +import org.apache.calcite.schema.Table; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** Default implementation of CalciteMetaColumnFactoryImpl. */ +public class CalciteMetaColumnFactoryImpl + implements CalciteMetaColumnFactory { + + /** Singleton instance. */ + public static final CalciteMetaColumnFactoryImpl INSTANCE = + new CalciteMetaColumnFactoryImpl(); + + /** Internal constructor; protected to allow subclassing. */ + protected CalciteMetaColumnFactoryImpl() {} + + @Override public MetaColumn createColumn( + Table table, + String tableCat, + String tableSchem, + String tableName, + String columnName, + int dataType, + String typeName, + Integer columnSize, + @Nullable Integer decimalDigits, + int numPrecRadix, + int nullable, + Integer charOctetLength, + int ordinalPosition, + String isNullable) { + return new MetaColumn( + tableCat, + tableSchem, + tableName, + columnName, + dataType, + typeName, + columnSize, + decimalDigits, + numPrecRadix, + nullable, + charOctetLength, + ordinalPosition, + isNullable); + } + + @Override public Class getMetaColumnClass() { + return MetaColumn.class; + } +} diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java index cd260838154..82be2738778 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java +++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java @@ -90,13 +90,140 @@ public class CalciteMetaImpl extends MetaImpl { static final Driver DRIVER = new Driver(); + private final CalciteMetaTableFactory metaTableFactory; + private final CalciteMetaColumnFactory metaColumnFactory; + + /** The columns returned by {@link DatabaseMetaData#getCatalogs()}. */ + public static final List CATALOG_COLUMNS = + ImmutableList.of("TABLE_CAT"); + + /** Column names returned by {@link DatabaseMetaData#getColumns}. */ + public static final List COLUMN_COLUMNS = + ImmutableList.of("TABLE_CAT", + "TABLE_SCHEM", + "TABLE_NAME", + "COLUMN_NAME", + "DATA_TYPE", + "TYPE_NAME", + "COLUMN_SIZE", + "BUFFER_LENGTH", + "DECIMAL_DIGITS", + "NUM_PREC_RADIX", + "NULLABLE", + "REMARKS", + "COLUMN_DEF", + "SQL_DATA_TYPE", + "SQL_DATETIME_SUB", + "CHAR_OCTET_LENGTH", + "ORDINAL_POSITION", + "IS_NULLABLE", + "SCOPE_CATALOG", + "SCOPE_SCHEMA", + "SCOPE_TABLE", + "SOURCE_DATA_TYPE", + "IS_AUTOINCREMENT", + "IS_GENERATEDCOLUMN"); + + /** The columns returned by {@link DatabaseMetaData#getFunctions}. */ + public static final List FUNCTION_COLUMNS = + ImmutableList.of("FUNCTION_CAT", + "FUNCTION_SCHEM", + "FUNCTION_NAME", + "REMARKS", + "FUNCTION_TYPE", + "SPECIFIC_NAME"); + + /** The columns returned by {@link DatabaseMetaData#getSchemas()}. */ + public static final List SCHEMA_COLUMNS = + ImmutableList.of("TABLE_SCHEM", + "TABLE_CATALOG"); + + /** The columns returned by {@link DatabaseMetaData#getTables}. */ + public static final List TABLE_COLUMNS = + ImmutableList.of("TABLE_CAT", + "TABLE_SCHEM", + "TABLE_NAME", + "TABLE_TYPE", + "REMARKS", + "TYPE_CAT", + "TYPE_SCHEM", + "TYPE_NAME", + "SELF_REFERENCING_COL_NAME", + "REF_GENERATION"); + + /** The columns returned by {@link DatabaseMetaData#getTableTypes()}. */ + public static final List TABLE_TYPE_COLUMNS = + ImmutableList.of("TABLE_TYPE"); + + /** The columns returned by {@link DatabaseMetaData#getTypeInfo()}. */ + public static final List TYPE_INFO_COLUMNS = + ImmutableList.of("TYPE_NAME", + "DATA_TYPE", + "PRECISION", + "LITERAL_PREFIX", + "LITERAL_SUFFIX", + "CREATE_PARAMS", + "NULLABLE", + "CASE_SENSITIVE", + "SEARCHABLE", + "UNSIGNED_ATTRIBUTE", + "FIXED_PREC_SCALE", + "AUTO_INCREMENT", + "LOCAL_TYPE_NAME", + "MINIMUM_SCALE", + "MAXIMUM_SCALE", + "SQL_DATA_TYPE", + "SQL_DATETIME_SUB", + "NUM_PREC_RADIX"); + + /** Creates a CalciteMetaImpl. + * + * @deprecated Use {@link #create(CalciteConnection)} instead. + */ + @Deprecated // to be removed before 2.0 public CalciteMetaImpl(CalciteConnectionImpl connection) { + this(connection, CalciteMetaTableFactoryImpl.INSTANCE, + CalciteMetaColumnFactoryImpl.INSTANCE); + } + + /** Internal constructor. Protected to allow subclassing. */ + protected CalciteMetaImpl(CalciteConnectionImpl connection, + CalciteMetaTableFactory metaTableFactory, + CalciteMetaColumnFactory metaColumnFactory) { super(connection); this.connProps .setAutoCommit(false) .setReadOnly(false) .setTransactionIsolation(Connection.TRANSACTION_NONE); this.connProps.setDirty(false); + this.metaTableFactory = + requireNonNull(metaTableFactory, "metaTableFactory"); + this.metaColumnFactory = + requireNonNull(metaColumnFactory, "metaColumnFactory"); + } + + /** + * Creates a CalciteMetaImpl. + * + * @param connection Calcite connection + */ + public static CalciteMetaImpl create(CalciteConnection connection) { + return create(connection, CalciteMetaTableFactoryImpl.INSTANCE, + CalciteMetaColumnFactoryImpl.INSTANCE); + } + + /** + * Creates a CalciteMetaImpl. + * + * @param connection Calcite connection + * @param metaTableFactory Factory for creating MetaTable (or subclass) + * @param metaColumnFactory Factory for creating MetaColumn (or subclass) + */ + public static CalciteMetaImpl create(CalciteConnection connection, + CalciteMetaTableFactory metaTableFactory, + CalciteMetaColumnFactory metaColumnFactory) { + return new CalciteMetaImpl((CalciteConnectionImpl) connection, + metaTableFactory, metaColumnFactory); } static Predicate1 namedMatcher(final Pat pattern) { @@ -170,19 +297,24 @@ public static Pattern likeToRegex(Pat pattern) { } private MetaResultSet createResultSet(Enumerable enumerable, - Class clazz, String... names) { - requireNonNull(names, "names"); - final List columns = new ArrayList<>(names.length); - final List fields = new ArrayList<>(names.length); - final List fieldNames = new ArrayList<>(names.length); + Class clazz, List names) { + assert names.size() > 0; + final List columns = new ArrayList<>(names.size()); + final List fields = new ArrayList<>(names.size()); + final List fieldNames = new ArrayList<>(names.size()); for (String name : names) { final int index = fields.size(); final String fieldName = AvaticaUtils.toCamelCase(name); - final Field field; + Field field; try { field = clazz.getField(fieldName); } catch (NoSuchFieldException e) { - throw new RuntimeException(e); + try { + // Check if subclass contains the desired field. + field = clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e2) { + throw new RuntimeException(e2); + } } columns.add(columnMetaData(name, index, field.getType(), false)); fields.add(field); @@ -263,44 +395,18 @@ private static ImmutableMap.Builder addProperty( typeFilter = v1 -> typeList.contains(v1.tableType); } final Predicate1 schemaMatcher = namedMatcher(schemaPattern); - return createResultSet(schemas(catalog) - .where(schemaMatcher) - .selectMany(schema -> tables(schema, matcher(tableNamePattern))) - .where(typeFilter), - MetaTable.class, - "TABLE_CAT", - "TABLE_SCHEM", - "TABLE_NAME", - "TABLE_TYPE", - "REMARKS", - "TYPE_CAT", - "TYPE_SCHEM", - "TYPE_NAME", - "SELF_REFERENCING_COL_NAME", - "REF_GENERATION"); + Enumerable tables = schemas(catalog) + .where(schemaMatcher) + .selectMany(schema -> tables(schema, matcher(tableNamePattern))) + .where(typeFilter); + return createResultSet(tables, + metaTableFactory.getMetaTableClass(), + metaTableFactory.getColumnNames()); } @Override public MetaResultSet getTypeInfo(ConnectionHandle ch) { return createResultSet(allTypeInfo(), - MetaTypeInfo.class, - "TYPE_NAME", - "DATA_TYPE", - "PRECISION", - "LITERAL_PREFIX", - "LITERAL_SUFFIX", - "CREATE_PARAMS", - "NULLABLE", - "CASE_SENSITIVE", - "SEARCHABLE", - "UNSIGNED_ATTRIBUTE", - "FIXED_PREC_SCALE", - "AUTO_INCREMENT", - "LOCAL_TYPE_NAME", - "MINIMUM_SCALE", - "MAXIMUM_SCALE", - "SQL_DATA_TYPE", - "SQL_DATETIME_SUB", - "NUM_PREC_RADIX"); + MetaTypeInfo.class, TYPE_INFO_COLUMNS); } @Override public MetaResultSet getColumns(ConnectionHandle ch, @@ -317,31 +423,8 @@ private static ImmutableMap.Builder addProperty( .selectMany(schema -> tables(schema, tableNameMatcher)) .selectMany(this::columns) .where(columnMatcher), - MetaColumn.class, - "TABLE_CAT", - "TABLE_SCHEM", - "TABLE_NAME", - "COLUMN_NAME", - "DATA_TYPE", - "TYPE_NAME", - "COLUMN_SIZE", - "BUFFER_LENGTH", - "DECIMAL_DIGITS", - "NUM_PREC_RADIX", - "NULLABLE", - "REMARKS", - "COLUMN_DEF", - "SQL_DATA_TYPE", - "SQL_DATETIME_SUB", - "CHAR_OCTET_LENGTH", - "ORDINAL_POSITION", - "IS_NULLABLE", - "SCOPE_CATALOG", - "SCOPE_SCHEMA", - "SCOPE_TABLE", - "SOURCE_DATA_TYPE", - "IS_AUTOINCREMENT", - "IS_GENERATEDCOLUMN"); + metaColumnFactory.getMetaColumnClass(), + metaColumnFactory.getColumnNames()); } Enumerable catalogs() { @@ -386,10 +469,8 @@ Enumerable tables(final MetaSchema schema_) { requireNonNull(schema.calciteSchema.getTable(name, true), () -> "table " + name + " is not found (case sensitive)") .getTable(); - return new CalciteMetaTable(table, - schema.tableCatalog, - schema.tableSchem, - name); + return metaTableFactory.createTable(table, schema.tableCatalog, + schema.tableSchem, name); }) .concat( Linq4j.asEnumerable( @@ -397,7 +478,7 @@ Enumerable tables(final MetaSchema schema_) { .entrySet()) .select(pair -> { final Table table = pair.getValue(); - return new CalciteMetaTable(table, + return metaTableFactory.createTable(table, schema.tableCatalog, schema.tableSchem, pair.getKey()); @@ -467,7 +548,8 @@ public Enumerable columns(final MetaTable table_) { .map(RelDataType::getSqlTypeName) .map(SqlTypeName::getJdbcOrdinal) .orElse(field.getType().getSqlTypeName().getJdbcOrdinal()); - return new MetaColumn( + return metaColumnFactory.createColumn( + table.calciteTable, table.tableCat, table.tableSchem, table.tableName, @@ -492,21 +574,17 @@ public Enumerable columns(final MetaTable table_) { Pat schemaPattern) { final Predicate1 schemaMatcher = namedMatcher(schemaPattern); return createResultSet(schemas(catalog).where(schemaMatcher), - MetaSchema.class, - "TABLE_SCHEM", - "TABLE_CATALOG"); + MetaSchema.class, SCHEMA_COLUMNS); } @Override public MetaResultSet getCatalogs(ConnectionHandle ch) { return createResultSet(catalogs(), - MetaCatalog.class, - "TABLE_CAT"); + MetaCatalog.class, CATALOG_COLUMNS); } @Override public MetaResultSet getTableTypes(ConnectionHandle ch) { return createResultSet(tableTypes(), - MetaTableType.class, - "TABLE_TYPE"); + MetaTableType.class, TABLE_TYPE_COLUMNS); } @Override public MetaResultSet getFunctions(ConnectionHandle ch, @@ -520,13 +598,7 @@ public Enumerable columns(final MetaTable table_) { .orderBy(x -> (Comparable) FlatLists.of( x.functionCat, x.functionSchem, x.functionName, x.specificName)), - MetaFunction.class, - "FUNCTION_CAT", - "FUNCTION_SCHEM", - "FUNCTION_NAME", - "REMARKS", - "FUNCTION_TYPE", - "SPECIFIC_NAME"); + MetaFunction.class, FUNCTION_COLUMNS); } Enumerable functions(final MetaSchema schema_, final String catalog) { @@ -816,10 +888,18 @@ public static CalciteConnection connect(CalciteSchema schema, } /** Metadata describing a Calcite table. */ - private static class CalciteMetaTable extends MetaTable { + public static class CalciteMetaTable extends MetaTable { private final Table calciteTable; - CalciteMetaTable(Table calciteTable, String tableCat, + /** + * Creates a CalciteMetaTable. + * + * @param calciteTable Table + * @param tableCat Table catalog, or null + * @param tableSchem Table schema, or null + * @param tableName Table name + */ + public CalciteMetaTable(Table calciteTable, String tableCat, String tableSchem, String tableName) { super(tableCat, tableSchem, tableName, calciteTable.getJdbcTableType().jdbcName); diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaTableFactory.java b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaTableFactory.java new file mode 100644 index 00000000000..16556d68a57 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaTableFactory.java @@ -0,0 +1,42 @@ +/* + * 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.jdbc; + +import org.apache.calcite.avatica.MetaImpl.MetaTable; +import org.apache.calcite.schema.Table; + +import java.util.List; + +/** Factory for creating instances of {@link MetaTable}. + * + * @see java.sql.DatabaseMetaData#getTables */ +public interface CalciteMetaTableFactory { + /** Instantiates a MetaTable. */ + MetaTable createTable(Table table, String tableCat, String tableSchem, + String tableName); + + /** Returns the list of expected column names. + * + *

The default implementation returns the columns described in the JDBC + * specification. */ + default List getColumnNames() { + return CalciteMetaImpl.TABLE_COLUMNS; + } + + /** Returns the type of object created. Must be a subclass of MetaTable. */ + Class getMetaTableClass(); +} diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaTableFactoryImpl.java b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaTableFactoryImpl.java new file mode 100644 index 00000000000..7b3709ac531 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaTableFactoryImpl.java @@ -0,0 +1,45 @@ +/* + * 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.jdbc; + +import org.apache.calcite.avatica.MetaImpl.MetaTable; +import org.apache.calcite.jdbc.CalciteMetaImpl.CalciteMetaTable; +import org.apache.calcite.schema.Table; + +/** Default implementation of CalciteMetaTableFactory. */ +public class CalciteMetaTableFactoryImpl + implements CalciteMetaTableFactory { + + /** Singleton instance. */ + public static final CalciteMetaTableFactoryImpl INSTANCE = + new CalciteMetaTableFactoryImpl(); + + /** Internal constructor; protected to allow subclassing. */ + protected CalciteMetaTableFactoryImpl() {} + + @Override public CalciteMetaTable createTable( + Table table, + String tableCat, + String tableSchem, + String tableName) { + return new CalciteMetaTable(table, tableCat, tableSchem, tableName); + } + + @Override public Class getMetaTableClass() { + return CalciteMetaTable.class; + } +} diff --git a/core/src/main/java/org/apache/calcite/jdbc/Driver.java b/core/src/main/java/org/apache/calcite/jdbc/Driver.java index 20753ebd821..c5d2c0e206c 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/Driver.java +++ b/core/src/main/java/org/apache/calcite/jdbc/Driver.java @@ -26,6 +26,7 @@ import org.apache.calcite.avatica.HandlerImpl; import org.apache.calcite.avatica.Meta; import org.apache.calcite.avatica.UnregisteredDriver; +import org.apache.calcite.config.CalciteConnectionConfig; import org.apache.calcite.config.CalciteConnectionProperty; import org.apache.calcite.linq4j.function.Function0; import org.apache.calcite.model.JsonSchema; @@ -211,7 +212,16 @@ protected Function0 createPrepareFactory() { } @Override public Meta createMeta(AvaticaConnection connection) { - return new CalciteMetaImpl((CalciteConnectionImpl) connection); + final CalciteConnectionConfig config = + (CalciteConnectionConfig) connection.config(); + CalciteMetaTableFactory metaTableFactory = + config.metaTableFactory(CalciteMetaTableFactory.class, + CalciteMetaTableFactoryImpl.INSTANCE); + CalciteMetaColumnFactory metaColumnFactory = + config.metaColumnFactory(CalciteMetaColumnFactory.class, + CalciteMetaColumnFactoryImpl.INSTANCE); + return CalciteMetaImpl.create((CalciteConnectionImpl) connection, + metaTableFactory, metaColumnFactory); } /** Creates an internal connection. */ 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 87854e77cec..baeee2c19ec 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 @@ -53,8 +53,8 @@ * * @see org.apache.calcite.rel.logical.LogicalTableFunctionScan */ -public abstract class TableFunctionScan extends AbstractRelNode implements - Hintable { +public abstract class TableFunctionScan extends AbstractRelNode + implements Hintable { //~ Instance fields -------------------------------------------------------- private final RexNode rexCall; diff --git a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java index 8415fcedc0c..e0dce8bebec 100644 --- a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java +++ b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java @@ -73,7 +73,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.function.Function; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; @@ -102,51 +101,6 @@ class CalciteRemoteDriverTest { CalciteSystemProperty.DEBUG.value() ? Util.printWriter(System.out) : new PrintWriter(new StringWriter()); - private static final Function GET_SCHEMAS = - connection -> { - try { - return connection.getMetaData().getSchemas(); - } catch (SQLException e) { - throw TestUtil.rethrow(e); - } - }; - - private static final Function GET_CATALOGS = - connection -> { - try { - return connection.getMetaData().getCatalogs(); - } catch (SQLException e) { - throw TestUtil.rethrow(e); - } - }; - - private static final Function GET_COLUMNS = - connection -> { - try { - return connection.getMetaData().getColumns(null, null, null, null); - } catch (SQLException e) { - throw TestUtil.rethrow(e); - } - }; - - private static final Function GET_TYPEINFO = - connection -> { - try { - return connection.getMetaData().getTypeInfo(); - } catch (SQLException e) { - throw TestUtil.rethrow(e); - } - }; - - private static final Function GET_TABLE_TYPES = - connection -> { - try { - return connection.getMetaData().getTableTypes(); - } catch (SQLException e) { - throw TestUtil.rethrow(e); - } - }; - private static @Nullable Connection localConnection; private static @Nullable HttpServer start; @@ -175,6 +129,54 @@ protected static Connection getRemoteConnection() throws SQLException { } } + private static ResultSet getSchemas(Connection connection) { + try { + return connection.getMetaData().getSchemas(); + } catch (SQLException e) { + throw TestUtil.rethrow(e); + } + } + + private static ResultSet getCatalogs(Connection connection) { + try { + return connection.getMetaData().getCatalogs(); + } catch (SQLException e) { + throw TestUtil.rethrow(e); + } + } + + private static ResultSet getColumns(Connection connection) { + try { + return connection.getMetaData().getColumns(null, null, null, null); + } catch (SQLException e) { + throw TestUtil.rethrow(e); + } + } + + private static ResultSet getTables(Connection connection) { + try { + return connection.getMetaData().getTables(null, null, null, null); + } catch (SQLException e) { + throw TestUtil.rethrow(e); + } + } + + private static ResultSet getTypeInfo(Connection connection) { + try { + return connection.getMetaData().getTypeInfo(); + } catch (SQLException e) { + throw TestUtil.rethrow(e); + } + } + + private static ResultSet getTableTypes(Connection connection) { + try { + return connection.getMetaData().getTableTypes(); + } catch (SQLException e) { + throw TestUtil.rethrow(e); + } + } + @Test void testCatalogsLocal() throws Exception { final Connection connection = DriverManager.getConnection("jdbc:avatica:remote:factory=" + LJS); @@ -263,39 +265,67 @@ protected static Connection getRemoteConnection() throws SQLException { @Test void testRemoteCatalogs() { CalciteAssert.hr() .with(CalciteRemoteDriverTest::getRemoteConnection) - .metaData(GET_CATALOGS) + .metaData(CalciteRemoteDriverTest::getCatalogs) .returns("TABLE_CAT=null\n"); } @Test void testRemoteSchemas() { CalciteAssert.hr() .with(CalciteRemoteDriverTest::getRemoteConnection) - .metaData(GET_SCHEMAS) + .metaData(CalciteRemoteDriverTest::getSchemas) .returns("TABLE_SCHEM=POST; TABLE_CATALOG=null\n" + "TABLE_SCHEM=foodmart; TABLE_CATALOG=null\n" + "TABLE_SCHEM=hr; TABLE_CATALOG=null\n" + "TABLE_SCHEM=metadata; TABLE_CATALOG=null\n"); } + /** Checks that the default {@code getColumns()} response + * contains the 24 standard columns specified in the JDBC specification + * and in the correct order. */ @Test void testRemoteColumns() { CalciteAssert.hr() .with(CalciteRemoteDriverTest::getRemoteConnection) - .metaData(GET_COLUMNS) - .returns(CalciteAssert.checkResultContains("COLUMN_NAME=EMPNO")); + .metaData(CalciteRemoteDriverTest::getColumns) + .returns( + CalciteAssert.checkResultContains("TABLE_CAT=null; " + + "TABLE_SCHEM=POST; TABLE_NAME=EMPS; COLUMN_NAME=EMPNO; " + + "DATA_TYPE=4; TYPE_NAME=INTEGER NOT NULL; COLUMN_SIZE=-1; " + + "BUFFER_LENGTH=null; DECIMAL_DIGITS=null; NUM_PREC_RADIX=10; " + + "NULLABLE=0; REMARKS=null; COLUMN_DEF=null; " + + "SQL_DATA_TYPE=null; SQL_DATETIME_SUB=null; " + + "CHAR_OCTET_LENGTH=-1; ORDINAL_POSITION=1; IS_NULLABLE=NO; " + + "SCOPE_CATALOG=null; SCOPE_SCHEMA=null; SCOPE_TABLE=null; " + + "SOURCE_DATA_TYPE=null; IS_AUTOINCREMENT=; " + + "IS_GENERATEDCOLUMN=")); + } + + /** Checks that the default {@code getTables()} response contains the 10 + * standard columns specified in the JDBC specification and in the correct + * order. */ + @Test void testRemoteTables() { + CalciteAssert.hr() + .with(CalciteRemoteDriverTest::getRemoteConnection) + .metaData(CalciteRemoteDriverTest::getTables) + .returns( + CalciteAssert.checkResultContains("TABLE_CAT=null; " + + "TABLE_SCHEM=POST; TABLE_NAME=DEPT; TABLE_TYPE=VIEW; " + + "REMARKS=null; TYPE_CAT=null; TYPE_SCHEM=null; " + + "TYPE_NAME=null; SELF_REFERENCING_COL_NAME=null; " + + "REF_GENERATION=null")); } @Test void testRemoteTypeInfo() { // TypeInfo does not include internal types (NULL, SYMBOL, ANY, etc.) CalciteAssert.hr() .with(CalciteRemoteDriverTest::getRemoteConnection) - .metaData(GET_TYPEINFO) + .metaData(CalciteRemoteDriverTest::getTypeInfo) .returns(CalciteAssert.checkResultCount(is(41))); } @Test void testRemoteTableTypes() { CalciteAssert.hr() .with(CalciteRemoteDriverTest::getRemoteConnection) - .metaData(GET_TABLE_TYPES) + .metaData(CalciteRemoteDriverTest::getTableTypes) .returns("TABLE_TYPE=TABLE\n" + "TABLE_TYPE=VIEW\n"); } @@ -553,7 +583,7 @@ private String pad(String x) { public static Connection makeConnection(boolean withMeasures) throws Exception { - List employees = new ArrayList(); + List employees = new ArrayList<>(); for (int i = 1; i <= 101; i++) { employees.add(new Employee(i, 0, "first", 0f, null)); } @@ -661,7 +691,7 @@ public static class LocalServiceMoreFactory implements Service.Factory { try { Connection conn = makeConnection(); final CalciteMetaImpl meta = - new CalciteMetaImpl(conn.unwrap(CalciteConnectionImpl.class)); + CalciteMetaImpl.create(conn.unwrap(CalciteConnection.class)); return new LocalService(meta); } catch (Exception e) { throw TestUtil.rethrow(e); @@ -818,7 +848,7 @@ public static class Factory implements Meta.Factory { public Meta create(List args) { try { final Connection connection = CalciteAssert.hr().connect(); - return new CalciteMetaImpl((CalciteConnectionImpl) connection); + return CalciteMetaImpl.create((CalciteConnection) connection); } catch (Exception e) { throw TestUtil.rethrow(e); } @@ -845,8 +875,7 @@ public static class LocalServiceModifiableFactory implements Service.Factory { try { Connection conn = JdbcFrontLinqBackTest.makeConnection(); final CalciteMetaImpl meta = - new CalciteMetaImpl( - conn.unwrap(CalciteConnectionImpl.class)); + CalciteMetaImpl.create(conn.unwrap(CalciteConnection.class)); return new LocalService(meta); } catch (Exception e) { throw TestUtil.rethrow(e); @@ -956,6 +985,6 @@ public static class LocalServiceModifiableFactory implements Service.Factory { /** * Remote PreparedStatement insert WITH bind variables. */ - @Test void testRemotePreparedStatementInsert2() throws Exception { + @Test void testRemotePreparedStatementInsert2() { } } diff --git a/core/src/test/java/org/apache/calcite/test/JdbcFrontJdbcBackTest.java b/core/src/test/java/org/apache/calcite/test/JdbcFrontJdbcBackTest.java index 4bb84046b6f..4293e57c0f2 100644 --- a/core/src/test/java/org/apache/calcite/test/JdbcFrontJdbcBackTest.java +++ b/core/src/test/java/org/apache/calcite/test/JdbcFrontJdbcBackTest.java @@ -16,14 +16,23 @@ */ package org.apache.calcite.test; +import org.apache.calcite.avatica.MetaImpl.MetaTable; +import org.apache.calcite.config.CalciteConnectionProperty; +import org.apache.calcite.jdbc.CalciteMetaImpl; +import org.apache.calcite.jdbc.CalciteMetaImpl.CalciteMetaTable; +import org.apache.calcite.jdbc.CalciteMetaTableFactory; +import org.apache.calcite.schema.Table; import org.apache.calcite.util.TestUtil; +import com.google.common.collect.ImmutableList; + import org.hamcrest.Matcher; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; import static org.apache.calcite.test.CalciteAssert.that; @@ -32,6 +41,7 @@ import static org.hamcrest.Matchers.hasToString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests for a JDBC front-end and JDBC back-end. @@ -73,6 +83,28 @@ class JdbcFrontJdbcBackTest { }); } + @Test void testTablesExtraColumn() throws Exception { + that() + .with(CalciteAssert.Config.JDBC_FOODMART) + .with( + CalciteConnectionProperty.META_TABLE_FACTORY.camelName(), + MetaExtraTableFactoryImpl.class.getName()) + .doWithConnection(connection -> { + try { + ResultSet rset = + connection.getMetaData().getTables( + null, null, null, null); + assertTrue(rset.next()); + // Asserts that the number of columns in the result set equals + // MetaExtraTableFactoryImpl's expected number of columns. + assertEquals(rset.getMetaData().getColumnCount(), 11); + assertEquals(rset.getMetaData().getColumnName(11), "EXTRA_LABEL"); + } catch (SQLException e) { + throw TestUtil.rethrow(e); + } + }); + } + @Test void testTablesByType() throws Exception { // check with the form recommended by JDBC checkTablesByType("SYSTEM TABLE", is("COLUMNS;TABLES;")); @@ -150,4 +182,43 @@ private void checkTablesByType(final String tableType, .returns2("c0=11.4\n" + "c0=8.55\n"); } + + /** Mock implementation of {@link CalciteMetaTable}. */ + private static class MetaExtraTable extends CalciteMetaTable { + final String extraLabel; + + MetaExtraTable(Table calciteTable, String tableCat, + String tableSchem, String tableName) { + super(calciteTable, tableCat, tableSchem, tableName); + this.extraLabel = "extraLabel1"; + } + } + + /** Mock implementation of {@link CalciteMetaTableFactory} that creates + * instances of {@link MetaExtraTable}. Must be public, otherwise it is + * inaccessible from {@link org.apache.calcite.jdbc.Driver}. */ + public static class MetaExtraTableFactoryImpl + implements CalciteMetaTableFactory { + public static final MetaExtraTableFactoryImpl INSTANCE = + new MetaExtraTableFactoryImpl(); + + MetaExtraTableFactoryImpl() {} + + @Override public MetaTable createTable(Table table, String tableCat, + String tableSchem, String tableName) { + return new MetaExtraTable(table, tableCat, tableSchem, tableName); + } + + @Override public List getColumnNames() { + // 11 columns total. + return ImmutableList.builder() + .addAll(CalciteMetaImpl.TABLE_COLUMNS) + .add("EXTRA_LABEL") + .build(); + } + + @Override public Class getMetaTableClass() { + return MetaExtraTable.class; + } + } }