diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java index de285d44c193..27055889571e 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java @@ -24,6 +24,9 @@ import com.facebook.presto.spi.type.Type; import com.facebook.presto.spi.type.VarcharType; import com.google.common.base.Joiner; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -40,11 +43,14 @@ import java.sql.Statement; import java.sql.Types; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import static com.facebook.presto.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; import static com.facebook.presto.spi.StandardErrorCode.NOT_FOUND; @@ -102,6 +108,9 @@ public class BaseJdbcClient protected final Properties connectionProperties; protected final String identifierQuote; + private final LoadingCache> schemaMappingCache; + private final Map>> schemaTableMapping = new ConcurrentHashMap<>(); + public BaseJdbcClient(JdbcConnectorId connectorId, BaseJdbcConfig config, String identifierQuote, Driver driver) { this.connectorId = requireNonNull(connectorId, "connectorId is null").toString(); @@ -118,18 +127,68 @@ public BaseJdbcClient(JdbcConnectorId connectorId, BaseJdbcConfig config, String if (config.getConnectionPassword() != null) { connectionProperties.setProperty("password", config.getConnectionPassword()); } + + // generate the map of lowercased schema names to JDBC schema names + schemaMappingCache = CacheBuilder.newBuilder().build(new CacheLoader>() + { + @Override + public Optional load(String key) + throws Exception + { + for (String schemaName : getOriginalSchemas()) { + if (schemaName.equals(key)) { + return Optional.of(schemaName); + } + + String schemaNameLower = schemaName.toLowerCase(ENGLISH); + if (schemaNameLower.equals(key)) { + return Optional.of(schemaName); + } + } + return Optional.empty(); + } + }); + + if (config.isPreloadSchemaTableMapping()) { + reloadCache(); + } } - @Override - public Set getSchemaNames() + /** + * Invalidates all of the caches and reloads + */ + protected void reloadCache() + { + schemaMappingCache.invalidateAll(); + + // this preloads the list of schema names + Set schemas = getSchemaNames(); + + // invalidate and remove from the schema table mapping all records, start from scratch + schemaTableMapping.keySet().forEach(key -> { + schemaTableMapping.get(key).invalidateAll(); + }); + schemaTableMapping.clear(); + + for (final String schema : schemas) { + // this preloads the list of table names for each schema + getTableNames(schema); + } + } + + /** + * Pull the list of Schema names from the RDBMS exactly as they are returned. Override if the RDBMS method to pull the schemas is different. + * + * @return schema names in original form + */ + protected Set getOriginalSchemas() { try (Connection connection = driver.connect(connectionUrl, connectionProperties); ResultSet resultSet = connection.getMetaData().getSchemas()) { ImmutableSet.Builder schemaNames = ImmutableSet.builder(); while (resultSet.next()) { - String schemaName = resultSet.getString("TABLE_SCHEM").toLowerCase(ENGLISH); - // skip internal schemas - if (!schemaName.equals("information_schema")) { + String schemaName = resultSet.getString("TABLE_SCHEM"); + if (!schemaName.toLowerCase(ENGLISH).equals("information_schema")) { schemaNames.add(schemaName); } } @@ -140,39 +199,199 @@ public Set getSchemaNames() } } + @Override + public Set getSchemaNames() + { + ImmutableSet.Builder schemaNames = ImmutableSet.builder(); + Set originalNames = getOriginalSchemas(); + Map> mappedNames = new HashMap<>(); + for (String schemaName : originalNames) { + String schemaNameLower = schemaName.toLowerCase(ENGLISH); + schemaNames.add(schemaNameLower); + mappedNames.put(schemaNameLower, Optional.of(schemaName)); + } + + // if someone is listing all of the schema names, throw them all into the cache as a refresh since we already spent the time pulling them from the DB + schemaMappingCache.putAll(mappedNames); + return schemaNames.build(); + } + + /** + * Pull the list of Table names from the RDBMS exactly as they are returned. Override if the RDBMS method to pull the tables is different. Each {@link String} array must have + * the table's original schema name as the first element, and the original table name as the second element. + * + * @param schema the schema to list the tables for, or NULL for all + * @return the schema + table names + */ + protected List getOriginalTablesWithSchema(Connection connection, String schema) + { + try (ResultSet resultSet = getTables(connection, schema, null)) { + ImmutableList.Builder list = ImmutableList.builder(); + while (resultSet.next()) { + String schemaName = resultSet.getString("TABLE_SCHEM"); + String tableName = resultSet.getString("TABLE_NAME"); + list.add(new CaseSensitiveMappedSchemaTableName(schemaName, tableName)); + } + return list.build(); + } + catch (SQLException e) { + throw new PrestoException(JDBC_ERROR, e); + } + } + @Override public List getTableNames(@Nullable String schema) { try (Connection connection = driver.connect(connectionUrl, connectionProperties)) { DatabaseMetaData metadata = connection.getMetaData(); - if (metadata.storesUpperCaseIdentifiers() && (schema != null)) { - schema = schema.toUpperCase(ENGLISH); + + schema = finalizeSchemaName(metadata, schema); + Map> schemaMappedNames = new HashMap<>(); + ImmutableList.Builder tableNameList = ImmutableList.builder(); + for (CaseSensitiveMappedSchemaTableName table : getOriginalTablesWithSchema(connection, schema)) { + Map mappedNames = schemaMappedNames.computeIfAbsent(table.getSchemaName(), s -> new HashMap<>()); + mappedNames.put(table.getSchemaNameLower(), table.getTableName()); + tableNameList.add(new SchemaTableName(table.getSchemaNameLower(), table.getTableNameLower())); } - try (ResultSet resultSet = getTables(connection, schema, null)) { - ImmutableList.Builder list = ImmutableList.builder(); - while (resultSet.next()) { - list.add(getSchemaTableName(resultSet)); - } - return list.build(); + // if someone is listing all of the table names, throw them all into the cache as a refresh since we already spent the time pulling them from the DB + for (Map.Entry> entry : schemaMappedNames.entrySet()) { + updateTableMapping(entry.getKey(), entry.getValue()); } + return tableNameList.build(); } catch (SQLException e) { throw new PrestoException(JDBC_ERROR, e); } } + /** + * Fill in the {@link LoadingCache} for the mapped table names for the schema provided + * + * @param jdbcSchema the original name of the schema as it exists in the JDBC server + * @param tableMappings mapping of lowercase to original table names + */ + private void updateTableMapping(String jdbcSchema, Map tableMappings) + { + LoadingCache> tableNameMapping = getTableMapping(jdbcSchema); + Map> tmp = new HashMap<>(); + for (Map.Entry entry : tableMappings.entrySet()) { + tmp.put(entry.getKey(), Optional.of(entry.getValue())); + } + tableNameMapping.putAll(tmp); + } + + /** + * Fetch the {@link LoadingCache} of table mapped names for the given schema name. + * + * @param jdbcSchema the original name of the schema as it exists in the JDBC server + * @return the {@link LoadingCache} which contains the table mapped names + */ + private LoadingCache> getTableMapping(String jdbcSchema) + { + return schemaTableMapping.computeIfAbsent(jdbcSchema, (String s) -> + CacheBuilder.newBuilder().build(new CacheLoader>() + { + @Override + public Optional load(String key) + throws Exception + { + try (Connection connection = driver.connect(connectionUrl, connectionProperties)) { + DatabaseMetaData metadata = connection.getMetaData(); + String jdbcSchemaName = finalizeSchemaName(metadata, jdbcSchema); + + for (CaseSensitiveMappedSchemaTableName table : getOriginalTablesWithSchema(connection, jdbcSchemaName)) { + String tableName = table.getTableName(); + if (tableName.equals(key)) { + return Optional.of(tableName); + } + String tableNameLower = table.getTableNameLower(); + if (tableNameLower.equals(key)) { + return Optional.of(tableName); + } + } + } + catch (SQLException e) { + throw new PrestoException(JDBC_ERROR, e); + } + return Optional.empty(); + } + })); + } + + /** + * Looks up the table name given to map it to it's original, case-sensitive name in the database. + * + * @param jdbcSchema The database schema name + * @param tableName The table name within the schema that needs to be mapped to it's original, or NULL if it couldn't be found in the cache + * @return The mapped case-sensitive table name, if found, otherwise the original table name passed in + */ + private String getMappedTableName(String jdbcSchema, String tableName) + { + LoadingCache> tableNameMapping = getTableMapping(jdbcSchema); + Optional value = tableNameMapping.getUnchecked(tableName); + if (value.isPresent()) { + return value.get(); + } + // If we get back an Empty value, invalidate it now so that in the future we can try and load it up again into cache, and return NULL to signify we couldn't look it up + // At this point it is extremely likely we are trying to create the table, since it wasn't initially present and couldn't be found currently. + tableNameMapping.invalidate(jdbcSchema); + return null; + } + + protected String finalizeSchemaName(DatabaseMetaData metadata, String schemaName) + throws SQLException + { + if (schemaName == null) { + return null; + } + if (metadata.storesUpperCaseIdentifiers()) { + return schemaName.toUpperCase(ENGLISH); + } + else { + Optional value = schemaMappingCache.getUnchecked(schemaName); + if (value.isPresent()) { + return value.get(); + } + // If we get back an Empty value, invalidate it now so that in the future we can try and load it up again into cache, and return the value we were given + // At this point it is extremely likely we are trying to create the schema, since it wasn't initially present and couldn't be found currently. + schemaMappingCache.invalidate(schemaName); + return schemaName; + } + } + + protected String finalizeTableName(DatabaseMetaData metadata, String schemaName, String tableName) + throws SQLException + { + if (schemaName == null || tableName == null) { + return null; + } + if (metadata.storesUpperCaseIdentifiers()) { + return tableName.toUpperCase(ENGLISH); + } + else { + String value = getMappedTableName(schemaName, tableName); + if (value == null) { + return tableName; + } + // if we couldn't look up the table in the mapping cache, return the value we were given + return value; + } + } + @Nullable @Override public JdbcTableHandle getTableHandle(SchemaTableName schemaTableName) { try (Connection connection = driver.connect(connectionUrl, connectionProperties)) { DatabaseMetaData metadata = connection.getMetaData(); - String jdbcSchemaName = schemaTableName.getSchemaName(); - String jdbcTableName = schemaTableName.getTableName(); - if (metadata.storesUpperCaseIdentifiers()) { - jdbcSchemaName = jdbcSchemaName.toUpperCase(ENGLISH); - jdbcTableName = jdbcTableName.toUpperCase(ENGLISH); + + String jdbcSchemaName = finalizeSchemaName(metadata, schemaTableName.getSchemaName()); + String jdbcTableName = finalizeTableName(metadata, jdbcSchemaName, schemaTableName.getTableName()); + + if (jdbcTableName == null) { + return null; } + try (ResultSet resultSet = getTables(connection, jdbcSchemaName, jdbcTableName)) { List tableHandles = new ArrayList<>(); while (resultSet.next()) { @@ -294,11 +513,12 @@ private JdbcOutputTableHandle beginWriteTable(ConnectorTableMetadata tableMetada } try (Connection connection = driver.connect(connectionUrl, connectionProperties)) { - boolean uppercase = connection.getMetaData().storesUpperCaseIdentifiers(); - if (uppercase) { - schema = schema.toUpperCase(ENGLISH); - table = table.toUpperCase(ENGLISH); - } + DatabaseMetaData metadata = connection.getMetaData(); + boolean uppercase = metadata.storesUpperCaseIdentifiers(); + + schema = finalizeSchemaName(metadata, schemaTableName.getSchemaName()); + table = finalizeTableName(metadata, table, schemaTableName.getTableName()); + String catalog = connection.getCatalog(); String temporaryName = "tmp_presto_" + UUID.randomUUID().toString().replace("-", ""); @@ -446,14 +666,6 @@ protected ResultSet getTables(Connection connection, String schemaName, String t new String[] {"TABLE", "VIEW"}); } - protected SchemaTableName getSchemaTableName(ResultSet resultSet) - throws SQLException - { - return new SchemaTableName( - resultSet.getString("TABLE_SCHEM").toLowerCase(ENGLISH), - resultSet.getString("TABLE_NAME").toLowerCase(ENGLISH)); - } - protected void execute(Connection connection, String query) throws SQLException { diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcConfig.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcConfig.java index 5725721aab7a..22ef07d95018 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcConfig.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcConfig.java @@ -23,6 +23,7 @@ public class BaseJdbcConfig private String connectionUrl; private String connectionUser; private String connectionPassword; + private boolean preloadSchemaTableMapping = true; @NotNull public String getConnectionUrl() @@ -61,4 +62,16 @@ public BaseJdbcConfig setConnectionPassword(String connectionPassword) this.connectionPassword = connectionPassword; return this; } + + public boolean isPreloadSchemaTableMapping() + { + return preloadSchemaTableMapping; + } + + @Config("connection-load-table-mappings") + public BaseJdbcConfig setPreloadSchemaTableMapping(boolean preloadSchemaTableMapping) + { + this.preloadSchemaTableMapping = preloadSchemaTableMapping; + return this; + } } diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/CaseSensitiveMappedSchemaTableName.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/CaseSensitiveMappedSchemaTableName.java new file mode 100644 index 000000000000..8dbb5ec6dbbf --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/CaseSensitiveMappedSchemaTableName.java @@ -0,0 +1,60 @@ +/* + * Licensed 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 com.facebook.presto.plugin.jdbc; + +import static java.util.Locale.ENGLISH; +import static java.util.Objects.requireNonNull; + +public class CaseSensitiveMappedSchemaTableName +{ + private final String schemaName; + private final String schemaNameLower; + private final String tableName; + private final String tableNameLower; + + public CaseSensitiveMappedSchemaTableName(String schemaName, String tableName) + { + this.schemaName = requireNonNull(schemaName, "schemaName is null"); + if (schemaName.isEmpty()) { + throw new IllegalArgumentException("schemaName is empty"); + } + this.schemaNameLower = schemaName.toLowerCase(ENGLISH); + + this.tableName = requireNonNull(tableName, "tableName is null"); + if (tableName.isEmpty()) { + throw new IllegalArgumentException("tableName is empty"); + } + this.tableNameLower = tableName.toLowerCase(ENGLISH); + } + + public String getSchemaName() + { + return schemaName; + } + + public String getSchemaNameLower() + { + return schemaNameLower; + } + + public String getTableName() + { + return tableName; + } + + public String getTableNameLower() + { + return tableNameLower; + } +} diff --git a/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestBaseJdbcConfig.java b/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestBaseJdbcConfig.java index bf8508a5130e..3781c9fd2753 100644 --- a/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestBaseJdbcConfig.java +++ b/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestBaseJdbcConfig.java @@ -27,7 +27,8 @@ public void testDefaults() ConfigAssertions.assertRecordedDefaults(ConfigAssertions.recordDefaults(BaseJdbcConfig.class) .setConnectionUrl(null) .setConnectionUser(null) - .setConnectionPassword(null)); + .setConnectionPassword(null) + .setPreloadSchemaTableMapping(true)); } @Test @@ -37,12 +38,14 @@ public void testExplicitPropertyMappings() .put("connection-url", "jdbc:h2:mem:config") .put("connection-user", "user") .put("connection-password", "password") + .put("connection-load-table-mappings", "false") .build(); BaseJdbcConfig expected = new BaseJdbcConfig() .setConnectionUrl("jdbc:h2:mem:config") .setConnectionUser("user") - .setConnectionPassword("password"); + .setConnectionPassword("password") + .setPreloadSchemaTableMapping(false); ConfigAssertions.assertFullMapping(properties, expected); } diff --git a/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestJdbcClientNameMapping.java b/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestJdbcClientNameMapping.java new file mode 100644 index 000000000000..2502d00ad5e3 --- /dev/null +++ b/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestJdbcClientNameMapping.java @@ -0,0 +1,81 @@ +/* + * Licensed 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 com.facebook.presto.plugin.jdbc; + +import com.facebook.presto.spi.SchemaTableName; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +public class TestJdbcClientNameMapping +{ + private JdbcClient jdbcClient; + + @BeforeClass + public void setUp() + throws Exception + { + Map> schemaTableNames = new TreeMap<>(); + + schemaTableNames.put("schemaOne", Arrays.asList("MixedCaseTable1", "aTableTwo", "OneMoreTable", "lastone")); + schemaTableNames.put("SchemaTwo", Arrays.asList("MixedCaseTable1", "aTableTwo", "OneMoreTable", "lastone")); + schemaTableNames.put("schema_three", Arrays.asList("table_one", "table_two", "table_three")); + + jdbcClient = new BaseJdbcClient( + new JdbcConnectorId("test"), + new BaseJdbcConfig().setConnectionUrl(""), + "\"", + new TestingNameMappingDriver(schemaTableNames)); + } + + @Test + public void testSchemaAndTableMapping() + { + assertTrue(jdbcClient.getSchemaNames().containsAll(ImmutableSet.of("schemaone", "schematwo", "schema_three"))); + + assertEquals(jdbcClient.getTableNames("schemaone"), ImmutableList.of( + new SchemaTableName("schemaone", "mixedcasetable1"), + new SchemaTableName("schemaone", "atabletwo"), + new SchemaTableName("schemaone", "onemoretable"), + new SchemaTableName("schemaone", "lastone"))); + + assertEquals(jdbcClient.getTableNames("schematwo"), ImmutableList.of( + new SchemaTableName("schematwo", "mixedcasetable1"), + new SchemaTableName("schematwo", "atabletwo"), + new SchemaTableName("schematwo", "onemoretable"), + new SchemaTableName("schematwo", "lastone"))); + + assertEquals(jdbcClient.getTableNames("schema_three"), ImmutableList.of( + new SchemaTableName("schema_three", "table_one"), + new SchemaTableName("schema_three", "table_two"), + new SchemaTableName("schema_three", "table_three"))); + + SchemaTableName schemaTableName = new SchemaTableName("schemaone", "mixedcasetable1"); + JdbcTableHandle table = jdbcClient.getTableHandle(schemaTableName); + assertNotNull(table, "table is null"); + assertEquals(table.getSchemaName(), "schemaOne"); + assertEquals(table.getTableName(), "MixedCaseTable1"); + assertEquals(table.getSchemaTableName(), schemaTableName); + } +} diff --git a/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestingNameMappingDriver.java b/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestingNameMappingDriver.java new file mode 100644 index 000000000000..25d1a8d74b64 --- /dev/null +++ b/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestingNameMappingDriver.java @@ -0,0 +1,1732 @@ +/* + * Licensed 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 com.facebook.presto.plugin.jdbc; + +import org.h2.tools.SimpleResultSet; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.DriverPropertyInfo; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.RowIdLifetime; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.sql.Types; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.TreeMap; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +public class TestingNameMappingDriver + implements Driver +{ + private Map> originalTableNamesBySchema = new TreeMap<>(); + + MockConnection connection = new MockConnection(); + + public TestingNameMappingDriver(Map> originalTableNamesBySchema) + { + this.originalTableNamesBySchema.putAll(originalTableNamesBySchema); + } + + @Override + public Connection connect(String url, Properties info) + throws SQLException + { + return connection; + } + + @Override + public boolean acceptsURL(String url) + throws SQLException + { + return false; + } + + @Override + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) + throws SQLException + { + return new DriverPropertyInfo[0]; + } + + @Override + public int getMajorVersion() + { + return 0; + } + + @Override + public int getMinorVersion() + { + return 0; + } + + @Override + public boolean jdbcCompliant() + { + return false; + } + + @Override + public Logger getParentLogger() + throws SQLFeatureNotSupportedException + { + return null; + } + + private class MockConnection + implements Connection + { + MockMetaData metaData = new MockMetaData(); + + @Override + public Statement createStatement() + throws SQLException + { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql) + throws SQLException + { + return null; + } + + @Override + public CallableStatement prepareCall(String sql) + throws SQLException + { + return null; + } + + @Override + public String nativeSQL(String sql) + throws SQLException + { + return null; + } + + @Override + public void setAutoCommit(boolean autoCommit) + throws SQLException + { + } + + @Override + public boolean getAutoCommit() + throws SQLException + { + return false; + } + + @Override + public void commit() + throws SQLException + { + } + + @Override + public void rollback() + throws SQLException + { + } + + @Override + public void close() + throws SQLException + { + } + + @Override + public boolean isClosed() + throws SQLException + { + return false; + } + + @Override + public DatabaseMetaData getMetaData() + throws SQLException + { + return metaData; + } + + @Override + public void setReadOnly(boolean readOnly) + throws SQLException + { + } + + @Override + public boolean isReadOnly() + throws SQLException + { + return false; + } + + @Override + public void setCatalog(String catalog) + throws SQLException + { + } + + @Override + public String getCatalog() + throws SQLException + { + return null; + } + + @Override + public void setTransactionIsolation(int level) + throws SQLException + { + } + + @Override + public int getTransactionIsolation() + throws SQLException + { + return 0; + } + + @Override + public SQLWarning getWarnings() + throws SQLException + { + return null; + } + + @Override + public void clearWarnings() + throws SQLException + { + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) + throws SQLException + { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException + { + return null; + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException + { + return null; + } + + @Override + public Map> getTypeMap() + throws SQLException + { + return null; + } + + @Override + public void setTypeMap(Map> map) + throws SQLException + { + } + + @Override + public void setHoldability(int holdability) + throws SQLException + { + } + + @Override + public int getHoldability() + throws SQLException + { + return 0; + } + + @Override + public Savepoint setSavepoint() + throws SQLException + { + return null; + } + + @Override + public Savepoint setSavepoint(String name) + throws SQLException + { + return null; + } + + @Override + public void rollback(Savepoint savepoint) + throws SQLException + { + } + + @Override + public void releaseSavepoint(Savepoint savepoint) + throws SQLException + { + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return null; + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) + throws SQLException + { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) + throws SQLException + { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) + throws SQLException + { + return null; + } + + @Override + public Clob createClob() + throws SQLException + { + return null; + } + + @Override + public Blob createBlob() + throws SQLException + { + return null; + } + + @Override + public NClob createNClob() + throws SQLException + { + return null; + } + + @Override + public SQLXML createSQLXML() + throws SQLException + { + return null; + } + + @Override + public boolean isValid(int timeout) + throws SQLException + { + return false; + } + + @Override + public void setClientInfo(String name, String value) + throws SQLClientInfoException + { + } + + @Override + public void setClientInfo(Properties properties) + throws SQLClientInfoException + { + } + + @Override + public String getClientInfo(String name) + throws SQLException + { + return null; + } + + @Override + public Properties getClientInfo() + throws SQLException + { + return null; + } + + @Override + public Array createArrayOf(String typeName, Object[] elements) + throws SQLException + { + return null; + } + + @Override + public Struct createStruct(String typeName, Object[] attributes) + throws SQLException + { + return null; + } + + @Override + public void setSchema(String schema) + throws SQLException + { + } + + @Override + public String getSchema() + throws SQLException + { + return null; + } + + @Override + public void abort(Executor executor) + throws SQLException + { + } + + @Override + public void setNetworkTimeout(Executor executor, int milliseconds) + throws SQLException + { + } + + @Override + public int getNetworkTimeout() + throws SQLException + { + return 0; + } + + @Override + public T unwrap(Class iface) + throws SQLException + { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) + throws SQLException + { + return false; + } + } + + private class MockMetaData + implements DatabaseMetaData + { + @Override + public boolean allProceduresAreCallable() + throws SQLException + { + return false; + } + + @Override + public boolean allTablesAreSelectable() + throws SQLException + { + return false; + } + + @Override + public String getURL() + throws SQLException + { + return null; + } + + @Override + public String getUserName() + throws SQLException + { + return null; + } + + @Override + public boolean isReadOnly() + throws SQLException + { + return false; + } + + @Override + public boolean nullsAreSortedHigh() + throws SQLException + { + return false; + } + + @Override + public boolean nullsAreSortedLow() + throws SQLException + { + return false; + } + + @Override + public boolean nullsAreSortedAtStart() + throws SQLException + { + return false; + } + + @Override + public boolean nullsAreSortedAtEnd() + throws SQLException + { + return false; + } + + @Override + public String getDatabaseProductName() + throws SQLException + { + return null; + } + + @Override + public String getDatabaseProductVersion() + throws SQLException + { + return null; + } + + @Override + public String getDriverName() + throws SQLException + { + return null; + } + + @Override + public String getDriverVersion() + throws SQLException + { + return null; + } + + @Override + public int getDriverMajorVersion() + { + return 0; + } + + @Override + public int getDriverMinorVersion() + { + return 0; + } + + @Override + public boolean usesLocalFiles() + throws SQLException + { + return false; + } + + @Override + public boolean usesLocalFilePerTable() + throws SQLException + { + return false; + } + + @Override + public boolean supportsMixedCaseIdentifiers() + throws SQLException + { + return false; + } + + @Override + public boolean storesUpperCaseIdentifiers() + throws SQLException + { + return false; + } + + @Override + public boolean storesLowerCaseIdentifiers() + throws SQLException + { + return false; + } + + @Override + public boolean storesMixedCaseIdentifiers() + throws SQLException + { + return false; + } + + @Override + public boolean supportsMixedCaseQuotedIdentifiers() + throws SQLException + { + return false; + } + + @Override + public boolean storesUpperCaseQuotedIdentifiers() + throws SQLException + { + return false; + } + + @Override + public boolean storesLowerCaseQuotedIdentifiers() + throws SQLException + { + return false; + } + + @Override + public boolean storesMixedCaseQuotedIdentifiers() + throws SQLException + { + return false; + } + + @Override + public String getIdentifierQuoteString() + throws SQLException + { + return null; + } + + @Override + public String getSQLKeywords() + throws SQLException + { + return null; + } + + @Override + public String getNumericFunctions() + throws SQLException + { + return null; + } + + @Override + public String getStringFunctions() + throws SQLException + { + return null; + } + + @Override + public String getSystemFunctions() + throws SQLException + { + return null; + } + + @Override + public String getTimeDateFunctions() + throws SQLException + { + return null; + } + + @Override + public String getSearchStringEscape() + throws SQLException + { + return null; + } + + @Override + public String getExtraNameCharacters() + throws SQLException + { + return null; + } + + @Override + public boolean supportsAlterTableWithAddColumn() + throws SQLException + { + return false; + } + + @Override + public boolean supportsAlterTableWithDropColumn() + throws SQLException + { + return false; + } + + @Override + public boolean supportsColumnAliasing() + throws SQLException + { + return false; + } + + @Override + public boolean nullPlusNonNullIsNull() + throws SQLException + { + return false; + } + + @Override + public boolean supportsConvert() + throws SQLException + { + return false; + } + + @Override + public boolean supportsConvert(int fromType, int toType) + throws SQLException + { + return false; + } + + @Override + public boolean supportsTableCorrelationNames() + throws SQLException + { + return false; + } + + @Override + public boolean supportsDifferentTableCorrelationNames() + throws SQLException + { + return false; + } + + @Override + public boolean supportsExpressionsInOrderBy() + throws SQLException + { + return false; + } + + @Override + public boolean supportsOrderByUnrelated() + throws SQLException + { + return false; + } + + @Override + public boolean supportsGroupBy() + throws SQLException + { + return false; + } + + @Override + public boolean supportsGroupByUnrelated() + throws SQLException + { + return false; + } + + @Override + public boolean supportsGroupByBeyondSelect() + throws SQLException + { + return false; + } + + @Override + public boolean supportsLikeEscapeClause() + throws SQLException + { + return false; + } + + @Override + public boolean supportsMultipleResultSets() + throws SQLException + { + return false; + } + + @Override + public boolean supportsMultipleTransactions() + throws SQLException + { + return false; + } + + @Override + public boolean supportsNonNullableColumns() + throws SQLException + { + return false; + } + + @Override + public boolean supportsMinimumSQLGrammar() + throws SQLException + { + return false; + } + + @Override + public boolean supportsCoreSQLGrammar() + throws SQLException + { + return false; + } + + @Override + public boolean supportsExtendedSQLGrammar() + throws SQLException + { + return false; + } + + @Override + public boolean supportsANSI92EntryLevelSQL() + throws SQLException + { + return false; + } + + @Override + public boolean supportsANSI92IntermediateSQL() + throws SQLException + { + return false; + } + + @Override + public boolean supportsANSI92FullSQL() + throws SQLException + { + return false; + } + + @Override + public boolean supportsIntegrityEnhancementFacility() + throws SQLException + { + return false; + } + + @Override + public boolean supportsOuterJoins() + throws SQLException + { + return false; + } + + @Override + public boolean supportsFullOuterJoins() + throws SQLException + { + return false; + } + + @Override + public boolean supportsLimitedOuterJoins() + throws SQLException + { + return false; + } + + @Override + public String getSchemaTerm() + throws SQLException + { + return null; + } + + @Override + public String getProcedureTerm() + throws SQLException + { + return null; + } + + @Override + public String getCatalogTerm() + throws SQLException + { + return null; + } + + @Override + public boolean isCatalogAtStart() + throws SQLException + { + return false; + } + + @Override + public String getCatalogSeparator() + throws SQLException + { + return null; + } + + @Override + public boolean supportsSchemasInDataManipulation() + throws SQLException + { + return false; + } + + @Override + public boolean supportsSchemasInProcedureCalls() + throws SQLException + { + return false; + } + + @Override + public boolean supportsSchemasInTableDefinitions() + throws SQLException + { + return false; + } + + @Override + public boolean supportsSchemasInIndexDefinitions() + throws SQLException + { + return false; + } + + @Override + public boolean supportsSchemasInPrivilegeDefinitions() + throws SQLException + { + return false; + } + + @Override + public boolean supportsCatalogsInDataManipulation() + throws SQLException + { + return false; + } + + @Override + public boolean supportsCatalogsInProcedureCalls() + throws SQLException + { + return false; + } + + @Override + public boolean supportsCatalogsInTableDefinitions() + throws SQLException + { + return false; + } + + @Override + public boolean supportsCatalogsInIndexDefinitions() + throws SQLException + { + return false; + } + + @Override + public boolean supportsCatalogsInPrivilegeDefinitions() + throws SQLException + { + return false; + } + + @Override + public boolean supportsPositionedDelete() + throws SQLException + { + return false; + } + + @Override + public boolean supportsPositionedUpdate() + throws SQLException + { + return false; + } + + @Override + public boolean supportsSelectForUpdate() + throws SQLException + { + return false; + } + + @Override + public boolean supportsStoredProcedures() + throws SQLException + { + return false; + } + + @Override + public boolean supportsSubqueriesInComparisons() + throws SQLException + { + return false; + } + + @Override + public boolean supportsSubqueriesInExists() + throws SQLException + { + return false; + } + + @Override + public boolean supportsSubqueriesInIns() + throws SQLException + { + return false; + } + + @Override + public boolean supportsSubqueriesInQuantifieds() + throws SQLException + { + return false; + } + + @Override + public boolean supportsCorrelatedSubqueries() + throws SQLException + { + return false; + } + + @Override + public boolean supportsUnion() + throws SQLException + { + return false; + } + + @Override + public boolean supportsUnionAll() + throws SQLException + { + return false; + } + + @Override + public boolean supportsOpenCursorsAcrossCommit() + throws SQLException + { + return false; + } + + @Override + public boolean supportsOpenCursorsAcrossRollback() + throws SQLException + { + return false; + } + + @Override + public boolean supportsOpenStatementsAcrossCommit() + throws SQLException + { + return false; + } + + @Override + public boolean supportsOpenStatementsAcrossRollback() + throws SQLException + { + return false; + } + + @Override + public int getMaxBinaryLiteralLength() + throws SQLException + { + return 0; + } + + @Override + public int getMaxCharLiteralLength() + throws SQLException + { + return 0; + } + + @Override + public int getMaxColumnNameLength() + throws SQLException + { + return 0; + } + + @Override + public int getMaxColumnsInGroupBy() + throws SQLException + { + return 0; + } + + @Override + public int getMaxColumnsInIndex() + throws SQLException + { + return 0; + } + + @Override + public int getMaxColumnsInOrderBy() + throws SQLException + { + return 0; + } + + @Override + public int getMaxColumnsInSelect() + throws SQLException + { + return 0; + } + + @Override + public int getMaxColumnsInTable() + throws SQLException + { + return 0; + } + + @Override + public int getMaxConnections() + throws SQLException + { + return 0; + } + + @Override + public int getMaxCursorNameLength() + throws SQLException + { + return 0; + } + + @Override + public int getMaxIndexLength() + throws SQLException + { + return 0; + } + + @Override + public int getMaxSchemaNameLength() + throws SQLException + { + return 0; + } + + @Override + public int getMaxProcedureNameLength() + throws SQLException + { + return 0; + } + + @Override + public int getMaxCatalogNameLength() + throws SQLException + { + return 0; + } + + @Override + public int getMaxRowSize() + throws SQLException + { + return 0; + } + + @Override + public boolean doesMaxRowSizeIncludeBlobs() + throws SQLException + { + return false; + } + + @Override + public int getMaxStatementLength() + throws SQLException + { + return 0; + } + + @Override + public int getMaxStatements() + throws SQLException + { + return 0; + } + + @Override + public int getMaxTableNameLength() + throws SQLException + { + return 0; + } + + @Override + public int getMaxTablesInSelect() + throws SQLException + { + return 0; + } + + @Override + public int getMaxUserNameLength() + throws SQLException + { + return 0; + } + + @Override + public int getDefaultTransactionIsolation() + throws SQLException + { + return 0; + } + + @Override + public boolean supportsTransactions() + throws SQLException + { + return false; + } + + @Override + public boolean supportsTransactionIsolationLevel(int level) + throws SQLException + { + return false; + } + + @Override + public boolean supportsDataDefinitionAndDataManipulationTransactions() + throws SQLException + { + return false; + } + + @Override + public boolean supportsDataManipulationTransactionsOnly() + throws SQLException + { + return false; + } + + @Override + public boolean dataDefinitionCausesTransactionCommit() + throws SQLException + { + return false; + } + + @Override + public boolean dataDefinitionIgnoredInTransactions() + throws SQLException + { + return false; + } + + @Override + public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) + throws SQLException + { + return null; + } + + @Override + public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) + throws SQLException + { + return null; + } + + @Override + public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) + throws SQLException + { + SimpleResultSet ret = new SimpleResultSet(); + + ret.addColumn("TABLE_SCHEM", Types.VARCHAR, 0, 0); + ret.addColumn("TABLE_NAME", Types.VARCHAR, 0, 0); + ret.addColumn("TABLE_CAT", Types.VARCHAR, 0, 0); + + if (tableNamePattern == null) { + for (String tableName : originalTableNamesBySchema.get(schemaPattern)) { + ret.addRow(schemaPattern, tableName, null); + } + } + else { + for (String tableName : originalTableNamesBySchema.get(schemaPattern)) { + if (tableNamePattern.equals(tableName)) { + ret.addRow(schemaPattern, tableName, null); + } + } + } + + return ret; + } + + @Override + public ResultSet getSchemas() + throws SQLException + { + SimpleResultSet ret = new SimpleResultSet(); + + ret.addColumn("TABLE_SCHEM", Types.VARCHAR, 0, 0); + ret.addColumn("TABLE_CAT", Types.VARCHAR, 0, 0); + + for (String schemaName : originalTableNamesBySchema.keySet()) { + ret.addRow(schemaName, null); + } + + return ret; + } + + @Override + public ResultSet getCatalogs() + throws SQLException + { + return null; + } + + @Override + public ResultSet getTableTypes() + throws SQLException + { + return null; + } + + @Override + public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) + throws SQLException + { + return null; + } + + @Override + public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) + throws SQLException + { + return null; + } + + @Override + public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) + throws SQLException + { + return null; + } + + @Override + public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) + throws SQLException + { + return null; + } + + @Override + public ResultSet getVersionColumns(String catalog, String schema, String table) + throws SQLException + { + return null; + } + + @Override + public ResultSet getPrimaryKeys(String catalog, String schema, String table) + throws SQLException + { + return null; + } + + @Override + public ResultSet getImportedKeys(String catalog, String schema, String table) + throws SQLException + { + return null; + } + + @Override + public ResultSet getExportedKeys(String catalog, String schema, String table) + throws SQLException + { + return null; + } + + @Override + public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable) + throws SQLException + { + return null; + } + + @Override + public ResultSet getTypeInfo() + throws SQLException + { + return null; + } + + @Override + public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) + throws SQLException + { + return null; + } + + @Override + public boolean supportsResultSetType(int type) + throws SQLException + { + return false; + } + + @Override + public boolean supportsResultSetConcurrency(int type, int concurrency) + throws SQLException + { + return false; + } + + @Override + public boolean ownUpdatesAreVisible(int type) + throws SQLException + { + return false; + } + + @Override + public boolean ownDeletesAreVisible(int type) + throws SQLException + { + return false; + } + + @Override + public boolean ownInsertsAreVisible(int type) + throws SQLException + { + return false; + } + + @Override + public boolean othersUpdatesAreVisible(int type) + throws SQLException + { + return false; + } + + @Override + public boolean othersDeletesAreVisible(int type) + throws SQLException + { + return false; + } + + @Override + public boolean othersInsertsAreVisible(int type) + throws SQLException + { + return false; + } + + @Override + public boolean updatesAreDetected(int type) + throws SQLException + { + return false; + } + + @Override + public boolean deletesAreDetected(int type) + throws SQLException + { + return false; + } + + @Override + public boolean insertsAreDetected(int type) + throws SQLException + { + return false; + } + + @Override + public boolean supportsBatchUpdates() + throws SQLException + { + return false; + } + + @Override + public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) + throws SQLException + { + return null; + } + + @Override + public Connection getConnection() + throws SQLException + { + return null; + } + + @Override + public boolean supportsSavepoints() + throws SQLException + { + return false; + } + + @Override + public boolean supportsNamedParameters() + throws SQLException + { + return false; + } + + @Override + public boolean supportsMultipleOpenResults() + throws SQLException + { + return false; + } + + @Override + public boolean supportsGetGeneratedKeys() + throws SQLException + { + return false; + } + + @Override + public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) + throws SQLException + { + return null; + } + + @Override + public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) + throws SQLException + { + return null; + } + + @Override + public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) + throws SQLException + { + return null; + } + + @Override + public boolean supportsResultSetHoldability(int holdability) + throws SQLException + { + return false; + } + + @Override + public int getResultSetHoldability() + throws SQLException + { + return 0; + } + + @Override + public int getDatabaseMajorVersion() + throws SQLException + { + return 0; + } + + @Override + public int getDatabaseMinorVersion() + throws SQLException + { + return 0; + } + + @Override + public int getJDBCMajorVersion() + throws SQLException + { + return 0; + } + + @Override + public int getJDBCMinorVersion() + throws SQLException + { + return 0; + } + + @Override + public int getSQLStateType() + throws SQLException + { + return 0; + } + + @Override + public boolean locatorsUpdateCopy() + throws SQLException + { + return false; + } + + @Override + public boolean supportsStatementPooling() + throws SQLException + { + return false; + } + + @Override + public RowIdLifetime getRowIdLifetime() + throws SQLException + { + return null; + } + + @Override + public ResultSet getSchemas(String catalog, String schemaPattern) + throws SQLException + { + return null; + } + + @Override + public boolean supportsStoredFunctionsUsingCallSyntax() + throws SQLException + { + return false; + } + + @Override + public boolean autoCommitFailureClosesAllResultSets() + throws SQLException + { + return false; + } + + @Override + public ResultSet getClientInfoProperties() + throws SQLException + { + return null; + } + + @Override + public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) + throws SQLException + { + return null; + } + + @Override + public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) + throws SQLException + { + return null; + } + + @Override + public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) + throws SQLException + { + return null; + } + + @Override + public boolean generatedKeyAlwaysReturned() + throws SQLException + { + return false; + } + + @Override + public T unwrap(Class iface) + throws SQLException + { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) + throws SQLException + { + return false; + } + } +} diff --git a/presto-mysql/pom.xml b/presto-mysql/pom.xml index e4674656d665..e2a3ad4026d5 100644 --- a/presto-mysql/pom.xml +++ b/presto-mysql/pom.xml @@ -84,6 +84,12 @@ test + + io.airlift + concurrent + test + + io.airlift testing diff --git a/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java b/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java index 3b93dab75971..1ed3cc423163 100644 --- a/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java +++ b/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java @@ -15,12 +15,14 @@ import com.facebook.presto.plugin.jdbc.BaseJdbcClient; import com.facebook.presto.plugin.jdbc.BaseJdbcConfig; +import com.facebook.presto.plugin.jdbc.CaseSensitiveMappedSchemaTableName; import com.facebook.presto.plugin.jdbc.JdbcConnectorId; -import com.facebook.presto.spi.SchemaTableName; +import com.facebook.presto.spi.PrestoException; import com.facebook.presto.spi.type.Type; import com.facebook.presto.spi.type.VarcharType; import com.facebook.presto.spi.type.Varchars; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.mysql.jdbc.Driver; import com.mysql.jdbc.Statement; @@ -32,9 +34,10 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; import java.util.Set; -import static java.util.Locale.ENGLISH; +import static com.facebook.presto.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; public class MySqlClient extends BaseJdbcClient @@ -58,14 +61,14 @@ public MySqlClient(JdbcConnectorId connectorId, BaseJdbcConfig config, MySqlConf } @Override - public Set getSchemaNames() + protected Set getOriginalSchemas() { // for MySQL, we need to list catalogs instead of schemas try (Connection connection = driver.connect(connectionUrl, connectionProperties); ResultSet resultSet = connection.getMetaData().getCatalogs()) { ImmutableSet.Builder schemaNames = ImmutableSet.builder(); while (resultSet.next()) { - String schemaName = resultSet.getString("TABLE_CAT").toLowerCase(ENGLISH); + String schemaName = resultSet.getString("TABLE_CAT"); // skip internal schemas if (!schemaName.equals("information_schema") && !schemaName.equals("mysql")) { schemaNames.add(schemaName); @@ -89,6 +92,22 @@ public PreparedStatement getPreparedStatement(Connection connection, String sql) return statement; } + protected List getOriginalTablesWithSchema(Connection connection, String schema) + { + try (ResultSet resultSet = getTables(connection, schema, null)) { + ImmutableList.Builder list = ImmutableList.builder(); + while (resultSet.next()) { + String schemaName = resultSet.getString("TABLE_CAT"); + String tableName = resultSet.getString("TABLE_NAME"); + list.add(new CaseSensitiveMappedSchemaTableName(schemaName, tableName)); + } + return list.build(); + } + catch (SQLException e) { + throw new PrestoException(JDBC_ERROR, e); + } + } + @Override protected ResultSet getTables(Connection connection, String schemaName, String tableName) throws SQLException @@ -103,16 +122,6 @@ protected ResultSet getTables(Connection connection, String schemaName, String t new String[] {"TABLE", "VIEW"}); } - @Override - protected SchemaTableName getSchemaTableName(ResultSet resultSet) - throws SQLException - { - // MySQL uses catalogs instead of schemas - return new SchemaTableName( - resultSet.getString("TABLE_CAT").toLowerCase(ENGLISH), - resultSet.getString("TABLE_NAME").toLowerCase(ENGLISH)); - } - @Override protected String toSqlType(Type type) { diff --git a/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestJdbcCaseSensitive.java b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestJdbcCaseSensitive.java new file mode 100644 index 000000000000..1c2efb0cff8d --- /dev/null +++ b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestJdbcCaseSensitive.java @@ -0,0 +1,130 @@ +/* + * Licensed 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 com.facebook.presto.plugin.mysql; + +import com.facebook.presto.plugin.jdbc.JdbcClient; +import com.facebook.presto.plugin.jdbc.JdbcColumnHandle; +import com.facebook.presto.plugin.jdbc.JdbcRecordSet; +import com.facebook.presto.plugin.jdbc.JdbcSplit; +import com.facebook.presto.plugin.jdbc.JdbcTableHandle; +import com.facebook.presto.spi.RecordCursor; +import com.facebook.presto.spi.RecordSet; +import com.facebook.presto.spi.SchemaTableName; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.LinkedHashMap; +import java.util.Map; + +import static com.facebook.presto.spi.type.BigintType.BIGINT; +import static com.facebook.presto.spi.type.VarcharType.createVarcharType; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +@Test +public class TestJdbcCaseSensitive +{ + private TestingMysqlDatabase testingMysqlDatabase; + + @BeforeClass + public void setUp() + throws Exception + { + this.testingMysqlDatabase = new TestingMysqlDatabase("testuser", "testpass"); + } + + @AfterClass(alwaysRun = true) + public final void destroy() + { + testingMysqlDatabase.close(); + } + + @Test + public void testMetadata() + { + JdbcClient jdbcClient = testingMysqlDatabase.getJdbcClient(); + + assertTrue(jdbcClient.getSchemaNames().containsAll(ImmutableSet.of("example", "examplecamelcase"))); + assertEquals(jdbcClient.getTableNames("example"), ImmutableList.of( + new SchemaTableName("example", "numbers"))); + assertEquals(jdbcClient.getTableNames("examplecamelcase"), ImmutableList.of( + new SchemaTableName("examplecamelcase", "camelcasenumbers"))); + + SchemaTableName schemaTableName = new SchemaTableName("example", "numbers"); + JdbcTableHandle table = jdbcClient.getTableHandle(schemaTableName); + assertNotNull(table, "table is null"); + assertEquals(table.getCatalogName(), "example"); + assertEquals(table.getTableName(), "numbers"); + assertEquals(table.getSchemaTableName(), schemaTableName); + assertEquals(jdbcClient.getColumns(table), ImmutableList.of( + new JdbcColumnHandle(TestingMysqlDatabase.CONNECTOR_ID, "text", createVarcharType(255)), + new JdbcColumnHandle(TestingMysqlDatabase.CONNECTOR_ID, "text_short", createVarcharType(32)), + new JdbcColumnHandle(TestingMysqlDatabase.CONNECTOR_ID, "value", BIGINT))); + + schemaTableName = new SchemaTableName("examplecamelcase", "camelcasenumbers"); + table = jdbcClient.getTableHandle(schemaTableName); + assertNotNull(table, "table is null"); + assertEquals(table.getCatalogName(), "exampleCamelCase"); + assertEquals(table.getTableName(), "camelCaseNumbers"); + assertEquals(table.getSchemaTableName(), schemaTableName); + assertEquals(jdbcClient.getColumns(table), ImmutableList.of( + new JdbcColumnHandle(TestingMysqlDatabase.CONNECTOR_ID, "text", createVarcharType(255)), + new JdbcColumnHandle(TestingMysqlDatabase.CONNECTOR_ID, "text_short", createVarcharType(32)), + new JdbcColumnHandle(TestingMysqlDatabase.CONNECTOR_ID, "value", BIGINT))); + } + + @Test + public void testCorrectTableNames() + throws Exception + { + JdbcSplit split = testingMysqlDatabase.getSplit("examplecamelcase", "camelcasenumbers"); + Map columnHandles = testingMysqlDatabase.getColumnHandles("example", "numbers"); + JdbcClient jdbcClient = testingMysqlDatabase.getJdbcClient(); + + RecordSet recordSet = new JdbcRecordSet(jdbcClient, split, ImmutableList.of( + columnHandles.get("text"), + columnHandles.get("text_short"), + columnHandles.get("value"))); + + try (RecordCursor cursor = recordSet.cursor()) { + assertEquals(cursor.getType(0), createVarcharType(255)); + assertEquals(cursor.getType(1), createVarcharType(32)); + assertEquals(cursor.getType(2), BIGINT); + + Map data = new LinkedHashMap<>(); + while (cursor.advanceNextPosition()) { + data.put(cursor.getSlice(0).toStringUtf8(), cursor.getLong(2)); + assertEquals(cursor.getSlice(0), cursor.getSlice(1)); + assertFalse(cursor.isNull(0)); + assertFalse(cursor.isNull(1)); + assertFalse(cursor.isNull(2)); + } + + assertEquals(data, ImmutableMap.builder() + .put("one", 1L) + .put("two", 2L) + .put("three", 3L) + .put("ten", 10L) + .put("eleven", 11L) + .put("twelve", 12L) + .build()); + } + } +} diff --git a/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlPlugin.java b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlPlugin.java index 0092db2b62da..6085c36f27ab 100644 --- a/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlPlugin.java +++ b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlPlugin.java @@ -29,6 +29,6 @@ public void testCreateConnector() { Plugin plugin = new MySqlPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:mysql://test"), new TestingConnectorContext()); + factory.create("test", ImmutableMap.of("connection-url", "jdbc:mysql://test", "connection-load-table-mappings", "false"), new TestingConnectorContext()); } } diff --git a/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestingMysqlDatabase.java b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestingMysqlDatabase.java new file mode 100644 index 000000000000..e417fc16d0ce --- /dev/null +++ b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestingMysqlDatabase.java @@ -0,0 +1,117 @@ +/* + * Licensed 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 com.facebook.presto.plugin.mysql; + +import com.facebook.presto.plugin.jdbc.BaseJdbcConfig; +import com.facebook.presto.plugin.jdbc.JdbcClient; +import com.facebook.presto.plugin.jdbc.JdbcColumnHandle; +import com.facebook.presto.plugin.jdbc.JdbcConnectorId; +import com.facebook.presto.plugin.jdbc.JdbcSplit; +import com.facebook.presto.plugin.jdbc.JdbcTableHandle; +import com.facebook.presto.plugin.jdbc.JdbcTableLayoutHandle; +import com.facebook.presto.spi.ConnectorSplitSource; +import com.facebook.presto.spi.SchemaTableName; +import com.facebook.presto.spi.predicate.TupleDomain; +import com.google.common.collect.ImmutableMap; +import io.airlift.testing.mysql.TestingMySqlServer; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.List; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Iterables.getOnlyElement; +import static io.airlift.concurrent.MoreFutures.getFutureValue; + +public class TestingMysqlDatabase + implements AutoCloseable +{ + public static final String CONNECTOR_ID = "test"; + private final TestingMySqlServer mysqlServer; + private final JdbcClient jdbcClient; + + public TestingMysqlDatabase(String username, String password) + throws Exception + { + this.mysqlServer = new TestingMySqlServer(username, password, "example", "exampleCamelCase"); + + jdbcClient = new MySqlClient( + new JdbcConnectorId(CONNECTOR_ID), + new BaseJdbcConfig().setConnectionUrl(mysqlServer.getJdbcUrl()), + new MySqlConfig()); + + loadInitialData(); + } + + protected void loadInitialData() + throws Exception + { + Connection connection = DriverManager.getConnection(mysqlServer.getJdbcUrl()); + + connection.createStatement().execute("CREATE TABLE example.numbers(text VARCHAR(255) DEFAULT NULL, text_short VARCHAR(32) DEFAULT NULL, value bigint(20) DEFAULT NULL)"); + connection.createStatement().execute("CREATE TABLE exampleCamelCase.camelCaseNumbers(text VARCHAR(255) DEFAULT NULL, text_short VARCHAR(32) DEFAULT NULL, value bigint(20) DEFAULT NULL)"); + + connection.createStatement().execute("INSERT INTO example.numbers(text, text_short, value) VALUES " + + "('one', 'one', 1)," + + "('two', 'two', 2)," + + "('three', 'three', 3)," + + "('ten', 'ten', 10)," + + "('eleven', 'eleven', 11)," + + "('twelve', 'twelve', 12)" + + ""); + + connection.createStatement().execute("INSERT INTO exampleCamelCase.camelCaseNumbers(text, text_short, value) VALUES " + + "('one', 'one', 1)," + + "('two', 'two', 2)," + + "('three', 'three', 3)," + + "('ten', 'ten', 10)," + + "('eleven', 'eleven', 11)," + + "('twelve', 'twelve', 12)" + + ""); + } + + @Override + public void close() + { + mysqlServer.close(); + } + + public JdbcClient getJdbcClient() + { + return jdbcClient; + } + + public JdbcSplit getSplit(String schemaName, String tableName) + throws InterruptedException + { + JdbcTableHandle jdbcTableHandle = jdbcClient.getTableHandle(new SchemaTableName(schemaName, tableName)); + JdbcTableLayoutHandle jdbcLayoutHandle = new JdbcTableLayoutHandle(jdbcTableHandle, TupleDomain.all()); + ConnectorSplitSource splits = jdbcClient.getSplits(jdbcLayoutHandle); + return (JdbcSplit) getOnlyElement(getFutureValue(splits.getNextBatch(1000))); + } + + public Map getColumnHandles(String schemaName, String tableName) + { + JdbcTableHandle tableHandle = jdbcClient.getTableHandle(new SchemaTableName(schemaName, tableName)); + List columns = jdbcClient.getColumns(tableHandle); + checkArgument(columns != null, "table not found: %s.%s", schemaName, tableName); + + ImmutableMap.Builder columnHandles = ImmutableMap.builder(); + for (JdbcColumnHandle column : columns) { + columnHandles.put(column.getColumnMetadata().getName(), column); + } + return columnHandles.build(); + } +} diff --git a/presto-postgresql/src/test/java/com/facebook/presto/plugin/postgresql/TestPostgreSqlPlugin.java b/presto-postgresql/src/test/java/com/facebook/presto/plugin/postgresql/TestPostgreSqlPlugin.java index dd64e4e01b51..92fc97234a10 100644 --- a/presto-postgresql/src/test/java/com/facebook/presto/plugin/postgresql/TestPostgreSqlPlugin.java +++ b/presto-postgresql/src/test/java/com/facebook/presto/plugin/postgresql/TestPostgreSqlPlugin.java @@ -29,6 +29,6 @@ public void testCreateConnector() { Plugin plugin = new PostgreSqlPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "test"), new TestingConnectorContext()); + factory.create("test", ImmutableMap.of("connection-url", "test", "connection-load-table-mappings", "false"), new TestingConnectorContext()); } } diff --git a/presto-sqlserver/src/test/java/com/facebook/presto/plugin/sqlserver/TestSqlServerPlugin.java b/presto-sqlserver/src/test/java/com/facebook/presto/plugin/sqlserver/TestSqlServerPlugin.java index a8a0042b8684..17e3bdb5297b 100644 --- a/presto-sqlserver/src/test/java/com/facebook/presto/plugin/sqlserver/TestSqlServerPlugin.java +++ b/presto-sqlserver/src/test/java/com/facebook/presto/plugin/sqlserver/TestSqlServerPlugin.java @@ -29,6 +29,6 @@ public void testCreateConnector() { Plugin plugin = new SqlServerPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "test"), new TestingConnectorContext()); + factory.create("test", ImmutableMap.of("connection-url", "test", "connection-load-table-mappings", "false"), new TestingConnectorContext()); } }