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 2019/05/29 20:04:24 UTC
[calcite] 03/06: [CALCITE-3048] Improve how JDBC adapter deduces
current schema on Redshift
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 7429c25a178401bd49b74d96645ef4226fc228ce
Author: Julian Hyde <jh...@apache.org>
AuthorDate: Tue Apr 30 14:39:53 2019 -0700
[CALCITE-3048] Improve how JDBC adapter deduces current schema on Redshift
In JdbcSchema on PostgreSQL or Redshift, if schema is null, call
CURRENT_SCHEMA() to get it. Similarly catalog and CURRENT_DATABASE().
Without this fix, we sometimes call DatabaseMetaData.getTables with null
or empty schema, and get tables from other schemas, resulting in
a Guava "Multiple entries with same key" error.
---
.../apache/calcite/adapter/jdbc/JdbcSchema.java | 112 ++++++++++++++++-----
.../apache/calcite/sql/SqlDialectFactoryImpl.java | 8 ++
2 files changed, 94 insertions(+), 26 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java
index 0142adc..5b91c3d 100644
--- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java
+++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java
@@ -17,7 +17,9 @@
package org.apache.calcite.adapter.jdbc;
import org.apache.calcite.avatica.AvaticaUtils;
+import org.apache.calcite.avatica.MetaImpl;
import org.apache.calcite.avatica.SqlType;
+import org.apache.calcite.linq4j.function.Experimental;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -36,23 +38,29 @@ import org.apache.calcite.sql.SqlDialectFactory;
import org.apache.calcite.sql.SqlDialectFactoryImpl;
import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
+import com.google.common.collect.Ordering;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.function.BiFunction;
import javax.sql.DataSource;
/**
@@ -71,6 +79,12 @@ public class JdbcSchema implements Schema {
private ImmutableMap<String, JdbcTable> tableMap;
private final boolean snapshot;
+ @Experimental
+ public static final ThreadLocal<Foo> THREAD_METADATA = new ThreadLocal<>();
+
+ private static final Ordering<Iterable<Integer>> VERSION_ORDERING =
+ Ordering.<Integer>natural().lexicographical();
+
/**
* Creates a JDBC schema.
*
@@ -88,7 +102,6 @@ public class JdbcSchema implements Schema {
private JdbcSchema(DataSource dataSource, SqlDialect dialect,
JdbcConvention convention, String catalog, String schema,
ImmutableMap<String, JdbcTable> tableMap) {
- super();
this.dataSource = Objects.requireNonNull(dataSource);
this.dialect = Objects.requireNonNull(dialect);
this.convention = convention;
@@ -231,31 +244,31 @@ public class JdbcSchema implements Schema {
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
- DatabaseMetaData metaData = connection.getMetaData();
- String catalog;
- String schema;
- if (metaData.getJDBCMajorVersion() > 4
- || (metaData.getJDBCMajorVersion() == 4 && metaData.getJDBCMinorVersion() >= 1)) {
- // From JDBC 4.1, catalog and schema can be retrieved from the connection object,
- // hence try to get it from there if it was not specified by user
- catalog = Util.first(this.catalog, connection.getCatalog());
- schema = Util.first(this.schema, connection.getSchema());
+ final Pair<String, String> catalogSchema = getCatalogSchema(connection);
+ final String catalog = catalogSchema.left;
+ final String schema = catalogSchema.right;
+ final Iterable<MetaImpl.MetaTable> tableDefs;
+ if (THREAD_METADATA.get() != null) {
+ tableDefs = THREAD_METADATA.get().apply(catalog, schema);
} else {
- catalog = this.catalog;
- schema = this.schema;
+ final List<MetaImpl.MetaTable> tableDefList = new ArrayList<>();
+ final DatabaseMetaData metaData = connection.getMetaData();
+ resultSet = metaData.getTables(catalog, schema, null, null);
+ while (resultSet.next()) {
+ final String catalogName = resultSet.getString(1);
+ final String schemaName = resultSet.getString(2);
+ final String tableName = resultSet.getString(3);
+ final String tableTypeName = resultSet.getString(4);
+ tableDefList.add(
+ new MetaImpl.MetaTable(catalogName, schemaName, tableName,
+ tableTypeName));
+ }
+ tableDefs = tableDefList;
}
- resultSet = metaData.getTables(
- catalog,
- schema,
- null,
- null);
+
final ImmutableMap.Builder<String, JdbcTable> builder =
ImmutableMap.builder();
- while (resultSet.next()) {
- final String tableName = resultSet.getString(3);
- final String catalogName = resultSet.getString(1);
- final String schemaName = resultSet.getString(2);
- final String tableTypeName = resultSet.getString(4);
+ for (MetaImpl.MetaTable tableDef : tableDefs) {
// Clean up table type. In particular, this ensures that 'SYSTEM TABLE',
// returned by Phoenix among others, maps to TableType.SYSTEM_TABLE.
// We know enum constants are upper-case without spaces, so we can't
@@ -266,17 +279,18 @@ public class JdbcSchema implements Schema {
// The tables are not designed to be queried by users, however we do
// not filter them as we keep all the other table types.
final String tableTypeName2 =
- tableTypeName == null
+ tableDef.tableType == null
? null
- : tableTypeName.toUpperCase(Locale.ROOT).replace(' ', '_');
+ : tableDef.tableType.toUpperCase(Locale.ROOT).replace(' ', '_');
final TableType tableType =
Util.enumVal(TableType.OTHER, tableTypeName2);
if (tableType == TableType.OTHER && tableTypeName2 != null) {
System.out.println("Unknown table type: " + tableTypeName2);
}
final JdbcTable table =
- new JdbcTable(this, catalogName, schemaName, tableName, tableType);
- builder.put(tableName, table);
+ new JdbcTable(this, tableDef.tableCat, tableDef.tableSchem,
+ tableDef.tableName, tableType);
+ builder.put(tableDef.tableName, table);
}
return builder.build();
} catch (SQLException e) {
@@ -287,6 +301,46 @@ public class JdbcSchema implements Schema {
}
}
+ /** Returns [major, minor] version from a database metadata. */
+ private List<Integer> version(DatabaseMetaData metaData) throws SQLException {
+ return ImmutableList.of(metaData.getJDBCMajorVersion(),
+ metaData.getJDBCMinorVersion());
+ }
+
+ /** Returns a pair of (catalog, schema) for the current connection. */
+ private Pair<String, String> getCatalogSchema(Connection connection)
+ throws SQLException {
+ final DatabaseMetaData metaData = connection.getMetaData();
+ final List<Integer> version41 = ImmutableList.of(4, 1); // JDBC 4.1
+ String catalog = this.catalog;
+ String schema = this.schema;
+ final boolean jdbc41OrAbove =
+ VERSION_ORDERING.compare(version(metaData), version41) >= 0;
+ if (catalog == null && jdbc41OrAbove) {
+ // From JDBC 4.1, catalog and schema can be retrieved from the connection
+ // object, hence try to get it from there if it was not specified by user
+ catalog = connection.getCatalog();
+ }
+ if (schema == null && jdbc41OrAbove) {
+ schema = connection.getSchema();
+ if ("".equals(schema)) {
+ schema = null; // PostgreSQL returns useless "" sometimes
+ }
+ }
+ if ((catalog == null || schema == null)
+ && metaData.getDatabaseProductName().equals("PostgreSQL")) {
+ final String sql = "select current_database(), current_schema()";
+ try (Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery(sql)) {
+ if (resultSet.next()) {
+ catalog = resultSet.getString(1);
+ schema = resultSet.getString(2);
+ }
+ }
+ }
+ return Pair.of(catalog, schema);
+ }
+
public Table getTable(String name) {
return getTableMap(false).get(name);
}
@@ -506,6 +560,12 @@ public class JdbcSchema implements Schema {
return JdbcSchema.create(parentSchema, name, operand);
}
}
+
+ /** Do not use */
+ @Experimental
+ public interface Foo
+ extends BiFunction<String, String, Iterable<MetaImpl.MetaTable>> {
+ }
}
// End JdbcSchema.java
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlDialectFactoryImpl.java b/core/src/main/java/org/apache/calcite/sql/SqlDialectFactoryImpl.java
index 280c5fa..4957970 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlDialectFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlDialectFactoryImpl.java
@@ -209,6 +209,8 @@ public class SqlDialectFactoryImpl implements SqlDialectFactory {
return NullCollation.LOW;
} else if (databaseMetaData.nullsAreSortedHigh()) {
return NullCollation.HIGH;
+ } else if (isBigQuery(databaseMetaData)) {
+ return NullCollation.LOW;
} else {
throw new IllegalArgumentException("cannot deduce null collation");
}
@@ -217,6 +219,12 @@ public class SqlDialectFactoryImpl implements SqlDialectFactory {
}
}
+ private static boolean isBigQuery(DatabaseMetaData databaseMetaData)
+ throws SQLException {
+ return databaseMetaData.getDatabaseProductName()
+ .equals("Google Big Query");
+ }
+
private String getIdentifierQuoteString(DatabaseMetaData databaseMetaData) {
try {
return databaseMetaData.getIdentifierQuoteString();