diff --git a/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClient.java b/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClient.java index 34301bbaa6b75..8cbe95dd07340 100644 --- a/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClient.java +++ b/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClient.java @@ -460,7 +460,12 @@ public void setColumnComment(ConnectorSession session, JdbcTableHandle handle, J @Override public void setColumnType(ConnectorSession session, JdbcTableHandle handle, JdbcColumnHandle column, Type type) { - throw new TrinoException(NOT_SUPPORTED, "This connector does not support setting column types"); + String sql = format( + "ALTER TABLE %s MODIFY COLUMN %s %s", + quoted(handle.asPlainTable().getRemoteTableName()), + quoted(column.getColumnName()), + toWriteMapping(session, type).getDataType()); + execute(session, sql); } private static String clickhouseVarcharLiteral(String value) 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 5e22e9c10d8e5..807b30748c58e 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 @@ -45,6 +45,9 @@ import static io.trino.plugin.jdbc.JdbcMetadataSessionProperties.DOMAIN_COMPACTION_THRESHOLD; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.MaterializedResult.resultBuilder; +import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_CREATE_TABLE_WITH_COLUMN_COMMENT; +import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_CREATE_TABLE_WITH_DATA; +import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_SET_COLUMN_TYPE; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; @@ -82,9 +85,6 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) case SUPPORTS_AGGREGATION_PUSHDOWN_COUNT_DISTINCT: return false; - case SUPPORTS_SET_COLUMN_TYPE: - return false; - case SUPPORTS_ARRAY: case SUPPORTS_ROW_TYPE: case SUPPORTS_NEGATIVE_DATE: @@ -325,7 +325,7 @@ protected TestTable createTableWithDefaultColumns() "col_nullable Nullable(Int64)," + "col_default Nullable(Int64) DEFAULT 43," + "col_nonnull_default Int64 DEFAULT 42," + - "col_required2 Int64) ENGINE=Log"); + "col_required2 Int64) ENGINE=MergeTree() ORDER BY col_required2"); } @Override @@ -817,4 +817,162 @@ private Map getTableProperties(String schemaName, String tableNa return properties.buildOrThrow(); } } + + @Test + @Override + public void testSetColumnType() + { + String tableName = "test_set_column_type" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + " (a bigint, b double NOT NULL, c varchar(50)) WITH (order_by=ARRAY['b'], engine = 'MergeTree')"); + assertUpdate("ALTER TABLE " + tableName + " ALTER COLUMN a SET DATA TYPE varchar(50)"); + assertEquals(getColumnType(tableName, "a"), "varchar"); + assertThat((String) computeScalar("show create table " + tableName)).contains("CREATE TABLE " + "clickhouse.tpch." + tableName + " (\n" + + " a varchar NOT NULL,\n" + + " b double NOT NULL,\n" + + " c varchar\n" + + ")\n" + + "WITH (\n" + + " engine = 'MERGETREE',\n" + + " order_by = ARRAY['b'],\n" + + " primary_key = ARRAY['b']\n" + + ")"); + } + + @Test + @Override + public void testSetColumnIncompatibleType() + { + skipTestUnless(hasBehavior(SUPPORTS_SET_COLUMN_TYPE) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); + + String tableName = "test_set_column_incompatible_type" + randomNameSuffix(); + + assertUpdate("CREATE TABLE " + tableName + " (col bigint, col2 int not null) WITH (order_by=ARRAY['col2'], engine = 'MergeTree')"); + assertUpdate("ALTER TABLE " + tableName + " ALTER COLUMN col SET DATA TYPE integer"); + assertEquals(getColumnType(tableName, "col"), "integer"); + } + + @Test + @Override + public void testSetColumnOutOfRangeType() + { + String tableName = "test_set_column_out_of_range" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + " (col bigint, col2 int not null) WITH (order_by=ARRAY['col2'], engine = 'MergeTree')"); + query("insert into " + tableName + " values(9223372036854775807, 22)"); + assertUpdate("ALTER TABLE " + tableName + " ALTER COLUMN col SET DATA TYPE integer"); + assertEquals(getColumnType(tableName, "col"), "integer"); + assertQuery("SELECT col FROM " + tableName, "VALUES -1"); + } + + @Test + @Override + public void testSetColumnTypeWithComment() + { + skipTestUnless(hasBehavior(SUPPORTS_SET_COLUMN_TYPE) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_COLUMN_COMMENT)); + String tableName = "test_set_column_with_comment" + randomNameSuffix(); + + assertUpdate("CREATE TABLE " + tableName + " (col bigint COMMENT 'test comment', col2 int not null) WITH (order_by=ARRAY['col2'], engine = 'MergeTree')"); + assertEquals(getColumnComment(tableName, "col"), "test comment"); + + assertUpdate("ALTER TABLE " + tableName + " ALTER COLUMN col SET DATA TYPE bigint"); + assertEquals(getColumnComment(tableName, "col"), "test comment"); + } + + @Test + @Override + public void testSetColumnTypeWithDefaultColumn() + { + try (TestTable table = createTableWithDefaultColumns()) { + // col_default column inserts 43 by default + assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col_default SET DATA TYPE bigint"); + assertUpdate("INSERT INTO " + table.getName() + " (col_required, col_required2) VALUES (1, 10)", 1); + assertQuery("SELECT col_default FROM " + table.getName(), "VALUES 43"); + } + } + + @Test + @Override + public void testSetColumnTypeWithNotNull() + { + String tableName = "test_set_column_with_not_null" + randomNameSuffix(); + + assertUpdate("CREATE TABLE " + tableName + " (col bigint not null, col2 int not null) WITH (order_by=ARRAY['col2'], engine = 'MergeTree')"); + assertFalse(columnIsNullable(tableName, "col")); + + assertUpdate("ALTER TABLE " + tableName + " ALTER COLUMN col SET DATA TYPE bigint"); + assertFalse(columnIsNullable(tableName, "col")); + } + + @Override + protected Optional filterSetColumnTypesDataProvider(SetColumnTypeSetup setup) + { + switch (setup.sourceColumnType()) { + case "tinyint": + return Optional.of(new SetColumnTypeSetup("tinyint", "TINYINT '127'", "smallint")); + case "smallint": + return Optional.of(new SetColumnTypeSetup("smallint", "SMALLINT '32767'", "integer")); + case "integer": + return Optional.of(new SetColumnTypeSetup("integer", "2147483647", "bigint")); + case "bigint": + return Optional.of(new SetColumnTypeSetup("bigint", "BIGINT '-2147483648'", "integer")); + case "real": + if (setup.sourceValueLiteral().equals("REAL '10.3'")) { + return Optional.of(new SetColumnTypeSetup("real", "REAL '10.3'", "double")); + } + else if (setup.sourceValueLiteral().equals("REAL 'NaN'")) { + return Optional.of(new SetColumnTypeSetup("real", "REAL 'NaN'", "double")); + } + else { + return Optional.empty(); + } + case "decimal(5,3)": + if (setup.sourceValueLiteral().equals("12.345") && setup.newColumnType().equals("decimal(10,3)")) { + return Optional.of(new SetColumnTypeSetup("decimal(5,3)", "12.345", "decimal(10,3)")); + } + else if (setup.sourceValueLiteral().equals("12.345") && setup.newColumnType().equals("decimal(38,3)")) { + return Optional.of(new SetColumnTypeSetup("decimal(28,3)", "12.345", "decimal(38,3)")); + } + else if (setup.sourceValueLiteral().equals("12.340")) { + return Optional.of(new SetColumnTypeSetup("decimal(5,3)", "12.340", "decimal(5,2)")); + } + else if (setup.sourceValueLiteral().equals("12.35")) { + return Optional.of(new SetColumnTypeSetup("decimal(5,3)", "12.35", "decimal(5,2)")); + } + else { + return Optional.empty(); + } + case "decimal(28,3)": + return Optional.of(new SetColumnTypeSetup("decimal(28,3)", "12.345", "decimal(38,3)")); + case "varchar(100)": + return Optional.of(new SetColumnTypeSetup("varchar(100)", "'shorten-varchar'", "varchar")); + case "char(25)": + return Optional.of(new SetColumnTypeSetup("char(25)", "'shorten-char'", "varchar")); + case "char(20)": + return Optional.of(new SetColumnTypeSetup("char(20)", "'char-to-varchar'", "varchar")); + case "varchar": + return Optional.of(new SetColumnTypeSetup("varchar", "'varchar-to-char'", "varchar")); + default: + return Optional.empty(); + } + } + + @Test(dataProvider = "setColumnTypesDataProvider") + @Override + public void testSetColumnTypes(SetColumnTypeSetup setup) + { + String tableName = "test_set_column_type_" + System.currentTimeMillis(); + assertUpdate("CREATE TABLE " + tableName + "(col " + setup.sourceColumnType() + ", col2 int not null) WITH (engine='mergetree', order_by=ARRAY['col2'])"); + query("insert into " + tableName + "(col, col2) values(CAST(" + setup.sourceValueLiteral() + " AS " + setup.sourceColumnType() + "), 2)"); + Runnable setColumnType = () -> assertUpdate("ALTER TABLE " + tableName + " ALTER COLUMN col SET DATA TYPE " + setup.newColumnType()); + if (setup.unsupportedType()) { + assertThatThrownBy(setColumnType::run) + .satisfies(this::verifySetColumnTypeFailurePermissible); + return; + } + setColumnType.run(); + + assertEquals(getColumnType(tableName, "col"), setup.newColumnType()); + assertThat(query("SELECT col FROM " + tableName)) + .skippingTypesCheck() + .matches("SELECT " + setup.newValueLiteral()); + } } diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index 8956a47cb880f..37cc7fa3ec8f4 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -2890,7 +2890,7 @@ protected void verifySetColumnTypeFailurePermissible(Throwable e) throw new AssertionError("Unexpected set column type failure", e); } - private String getColumnType(String tableName, String columnName) + protected String getColumnType(String tableName, String columnName) { return (String) computeScalar(format("SELECT data_type FROM information_schema.columns WHERE table_schema = CURRENT_SCHEMA AND table_name = '%s' AND column_name = '%s'", tableName,