Skip to content

Commit

Permalink
Add map_string_as_varchar session property in ClickHouse
Browse files Browse the repository at this point in the history
  • Loading branch information
ebyhr committed Jan 14, 2022
1 parent 0870114 commit 1f3653a
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -125,7 +126,6 @@ public class ClickHouseClient
{
static final int CLICKHOUSE_MAX_DECIMAL_PRECISION = 76;

private final boolean mapStringAsVarchar;
private final AggregateFunctionRewriter<JdbcExpression> aggregateFunctionRewriter;
private final Type uuidType;

Expand All @@ -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,
Expand Down Expand Up @@ -384,7 +381,7 @@ public Optional<ColumnMapping> toColumnMapping(ConnectorSession session, Connect

case "FixedString": // FixedString(n)
case "String":
if (mapStringAsVarchar) {
if (isMapStringAsVarchar(session)) {
return Optional.of(ColumnMapping.sliceMapping(
createUnboundedVarcharType(),
varcharReadFunction(createUnboundedVarcharType()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PropertyMetadata<?>> 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<PropertyMetadata<?>> getSessionProperties()
{
return sessionProperties;
}

public static boolean isMapStringAsVarchar(ConnectorSession session)
{
return session.getProperty(MAP_STRING_AS_VARCHAR, Boolean.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

public final class ClickHouseQueryRunner
{
private static final String TPCH_SCHEMA = "tpch";
public static final String TPCH_SCHEMA = "tpch";

private ClickHouseQueryRunner() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 1f3653a

Please sign in to comment.