diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java index 1e6520c01c7d..9d487e0b7ab0 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java @@ -19,6 +19,7 @@ import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; import io.trino.testing.TestingConnectorBehavior; +import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; import org.testng.annotations.Test; @@ -31,6 +32,8 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static io.trino.testing.assertions.Assert.assertEquals; import static java.lang.String.format; +import static java.util.stream.Collectors.joining; +import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @@ -38,6 +41,8 @@ public abstract class BaseMySqlConnectorTest extends BaseJdbcConnectorTest { + protected TestingMySqlServer mySqlServer; + @Override protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) { @@ -339,4 +344,42 @@ public void testPredicatePushdown() .matches("VALUES (BIGINT '3', BIGINT '77')") .isFullyPushedDown(); } + + /** + * This test helps to tune TupleDomain simplification threshold. + */ + @Test + public void testNativeLargeIn() + { + // Using IN list of size 140_000 as bigger list causes error: + // "com.mysql.jdbc.PacketTooBigException: Packet for query is too large (XXX > 1048576). + // You can change this value on the server by setting the max_allowed_packet' variable." + mySqlServer.execute("SELECT count(*) FROM tpch.orders WHERE " + getLongInClause(0, 140_000)); + } + + /** + * This test helps to tune TupleDomain simplification threshold. + */ + @Test + public void testNativeMultipleInClauses() + { + String longInClauses = range(0, 14) + .mapToObj(value -> getLongInClause(value * 10_000, 10_000)) + .collect(joining(" OR ")); + mySqlServer.execute("SELECT count(*) FROM tpch.orders WHERE " + longInClauses); + } + + private String getLongInClause(int start, int length) + { + String longValues = range(start, start + length) + .mapToObj(Integer::toString) + .collect(joining(", ")); + return "orderkey IN (" + longValues + ")"; + } + + @Override + protected SqlExecutor onRemoteDatabase() + { + return mySqlServer::execute; + } } diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlConnectorTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlConnectorTest.java index 822025b64174..c12ca308f003 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlConnectorTest.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlConnectorTest.java @@ -15,18 +15,12 @@ import com.google.common.collect.ImmutableMap; import io.trino.testing.QueryRunner; -import io.trino.testing.sql.SqlExecutor; -import org.testng.annotations.Test; import static io.trino.plugin.mysql.MySqlQueryRunner.createMySqlQueryRunner; -import static java.util.stream.Collectors.joining; -import static java.util.stream.IntStream.range; public class TestMySqlConnectorTest extends BaseMySqlConnectorTest { - private TestingMySqlServer mySqlServer; - @Override protected QueryRunner createQueryRunner() throws Exception @@ -34,42 +28,4 @@ protected QueryRunner createQueryRunner() mySqlServer = closeAfterClass(new TestingMySqlServer(false)); return createMySqlQueryRunner(mySqlServer, ImmutableMap.of(), ImmutableMap.of(), REQUIRED_TPCH_TABLES); } - - @Override - protected SqlExecutor onRemoteDatabase() - { - return mySqlServer::execute; - } - - /** - * This test helps to tune TupleDomain simplification threshold. - */ - @Test - public void testNativeLargeIn() - { - // Using IN list of size 140_000 as bigger list causes error: - // "com.mysql.jdbc.PacketTooBigException: Packet for query is too large (XXX > 1048576). - // You can change this value on the server by setting the max_allowed_packet' variable." - mySqlServer.execute("SELECT count(*) FROM tpch.orders WHERE " + getLongInClause(0, 140_000)); - } - - /** - * This test helps to tune TupleDomain simplification threshold. - */ - @Test - public void testNativeMultipleInClauses() - { - String longInClauses = range(0, 14) - .mapToObj(value -> getLongInClause(value * 10_000, 10_000)) - .collect(joining(" OR ")); - mySqlServer.execute("SELECT count(*) FROM tpch.orders WHERE " + longInClauses); - } - - private String getLongInClause(int start, int length) - { - String longValues = range(start, start + length) - .mapToObj(Integer::toString) - .collect(joining(", ")); - return "orderkey IN (" + longValues + ")"; - } } diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java new file mode 100644 index 000000000000..c7acbed191dd --- /dev/null +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java @@ -0,0 +1,148 @@ +/* + * 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.Session; +import io.trino.sql.planner.plan.AggregationNode; +import io.trino.sql.planner.plan.ExchangeNode; +import io.trino.sql.planner.plan.MarkDistinctNode; +import io.trino.sql.planner.plan.ProjectNode; +import io.trino.testing.QueryRunner; +import io.trino.testing.sql.TestTable; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.plugin.mysql.MySqlQueryRunner.createMySqlQueryRunner; +import static io.trino.plugin.mysql.TestingMySqlServer.LEGACY_IMAGE; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class TestMySqlLegacyConnectorTest + extends BaseMySqlConnectorTest +{ + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + mySqlServer = closeAfterClass(new TestingMySqlServer(LEGACY_IMAGE, false)); + return createMySqlQueryRunner(mySqlServer, ImmutableMap.of(), ImmutableMap.of(), REQUIRED_TPCH_TABLES); + } + + @Test + @Override + public void testCreateTableAsSelectWithUnicode() + { + assertThatThrownBy(super::testCreateTableAsSelectWithUnicode) + .hasStackTraceContaining("Failed to insert data: Incorrect string value: '\\xE2\\x98\\x83'"); + } + + @Test + @Override + public void testInsertUnicode() + { + // Create tables with CHARACTER SET utf8mb4 option in MySQL side to allow inserting unicode values + try (TestTable table = new TestTable(onRemoteDatabase(), "tpch.test_insert_unicode_", "(test varchar(50) CHARACTER SET utf8mb4)")) { + assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'Hello', U&'hello\\6d4B\\8Bd5world\\7F16\\7801' ", 2); + assertThat(computeActual("SELECT test FROM " + table.getName()).getOnlyColumnAsSet()) + .containsExactlyInAnyOrder("Hello", "hello测试world编码"); + } + + try (TestTable table = new TestTable(onRemoteDatabase(), "tpch.test_insert_unicode_", "(test varchar(50) CHARACTER SET utf8mb4)")) { + assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'aa', 'bé'", 2); + assertQuery("SELECT test FROM " + table.getName(), "VALUES 'aa', 'bé'"); + assertQuery("SELECT test FROM " + table.getName() + " WHERE test = 'aa'", "VALUES 'aa'"); + assertQuery("SELECT test FROM " + table.getName() + " WHERE test > 'ba'", "VALUES 'bé'"); + assertQuery("SELECT test FROM " + table.getName() + " WHERE test < 'ba'", "VALUES 'aa'"); + assertQueryReturnsEmptyResult("SELECT test FROM " + table.getName() + " WHERE test = 'ba'"); + } + + try (TestTable table = new TestTable(onRemoteDatabase(), "tpch.test_insert_unicode_", "(test varchar(50) CHARACTER SET utf8mb4)")) { + assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'a', 'é'", 2); + assertQuery("SELECT test FROM " + table.getName(), "VALUES 'a', 'é'"); + assertQuery("SELECT test FROM " + table.getName() + " WHERE test = 'a'", "VALUES 'a'"); + assertQuery("SELECT test FROM " + table.getName() + " WHERE test > 'b'", "VALUES 'é'"); + assertQuery("SELECT test FROM " + table.getName() + " WHERE test < 'b'", "VALUES 'a'"); + assertQueryReturnsEmptyResult("SELECT test FROM " + table.getName() + " WHERE test = 'b'"); + } + } + + @Test + @Override + public void testInsertHighestUnicodeCharacter() + { + // Create a table with CHARACTER SET utf8mb4 option in MySQL side to allow inserting unicode values + try (TestTable table = new TestTable(onRemoteDatabase(), "tpch.test_insert_unicode_", "(test varchar(50) CHARACTER SET utf8mb4)")) { + assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'Hello', U&'hello\\6d4B\\8Bd5\\+10FFFFworld\\7F16\\7801' ", 2); + assertThat(computeActual("SELECT test FROM " + table.getName()).getOnlyColumnAsSet()) + .containsExactlyInAnyOrder("Hello", "hello测试􏿿world编码"); + } + } + + @Test + @Override + public void testCountDistinctWithStringTypes() + { + // Create a table with CHARACTER SET utf8mb4 option in MySQL side to insert unicode values + List rows = Stream.of("a", "b", "A", "B", " a ", "a", "b", " b ", "ą") + .map(value -> format("'%1$s', '%1$s'", value)) + .collect(toImmutableList()); + + try (TestTable testTable = new TestTable(onRemoteDatabase(), "tpch.distinct_strings", "(t_char CHAR(5), t_varchar VARCHAR(5) CHARACTER SET utf8mb4)", rows)) { + // disabling hash generation to prevent extra projections in the plan which make it hard to write matchers for isNotFullyPushedDown + Session optimizeHashGenerationDisabled = Session.builder(getSession()) + .setSystemProperty("optimize_hash_generation", "false") + .build(); + + // It is not captured in the `isNotFullyPushedDown` calls (can't do that) but depending on the connector in use some aggregations + // still can be pushed down to connector. + // the DISTINCT part of aggregation will still be pushed down to connector as `GROUP BY`. Only the `count` part will remain on the Trino side. + assertThat(query(optimizeHashGenerationDisabled, "SELECT count(DISTINCT t_varchar) FROM " + testTable.getName())) + .matches("VALUES BIGINT '7'") + .isNotFullyPushedDown(AggregationNode.class); + assertThat(query(optimizeHashGenerationDisabled, "SELECT count(DISTINCT t_char) FROM " + testTable.getName())) + .matches("VALUES BIGINT '7'") + .isNotFullyPushedDown(AggregationNode.class); + + assertThat(query("SELECT count(DISTINCT t_char), count(DISTINCT t_varchar) FROM " + testTable.getName())) + .matches("VALUES (BIGINT '7', BIGINT '7')") + .isNotFullyPushedDown(MarkDistinctNode.class, ExchangeNode.class, ExchangeNode.class, ProjectNode.class); + } + } + + @Test + @Override + public void testRenameColumn() + { + assertThatThrownBy(super::testRenameColumn) + .hasMessageContaining("You have an error in your SQL syntax") + .hasStackTraceContaining("RENAME COLUMN x TO before_y"); + } + + @Override + protected Optional filterDataMappingSmokeTestData(DataMappingTestSetup dataMappingTestSetup) + { + String typeName = dataMappingTestSetup.getTrinoTypeName(); + // CREATE TABLE AS SELECT unicode values fail and the negative case is covered in testCreateTableAsSelectWithUnicode + if (typeName.equals("varchar")) { + return Optional.empty(); + } + return super.filterDataMappingSmokeTestData(dataMappingTestSetup); + } +} diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestingMySqlServer.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestingMySqlServer.java index 5a2e28df1cd5..d8b788174e74 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestingMySqlServer.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestingMySqlServer.java @@ -30,6 +30,9 @@ public class TestingMySqlServer implements AutoCloseable { + public static final String DEFAULT_IMAGE = "mysql:8.0.12"; + public static final String LEGACY_IMAGE = "mysql:5.7.35"; + private final MySQLContainer container; private final Closeable cleanup; @@ -40,7 +43,7 @@ public TestingMySqlServer() public TestingMySqlServer(boolean globalTransactionEnable) { - this("mysql:8.0.12", globalTransactionEnable); + this(DEFAULT_IMAGE, globalTransactionEnable); } public TestingMySqlServer(String dockerImageName, boolean globalTransactionEnable)