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 6028d62123ff..783008905365 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 @@ -69,6 +69,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Verify.verify; +import static io.trino.plugin.clickhouse.ClickHouseSessionProperties.isMapStringAsVarchar; import static io.trino.plugin.clickhouse.ClickHouseTableProperties.SAMPLE_BY_PROPERTY; import static io.trino.plugin.jdbc.DecimalConfig.DecimalMapping.ALLOW_OVERFLOW; import static io.trino.plugin.jdbc.DecimalSessionSessionProperties.getDecimalDefaultScale; @@ -125,7 +126,6 @@ public class ClickHouseClient { static final int CLICKHOUSE_MAX_DECIMAL_PRECISION = 76; - private final boolean mapStringAsVarchar; private final AggregateFunctionRewriter aggregateFunctionRewriter; private final Type uuidType; @@ -134,13 +134,10 @@ public ClickHouseClient( BaseJdbcConfig config, ConnectionFactory connectionFactory, TypeManager typeManager, - ClickHouseConfig clickHouseConfig, IdentifierMapping identifierMapping) { super(config, "\"", connectionFactory, identifierMapping); this.uuidType = typeManager.getType(new TypeSignature(StandardTypes.UUID)); - // TODO (https://github.com/trinodb/trino/issues/7102) define session property - this.mapStringAsVarchar = clickHouseConfig.isMapStringAsVarchar(); JdbcTypeHandle bigintTypeHandle = new JdbcTypeHandle(Types.BIGINT, Optional.of("bigint"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); this.aggregateFunctionRewriter = new AggregateFunctionRewriter<>( this::quoted, @@ -384,7 +381,7 @@ public Optional toColumnMapping(ConnectorSession session, Connect case "FixedString": // FixedString(n) case "String": - if (mapStringAsVarchar) { + if (isMapStringAsVarchar(session)) { return Optional.of(ColumnMapping.sliceMapping( createUnboundedVarcharType(), varcharReadFunction(createUnboundedVarcharType()), diff --git a/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClientModule.java b/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClientModule.java index 962e03c8e93f..1a538cda6dd2 100644 --- a/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClientModule.java +++ b/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClientModule.java @@ -28,6 +28,7 @@ import io.trino.plugin.jdbc.credential.CredentialProvider; import ru.yandex.clickhouse.ClickHouseDriver; +import static io.trino.plugin.jdbc.JdbcModule.bindSessionPropertiesProvider; import static io.trino.plugin.jdbc.JdbcModule.bindTablePropertiesProvider; public class ClickHouseClientModule @@ -37,6 +38,7 @@ public class ClickHouseClientModule public void configure(Binder binder) { ConfigBinder.configBinder(binder).bindConfig(ClickHouseConfig.class); + bindSessionPropertiesProvider(binder, ClickHouseSessionProperties.class); binder.bind(JdbcClient.class).annotatedWith(ForBaseJdbc.class).to(ClickHouseClient.class).in(Scopes.SINGLETON); bindTablePropertiesProvider(binder, ClickHouseTableProperties.class); binder.install(new DecimalModule()); diff --git a/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseSessionProperties.java b/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseSessionProperties.java new file mode 100644 index 000000000000..5e9a46aa0012 --- /dev/null +++ b/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseSessionProperties.java @@ -0,0 +1,55 @@ +/* + * 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.clickhouse; + +import com.google.common.collect.ImmutableList; +import io.trino.plugin.base.session.SessionPropertiesProvider; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.session.PropertyMetadata; + +import javax.inject.Inject; + +import java.util.List; + +import static io.trino.spi.session.PropertyMetadata.booleanProperty; + +public class ClickHouseSessionProperties + implements SessionPropertiesProvider +{ + public static final String MAP_STRING_AS_VARCHAR = "map_string_as_varchar"; + + private final List> sessionProperties; + + @Inject + public ClickHouseSessionProperties(ClickHouseConfig clickHouseConfig) + { + sessionProperties = ImmutableList.of( + booleanProperty( + MAP_STRING_AS_VARCHAR, + "Map ClickHouse String and FixedString as varchar instead of varbinary", + clickHouseConfig.isMapStringAsVarchar(), + false)); + } + + @Override + public List> getSessionProperties() + { + return sessionProperties; + } + + public static boolean isMapStringAsVarchar(ConnectorSession session) + { + return session.getProperty(MAP_STRING_AS_VARCHAR, Boolean.class); + } +} diff --git a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/ClickHouseQueryRunner.java b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/ClickHouseQueryRunner.java index 1233ae8b8f2f..7c54fbe0ee03 100644 --- a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/ClickHouseQueryRunner.java +++ b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/ClickHouseQueryRunner.java @@ -33,7 +33,7 @@ public final class ClickHouseQueryRunner { - private static final String TPCH_SCHEMA = "tpch"; + public static final String TPCH_SCHEMA = "tpch"; private ClickHouseQueryRunner() {} diff --git a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseTypeMapping.java b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseTypeMapping.java index f557271ea086..d1c0dfcc35e9 100644 --- a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseTypeMapping.java +++ b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseTypeMapping.java @@ -37,6 +37,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; +import static io.trino.plugin.clickhouse.ClickHouseQueryRunner.TPCH_SCHEMA; import static io.trino.plugin.clickhouse.ClickHouseQueryRunner.createClickHouseQueryRunner; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DateType.DATE; @@ -48,7 +49,9 @@ import static io.trino.spi.type.TimestampType.createTimestampType; import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.VarbinaryType.VARBINARY; +import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; +import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.String.format; import static java.time.ZoneOffset.UTC; @@ -238,6 +241,21 @@ public void testClickHouseChar() .addRoundTrip("Nullable(char(10))", "'text_a'", VARBINARY, "to_utf8('text_a')") .addRoundTrip("Nullable(char(1))", "'😂'", VARBINARY, "to_utf8('😂')") .execute(getQueryRunner(), clickhouseCreateAndInsert("tpch.test_char")); + + // Set map_string_as_varchar session property as true + SqlDataTypeTest.create() + // plain + .addRoundTrip("char(10)", "'text_a'", VARCHAR, "CAST('text_a' AS varchar)") + .addRoundTrip("char(255)", "'text_b'", VARCHAR, "CAST('text_b' AS varchar)") + .addRoundTrip("char(5)", "'攻殻機動隊'", VARCHAR, "CAST('攻殻機動隊' AS varchar)") + .addRoundTrip("char(32)", "'攻殻機動隊'", VARCHAR, "CAST('攻殻機動隊' AS varchar)") + .addRoundTrip("char(1)", "'😂'", VARCHAR, "CAST('😂' AS varchar)") + .addRoundTrip("char(77)", "'Ну, погоди!'", VARCHAR, "CAST('Ну, погоди!' AS varchar)") + // nullable + .addRoundTrip("Nullable(char(10))", "NULL", VARCHAR, "CAST(NULL AS varchar)") + .addRoundTrip("Nullable(char(10))", "'text_a'", VARCHAR, "CAST('text_a' AS varchar)") + .addRoundTrip("Nullable(char(1))", "'😂'", VARCHAR, "CAST('😂' AS varchar)") + .execute(getQueryRunner(), mapStringAsVarcharSession(), clickhouseCreateAndInsert("tpch.test_char")); } @Test @@ -252,6 +270,17 @@ public void testClickHouseFixedString() .addRoundTrip("Nullable(FixedString(10))", "'c12345678b'", VARBINARY, "to_utf8('c12345678b')") .addRoundTrip("Nullable(FixedString(10))", "'c123'", VARBINARY, "to_utf8('c123\0\0\0\0\0\0')") .execute(getQueryRunner(), clickhouseCreateAndInsert("tpch.test_fixed_string")); + + // Set map_string_as_varchar session property as true + SqlDataTypeTest.create() + // plain + .addRoundTrip("FixedString(10)", "'c12345678b'", VARCHAR, "CAST('c12345678b' AS varchar)") + .addRoundTrip("FixedString(10)", "'c123'", VARCHAR, "CAST('c123\0\0\0\0\0\0' AS varchar)") + // nullable + .addRoundTrip("Nullable(FixedString(10))", "NULL", VARCHAR, "CAST(NULL AS varchar)") + .addRoundTrip("Nullable(FixedString(10))", "'c12345678b'", VARCHAR, "CAST('c12345678b' AS varchar)") + .addRoundTrip("Nullable(FixedString(10))", "'c123'", VARCHAR, "CAST('c123\0\0\0\0\0\0' AS varchar)") + .execute(getQueryRunner(), mapStringAsVarcharSession(), clickhouseCreateAndInsert("tpch.test_fixed_string")); } @Test @@ -265,34 +294,65 @@ public void testTrinoChar() .addRoundTrip("char(32)", "'攻殻機動隊'", VARBINARY, "to_utf8('攻殻機動隊')") .addRoundTrip("char(1)", "'😂'", VARBINARY, "to_utf8('😂')") .addRoundTrip("char(77)", "'Ну, погоди!'", VARBINARY, "to_utf8('Ну, погоди!')") - .execute(getQueryRunner(), trinoCreateAsSelect("test_char")); + .execute(getQueryRunner(), trinoCreateAsSelect("test_char")) + .execute(getQueryRunner(), trinoCreateAsSelect(mapStringAsVarcharSession(), "test_char")); + + // Set map_string_as_varchar session property as true + SqlDataTypeTest.create() + .addRoundTrip("char(10)", "NULL", VARCHAR, "CAST(NULL AS varchar)") + .addRoundTrip("char(10)", "'text_a'", VARCHAR, "CAST('text_a' AS varchar)") + .addRoundTrip("char(255)", "'text_b'", VARCHAR, "CAST('text_b' AS varchar)") + .addRoundTrip("char(5)", "'攻殻機動隊'", VARCHAR, "CAST('攻殻機動隊' AS varchar)") + .addRoundTrip("char(32)", "'攻殻機動隊'", VARCHAR, "CAST('攻殻機動隊' AS varchar)") + .addRoundTrip("char(1)", "'😂'", VARCHAR, "CAST('😂' AS varchar)") + .addRoundTrip("char(77)", "'Ну, погоди!'", VARCHAR, "CAST('Ну, погоди!' AS varchar)") + .execute(getQueryRunner(), mapStringAsVarcharSession(), trinoCreateAsSelect("test_char")) + .execute(getQueryRunner(), mapStringAsVarcharSession(), trinoCreateAsSelect(mapStringAsVarcharSession(), "test_char")); } @Test public void testClickHouseVarchar() { + // TODO add more test cases // ClickHouse varchar is String, which is arbitrary bytes SqlDataTypeTest.create() - // TODO add more test cases // plain .addRoundTrip("varchar(30)", "'Piękna łąka w 東京都'", VARBINARY, "to_utf8('Piękna łąka w 東京都')") // nullable .addRoundTrip("Nullable(varchar(30))", "NULL", VARBINARY, "CAST(NULL AS varbinary)") .addRoundTrip("Nullable(varchar(30))", "'Piękna łąka w 東京都'", VARBINARY, "to_utf8('Piękna łąka w 東京都')") .execute(getQueryRunner(), clickhouseCreateAndInsert("tpch.test_varchar")); + + // Set map_string_as_varchar session property as true + SqlDataTypeTest.create() + // plain + .addRoundTrip("varchar(30)", "'Piękna łąka w 東京都'", VARCHAR, "CAST('Piękna łąka w 東京都' AS varchar)") + // nullable + .addRoundTrip("Nullable(varchar(30))", "NULL", VARCHAR, "CAST(NULL AS varchar)") + .addRoundTrip("Nullable(varchar(30))", "'Piękna łąka w 東京都'", VARCHAR, "CAST('Piękna łąka w 東京都' AS varchar)") + .execute(getQueryRunner(), mapStringAsVarcharSession(), clickhouseCreateAndInsert("tpch.test_varchar")); } @Test public void testClickHouseString() { + // TODO add more test cases SqlDataTypeTest.create() - // TODO add more test cases // plain .addRoundTrip("String", "'Piękna łąka w 東京都'", VARBINARY, "to_utf8('Piękna łąka w 東京都')") // nullable .addRoundTrip("Nullable(String)", "NULL", VARBINARY, "CAST(NULL AS varbinary)") .addRoundTrip("Nullable(String)", "'Piękna łąka w 東京都'", VARBINARY, "to_utf8('Piękna łąka w 東京都')") .execute(getQueryRunner(), clickhouseCreateAndInsert("tpch.test_varchar")); + + // Set map_string_as_varchar session property as true + SqlDataTypeTest.create() + // plain + .addRoundTrip("String", "'Piękna łąka w 東京都'", VARCHAR, "CAST('Piękna łąka w 東京都' AS varchar)") + // nullable + .addRoundTrip("Nullable(String)", "NULL", VARCHAR, "CAST(NULL AS varchar)") + .addRoundTrip("Nullable(String)", "'Piękna łąka w 東京都'", VARCHAR, "CAST('Piękna łąka w 東京都' AS varchar)") + .execute(getQueryRunner(), mapStringAsVarcharSession(), clickhouseCreateAndInsert("tpch.test_varchar")); } @Test @@ -301,7 +361,15 @@ public void testTrinoVarchar() SqlDataTypeTest.create() .addRoundTrip("varchar(30)", "NULL", VARBINARY, "CAST(NULL AS varbinary)") .addRoundTrip("varchar(30)", "'Piękna łąka w 東京都'", VARBINARY, "to_utf8('Piękna łąka w 東京都')") - .execute(getQueryRunner(), trinoCreateAsSelect("test_varchar")); + .execute(getQueryRunner(), trinoCreateAsSelect("test_varchar")) + .execute(getQueryRunner(), trinoCreateAsSelect(mapStringAsVarcharSession(), "test_varchar")); + + // Set map_string_as_varchar session property as true + SqlDataTypeTest.create() + .addRoundTrip("varchar(30)", "NULL", VARCHAR, "CAST(NULL AS varchar)") + .addRoundTrip("varchar(30)", "'Piękna łąka w 東京都'", VARCHAR, "CAST('Piękna łąka w 東京都' AS varchar)") + .execute(getQueryRunner(), mapStringAsVarcharSession(), trinoCreateAsSelect("test_varchar")) + .execute(getQueryRunner(), mapStringAsVarcharSession(), trinoCreateAsSelect(mapStringAsVarcharSession(), "test_varchar")); } @Test @@ -425,6 +493,15 @@ public void testIp() // TODO add test with IPADDRESS written from Trino } + private static Session mapStringAsVarcharSession() + { + return testSessionBuilder() + .setCatalog("clickhouse") + .setSchema(TPCH_SCHEMA) + .setCatalogSessionProperty("clickhouse", "map_string_as_varchar", "true") + .build(); + } + private DataSetup trinoCreateAsSelect(String tableNamePrefix) { return trinoCreateAsSelect(getSession(), tableNamePrefix);