diff --git a/plugin/trino-sqlserver/pom.xml b/plugin/trino-sqlserver/pom.xml index 6cdd6b42231f..e1bd9785445e 100644 --- a/plugin/trino-sqlserver/pom.xml +++ b/plugin/trino-sqlserver/pom.xml @@ -164,6 +164,12 @@ test + + org.jetbrains + annotations + test + + org.testcontainers mssqlserver diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java index 218894e10c57..e9e5d4e55c30 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java @@ -25,6 +25,7 @@ import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; +import org.intellij.lang.annotations.Language; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -55,6 +56,7 @@ import static io.trino.testing.sql.TestTable.randomTableSuffix; import static java.lang.String.format; import static java.time.ZoneOffset.UTC; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public abstract class BaseSqlServerTypeMapping extends AbstractTestQueryFramework @@ -89,21 +91,114 @@ public void setUp() } @Test - public void testBasicTypes() + public void testTrinoBoolean() { SqlDataTypeTest.create() .addRoundTrip("boolean", "null", BOOLEAN, "CAST(NULL AS BOOLEAN)") .addRoundTrip("boolean", "true", BOOLEAN) .addRoundTrip("boolean", "false", BOOLEAN) - .addRoundTrip("bigint", "null", BIGINT, "CAST(NULL AS BIGINT)") - .addRoundTrip("bigint", "123456789012", BIGINT) - .addRoundTrip("integer", "null", INTEGER, "CAST(NULL AS INTEGER)") - .addRoundTrip("integer", "123456789", INTEGER) - .addRoundTrip("smallint", "null", SMALLINT, "CAST(NULL AS SMALLINT)") - .addRoundTrip("smallint", "32456", SMALLINT, "SMALLINT '32456'") - .addRoundTrip("tinyint", "null", TINYINT, "CAST(NULL AS TINYINT)") + .execute(getQueryRunner(), trinoCreateAsSelect("test_boolean")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_boolean")); + } + + @Test + public void testSqlServerBit() + { + SqlDataTypeTest.create() + .addRoundTrip("bit", "null", BOOLEAN, "CAST(NULL AS BOOLEAN)") + .addRoundTrip("bit", "1", BOOLEAN, "true") + .addRoundTrip("bit", "0", BOOLEAN, "false") + .execute(getQueryRunner(), sqlServerCreateAndInsert("test_bit")); + } + + @Test + public void testTinyint() + { + // TODO: Add min/max/min-1/max+1 tests (https://github.com/trinodb/trino/issues/11209) + SqlDataTypeTest.create() + .addRoundTrip("tinyint", "NULL", TINYINT, "CAST(NULL AS TINYINT)") .addRoundTrip("tinyint", "5", TINYINT, "TINYINT '5'") - .execute(getQueryRunner(), trinoCreateAsSelect("test_basic_types")); + .execute(getQueryRunner(), sqlServerCreateAndInsert("test_tinyint")) + .execute(getQueryRunner(), trinoCreateAsSelect("test_tinyint")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_tinyint")); + } + + @Test + public void testSmallint() + { + SqlDataTypeTest.create() + .addRoundTrip("smallint", "NULL", SMALLINT, "CAST(NULL AS SMALLINT)") + .addRoundTrip("smallint", "-32768", SMALLINT, "SMALLINT '-32768'") // min value in SQL Server and Trino + .addRoundTrip("smallint", "32456", SMALLINT, "SMALLINT '32456'") + .addRoundTrip("smallint", "32767", SMALLINT, "SMALLINT '32767'") // max value in SQL Server and Trino + .execute(getQueryRunner(), sqlServerCreateAndInsert("test_smallint")) + .execute(getQueryRunner(), trinoCreateAsSelect("test_smallint")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_smallint")); + } + + @Test + public void testUnsupportedSmallint() + { + try (TestTable table = new TestTable(onRemoteDatabase(), "test_unsupported_smallint", "(data smallint)")) { + assertSqlServerQueryFails( + format("INSERT INTO %s VALUES (-32769)", table.getName()), // min - 1 + "Arithmetic overflow error for data type smallint, value = -32769."); + assertSqlServerQueryFails( + format("INSERT INTO %s VALUES (32768)", table.getName()), // max + 1 + "Arithmetic overflow error for data type smallint, value = 32768."); + } + } + + @Test + public void testInteger() + { + SqlDataTypeTest.create() + .addRoundTrip("integer", "NULL", INTEGER, "CAST(NULL AS INTEGER)") + .addRoundTrip("integer", "-2147483648", INTEGER, "-2147483648") // min value in SQL Server and Trino + .addRoundTrip("integer", "1234567890", INTEGER, "1234567890") + .addRoundTrip("integer", "2147483647", INTEGER, "2147483647") // max value in SQL Server and Trino + .execute(getQueryRunner(), sqlServerCreateAndInsert("test_int")) + .execute(getQueryRunner(), trinoCreateAsSelect("test_int")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_int")); + } + + @Test + public void testUnsupportedInteger() + { + try (TestTable table = new TestTable(onRemoteDatabase(), "test_unsupported_integer", "(data integer)")) { + assertSqlServerQueryFails( + format("INSERT INTO %s VALUES (-2147483649)", table.getName()), // min - 1 + "Arithmetic overflow error converting expression to data type int."); + assertSqlServerQueryFails( + format("INSERT INTO %s VALUES (2147483648)", table.getName()), // max + 1 + "Arithmetic overflow error converting expression to data type int."); + } + } + + @Test + public void testBigint() + { + SqlDataTypeTest.create() + .addRoundTrip("bigint", "NULL", BIGINT, "CAST(NULL AS BIGINT)") + .addRoundTrip("bigint", "-9223372036854775808", BIGINT, "-9223372036854775808") // min value in SQL Server and Trino + .addRoundTrip("bigint", "123456789012", BIGINT, "123456789012") + .addRoundTrip("bigint", "9223372036854775807", BIGINT, "9223372036854775807") // max value in SQL Server and Trino + .execute(getQueryRunner(), sqlServerCreateAndInsert("test_bigint")) + .execute(getQueryRunner(), trinoCreateAsSelect("test_bigint")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_bigint")); + } + + @Test + public void testUnsupportedBigint() + { + try (TestTable table = new TestTable(onRemoteDatabase(), "test_unsupported_bigint", "(data bigint)")) { + assertSqlServerQueryFails( + format("INSERT INTO %s VALUES (-9223372036854775809)", table.getName()), // min - 1 + "Arithmetic overflow error converting expression to data type bigint."); + assertSqlServerQueryFails( + format("INSERT INTO %s VALUES (9223372036854775808)", table.getName()), // max + 1 + "Arithmetic overflow error converting expression to data type bigint."); + } } @Test @@ -120,7 +215,8 @@ public void testReal() .addRoundTrip("real", "NULL", REAL, "CAST(NULL AS real)") .addRoundTrip("real", "3.14", REAL, "REAL '3.14'") .addRoundTrip("real", "3.1415927", REAL, "REAL '3.1415927'") - .execute(getQueryRunner(), trinoCreateAsSelect("test_real")); + .execute(getQueryRunner(), trinoCreateAsSelect("test_real")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_real")); } @Test @@ -178,7 +274,8 @@ public void testDecimal() .addRoundTrip("decimal(30, 5)", "3141592653589793238462643.38327", createDecimalType(30, 5), "CAST('3141592653589793238462643.38327' AS decimal(30, 5))") .addRoundTrip("decimal(30, 5)", "-3141592653589793238462643.38327", createDecimalType(30, 5), "CAST('-3141592653589793238462643.38327' AS decimal(30, 5))") .execute(getQueryRunner(), sqlServerCreateAndInsert("test_decimal")) - .execute(getQueryRunner(), trinoCreateAsSelect("test_decimal")); + .execute(getQueryRunner(), trinoCreateAsSelect("test_decimal")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_decimal")); } @Test @@ -199,7 +296,7 @@ public void testChar() .addRoundTrip("char(32)", "CAST('攻殻機動隊' AS char(32))", createCharType(32), "CAST('攻殻機動隊' AS char(32))") .addRoundTrip("char(20)", "CAST('😂' AS char(20))", createCharType(20), "CAST('😂' AS char(20))") .addRoundTrip("char(77)", "CAST('Ну, погоди!' AS char(77))", createCharType(77), "CAST('Ну, погоди!' AS char(77))") - .execute(getQueryRunner(), trinoCreateAndInsert(getSession(), "test_char")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_char")) .execute(getQueryRunner(), trinoCreateAsSelect("test_char")); } @@ -219,7 +316,7 @@ public void testTrinoLongChar() // testing mapping char > 4000 -> varchar(max) SqlDataTypeTest.create() .addRoundTrip("char(4001)", "'text_c'", createUnboundedVarcharType(), "VARCHAR 'text_c'") - .execute(getQueryRunner(), trinoCreateAndInsert(getSession(), "test_long_char")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_long_char")) .execute(getQueryRunner(), trinoCreateAsSelect("test_long_char")); } @@ -239,7 +336,7 @@ public void testVarchar() .addRoundTrip("varchar(32)", "CAST('攻殻機動隊' AS varchar(32))", createVarcharType(32), "CAST('攻殻機動隊' AS varchar(32))") .addRoundTrip("varchar(20)", "CAST('😂' AS varchar(20))", createVarcharType(20), "CAST('😂' AS varchar(20))") .addRoundTrip("varchar(77)", "CAST('Ну, погоди!' AS varchar(77))", createVarcharType(77), "CAST('Ну, погоди!' AS varchar(77))") - .execute(getQueryRunner(), trinoCreateAndInsert(getSession(), "test_varchar")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_varchar")) .execute(getQueryRunner(), trinoCreateAsSelect("test_varchar")); } @@ -262,7 +359,7 @@ public void testTrinoLongVarchar() // testing mapping varchar > 4000 -> varchar(max) SqlDataTypeTest.create() .addRoundTrip("varchar(4001)", "'text_c'", createUnboundedVarcharType(), "VARCHAR 'text_c'") - .execute(getQueryRunner(), trinoCreateAndInsert(getSession(), "test_long_varchar")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_long_varchar")) .execute(getQueryRunner(), trinoCreateAsSelect("test_long_varchar")); } @@ -288,7 +385,7 @@ public void testTrinoUnboundedVarchar() .addRoundTrip("varchar", "VARCHAR '😂'", createUnboundedVarcharType(), "VARCHAR '😂'") .addRoundTrip("varchar", "VARCHAR 'Ну, погоди!'", createUnboundedVarcharType(), "VARCHAR 'Ну, погоди!'") .addRoundTrip("varchar", "'text_f'", createUnboundedVarcharType(), "VARCHAR 'text_f'") - .execute(getQueryRunner(), trinoCreateAndInsert(getSession(), "test_unbounded_varchar")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_unbounded_varchar")) .execute(getQueryRunner(), trinoCreateAsSelect("test_unbounded_varchar")); } @@ -303,7 +400,8 @@ public void testVarbinary() .addRoundTrip("varbinary", "X'4261672066756C6C206F6620F09F92B0'", VARBINARY, "to_utf8('Bag full of 💰')") .addRoundTrip("varbinary", "X'0001020304050607080DF9367AA7000000'", VARBINARY, "X'0001020304050607080DF9367AA7000000'") // non-text .addRoundTrip("varbinary", "X'000000000000'", VARBINARY, "X'000000000000'") - .execute(getQueryRunner(), trinoCreateAsSelect("test_varbinary")); + .execute(getQueryRunner(), trinoCreateAsSelect("test_varbinary")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_varbinary")); // Binary literals must be prefixed with 0x // https://docs.microsoft.com/en-us/sql/analytics-platform-system/load-with-insert?view=aps-pdw-2016-au7#InsertingLiteralsBinary @@ -447,8 +545,8 @@ public void testTime() .addRoundTrip("TIME '23:59:59.999999'", "TIME '23:59:59.999999'") .addRoundTrip("TIME '23:59:59.9999999'", "TIME '23:59:59.9999999'") - .execute(getQueryRunner(), trinoCreateAsSelect(getSession(), "test_time")) - .execute(getQueryRunner(), trinoCreateAndInsert(getSession(), "test_time")); + .execute(getQueryRunner(), trinoCreateAsSelect("test_time")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_time")); SqlDataTypeTest.create() // round down @@ -479,8 +577,8 @@ public void testTime() // round down .addRoundTrip("TIME '23:59:59.999999949999'", "TIME '23:59:59.9999999'") - .execute(getQueryRunner(), trinoCreateAndInsert(getSession(), "test_time")) - .execute(getQueryRunner(), trinoCreateAsSelect(getSession(), "test_time")); + .execute(getQueryRunner(), trinoCreateAndInsert("test_time")) + .execute(getQueryRunner(), trinoCreateAsSelect("test_time")); } @Test(dataProvider = "sessionZonesDataProvider") @@ -583,7 +681,7 @@ public void testTimestamp(ZoneId sessionZone) .build(); tests.execute(getQueryRunner(), session, trinoCreateAsSelect(session, "test_timestamp")); - tests.execute(getQueryRunner(), session, trinoCreateAsSelect(getSession(), "test_timestamp")); + tests.execute(getQueryRunner(), session, trinoCreateAsSelect("test_timestamp")); tests.execute(getQueryRunner(), session, trinoCreateAndInsert(session, "test_timestamp")); } @@ -733,6 +831,11 @@ protected DataSetup trinoCreateAsSelect(Session session, String tableNamePrefix) return new CreateAsSelectDataSetup(new TrinoSqlExecutor(getQueryRunner(), session), tableNamePrefix); } + protected DataSetup trinoCreateAndInsert(String tableNamePrefix) + { + return new CreateAndInsertDataSetup(new TrinoSqlExecutor(getQueryRunner(), getSession()), tableNamePrefix); + } + protected DataSetup trinoCreateAndInsert(Session session, String tableNamePrefix) { return new CreateAndInsertDataSetup(new TrinoSqlExecutor(getQueryRunner(), session), tableNamePrefix); @@ -758,6 +861,13 @@ private static void checkIsGap(ZoneId zone, LocalDateTime dateTime) verify(isGap(zone, dateTime), "Expected %s to be a gap in %s", dateTime, zone); } + private void assertSqlServerQueryFails(@Language("SQL") String sql, String expectedMessage) + { + assertThatThrownBy(() -> onRemoteDatabase().execute(sql)) + .getCause() + .hasMessageContaining(expectedMessage); + } + protected SqlExecutor onRemoteDatabase() { return sqlServer::execute;