diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseCaseInsensitiveMappingTest.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseCaseInsensitiveMappingTest.java index 59fbc60f7533..3841f872c319 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseCaseInsensitiveMappingTest.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseCaseInsensitiveMappingTest.java @@ -26,6 +26,7 @@ import java.nio.file.Path; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; import static com.google.common.collect.ImmutableSet.toImmutableSet; @@ -82,7 +83,7 @@ public void testNonLowerCaseTableName() quoted("lower_case_name") + " varchar(1), " + quoted("Mixed_Case_Name") + " varchar(1), " + quoted("UPPER_CASE_NAME") + " varchar(1))")) { - onRemoteDatabase().execute("INSERT INTO " + (quoted("SomeSchema") + "." + quoted("NonLowerCaseTable")) + " SELECT 'a', 'b', 'c'"); + onRemoteDatabase().execute("INSERT INTO " + (quoted("SomeSchema") + "." + quoted("NonLowerCaseTable")) + " SELECT 'a', 'b', 'c'" + optionalFromDual().orElse("")); assertQuery( "SELECT column_name FROM information_schema.columns WHERE table_schema = 'someschema' AND table_name = 'nonlowercasetable'", "VALUES 'lower_case_name', 'mixed_case_name', 'upper_case_name'"); @@ -115,6 +116,15 @@ public void testNonLowerCaseTableName() } } + /** + * Must return a non-empty optional with the FROM clause to select from a dual table for databases that don't allow a SELECT without a FROM clause like Oracle. + * Must return an empty optional for databases which don't require a FROM clause with a SELECT. + */ + protected Optional optionalFromDual() + { + return Optional.empty(); + } + @Test public void testSchemaNameClash() throws Exception @@ -235,17 +245,19 @@ public void testSchemaNameClashWithRuleMapping() public void testTableNameRuleMapping() throws Exception { + String schema = "remote_schema"; updateRuleBasedIdentifierMappingFile( getMappingFile(), ImmutableList.of(), - ImmutableList.of(new TableMappingRule(getSession().getSchema().orElseThrow(), "remote_table", "trino_table"))); + ImmutableList.of(new TableMappingRule(schema, "remote_table", "trino_table"))); - try (AutoCloseable ignore1 = withTable("remote_table", "(c varchar(5))")) { - assertThat(computeActual("SHOW TABLES").getOnlyColumn()) + try (AutoCloseable ignore = withSchema(schema); + AutoCloseable ignore1 = withTable(schema, "remote_table", "(c varchar(5))")) { + assertThat(computeActual("SHOW TABLES FROM " + schema).getOnlyColumn()) .contains("trino_table"); - assertQuery("SHOW COLUMNS FROM trino_table", "SELECT 'c', 'varchar(5)', '', ''"); - assertUpdate("INSERT INTO trino_table VALUES 'dane'", 1); - assertQuery("SELECT * FROM trino_table", "VALUES 'dane'"); + assertQuery("SHOW COLUMNS FROM " + schema + ".trino_table", "SELECT 'c', 'varchar(5)', '', ''"); + assertUpdate("INSERT INTO " + schema + ".trino_table VALUES 'dane'", 1); + assertQuery("SELECT * FROM " + schema + ".trino_table", "VALUES 'dane'"); } } @@ -253,7 +265,7 @@ public void testTableNameRuleMapping() public void testTableNameClashWithRuleMapping() throws Exception { - String schema = getSession().getSchema().orElseThrow(); + String schema = "remote_schema"; List tableMappingRules = ImmutableList.of( new TableMappingRule(schema, "casesensitivename", "casesensitivename_a"), new TableMappingRule(schema, "CaseSensitiveName", "casesensitivename_b"), @@ -270,21 +282,22 @@ public void testTableNameClashWithRuleMapping() for (int j = i + 1; j < nameVariants.length; j++) { String remoteTable = nameVariants[i]; String otherRemoteTable = nameVariants[j]; - try (AutoCloseable ignore1 = withTable(remoteTable, "(c varchar(5))"); - AutoCloseable ignore2 = withTable(otherRemoteTable, "(d varchar(5))")) { + try (AutoCloseable ignore = withSchema(schema); + AutoCloseable ignore1 = withTable(schema, remoteTable, "(c varchar(5))"); + AutoCloseable ignore2 = withTable(schema, otherRemoteTable, "(d varchar(5))")) { String table = tableMappingRules.stream() .filter(rule -> rule.getRemoteTable().equals(remoteTable)) .map(TableMappingRule::getMapping) .collect(onlyElement()); - assertThat(computeActual("SHOW TABLES") + assertThat(computeActual("SHOW TABLES FROM " + schema) .getOnlyColumn() .map(String.class::cast) .filter(anObject -> anObject.startsWith("casesensitivename"))) .hasSize(2); - assertQuery("SHOW COLUMNS FROM " + table, "SELECT 'c', 'varchar(5)', '', ''"); - assertUpdate("INSERT INTO " + table + " VALUES 'dane'", 1); - assertQuery("SELECT * FROM " + table, "VALUES 'dane'"); + assertQuery("SHOW COLUMNS FROM " + schema + "." + table, "SELECT 'c', 'varchar(5)', '', ''"); + assertUpdate("INSERT INTO " + schema + "." + table + " VALUES 'dane'", 1); + assertQuery("SELECT * FROM " + schema + "." + table, "VALUES 'dane'"); } } } diff --git a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java index d8ede1a3c65b..4ace78749914 100644 --- a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java +++ b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java @@ -214,7 +214,7 @@ public void testDescribeTable() protected TestTable createTableWithDefaultColumns() { return new TestTable( - clickhouseServer::execute, + onRemoteDatabase(), "tpch.tbl", "(col_required Int64," + "col_nullable Nullable(Int64)," + @@ -456,7 +456,7 @@ public void testAlterInvalidTableProperties() protected TestTable createTableWithUnsupportedColumn() { return new TestTable( - clickhouseServer::execute, + onRemoteDatabase(), "tpch.test_unsupported_column_present", "(one bigint, two Array(UInt8), three String) ENGINE=Log"); } diff --git a/plugin/trino-memsql/src/test/java/io/trino/plugin/memsql/TestMemSqlCaseInsensitiveMapping.java b/plugin/trino-memsql/src/test/java/io/trino/plugin/memsql/TestMemSqlCaseInsensitiveMapping.java index e62a982ac040..910b5686902e 100644 --- a/plugin/trino-memsql/src/test/java/io/trino/plugin/memsql/TestMemSqlCaseInsensitiveMapping.java +++ b/plugin/trino-memsql/src/test/java/io/trino/plugin/memsql/TestMemSqlCaseInsensitiveMapping.java @@ -15,170 +15,68 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import io.trino.testing.AbstractTestQueryFramework; +import io.trino.plugin.jdbc.BaseCaseInsensitiveMappingTest; import io.trino.testing.QueryRunner; -import io.trino.testing.sql.TestTable; -import org.testng.annotations.AfterClass; +import io.trino.testing.sql.SqlExecutor; import org.testng.annotations.Test; -import java.util.stream.Stream; +import java.nio.file.Path; -import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static io.trino.plugin.jdbc.mapping.RuleBasedIdentifierMappingUtils.createRuleBasedIdentifierMappingFile; import static io.trino.plugin.memsql.MemSqlQueryRunner.createMemSqlQueryRunner; -import static io.trino.testing.assertions.Assert.assertEquals; -import static java.lang.String.format; -import static java.util.Locale.ENGLISH; -import static org.assertj.core.api.Assertions.assertThat; +import static java.util.Objects.requireNonNull; // With case-insensitive-name-matching enabled colliding schema/table names are considered as errors. // Some tests here create colliding names which can cause any other concurrent test to fail. @Test(singleThreaded = true) public class TestMemSqlCaseInsensitiveMapping - // TODO extends BaseCaseInsensitiveMappingTest - https://github.com/trinodb/trino/issues/7864 - extends AbstractTestQueryFramework + extends BaseCaseInsensitiveMappingTest { + protected Path mappingFile; protected TestingMemSqlServer memSqlServer; @Override protected QueryRunner createQueryRunner() throws Exception { - memSqlServer = new TestingMemSqlServer(); - return createMemSqlQueryRunner(memSqlServer, ImmutableMap.of(), ImmutableMap.of("case-insensitive-name-matching", "true"), ImmutableList.of()); + mappingFile = createRuleBasedIdentifierMappingFile(); + memSqlServer = closeAfterClass(new TestingMemSqlServer()); + return createMemSqlQueryRunner( + memSqlServer, + ImmutableMap.of(), + ImmutableMap.builder() + .put("case-insensitive-name-matching", "true") + .put("case-insensitive-name-matching.config-file", mappingFile.toFile().getAbsolutePath()) + .put("case-insensitive-name-matching.config-file.refresh-period", "1ms") // ~always refresh + .buildOrThrow(), + ImmutableList.of()); } - @AfterClass(alwaysRun = true) - public final void destroy() - { - memSqlServer.close(); - } - - @Test - public void testNonLowerCaseSchemaName() - throws Exception + @Override + protected Path getMappingFile() { - try (AutoCloseable ignore1 = withSchema("NonLowerCaseSchema"); - AutoCloseable ignore2 = withTable("NonLowerCaseSchema.lower_case_name", "(c varchar(5))"); - AutoCloseable ignore3 = withTable("NonLowerCaseSchema.Mixed_Case_Name", "(c varchar(5))"); - AutoCloseable ignore4 = withTable("NonLowerCaseSchema.UPPER_CASE_NAME", "(c varchar(5))")) { - assertThat(computeActual("SHOW SCHEMAS").getOnlyColumn()).contains("nonlowercaseschema"); - assertQuery("SHOW SCHEMAS LIKE 'nonlowerc%'", "VALUES 'nonlowercaseschema'"); - assertQuery("SELECT schema_name FROM information_schema.schemata WHERE schema_name LIKE '%nonlowercaseschema'", "VALUES 'nonlowercaseschema'"); - assertQuery("SHOW TABLES FROM nonlowercaseschema", "VALUES 'lower_case_name', 'mixed_case_name', 'upper_case_name'"); - assertQuery("SELECT table_name FROM information_schema.tables WHERE table_schema = 'nonlowercaseschema'", "VALUES 'lower_case_name', 'mixed_case_name', 'upper_case_name'"); - assertQueryReturnsEmptyResult("SELECT * FROM nonlowercaseschema.lower_case_name"); - } + return requireNonNull(mappingFile, "mappingFile is null"); } - @Test - public void testNonLowerCaseTableName() - throws Exception + @Override + protected SqlExecutor onRemoteDatabase() { - try (AutoCloseable ignore1 = withSchema("SomeSchema"); - AutoCloseable ignore2 = withTable( - "SomeSchema.NonLowerCaseTable", "(lower_case_name varchar(10), Mixed_Case_Name varchar(10), UPPER_CASE_NAME varchar(10))")) { - execute("INSERT INTO SomeSchema.NonLowerCaseTable VALUES ('a', 'b', 'c')"); - - assertQuery( - "SELECT column_name FROM information_schema.columns WHERE table_schema = 'someschema' AND table_name = 'nonlowercasetable'", - "VALUES 'lower_case_name', 'mixed_case_name', 'upper_case_name'"); - assertQuery( - "SELECT column_name FROM information_schema.columns WHERE table_name = 'nonlowercasetable'", - "VALUES 'lower_case_name', 'mixed_case_name', 'upper_case_name'"); - assertEquals( - computeActual("SHOW COLUMNS FROM someschema.nonlowercasetable").getMaterializedRows().stream() - .map(row -> row.getField(0)) - .collect(toImmutableSet()), - ImmutableSet.of("lower_case_name", "mixed_case_name", "upper_case_name")); - - // Note: until https://github.com/prestodb/presto/issues/2863 is resolved, this is *the* way to access the tables. - - assertQuery("SELECT lower_case_name FROM someschema.nonlowercasetable", "VALUES 'a'"); - assertQuery("SELECT mixed_case_name FROM someschema.nonlowercasetable", "VALUES 'b'"); - assertQuery("SELECT upper_case_name FROM someschema.nonlowercasetable", "VALUES 'c'"); - assertQuery("SELECT upper_case_name FROM SomeSchema.NonLowerCaseTable", "VALUES 'c'"); - assertQuery("SELECT upper_case_name FROM \"SomeSchema\".\"NonLowerCaseTable\"", "VALUES 'c'"); - - assertUpdate("INSERT INTO someschema.nonlowercasetable (lower_case_name) VALUES ('lower')", 1); - assertUpdate("INSERT INTO someschema.nonlowercasetable (mixed_case_name) VALUES ('mixed')", 1); - assertUpdate("INSERT INTO someschema.nonlowercasetable (upper_case_name) VALUES ('upper')", 1); - assertQuery( - "SELECT * FROM someschema.nonlowercasetable", - "VALUES ('a', 'b', 'c')," + - "('lower', NULL, NULL)," + - "(NULL, 'mixed', NULL)," + - "(NULL, NULL, 'upper')"); - } + return requireNonNull(memSqlServer, "memSqlServer is null")::execute; } - @Test - public void testSchemaNameClash() - throws Exception + @Override + protected String quoted(String name) { - String[] nameVariants = {"casesensitivename", "CaseSensitiveName", "CASESENSITIVENAME"}; - assertThat(Stream.of(nameVariants) - .map(name -> name.toLowerCase(ENGLISH)) - .collect(toImmutableSet())) - .hasSize(1); - - for (int i = 0; i < nameVariants.length; i++) { - for (int j = i + 1; j < nameVariants.length; j++) { - String schemaName = nameVariants[i]; - String otherSchemaName = nameVariants[j]; - try (AutoCloseable ignore1 = withSchema(schemaName); - AutoCloseable ignore2 = withSchema(otherSchemaName); - AutoCloseable ignore3 = withTable(schemaName + ".some_table_name", "(c varchar(5))")) { - assertThat(computeActual("SHOW SCHEMAS").getOnlyColumn()).contains("casesensitivename"); - assertThat(computeActual("SHOW SCHEMAS").getOnlyColumn().filter("casesensitivename"::equals)).hasSize(1); // TODO change io.trino.plugin.jdbc.JdbcClient.getSchemaNames to return a List - assertQueryFails("SHOW TABLES FROM casesensitivename", "Failed to find remote schema name: Ambiguous name: casesensitivename"); - assertQueryFails("SELECT * FROM casesensitivename.some_table_name", "Failed to find remote schema name: Ambiguous name: casesensitivename"); - } - } - } + String identifierQuote = "`"; + name = name.replace(identifierQuote, identifierQuote + identifierQuote); + return identifierQuote + name + identifierQuote; } @Test - public void testTableNameClash() - throws Exception - { - String[] nameVariants = {"casesensitivename", "CaseSensitiveName", "CASESENSITIVENAME"}; - assertThat(Stream.of(nameVariants) - .map(name -> name.toLowerCase(ENGLISH)) - .collect(toImmutableSet())) - .hasSize(1); - - for (int i = 0; i < nameVariants.length; i++) { - for (int j = i + 1; j < nameVariants.length; j++) { - try (AutoCloseable ignore1 = withTable("tpch." + nameVariants[i], "(c varchar(5))"); - AutoCloseable ignore2 = withTable("tpch." + nameVariants[j], "(d varchar(5))")) { - assertThat(computeActual("SHOW TABLES").getOnlyColumn()).contains("casesensitivename"); - assertThat(computeActual("SHOW TABLES").getOnlyColumn().filter("casesensitivename"::equals)).hasSize(1); // TODO, should be 2 - assertQueryFails("SHOW COLUMNS FROM casesensitivename", "Failed to find remote table name: Ambiguous name: casesensitivename"); - assertQueryFails("SELECT * FROM casesensitivename", "Failed to find remote table name: Ambiguous name: casesensitivename"); - } - } - } - } - - private AutoCloseable withSchema(String schemaName) - { - execute(format("CREATE SCHEMA `%s`", schemaName)); - return () -> execute(format("DROP SCHEMA `%s`", schemaName)); - } - - /** - * @deprecated Use {@link TestTable} instead. - */ - @Deprecated - private AutoCloseable withTable(String tableName, String tableDefinition) - { - execute(format("CREATE TABLE %s %s", tableName, tableDefinition)); - return () -> execute(format("DROP TABLE %s", tableName)); - } - - private void execute(String sql) + public void forceTestNgToRespectSingleThreaded() { - memSqlServer.execute(sql); + // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) + // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to + // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. } } diff --git a/plugin/trino-memsql/src/test/java/io/trino/plugin/memsql/TestMemSqlConnectorTest.java b/plugin/trino-memsql/src/test/java/io/trino/plugin/memsql/TestMemSqlConnectorTest.java index bc2076db8f7f..98b43ecc4461 100644 --- a/plugin/trino-memsql/src/test/java/io/trino/plugin/memsql/TestMemSqlConnectorTest.java +++ b/plugin/trino-memsql/src/test/java/io/trino/plugin/memsql/TestMemSqlConnectorTest.java @@ -99,7 +99,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) protected TestTable createTableWithDefaultColumns() { return new TestTable( - this::execute, + onRemoteDatabase(), "tpch.table", "(col_required BIGINT NOT NULL," + "col_nullable BIGINT," + @@ -178,9 +178,9 @@ public void testDropTable() @Test public void testReadFromView() { - execute("CREATE VIEW tpch.test_view AS SELECT * FROM tpch.orders"); + onRemoteDatabase().execute("CREATE VIEW tpch.test_view AS SELECT * FROM tpch.orders"); assertQuery("SELECT orderkey FROM test_view", "SELECT orderkey FROM orders"); - execute("DROP VIEW IF EXISTS tpch.test_view"); + onRemoteDatabase().execute("DROP VIEW IF EXISTS tpch.test_view"); } @Test @@ -200,7 +200,7 @@ public void testNameEscaping() @Test public void testMemSqlTinyint() { - execute("CREATE TABLE tpch.mysql_test_tinyint1 (c_tinyint tinyint(1))"); + onRemoteDatabase().execute("CREATE TABLE tpch.mysql_test_tinyint1 (c_tinyint tinyint(1))"); MaterializedResult actual = computeActual("SHOW COLUMNS FROM mysql_test_tinyint1"); MaterializedResult expected = resultBuilder(getSession(), VARCHAR, VARCHAR, VARCHAR, VARCHAR) @@ -209,7 +209,7 @@ public void testMemSqlTinyint() assertEquals(actual, expected); - execute("INSERT INTO tpch.mysql_test_tinyint1 VALUES (127), (-128)"); + onRemoteDatabase().execute("INSERT INTO tpch.mysql_test_tinyint1 VALUES (127), (-128)"); MaterializedResult materializedRows = computeActual("SELECT * FROM tpch.mysql_test_tinyint1 WHERE c_tinyint = 127"); assertEquals(materializedRows.getRowCount(), 1); MaterializedRow row = getOnlyElement(materializedRows); @@ -223,7 +223,7 @@ public void testMemSqlTinyint() @Test public void testCharTrailingSpace() { - execute("CREATE TABLE tpch.char_trailing_space (x char(10))"); + onRemoteDatabase().execute("CREATE TABLE tpch.char_trailing_space (x char(10))"); assertUpdate("INSERT INTO char_trailing_space VALUES ('test')", 1); assertQuery("SELECT * FROM char_trailing_space WHERE x = char 'test'", "VALUES 'test'"); @@ -246,7 +246,7 @@ public void testColumnComment() { // TODO add support for setting comments on existing column and replace the test with io.trino.testing.BaseConnectorTest#testCommentColumn - execute("CREATE TABLE tpch.test_column_comment (col1 bigint COMMENT 'test comment', col2 bigint COMMENT '', col3 bigint)"); + onRemoteDatabase().execute("CREATE TABLE tpch.test_column_comment (col1 bigint COMMENT 'test comment', col2 bigint COMMENT '', col3 bigint)"); assertQuery( "SELECT column_name, comment FROM information_schema.columns WHERE table_schema = 'tpch' AND table_name = 'test_column_comment'", @@ -326,7 +326,7 @@ public void testInsertNegativeDate() @Test public void testNativeLargeIn() { - memSqlServer.execute("SELECT count(*) FROM tpch.orders WHERE " + getLongInClause(0, 300_000)); + onRemoteDatabase().execute("SELECT count(*) FROM tpch.orders WHERE " + getLongInClause(0, 300_000)); } /** @@ -338,7 +338,7 @@ public void testNativeMultipleInClauses() String longInClauses = range(0, 30) .mapToObj(value -> getLongInClause(value * 10_000, 10_000)) .collect(joining(" OR ")); - memSqlServer.execute("SELECT count(*) FROM tpch.orders WHERE " + longInClauses); + onRemoteDatabase().execute("SELECT count(*) FROM tpch.orders WHERE " + longInClauses); } private String getLongInClause(int start, int length) @@ -349,11 +349,6 @@ private String getLongInClause(int start, int length) return "orderkey IN (" + longValues + ")"; } - private void execute(String sql) - { - memSqlServer.execute(sql); - } - @Override protected SqlExecutor onRemoteDatabase() { 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 e8cc8651c41a..80bd165cce73 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 @@ -354,7 +354,7 @@ 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)); + onRemoteDatabase().execute("SELECT count(*) FROM tpch.orders WHERE " + getLongInClause(0, 140_000)); } /** @@ -366,7 +366,7 @@ 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); + onRemoteDatabase().execute("SELECT count(*) FROM tpch.orders WHERE " + longInClauses); } private String getLongInClause(int start, int length) diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleCaseInsensitiveMapping.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleCaseInsensitiveMapping.java index d1c849e33aec..c995fd4cf9a9 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleCaseInsensitiveMapping.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleCaseInsensitiveMapping.java @@ -15,34 +15,34 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import io.trino.testing.AbstractTestQueryFramework; +import io.trino.plugin.jdbc.BaseCaseInsensitiveMappingTest; import io.trino.testing.QueryRunner; -import io.trino.testing.sql.TestTable; +import io.trino.testing.sql.SqlExecutor; import org.testng.annotations.Test; -import java.util.stream.Stream; +import java.nio.file.Path; +import java.util.Optional; -import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static io.trino.plugin.jdbc.mapping.RuleBasedIdentifierMappingUtils.createRuleBasedIdentifierMappingFile; import static io.trino.plugin.oracle.OracleQueryRunner.createOracleQueryRunner; +import static io.trino.plugin.oracle.TestingOracleServer.TEST_USER; import static java.lang.String.format; -import static java.util.Locale.ENGLISH; -import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; +import static java.util.Objects.requireNonNull; // With case-insensitive-name-matching enabled colliding schema/table names are considered as errors. // Some tests here create colliding names which can cause any other concurrent test to fail. @Test(singleThreaded = true) public class TestOracleCaseInsensitiveMapping - // TODO extends BaseCaseInsensitiveMappingTest - https://github.com/trinodb/trino/issues/7864 - extends AbstractTestQueryFramework + extends BaseCaseInsensitiveMappingTest { + private Path mappingFile; private TestingOracleServer oracleServer; @Override protected QueryRunner createQueryRunner() throws Exception { + mappingFile = createRuleBasedIdentifierMappingFile(); oracleServer = closeAfterClass(new TestingOracleServer()); return createOracleQueryRunner( oracleServer, @@ -50,127 +50,56 @@ protected QueryRunner createQueryRunner() ImmutableMap.builder() .putAll(OracleQueryRunner.connectionProperties(oracleServer)) .put("case-insensitive-name-matching", "true") + .put("case-insensitive-name-matching.config-file", mappingFile.toFile().getAbsolutePath()) + .put("case-insensitive-name-matching.config-file.refresh-period", "1ms") // ~always refresh .buildOrThrow(), ImmutableList.of()); } - @Test - public void testNonLowerCaseSchemaName() - throws Exception + @Override + protected Path getMappingFile() { - try (AutoCloseable ignore1 = withSchema("\"NonLowerCaseSchema\""); - AutoCloseable ignore2 = withTable("\"NonLowerCaseSchema\".lower_case_name", "(c varchar(5))"); - AutoCloseable ignore3 = withTable("\"NonLowerCaseSchema\".\"Mixed_Case_Name\"", "(c varchar(5))"); - AutoCloseable ignore4 = withTable("\"NonLowerCaseSchema\".\"UPPER_CASE_NAME\"", "(c varchar(5))")) { - assertThat(computeActual("SHOW SCHEMAS").getOnlyColumn()).contains("nonlowercaseschema"); - assertQuery("SHOW SCHEMAS LIKE 'nonlowerc%'", "VALUES 'nonlowercaseschema'"); - assertQuery("SELECT schema_name FROM information_schema.schemata WHERE schema_name LIKE '%nonlowercaseschema'", "VALUES 'nonlowercaseschema'"); - assertQuery("SHOW TABLES FROM nonlowercaseschema", "VALUES 'lower_case_name', 'mixed_case_name', 'upper_case_name'"); - assertQuery("SELECT table_name FROM information_schema.tables WHERE table_schema = 'nonlowercaseschema'", "VALUES 'lower_case_name', 'mixed_case_name', 'upper_case_name'"); - assertQueryReturnsEmptyResult("SELECT * FROM nonlowercaseschema.lower_case_name"); - } + return requireNonNull(mappingFile, "mappingFile is null"); } - @Test - public void testNonLowerCaseTableName() - throws Exception + @Override + protected Optional optionalFromDual() { - try (AutoCloseable ignore1 = withSchema("\"SomeSchema\""); - AutoCloseable ignore2 = withTable( - "\"SomeSchema\".\"NonLowerCaseTable\"", "(\"lower_case_name\", \"Mixed_Case_Name\", \"UPPER_CASE_NAME\") AS SELECT 'a', 'b', 'c' FROM dual")) { - assertQuery( - "SELECT column_name FROM information_schema.columns WHERE table_schema = 'someschema' AND table_name = 'nonlowercasetable'", - "VALUES 'lower_case_name', 'mixed_case_name', 'upper_case_name'"); - assertQuery( - "SELECT column_name FROM information_schema.columns WHERE table_name = 'nonlowercasetable'", - "VALUES 'lower_case_name', 'mixed_case_name', 'upper_case_name'"); - assertEquals( - computeActual("SHOW COLUMNS FROM someschema.nonlowercasetable").getMaterializedRows().stream() - .map(row -> row.getField(0)) - .collect(toImmutableSet()), - ImmutableSet.of("lower_case_name", "mixed_case_name", "upper_case_name")); - - // Note: until https://github.com/prestodb/presto/issues/2863 is resolved, this is *the* way to access the tables. - - assertQuery("SELECT lower_case_name FROM someschema.nonlowercasetable", "VALUES 'a'"); - assertQuery("SELECT mixed_case_name FROM someschema.nonlowercasetable", "VALUES 'b'"); - assertQuery("SELECT upper_case_name FROM someschema.nonlowercasetable", "VALUES 'c'"); - assertQuery("SELECT upper_case_name FROM SomeSchema.NonLowerCaseTable", "VALUES 'c'"); - assertQuery("SELECT upper_case_name FROM \"SomeSchema\".\"NonLowerCaseTable\"", "VALUES 'c'"); - - assertUpdate("INSERT INTO someschema.nonlowercasetable (lower_case_name) VALUES ('l')", 1); - assertUpdate("INSERT INTO someschema.nonlowercasetable (mixed_case_name) VALUES ('m')", 1); - assertUpdate("INSERT INTO someschema.nonlowercasetable (upper_case_name) VALUES ('u')", 1); - assertQuery( - "SELECT * FROM someschema.nonlowercasetable", - "VALUES ('a', 'b', 'c')," + - "('l', NULL, NULL)," + - "(NULL, 'm', NULL)," + - "(NULL, NULL, 'u')"); - } + return Optional.of("FROM dual"); } - @Test - public void testSchemaNameClash() - throws Exception + @Override + protected AutoCloseable withSchema(String schemaName) { - String[] nameVariants = {"\"casesensitivename\"", "\"CaseSensitiveName\"", "\"CASESENSITIVENAME\""}; - assertThat(Stream.of(nameVariants) - .map(name -> name.replace("\"", "").toLowerCase(ENGLISH)) - .collect(toImmutableSet())) - .hasSize(1); - - for (int i = 0; i < nameVariants.length; i++) { - for (int j = i + 1; j < nameVariants.length; j++) { - String schemaName = nameVariants[i]; - String otherSchemaName = nameVariants[j]; - try (AutoCloseable ignore1 = withSchema(schemaName); - AutoCloseable ignore2 = withSchema(otherSchemaName); - AutoCloseable ignore3 = withTable(schemaName + ".some_table_name", "(c varchar(5))")) { - assertThat(computeActual("SHOW SCHEMAS").getOnlyColumn().filter("casesensitivename"::equals)).hasSize(1); // TODO change io.trino.plugin.jdbc.JdbcClient.getSchemaNames to return a List - assertQueryFails("SHOW TABLES FROM casesensitivename", "Failed to find remote schema name: Ambiguous name: casesensitivename"); - assertQueryFails("SELECT * FROM casesensitivename.some_table_name", "Failed to find remote schema name: Ambiguous name: casesensitivename"); - } - } - } + onRemoteDatabase().execute(format("CREATE USER %s IDENTIFIED BY SCM", quoted(schemaName))); + onRemoteDatabase().execute(format("GRANT UNLIMITED TABLESPACE TO %s", quoted(schemaName))); + return () -> onRemoteDatabase().execute("DROP USER " + quoted(schemaName)); } - @Test - public void testTableNameClash() - throws Exception + @Override + protected AutoCloseable withTable(String remoteSchemaName, String remoteTableName, String tableDefinition) { - String[] nameVariants = {"\"casesensitivename\"", "\"CaseSensitiveName\"", "\"CASESENSITIVENAME\""}; - assertThat(Stream.of(nameVariants) - .map(name -> name.replace("\"", "").toLowerCase(ENGLISH)) - .collect(toImmutableSet())) - .hasSize(1); - - for (int i = 0; i < nameVariants.length; i++) { - for (int j = i + 1; j < nameVariants.length; j++) { - try (AutoCloseable ignore1 = withTable(TestingOracleServer.TEST_USER + "." + nameVariants[i], "(c varchar(5))"); - AutoCloseable ignore2 = withTable(TestingOracleServer.TEST_USER + "." + nameVariants[j], "(d varchar(5))")) { - assertThat(computeActual("SHOW TABLES").getOnlyColumn().filter("casesensitivename"::equals)).hasSize(1); // TODO, should be 2 - assertQueryFails("SHOW COLUMNS FROM casesensitivename", "Failed to find remote table name: Ambiguous name: casesensitivename"); - assertQueryFails("SELECT * FROM casesensitivename", "Failed to find remote table name: Ambiguous name: casesensitivename"); - } - } + String schemaName = quoted(remoteSchemaName); + // The TEST_USER is created without quoting in TestingOracleServer#createConfigureScript, quoting it here causes ORA-01918: user 'trino_test' does not exist + if (remoteSchemaName.equalsIgnoreCase(TEST_USER)) { + schemaName = remoteSchemaName; } + String quotedName = schemaName + "." + quoted(remoteTableName); + onRemoteDatabase().execute(format("CREATE TABLE %s %s", quotedName, tableDefinition)); + return () -> onRemoteDatabase().execute("DROP TABLE " + quotedName); } - private AutoCloseable withSchema(String schemaName) + @Override + protected SqlExecutor onRemoteDatabase() { - oracleServer.execute(format("CREATE USER %s IDENTIFIED BY SCM", schemaName)); - oracleServer.execute(format("ALTER USER %s QUOTA 100M ON SYSTEM", schemaName)); - return () -> oracleServer.execute("DROP USER " + schemaName); + return requireNonNull(oracleServer, "oracleServer is null")::execute; } - /** - * @deprecated Use {@link TestTable} instead. - */ - @Deprecated - private AutoCloseable withTable(String tableName, String tableDefinition) + @Test + public void forceTestNgToRespectSingleThreaded() { - oracleServer.execute(format("CREATE TABLE %s %s", tableName, tableDefinition)); - return () -> oracleServer.execute(format("DROP TABLE %s", tableName)); + // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) + // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to + // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. } } diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java index beba05b3582d..2db89aa7217a 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java @@ -69,7 +69,7 @@ public void testNativeMultipleInClauses() String longInClauses = range(0, 10) .mapToObj(value -> getLongInClause(value * 1_000, 1_000)) .collect(joining(" OR ")); - oracleServer.execute(format("SELECT count(*) FROM %s.orders WHERE %s", TEST_SCHEMA, longInClauses)); + onRemoteDatabase().execute(format("SELECT count(*) FROM %s.orders WHERE %s", TEST_SCHEMA, longInClauses)); } private String getLongInClause(int start, int length) diff --git a/plugin/trino-phoenix/src/test/java/io/trino/plugin/phoenix/TestPhoenixConnectorTest.java b/plugin/trino-phoenix/src/test/java/io/trino/plugin/phoenix/TestPhoenixConnectorTest.java index f71e24f68014..8a58c68a844c 100644 --- a/plugin/trino-phoenix/src/test/java/io/trino/plugin/phoenix/TestPhoenixConnectorTest.java +++ b/plugin/trino-phoenix/src/test/java/io/trino/plugin/phoenix/TestPhoenixConnectorTest.java @@ -314,9 +314,9 @@ public void testMultipleSomeColumnsRangesPredicate() public void testUnsupportedType() throws Exception { - executeInPhoenix("CREATE TABLE tpch.test_timestamp (pk bigint primary key, val1 timestamp)"); - executeInPhoenix("UPSERT INTO tpch.test_timestamp (pk, val1) VALUES (1, null)"); - executeInPhoenix("UPSERT INTO tpch.test_timestamp (pk, val1) VALUES (2, '2002-05-30T09:30:10.5')"); + onRemoteDatabase().execute("CREATE TABLE tpch.test_timestamp (pk bigint primary key, val1 timestamp)"); + onRemoteDatabase().execute("UPSERT INTO tpch.test_timestamp (pk, val1) VALUES (1, null)"); + onRemoteDatabase().execute("UPSERT INTO tpch.test_timestamp (pk, val1) VALUES (2, '2002-05-30T09:30:10.5')"); assertUpdate("INSERT INTO test_timestamp VALUES (3)", 1); assertQuery("SELECT * FROM test_timestamp", "VALUES 1, 2, 3"); assertQuery( @@ -337,8 +337,8 @@ public void testUnsupportedType() public void testDefaultDecimalTable() throws Exception { - executeInPhoenix("CREATE TABLE tpch.test_null_decimal (pk bigint primary key, val1 decimal)"); - executeInPhoenix("UPSERT INTO tpch.test_null_decimal (pk, val1) VALUES (1, 2)"); + onRemoteDatabase().execute("CREATE TABLE tpch.test_null_decimal (pk bigint primary key, val1 decimal)"); + onRemoteDatabase().execute("UPSERT INTO tpch.test_null_decimal (pk, val1) VALUES (1, 2)"); assertQuery("SELECT * FROM tpch.test_null_decimal", "VALUES (1, 2) "); } @@ -386,8 +386,8 @@ public void testSecondaryIndex() throws Exception { assertUpdate("CREATE TABLE test_primary_table (pk bigint, val1 double, val2 double, val3 double) with(rowkeys = 'pk')"); - executeInPhoenix("CREATE LOCAL INDEX test_local_index ON tpch.test_primary_table (val1)"); - executeInPhoenix("CREATE INDEX test_global_index ON tpch.test_primary_table (val2)"); + onRemoteDatabase().execute("CREATE LOCAL INDEX test_local_index ON tpch.test_primary_table (val1)"); + onRemoteDatabase().execute("CREATE INDEX test_global_index ON tpch.test_primary_table (val2)"); assertUpdate("INSERT INTO test_primary_table VALUES (1, 1.1, 1.2, 1.3)", 1); assertQuery("SELECT val1,val3 FROM test_primary_table where val1 < 1.2", "SELECT 1.1,1.3"); assertQuery("SELECT val2,val3 FROM test_primary_table where val2 < 1.3", "SELECT 1.2,1.3"); @@ -398,16 +398,15 @@ public void testSecondaryIndex() public void testCaseInsensitiveNameMatching() throws Exception { - executeInPhoenix("CREATE TABLE tpch.\"TestCaseInsensitive\" (\"pK\" bigint primary key, \"Val1\" double)"); + onRemoteDatabase().execute("CREATE TABLE tpch.\"TestCaseInsensitive\" (\"pK\" bigint primary key, \"Val1\" double)"); assertUpdate("INSERT INTO testcaseinsensitive VALUES (1, 1.1)", 1); assertQuery("SELECT Val1 FROM testcaseinsensitive where Val1 < 1.2", "SELECT 1.1"); } @Test public void testMissingColumnsOnInsert() - throws Exception { - executeInPhoenix("CREATE TABLE tpch.test_col_insert(pk VARCHAR NOT NULL PRIMARY KEY, col1 VARCHAR, col2 VARCHAR)"); + onRemoteDatabase().execute("CREATE TABLE tpch.test_col_insert(pk VARCHAR NOT NULL PRIMARY KEY, col1 VARCHAR, col2 VARCHAR)"); assertUpdate("INSERT INTO test_col_insert(pk, col1) VALUES('1', 'val1')", 1); assertUpdate("INSERT INTO test_col_insert(pk, col2) VALUES('1', 'val2')", 1); assertQuery("SELECT * FROM test_col_insert", "SELECT 1, 'val1', 'val2'"); @@ -424,21 +423,15 @@ protected SqlExecutor onRemoteDatabase() { return sql -> { try { - executeInPhoenix(sql); + try (Connection connection = DriverManager.getConnection(testingPhoenixServer.getJdbcUrl()); + Statement statement = connection.createStatement()) { + statement.execute(sql); + connection.commit(); + } } catch (SQLException e) { throw new RuntimeException(e); } }; } - - private void executeInPhoenix(String sql) - throws SQLException - { - try (Connection connection = DriverManager.getConnection(testingPhoenixServer.getJdbcUrl()); - Statement statement = connection.createStatement()) { - statement.execute(sql); - connection.commit(); - } - } } diff --git a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java index cb1ab18824fa..1107244bff7b 100644 --- a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java +++ b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java @@ -332,9 +332,9 @@ public void testMultipleSomeColumnsRangesPredicate() public void testUnsupportedType() throws Exception { - executeInPhoenix("CREATE TABLE tpch.test_timestamp (pk bigint primary key, val1 timestamp)"); - executeInPhoenix("UPSERT INTO tpch.test_timestamp (pk, val1) VALUES (1, null)"); - executeInPhoenix("UPSERT INTO tpch.test_timestamp (pk, val1) VALUES (2, '2002-05-30T09:30:10.5')"); + onRemoteDatabase().execute("CREATE TABLE tpch.test_timestamp (pk bigint primary key, val1 timestamp)"); + onRemoteDatabase().execute("UPSERT INTO tpch.test_timestamp (pk, val1) VALUES (1, null)"); + onRemoteDatabase().execute("UPSERT INTO tpch.test_timestamp (pk, val1) VALUES (2, '2002-05-30T09:30:10.5')"); assertUpdate("INSERT INTO test_timestamp VALUES (3)", 1); assertQuery("SELECT * FROM test_timestamp", "VALUES 1, 2, 3"); assertQuery( @@ -355,8 +355,8 @@ public void testUnsupportedType() public void testDefaultDecimalTable() throws Exception { - executeInPhoenix("CREATE TABLE tpch.test_null_decimal (pk bigint primary key, val1 decimal)"); - executeInPhoenix("UPSERT INTO tpch.test_null_decimal (pk, val1) VALUES (1, 2)"); + onRemoteDatabase().execute("CREATE TABLE tpch.test_null_decimal (pk bigint primary key, val1 decimal)"); + onRemoteDatabase().execute("UPSERT INTO tpch.test_null_decimal (pk, val1) VALUES (1, 2)"); assertQuery("SELECT * FROM tpch.test_null_decimal", "VALUES (1, 2) "); } @@ -404,8 +404,8 @@ public void testSecondaryIndex() throws Exception { assertUpdate("CREATE TABLE test_primary_table (pk bigint, val1 double, val2 double, val3 double) with(rowkeys = 'pk')"); - executeInPhoenix("CREATE LOCAL INDEX test_local_index ON tpch.test_primary_table (val1)"); - executeInPhoenix("CREATE INDEX test_global_index ON tpch.test_primary_table (val2)"); + onRemoteDatabase().execute("CREATE LOCAL INDEX test_local_index ON tpch.test_primary_table (val1)"); + onRemoteDatabase().execute("CREATE INDEX test_global_index ON tpch.test_primary_table (val2)"); assertUpdate("INSERT INTO test_primary_table VALUES (1, 1.1, 1.2, 1.3)", 1); assertQuery("SELECT val1,val3 FROM test_primary_table where val1 < 1.2", "SELECT 1.1,1.3"); assertQuery("SELECT val2,val3 FROM test_primary_table where val2 < 1.3", "SELECT 1.2,1.3"); @@ -416,7 +416,7 @@ public void testSecondaryIndex() public void testCaseInsensitiveNameMatching() throws Exception { - executeInPhoenix("CREATE TABLE tpch.\"TestCaseInsensitive\" (\"pK\" bigint primary key, \"Val1\" double)"); + onRemoteDatabase().execute("CREATE TABLE tpch.\"TestCaseInsensitive\" (\"pK\" bigint primary key, \"Val1\" double)"); assertUpdate("INSERT INTO testcaseinsensitive VALUES (1, 1.1)", 1); assertQuery("SELECT Val1 FROM testcaseinsensitive where Val1 < 1.2", "SELECT 1.1"); } @@ -425,7 +425,7 @@ public void testCaseInsensitiveNameMatching() public void testMissingColumnsOnInsert() throws Exception { - executeInPhoenix("CREATE TABLE tpch.test_col_insert(pk VARCHAR NOT NULL PRIMARY KEY, col1 VARCHAR, col2 VARCHAR)"); + onRemoteDatabase().execute("CREATE TABLE tpch.test_col_insert(pk VARCHAR NOT NULL PRIMARY KEY, col1 VARCHAR, col2 VARCHAR)"); assertUpdate("INSERT INTO test_col_insert(pk, col1) VALUES('1', 'val1')", 1); assertUpdate("INSERT INTO test_col_insert(pk, col2) VALUES('1', 'val2')", 1); assertQuery("SELECT * FROM test_col_insert", "SELECT 1, 'val1', 'val2'"); @@ -554,21 +554,15 @@ protected SqlExecutor onRemoteDatabase() { return sql -> { try { - executeInPhoenix(sql); + try (Connection connection = DriverManager.getConnection(testingPhoenixServer.getJdbcUrl()); + Statement statement = connection.createStatement()) { + statement.execute(sql); + connection.commit(); + } } catch (SQLException e) { throw new RuntimeException(e); } }; } - - private void executeInPhoenix(String sql) - throws SQLException - { - try (Connection connection = DriverManager.getConnection(testingPhoenixServer.getJdbcUrl()); - Statement statement = connection.createStatement()) { - statement.execute(sql); - connection.commit(); - } - } } diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java index 6ac29b10bce1..04e2aadd8b28 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java @@ -88,9 +88,8 @@ protected QueryRunner createQueryRunner() @BeforeClass public void setExtensions() - throws SQLException { - execute("CREATE EXTENSION file_fdw"); + onRemoteDatabase().execute("CREATE EXTENSION file_fdw"); } @Override @@ -167,34 +166,31 @@ public void testDropTable() @Test public void testViews() - throws Exception { - execute("CREATE OR REPLACE VIEW test_view AS SELECT * FROM orders"); + onRemoteDatabase().execute("CREATE OR REPLACE VIEW test_view AS SELECT * FROM orders"); assertTrue(getQueryRunner().tableExists(getSession(), "test_view")); assertQuery("SELECT orderkey FROM test_view", "SELECT orderkey FROM orders"); - execute("DROP VIEW IF EXISTS test_view"); + onRemoteDatabase().execute("DROP VIEW IF EXISTS test_view"); } @Test public void testPostgreSqlMaterializedView() - throws Exception { - execute("CREATE MATERIALIZED VIEW test_mv as SELECT * FROM orders"); + onRemoteDatabase().execute("CREATE MATERIALIZED VIEW test_mv as SELECT * FROM orders"); assertTrue(getQueryRunner().tableExists(getSession(), "test_mv")); assertQuery("SELECT orderkey FROM test_mv", "SELECT orderkey FROM orders"); - execute("DROP MATERIALIZED VIEW test_mv"); + onRemoteDatabase().execute("DROP MATERIALIZED VIEW test_mv"); } @Test public void testForeignTable() - throws Exception { - execute("CREATE SERVER devnull FOREIGN DATA WRAPPER file_fdw"); - execute("CREATE FOREIGN TABLE test_ft (x bigint) SERVER devnull OPTIONS (filename '/dev/null')"); + onRemoteDatabase().execute("CREATE SERVER devnull FOREIGN DATA WRAPPER file_fdw"); + onRemoteDatabase().execute("CREATE FOREIGN TABLE test_ft (x bigint) SERVER devnull OPTIONS (filename '/dev/null')"); assertTrue(getQueryRunner().tableExists(getSession(), "test_ft")); computeActual("SELECT * FROM test_ft"); - execute("DROP FOREIGN TABLE test_ft"); - execute("DROP SERVER devnull"); + onRemoteDatabase().execute("DROP FOREIGN TABLE test_ft"); + onRemoteDatabase().execute("DROP SERVER devnull"); } @Test @@ -212,7 +208,6 @@ public void testSystemTable() @Test public void testPartitionedTables() - throws Exception { try (TestTable testTable = new TestTable( postgreSqlServer::execute, @@ -220,9 +215,9 @@ public void testPartitionedTables() "(id int NOT NULL, payload varchar, logdate date NOT NULL) PARTITION BY RANGE (logdate)")) { String values202111 = "(1, 'A', '2021-11-01'), (2, 'B', '2021-11-25')"; String values202112 = "(3, 'C', '2021-12-01')"; - execute(format("CREATE TABLE %s_2021_11 PARTITION OF %s FOR VALUES FROM ('2021-11-01') TO ('2021-12-01')", testTable.getName(), testTable.getName())); - execute(format("CREATE TABLE %s_2021_12 PARTITION OF %s FOR VALUES FROM ('2021-12-01') TO ('2022-01-01')", testTable.getName(), testTable.getName())); - execute(format("INSERT INTO %s VALUES %s ,%s", testTable.getName(), values202111, values202112)); + onRemoteDatabase().execute(format("CREATE TABLE %s_2021_11 PARTITION OF %s FOR VALUES FROM ('2021-11-01') TO ('2021-12-01')", testTable.getName(), testTable.getName())); + onRemoteDatabase().execute(format("CREATE TABLE %s_2021_12 PARTITION OF %s FOR VALUES FROM ('2021-12-01') TO ('2022-01-01')", testTable.getName(), testTable.getName())); + onRemoteDatabase().execute(format("INSERT INTO %s VALUES %s ,%s", testTable.getName(), values202111, values202112)); assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()) .contains(testTable.getName(), testTable.getName() + "_2021_11", testTable.getName() + "_2021_12"); assertQuery(format("SELECT * FROM %s", testTable.getName()), format("VALUES %s, %s", values202111, values202112)); @@ -235,8 +230,8 @@ public void testPartitionedTables() "(id int NOT NULL, type varchar, logdate varchar) PARTITION BY LIST (type)")) { String valuesA = "(1, 'A', '2021-11-11'), (4, 'A', '2021-12-25')"; String valuesB = "(3, 'B', '2021-12-12'), (2, 'B', '2021-12-28')"; - execute(format("CREATE TABLE %s_a PARTITION OF %s FOR VALUES IN ('A')", testTable.getName(), testTable.getName())); - execute(format("CREATE TABLE %s_b PARTITION OF %s FOR VALUES IN ('B')", testTable.getName(), testTable.getName())); + onRemoteDatabase().execute(format("CREATE TABLE %s_a PARTITION OF %s FOR VALUES IN ('A')", testTable.getName(), testTable.getName())); + onRemoteDatabase().execute(format("CREATE TABLE %s_b PARTITION OF %s FOR VALUES IN ('B')", testTable.getName(), testTable.getName())); assertUpdate(format("INSERT INTO %s VALUES %s ,%s", testTable.getName(), valuesA, valuesB), 4); assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()) .contains(testTable.getName(), testTable.getName() + "_a", testTable.getName() + "_b"); @@ -247,39 +242,38 @@ public void testPartitionedTables() @Test public void testTableWithNoSupportedColumns() - throws Exception { String unsupportedDataType = "interval"; String supportedDataType = "varchar(5)"; - try (AutoCloseable ignore1 = withTable("no_supported_columns", format("(c %s)", unsupportedDataType)); - AutoCloseable ignore2 = withTable("supported_columns", format("(good %s)", supportedDataType)); - AutoCloseable ignore3 = withTable("no_columns", "()")) { - assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()).contains("orders", "no_supported_columns", "supported_columns", "no_columns"); + try (TestTable noSupportedColumns = new TestTable(onRemoteDatabase(), "no_supported_columns", format("(c %s)", unsupportedDataType)); + TestTable supportedColumns = new TestTable(onRemoteDatabase(), "supported_columns", format("(good %s)", supportedDataType)); + TestTable noColumns = new TestTable(onRemoteDatabase(), "no_columns", "()")) { + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()).contains("orders", noSupportedColumns.getName(), supportedColumns.getName(), noColumns.getName()); - assertQueryFails("SELECT c FROM no_supported_columns", "\\QTable 'tpch.no_supported_columns' has no supported columns (all 1 columns are not supported)"); - assertQueryFails("SELECT * FROM no_supported_columns", "\\QTable 'tpch.no_supported_columns' has no supported columns (all 1 columns are not supported)"); - assertQueryFails("SELECT 'a' FROM no_supported_columns", "\\QTable 'tpch.no_supported_columns' has no supported columns (all 1 columns are not supported)"); + assertQueryFails("SELECT c FROM " + noSupportedColumns.getName(), "\\QTable 'tpch." + noSupportedColumns.getName() + "' has no supported columns (all 1 columns are not supported)"); + assertQueryFails("SELECT * FROM " + noSupportedColumns.getName(), "\\QTable 'tpch." + noSupportedColumns.getName() + "' has no supported columns (all 1 columns are not supported)"); + assertQueryFails("SELECT 'a' FROM " + noSupportedColumns.getName(), "\\QTable 'tpch." + noSupportedColumns.getName() + "' has no supported columns (all 1 columns are not supported)"); - assertQueryFails("SELECT c FROM no_columns", "\\QTable 'tpch.no_columns' has no supported columns (all 0 columns are not supported)"); - assertQueryFails("SELECT * FROM no_columns", "\\QTable 'tpch.no_columns' has no supported columns (all 0 columns are not supported)"); - assertQueryFails("SELECT 'a' FROM no_columns", "\\QTable 'tpch.no_columns' has no supported columns (all 0 columns are not supported)"); + assertQueryFails("SELECT c FROM " + noColumns.getName(), "\\QTable 'tpch." + noColumns.getName() + "' has no supported columns (all 0 columns are not supported)"); + assertQueryFails("SELECT * FROM " + noColumns.getName(), "\\QTable 'tpch." + noColumns.getName() + "' has no supported columns (all 0 columns are not supported)"); + assertQueryFails("SELECT 'a' FROM " + noColumns.getName(), "\\QTable 'tpch." + noColumns.getName() + "' has no supported columns (all 0 columns are not supported)"); assertQueryFails("SELECT c FROM non_existent", ".* Table .*tpch.non_existent.* does not exist"); assertQueryFails("SELECT * FROM non_existent", ".* Table .*tpch.non_existent.* does not exist"); assertQueryFails("SELECT 'a' FROM non_existent", ".* Table .*tpch.non_existent.* does not exist"); - assertQueryFails("SHOW COLUMNS FROM no_supported_columns", "\\QTable 'tpch.no_supported_columns' has no supported columns (all 1 columns are not supported)"); - assertQueryFails("SHOW COLUMNS FROM no_columns", "\\QTable 'tpch.no_columns' has no supported columns (all 0 columns are not supported)"); + assertQueryFails("SHOW COLUMNS FROM " + noSupportedColumns.getName(), "\\QTable 'tpch." + noSupportedColumns.getName() + "' has no supported columns (all 1 columns are not supported)"); + assertQueryFails("SHOW COLUMNS FROM " + noColumns.getName(), "\\QTable 'tpch." + noColumns.getName() + "' has no supported columns (all 0 columns are not supported)"); // Other tables should be visible in SHOW TABLES (the no_supported_columns might be included or might be not) and information_schema.tables assertThat(computeActual("SHOW TABLES").getOnlyColumn()) - .contains("orders", "no_supported_columns", "supported_columns", "no_columns"); + .contains("orders", noSupportedColumns.getName(), supportedColumns.getName(), noColumns.getName()); assertThat(computeActual("SELECT table_name FROM information_schema.tables WHERE table_schema = 'tpch'").getOnlyColumn()) - .contains("orders", "no_supported_columns", "supported_columns", "no_columns"); + .contains("orders", noSupportedColumns.getName(), supportedColumns.getName(), noColumns.getName()); // Other tables should be introspectable with SHOW COLUMNS and information_schema.columns - assertQuery("SHOW COLUMNS FROM supported_columns", "VALUES ('good', 'varchar(5)', '', '')"); + assertQuery("SHOW COLUMNS FROM " + supportedColumns.getName(), "VALUES ('good', 'varchar(5)', '', '')"); // Listing columns in all tables should not fail due to tables with no columns computeActual("SELECT column_name FROM information_schema.columns WHERE table_schema = 'tpch'"); @@ -292,13 +286,13 @@ public void testInsertWithFailureDoesNotLeaveBehindOrphanedTable() { String schemaName = format("tmp_schema_%s", UUID.randomUUID().toString().replaceAll("-", "")); try (AutoCloseable schema = withSchema(schemaName); - AutoCloseable table = withTable(format("%s.test_cleanup", schemaName), "(x INTEGER)")) { - assertQuery(format("SELECT table_name FROM information_schema.tables WHERE table_schema = '%s'", schemaName), "VALUES 'test_cleanup'"); + TestTable table = new TestTable(onRemoteDatabase(), format("%s.test_cleanup", schemaName), "(x INTEGER)")) { + assertQuery(format("SELECT table_name FROM information_schema.tables WHERE table_schema = '%s'", schemaName), "VALUES '" + table.getName().replace(schemaName + ".", "") + "'"); - execute(format("ALTER TABLE %s.test_cleanup ADD CHECK (x > 0)", schemaName)); + onRemoteDatabase().execute("ALTER TABLE " + table.getName() + " ADD CHECK (x > 0)"); - assertQueryFails(format("INSERT INTO %s.test_cleanup (x) VALUES (0)", schemaName), "ERROR: new row .* violates check constraint [\\s\\S]*"); - assertQuery(format("SELECT table_name FROM information_schema.tables WHERE table_schema = '%s'", schemaName), "VALUES 'test_cleanup'"); + assertQueryFails("INSERT INTO " + table.getName() + " (x) VALUES (0)", "ERROR: new row .* violates check constraint [\\s\\S]*"); + assertQuery(format("SELECT table_name FROM information_schema.tables WHERE table_schema = '%s'", schemaName), "VALUES '" + table.getName().replace(schemaName + ".", "") + "'"); } } @@ -627,31 +621,30 @@ public void testStringJoinPushdownWithCollate() @Test public void testDecimalPredicatePushdown() - throws Exception { - try (AutoCloseable ignore = withTable("test_decimal_pushdown", + try (TestTable table = new TestTable(onRemoteDatabase(), "test_decimal_pushdown", "(short_decimal decimal(9, 3), long_decimal decimal(30, 10))")) { - execute("INSERT INTO test_decimal_pushdown VALUES (123.321, 123456789.987654321)"); + onRemoteDatabase().execute("INSERT INTO " + table.getName() + " VALUES (123.321, 123456789.987654321)"); - assertThat(query("SELECT * FROM test_decimal_pushdown WHERE short_decimal <= 124")) + assertThat(query("SELECT * FROM " + table.getName() + " WHERE short_decimal <= 124")) .matches("VALUES (CAST(123.321 AS decimal(9,3)), CAST(123456789.987654321 AS decimal(30, 10)))") .isFullyPushedDown(); - assertThat(query("SELECT * FROM test_decimal_pushdown WHERE short_decimal <= 124")) + assertThat(query("SELECT * FROM " + table.getName() + " WHERE short_decimal <= 124")) .matches("VALUES (CAST(123.321 AS decimal(9,3)), CAST(123456789.987654321 AS decimal(30, 10)))") .isFullyPushedDown(); - assertThat(query("SELECT * FROM test_decimal_pushdown WHERE long_decimal <= 123456790")) + assertThat(query("SELECT * FROM " + table.getName() + " WHERE long_decimal <= 123456790")) .matches("VALUES (CAST(123.321 AS decimal(9,3)), CAST(123456789.987654321 AS decimal(30, 10)))") .isFullyPushedDown(); - assertThat(query("SELECT * FROM test_decimal_pushdown WHERE short_decimal <= 123.321")) + assertThat(query("SELECT * FROM " + table.getName() + " WHERE short_decimal <= 123.321")) .matches("VALUES (CAST(123.321 AS decimal(9,3)), CAST(123456789.987654321 AS decimal(30, 10)))") .isFullyPushedDown(); - assertThat(query("SELECT * FROM test_decimal_pushdown WHERE long_decimal <= 123456789.987654321")) + assertThat(query("SELECT * FROM " + table.getName() + " WHERE long_decimal <= 123456789.987654321")) .matches("VALUES (CAST(123.321 AS decimal(9,3)), CAST(123456789.987654321 AS decimal(30, 10)))") .isFullyPushedDown(); - assertThat(query("SELECT * FROM test_decimal_pushdown WHERE short_decimal = 123.321")) + assertThat(query("SELECT * FROM " + table.getName() + " WHERE short_decimal = 123.321")) .matches("VALUES (CAST(123.321 AS decimal(9,3)), CAST(123456789.987654321 AS decimal(30, 10)))") .isFullyPushedDown(); - assertThat(query("SELECT * FROM test_decimal_pushdown WHERE long_decimal = 123456789.987654321")) + assertThat(query("SELECT * FROM " + table.getName() + " WHERE long_decimal = 123456789.987654321")) .matches("VALUES (CAST(123.321 AS decimal(9,3)), CAST(123456789.987654321 AS decimal(30, 10)))") .isFullyPushedDown(); } @@ -659,21 +652,20 @@ public void testDecimalPredicatePushdown() @Test public void testCharPredicatePushdown() - throws Exception { - try (AutoCloseable ignore = withTable("test_char_pushdown", + try (TestTable table = new TestTable(onRemoteDatabase(), "test_char_pushdown", "(char_1 char(1), char_5 char(5), char_10 char(10))")) { - execute("INSERT INTO test_char_pushdown VALUES" + + onRemoteDatabase().execute("INSERT INTO " + table.getName() + " VALUES" + "('0', '0' , '0' )," + "('1', '12345', '1234567890')"); - assertThat(query("SELECT * FROM test_char_pushdown WHERE char_1 = '0' AND char_5 = '0'")) + assertThat(query("SELECT * FROM " + table.getName() + " WHERE char_1 = '0' AND char_5 = '0'")) .matches("VALUES (CHAR'0', CHAR'0 ', CHAR'0 ')") .isFullyPushedDown(); - assertThat(query("SELECT * FROM test_char_pushdown WHERE char_5 = CHAR'12345' AND char_10 = '1234567890'")) + assertThat(query("SELECT * FROM " + table.getName() + " WHERE char_5 = CHAR'12345' AND char_10 = '1234567890'")) .matches("VALUES (CHAR'1', CHAR'12345', CHAR'1234567890')") .isFullyPushedDown(); - assertThat(query("SELECT * FROM test_char_pushdown WHERE char_10 = CHAR'0'")) + assertThat(query("SELECT * FROM " + table.getName() + " WHERE char_10 = CHAR'0'")) .matches("VALUES (CHAR'0', CHAR'0 ', CHAR'0 ')") .isFullyPushedDown(); } @@ -681,9 +673,8 @@ public void testCharPredicatePushdown() @Test public void testCharTrailingSpace() - throws Exception { - execute("CREATE TABLE char_trailing_space (x char(10))"); + onRemoteDatabase().execute("CREATE TABLE char_trailing_space (x char(10))"); assertUpdate("INSERT INTO char_trailing_space VALUES ('test')", 1); assertQuery("SELECT * FROM char_trailing_space WHERE x = char 'test'", "VALUES 'test'"); @@ -706,9 +697,9 @@ public void testTopNWithEnum() { // Create an enum with non-lexicographically sorted entries String enumType = "test_enum_" + randomTableSuffix(); - postgreSqlServer.execute("CREATE TYPE " + enumType + " AS ENUM ('A', 'b', 'B', 'a')"); + onRemoteDatabase().execute("CREATE TYPE " + enumType + " AS ENUM ('A', 'b', 'B', 'a')"); try (TestTable testTable = new TestTable( - postgreSqlServer::execute, + onRemoteDatabase(), "test_case_sensitive_topn_pushdown_with_enums", "(an_enum " + enumType + ", a_bigint bigint)", List.of( @@ -727,7 +718,7 @@ public void testTopNWithEnum() .isNotFullyPushedDown(TopNNode.class); } finally { - postgreSqlServer.execute("DROP TYPE " + enumType); + onRemoteDatabase().execute("DROP TYPE " + enumType); } } @@ -736,9 +727,8 @@ public void testTopNWithEnum() */ @Test public void testNativeLargeIn() - throws SQLException { - execute("SELECT count(*) FROM orders WHERE " + getLongInClause(0, 500_000)); + onRemoteDatabase().execute("SELECT count(*) FROM orders WHERE " + getLongInClause(0, 500_000)); } /** @@ -746,12 +736,11 @@ public void testNativeLargeIn() */ @Test public void testNativeMultipleInClauses() - throws SQLException { String longInClauses = range(0, 20) .mapToObj(value -> getLongInClause(value * 10_000, 10_000)) .collect(joining(" OR ")); - execute("SELECT count(*) FROM orders WHERE " + longInClauses); + onRemoteDatabase().execute("SELECT count(*) FROM orders WHERE " + longInClauses); } /** @@ -759,19 +748,17 @@ public void testNativeMultipleInClauses() */ @Test public void testTimestampColumnAndTimestampWithTimeZoneConstant() - throws Exception { - String tableName = "test_timestamptz_unwrap_cast" + randomTableSuffix(); - try (AutoCloseable ignored = withTable(tableName, "(id integer, ts_col timestamp(6))")) { - execute("INSERT INTO " + tableName + " (id, ts_col) VALUES " + + try (TestTable table = new TestTable(onRemoteDatabase(), "test_timestamptz_unwrap_cast", "(id integer, ts_col timestamp(6))")) { + onRemoteDatabase().execute("INSERT INTO " + table.getName() + " (id, ts_col) VALUES " + "(1, timestamp '2020-01-01 01:01:01.000')," + "(2, timestamp '2019-01-01 01:01:01.000')"); - assertThat(query(format("SELECT id FROM %s WHERE ts_col >= TIMESTAMP '2019-01-01 00:00:00 %s'", tableName, getSession().getTimeZoneKey().getId()))) + assertThat(query(format("SELECT id FROM %s WHERE ts_col >= TIMESTAMP '2019-01-01 00:00:00 %s'", table.getName(), getSession().getTimeZoneKey().getId()))) .matches("VALUES 1, 2") .isFullyPushedDown(); - assertThat(query(format("SELECT id FROM %s WHERE ts_col >= TIMESTAMP '2019-01-01 00:00:00 %s'", tableName, "UTC"))) + assertThat(query(format("SELECT id FROM %s WHERE ts_col >= TIMESTAMP '2019-01-01 00:00:00 %s'", table.getName(), "UTC"))) .matches("VALUES 1") .isFullyPushedDown(); } @@ -786,35 +773,9 @@ private String getLongInClause(int start, int length) } private AutoCloseable withSchema(String schema) - throws Exception - { - execute(format("CREATE SCHEMA %s", schema)); - return () -> { - try { - execute(format("DROP SCHEMA %s", schema)); - } - catch (SQLException e) { - throw new RuntimeException(e); - } - }; - } - - /** - * @deprecated Use {@link TestTable} instead. - */ - @Deprecated - private AutoCloseable withTable(String tableName, String tableDefinition) - throws Exception { - execute(format("CREATE TABLE %s%s", tableName, tableDefinition)); - return () -> { - try { - execute(format("DROP TABLE %s", tableName)); - } - catch (SQLException e) { - throw new RuntimeException(e); - } - }; + onRemoteDatabase().execute(format("CREATE SCHEMA %s", schema)); + return () -> onRemoteDatabase().execute(format("DROP SCHEMA %s", schema)); } @Override @@ -822,7 +783,10 @@ protected SqlExecutor onRemoteDatabase() { return sql -> { try { - execute(sql); + try (Connection connection = DriverManager.getConnection(postgreSqlServer.getJdbcUrl(), postgreSqlServer.getProperties()); + Statement statement = connection.createStatement()) { + statement.execute(sql); + } } catch (SQLException e) { throw new RuntimeException(e); @@ -830,15 +794,6 @@ protected SqlExecutor onRemoteDatabase() }; } - private void execute(String sql) - throws SQLException - { - try (Connection connection = DriverManager.getConnection(postgreSqlServer.getJdbcUrl(), postgreSqlServer.getProperties()); - Statement statement = connection.createStatement()) { - statement.execute(sql); - } - } - @Override protected List getRemoteDatabaseEvents() {