diff --git a/plugin/trino-mysql/pom.xml b/plugin/trino-mysql/pom.xml index 023696684441..91ba1dc50698 100644 --- a/plugin/trino-mysql/pom.xml +++ b/plugin/trino-mysql/pom.xml @@ -160,6 +160,20 @@ test + + + org.mariadb.jdbc + mariadb-java-client + 2.4.0 + test + + + + org.testcontainers + mariadb + test + + org.testcontainers mysql diff --git a/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClient.java b/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClient.java index 8cf49e01b0e7..4e5970e6465e 100644 --- a/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClient.java +++ b/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClient.java @@ -14,7 +14,7 @@ package io.trino.plugin.mysql; import com.google.common.collect.ImmutableSet; -import com.mysql.jdbc.Statement; +import com.mysql.cj.jdbc.JdbcStatement; import io.trino.plugin.jdbc.BaseJdbcClient; import io.trino.plugin.jdbc.BaseJdbcConfig; import io.trino.plugin.jdbc.ColumnMapping; @@ -73,8 +73,8 @@ import static com.google.common.base.Verify.verify; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static com.mysql.jdbc.SQLError.SQL_STATE_ER_TABLE_EXISTS_ERROR; -import static com.mysql.jdbc.SQLError.SQL_STATE_SYNTAX_ERROR; +import static com.mysql.cj.exceptions.MysqlErrorNumbers.SQL_STATE_ER_TABLE_EXISTS_ERROR; +import static com.mysql.cj.exceptions.MysqlErrorNumbers.SQL_STATE_SYNTAX_ERROR; import static io.airlift.slice.Slices.utf8Slice; import static io.trino.plugin.base.util.JsonTypeUtil.jsonParse; import static io.trino.plugin.jdbc.DecimalConfig.DecimalMapping.ALLOW_OVERFLOW; @@ -212,8 +212,8 @@ public PreparedStatement getPreparedStatement(Connection connection, String sql) throws SQLException { PreparedStatement statement = connection.prepareStatement(sql); - if (statement.isWrapperFor(Statement.class)) { - statement.unwrap(Statement.class).enableStreamingResults(); + if (statement.isWrapperFor(JdbcStatement.class)) { + statement.unwrap(JdbcStatement.class).enableStreamingResults(); } return statement; } diff --git a/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClientModule.java b/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClientModule.java index 2f8f3908a835..8cfd1a105808 100644 --- a/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClientModule.java +++ b/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClientModule.java @@ -52,7 +52,6 @@ public static ConnectionFactory createConnectionFactory(BaseJdbcConfig config, C { Properties connectionProperties = new Properties(); connectionProperties.setProperty("useInformationSchema", Boolean.toString(mySqlConfig.isDriverUseInformationSchema())); - connectionProperties.setProperty("nullCatalogMeansCurrent", "false"); connectionProperties.setProperty("useUnicode", "true"); connectionProperties.setProperty("characterEncoding", "utf8"); connectionProperties.setProperty("tinyInt1isBit", "false"); diff --git a/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlJdbcConfig.java b/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlJdbcConfig.java index fe707ca3f2a8..c4735e135149 100644 --- a/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlJdbcConfig.java +++ b/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlJdbcConfig.java @@ -13,13 +13,14 @@ */ package io.trino.plugin.mysql; -import com.mysql.jdbc.Driver; +import com.mysql.cj.conf.ConnectionUrlParser; +import com.mysql.cj.exceptions.CJException; import io.trino.plugin.jdbc.BaseJdbcConfig; import javax.validation.constraints.AssertTrue; -import java.sql.SQLException; -import java.util.Properties; +import static com.google.common.base.Strings.isNullOrEmpty; +import static com.mysql.cj.conf.ConnectionUrlParser.parseConnectionString; public class MySqlJdbcConfig extends BaseJdbcConfig @@ -28,12 +29,11 @@ public class MySqlJdbcConfig public boolean isUrlValid() { try { - Driver driver = new Driver(); - Properties properties = driver.parseURL(getConnectionUrl(), null); - return properties != null; + parseConnectionString(getConnectionUrl()); + return true; } - catch (SQLException e) { - throw new RuntimeException(e); + catch (CJException ignored) { + return false; } } @@ -41,12 +41,11 @@ public boolean isUrlValid() public boolean isUrlWithoutDatabase() { try { - Driver driver = new Driver(); - Properties properties = driver.parseURL(getConnectionUrl(), null); - return (properties == null) || (driver.database(properties) == null); + ConnectionUrlParser parser = parseConnectionString(getConnectionUrl()); + return isNullOrEmpty(parser.getPath()); } - catch (SQLException e) { - throw new RuntimeException(e); + catch (CJException ignored) { + return false; } } } diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/MariaDBQueryRunner.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/MariaDBQueryRunner.java new file mode 100644 index 000000000000..3f444a401443 --- /dev/null +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/MariaDBQueryRunner.java @@ -0,0 +1,102 @@ +/* + * 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 io.trino.plugin.mysql; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.airlift.log.Logger; +import io.airlift.log.Logging; +import io.trino.Session; +import io.trino.plugin.tpch.TpchPlugin; +import io.trino.testing.DistributedQueryRunner; +import io.trino.testing.QueryRunner; +import io.trino.tpch.TpchTable; + +import java.util.HashMap; +import java.util.Map; + +import static io.airlift.testing.Closeables.closeAllSuppress; +import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; +import static io.trino.testing.QueryAssertions.copyTpchTables; +import static io.trino.testing.TestingSession.testSessionBuilder; + +public class MariaDBQueryRunner +{ + private MariaDBQueryRunner() {} + + private static final String TPCH_SCHEMA = "tpch"; + + public static QueryRunner createMariaDBQueryRunner(TestingMariaDBServer server, TpchTable... tables) + throws Exception + { + return createMariaDBQueryRunner(server, ImmutableMap.of(), ImmutableMap.of(), ImmutableList.copyOf(tables)); + } + + public static DistributedQueryRunner createMariaDBQueryRunner( + TestingMariaDBServer server, + Map extraProperties, + Map connectorProperties, + Iterable> tables) + throws Exception + { + DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(createSession()) + .setExtraProperties(extraProperties) + .build(); + try { + queryRunner.installPlugin(new TpchPlugin()); + queryRunner.createCatalog("tpch", "tpch"); + + connectorProperties = new HashMap<>(ImmutableMap.copyOf(connectorProperties)); + connectorProperties.putIfAbsent("connection-url", server.getJdbcUrl()); + connectorProperties.putIfAbsent("connection-user", server.getUsername()); + connectorProperties.putIfAbsent("connection-password", server.getPassword()); + connectorProperties.putIfAbsent("allow-drop-table", "true"); + + queryRunner.installPlugin(new MySqlPlugin()); + queryRunner.createCatalog("mysql", "mysql", connectorProperties); + + copyTpchTables(queryRunner, "tpch", TINY_SCHEMA_NAME, createSession(), tables); + + return queryRunner; + } + catch (Throwable e) { + closeAllSuppress(e, queryRunner); + throw e; + } + } + + private static Session createSession() + { + return testSessionBuilder() + .setCatalog("mysql") + .setSchema(TPCH_SCHEMA) + .build(); + } + + public static void main(String[] args) + throws Exception + { + Logging.initialize(); + + DistributedQueryRunner queryRunner = createMariaDBQueryRunner( + new TestingMariaDBServer(), + ImmutableMap.of("http-server.http.port", "8080"), + ImmutableMap.of(), + TpchTable.getTables()); + + Logger log = Logger.get(MariaDBQueryRunner.class); + log.info("======== SERVER STARTED ========"); + log.info("\n====\n%s\n====", queryRunner.getCoordinator().getBaseUrl()); + } +} diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMariaDbCompatibilityTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMariaDbCompatibilityTest.java new file mode 100644 index 000000000000..1c72c08d88ad --- /dev/null +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMariaDbCompatibilityTest.java @@ -0,0 +1,40 @@ +/* + * 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 io.trino.plugin.mysql; + +import com.google.common.collect.ImmutableMap; +import io.trino.testing.QueryRunner; +import io.trino.testing.sql.SqlExecutor; + +import static io.trino.plugin.mysql.MariaDBQueryRunner.createMariaDBQueryRunner; + +public class TestMariaDbCompatibilityTest + extends BaseMySqlConnectorTest +{ + private TestingMariaDBServer mariaDBServer; + + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + mariaDBServer = closeAfterClass(new TestingMariaDBServer()); + return createMariaDBQueryRunner(mariaDBServer, ImmutableMap.of(), ImmutableMap.of(), REQUIRED_TPCH_TABLES); + } + + @Override + protected SqlExecutor getMySqlExecutor() + { + return mariaDBServer::execute; + } +} diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestingMariaDBServer.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestingMariaDBServer.java new file mode 100644 index 000000000000..089ac4015702 --- /dev/null +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestingMariaDBServer.java @@ -0,0 +1,105 @@ +/* + * 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 io.trino.plugin.mysql; + +import org.testcontainers.containers.MariaDBContainer; + +import java.io.Closeable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; + +import static io.trino.testing.containers.TestContainers.startOrReuse; +import static java.lang.String.format; + +public class TestingMariaDBServer + implements AutoCloseable +{ + private final MariaDBContainer container; + private final Closeable cleanup; + + public TestingMariaDBServer() + { + this("mariadb:10.5.4"); + } + + public TestingMariaDBServer(String dockerImageName) + { + MariaDBContainer container = new MariaDBContainer<>(dockerImageName); + container = container.withDatabaseName("tpch"); + this.container = container; + configureContainer(container); + cleanup = startOrReuse(container); + execute(format("GRANT ALL PRIVILEGES ON *.* TO '%s'", container.getUsername()), "root", container.getPassword()); + } + + protected void configureContainer(MariaDBContainer container) {} + + public Connection createConnection() + throws SQLException + { + return container.createConnection(""); + } + + public void execute(String sql) + { + execute(sql, getUsername(), getPassword()); + } + + public void execute(String sql, String user, String password) + { + try (Connection connection = DriverManager.getConnection(getJdbcUrl(), user, password); + Statement statement = connection.createStatement()) { + statement.execute(sql); + } + catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public String getUsername() + { + return container.getUsername(); + } + + public String getPassword() + { + return container.getPassword(); + } + + public String getDatabaseName() + { + return container.getDatabaseName(); + } + + public String getJdbcUrl() + { + // The connection URL is still using mysql to ensure we test MariaDB compatibility with the MySQL connector + return format("jdbc:mysql://%s:%s?useSSL=false&allowPublicKeyRetrieval=true", container.getContainerIpAddress(), container.getMappedPort(3306)); + } + + @Override + public void close() + { + try { + cleanup.close(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index 1f49234104ba..6e65968b9075 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -147,6 +147,7 @@ mysql mysql-connector-java + ${dep.mysql5.version} diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index c58afd8abdb6..9f773f549e0e 100644 --- a/plugin/trino-resource-group-managers/pom.xml +++ b/plugin/trino-resource-group-managers/pom.xml @@ -102,6 +102,7 @@ mysql mysql-connector-java + ${dep.mysql5.version} diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index 4fc63dbc07dd..5d6016ef2800 100644 --- a/plugin/trino-session-property-managers/pom.xml +++ b/plugin/trino-session-property-managers/pom.xml @@ -102,6 +102,7 @@ mysql mysql-connector-java + ${dep.mysql5.version} diff --git a/pom.xml b/pom.xml index 7dc6483edaa0..b1c4fba1ce69 100644 --- a/pom.xml +++ b/pom.xml @@ -63,6 +63,7 @@ 3.2.7 1.0.25 5.5.2 + 5.1.48