Skip to content

Commit e77b31d

Browse files
committed
Add char->varchar type coercion in Delta CTAS
Similar to trinodb#21515, extend type coercion support to char type.
1 parent 3482e5c commit e77b31d

File tree

2 files changed

+137
-7
lines changed

2 files changed

+137
-7
lines changed

plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
import io.trino.spi.statistics.TableStatistics;
133133
import io.trino.spi.statistics.TableStatisticsMetadata;
134134
import io.trino.spi.type.ArrayType;
135+
import io.trino.spi.type.CharType;
135136
import io.trino.spi.type.DecimalType;
136137
import io.trino.spi.type.FixedWidthType;
137138
import io.trino.spi.type.HyperLogLogType;
@@ -663,6 +664,9 @@ private Type coerceType(Type type)
663664
if (type instanceof TimestampType) {
664665
return TIMESTAMP_MICROS;
665666
}
667+
if (type instanceof CharType) {
668+
return VARCHAR;
669+
}
666670
if (type instanceof ArrayType arrayType) {
667671
return new ArrayType(coerceType(arrayType.getElementType()));
668672
}

plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,9 @@ private static String transactionConflictErrors()
215215
protected Optional<DataMappingTestSetup> filterCaseSensitiveDataMappingTestData(DataMappingTestSetup dataMappingTestSetup)
216216
{
217217
String typeName = dataMappingTestSetup.getTrinoTypeName();
218-
if (typeName.equals("char(1)")) {
219-
return Optional.of(dataMappingTestSetup.asUnsupported());
218+
if (typeName.equals("char(3)")) {
219+
// Use explicitly padded literal in char mapping test due to whitespace padding on coercion to varchar
220+
return Optional.of(new DataMappingTestSetup(typeName, "'ab '", dataMappingTestSetup.getHighValueLiteral()));
220221
}
221222
return Optional.of(dataMappingTestSetup);
222223
}
@@ -227,10 +228,13 @@ protected Optional<DataMappingTestSetup> filterDataMappingSmokeTestData(DataMapp
227228
String typeName = dataMappingTestSetup.getTrinoTypeName();
228229
if (typeName.equals("time") ||
229230
typeName.equals("time(6)") ||
230-
typeName.equals("timestamp(6) with time zone") ||
231-
typeName.equals("char(3)")) {
231+
typeName.equals("timestamp(6) with time zone")) {
232232
return Optional.of(dataMappingTestSetup.asUnsupported());
233233
}
234+
if (typeName.equals("char(3)")) {
235+
// Use explicitly padded literal in char mapping test due to whitespace padding on coercion to varchar
236+
return Optional.of(new DataMappingTestSetup(typeName, "'ab '", dataMappingTestSetup.getHighValueLiteral()));
237+
}
234238
return Optional.of(dataMappingTestSetup);
235239
}
236240

@@ -551,9 +555,23 @@ public void testRenameColumnName()
551555
@Override
552556
public void testCharVarcharComparison()
553557
{
554-
// Delta Lake doesn't have a char type
555-
assertThatThrownBy(super::testCharVarcharComparison)
556-
.hasStackTraceContaining("Unsupported type: char(3)");
558+
// with char->varchar coercion on table creation, this is essentially varchar/varchar comparison
559+
try (TestTable table = new TestTable(
560+
getQueryRunner()::execute,
561+
"test_char_varchar",
562+
"(k, v) AS VALUES" +
563+
" (-1, CAST(NULL AS CHAR(3))), " +
564+
" (3, CAST(' ' AS CHAR(3)))," +
565+
" (6, CAST('x ' AS CHAR(3)))")) {
566+
// varchar of length shorter than column's length
567+
assertThat(query("SELECT k, v FROM " + table.getName() + " WHERE v = CAST(' ' AS varchar(2))")).returnsEmptyResult();
568+
// varchar of length longer than column's length
569+
assertThat(query("SELECT k, v FROM " + table.getName() + " WHERE v = CAST(' ' AS varchar(4))")).returnsEmptyResult();
570+
// value that's not all-spaces
571+
assertThat(query("SELECT k, v FROM " + table.getName() + " WHERE v = CAST('x ' AS varchar(2))")).returnsEmptyResult();
572+
// exact match
573+
assertQuery("SELECT k, v FROM " + table.getName() + " WHERE v = CAST(' ' AS varchar(3))", "VALUES (3, ' ')");
574+
}
557575
}
558576

559577
@Test
@@ -3960,6 +3978,12 @@ public void testTypeCoercionOnCreateTable()
39603978
testTimestampCoercionOnCreateTable("TIMESTAMP '1969-12-31 23:59:59.9999995'", "TIMESTAMP '1970-01-01 00:00:00.000000'");
39613979
testTimestampCoercionOnCreateTable("TIMESTAMP '1969-12-31 23:59:59.999999499999'", "TIMESTAMP '1969-12-31 23:59:59.999999'");
39623980
testTimestampCoercionOnCreateTable("TIMESTAMP '1969-12-31 23:59:59.9999994'", "TIMESTAMP '1969-12-31 23:59:59.999999'");
3981+
testCharCoercionOnCreateTable("CHAR 'ab '", "'ab '");
3982+
testCharCoercionOnCreateTable("CHAR 'A'", "'A'");
3983+
testCharCoercionOnCreateTable("CHAR 'é'", "'é'");
3984+
testCharCoercionOnCreateTable("CHAR 'A '", "'A '");
3985+
testCharCoercionOnCreateTable("CHAR ' A'", "' A'");
3986+
testCharCoercionOnCreateTable("CHAR 'ABc'", "'ABc'");
39633987
}
39643988

39653989
private void testTimestampCoercionOnCreateTable(@Language("SQL") String actualValue, @Language("SQL") String expectedValue)
@@ -3975,6 +3999,18 @@ private void testTimestampCoercionOnCreateTable(@Language("SQL") String actualVa
39753999
}
39764000
}
39774001

4002+
private void testCharCoercionOnCreateTable(@Language("SQL") String actualValue, @Language("SQL") String expectedValue)
4003+
{
4004+
try (TestTable testTable = new TestTable(
4005+
getQueryRunner()::execute,
4006+
"test_char_coercion_on_create_table",
4007+
"(vch VARCHAR)")) {
4008+
assertUpdate("INSERT INTO " + testTable.getName() + " VALUES (" + actualValue + ")", 1);
4009+
assertThat(getColumnType(testTable.getName(), "vch")).isEqualTo("varchar");
4010+
assertQuery("SELECT * FROM " + testTable.getName(), "VALUES " + expectedValue);
4011+
}
4012+
}
4013+
39784014
@Test
39794015
public void testTypeCoercionOnCreateTableAsSelect()
39804016
{
@@ -4004,6 +4040,12 @@ public void testTypeCoercionOnCreateTableAsSelect()
40044040
testTimestampCoercionOnCreateTableAsSelect("TIMESTAMP '1969-12-31 23:59:59.9999995'", "TIMESTAMP '1970-01-01 00:00:00.000000'");
40054041
testTimestampCoercionOnCreateTableAsSelect("TIMESTAMP '1969-12-31 23:59:59.999999499999'", "TIMESTAMP '1969-12-31 23:59:59.999999'");
40064042
testTimestampCoercionOnCreateTableAsSelect("TIMESTAMP '1969-12-31 23:59:59.9999994'", "TIMESTAMP '1969-12-31 23:59:59.999999'");
4043+
testCharCoercionOnCreateTableAsSelect("CHAR 'ab '", "'ab '");
4044+
testCharCoercionOnCreateTableAsSelect("CHAR 'A'", "'A'");
4045+
testCharCoercionOnCreateTableAsSelect("CHAR 'é'", "'é'");
4046+
testCharCoercionOnCreateTableAsSelect("CHAR 'A '", "'A '");
4047+
testCharCoercionOnCreateTableAsSelect("CHAR ' A'", "' A'");
4048+
testCharCoercionOnCreateTableAsSelect("CHAR 'ABc'", "'ABc'");
40074049
}
40084050

40094051
private void testTimestampCoercionOnCreateTableAsSelect(@Language("SQL") String actualValue, @Language("SQL") String expectedValue)
@@ -4018,6 +4060,17 @@ private void testTimestampCoercionOnCreateTableAsSelect(@Language("SQL") String
40184060
}
40194061
}
40204062

4063+
private void testCharCoercionOnCreateTableAsSelect(@Language("SQL") String actualValue, @Language("SQL") String expectedValue)
4064+
{
4065+
try (TestTable testTable = new TestTable(
4066+
getQueryRunner()::execute,
4067+
"test_char_coercion_on_create_table_as_select",
4068+
"AS SELECT %s vch".formatted(actualValue))) {
4069+
assertThat(getColumnType(testTable.getName(), "vch")).isEqualTo("varchar");
4070+
assertQuery("SELECT * FROM " + testTable.getName(), "VALUES " + expectedValue);
4071+
}
4072+
}
4073+
40214074
@Test
40224075
public void testTypeCoercionOnCreateTableAsSelectWithNoData()
40234076
{
@@ -4047,6 +4100,12 @@ public void testTypeCoercionOnCreateTableAsSelectWithNoData()
40474100
testTimestampCoercionOnCreateTableAsSelectWithNoData("TIMESTAMP '1969-12-31 23:59:59.9999995'");
40484101
testTimestampCoercionOnCreateTableAsSelectWithNoData("TIMESTAMP '1969-12-31 23:59:59.999999499999'");
40494102
testTimestampCoercionOnCreateTableAsSelectWithNoData("TIMESTAMP '1969-12-31 23:59:59.9999994'");
4103+
testCharCoercionOnCreateTableAsSelectWithNoData("CHAR 'ab '");
4104+
testCharCoercionOnCreateTableAsSelectWithNoData("CHAR 'A'");
4105+
testCharCoercionOnCreateTableAsSelectWithNoData("CHAR 'é'");
4106+
testCharCoercionOnCreateTableAsSelectWithNoData("CHAR 'A '");
4107+
testCharCoercionOnCreateTableAsSelectWithNoData("CHAR ' A'");
4108+
testCharCoercionOnCreateTableAsSelectWithNoData("CHAR 'ABc'");
40504109
}
40514110

40524111
private void testTimestampCoercionOnCreateTableAsSelectWithNoData(@Language("SQL") String actualValue)
@@ -4060,6 +4119,16 @@ private void testTimestampCoercionOnCreateTableAsSelectWithNoData(@Language("SQL
40604119
}
40614120
}
40624121

4122+
private void testCharCoercionOnCreateTableAsSelectWithNoData(@Language("SQL") String actualValue)
4123+
{
4124+
try (TestTable testTable = new TestTable(
4125+
getQueryRunner()::execute,
4126+
"test_char_coercion_on_create_table_as_select_with_no_data",
4127+
"AS SELECT %s vch WITH NO DATA".formatted(actualValue))) {
4128+
assertThat(getColumnType(testTable.getName(), "vch")).isEqualTo("varchar");
4129+
}
4130+
}
4131+
40634132
@Test
40644133
public void testTypeCoercionOnCreateTableAsWithRowType()
40654134
{
@@ -4089,6 +4158,13 @@ public void testTypeCoercionOnCreateTableAsWithRowType()
40894158
testTimestampCoercionOnCreateTableAsWithRowType("TIMESTAMP '1969-12-31 23:59:59.9999995'", "TIMESTAMP '1970-01-01 00:00:00.000000'");
40904159
testTimestampCoercionOnCreateTableAsWithRowType("TIMESTAMP '1969-12-31 23:59:59.999999499999'", "TIMESTAMP '1969-12-31 23:59:59.999999'");
40914160
testTimestampCoercionOnCreateTableAsWithRowType("TIMESTAMP '1969-12-31 23:59:59.9999994'", "TIMESTAMP '1969-12-31 23:59:59.999999'");
4161+
testCharCoercionOnCreateTableAsWithRowType("CHAR 'ab '", "CHAR(3)", "'ab '");
4162+
testCharCoercionOnCreateTableAsWithRowType("CHAR 'A'", "CHAR(3)", "'A '");
4163+
testCharCoercionOnCreateTableAsWithRowType("CHAR 'A'", "CHAR(1)", "'A'");
4164+
testCharCoercionOnCreateTableAsWithRowType("CHAR 'é'", "CHAR(3)", "'é '");
4165+
testCharCoercionOnCreateTableAsWithRowType("CHAR 'A '", "CHAR(3)", "'A '");
4166+
testCharCoercionOnCreateTableAsWithRowType("CHAR ' A'", "CHAR(3)", "' A '");
4167+
testCharCoercionOnCreateTableAsWithRowType("CHAR 'ABc'", "CHAR(3)", "'ABc'");
40924168
}
40934169

40944170
private void testTimestampCoercionOnCreateTableAsWithRowType(@Language("SQL") String actualValue, @Language("SQL") String expectedValue)
@@ -4105,6 +4181,19 @@ private void testTimestampCoercionOnCreateTableAsWithRowType(@Language("SQL") St
41054181
}
41064182
}
41074183

4184+
private void testCharCoercionOnCreateTableAsWithRowType(@Language("SQL") String actualValue, @Language("SQL") String actualTypeLiteral, @Language("SQL") String expectedValue)
4185+
{
4186+
try (TestTable testTable = new TestTable(
4187+
getQueryRunner()::execute,
4188+
"test_char_coercion_on_create_table_as_with_row_type",
4189+
"AS SELECT CAST(row(%s) AS row(value %s)) vch".formatted(actualValue, actualTypeLiteral))) {
4190+
assertThat(getColumnType(testTable.getName(), "vch")).isEqualTo("row(value varchar)");
4191+
assertThat(query("SELECT vch.value FROM " + testTable.getName()))
4192+
.skippingTypesCheck()
4193+
.matches("VALUES " + expectedValue);
4194+
}
4195+
}
4196+
41084197
@Test
41094198
public void testTypeCoercionOnCreateTableAsWithArrayType()
41104199
{
@@ -4134,6 +4223,12 @@ public void testTypeCoercionOnCreateTableAsWithArrayType()
41344223
testTimestampCoercionOnCreateTableAsWithArrayType("TIMESTAMP '1969-12-31 23:59:59.9999995'", "TIMESTAMP '1970-01-01 00:00:00.000000'");
41354224
testTimestampCoercionOnCreateTableAsWithArrayType("TIMESTAMP '1969-12-31 23:59:59.999999499999'", "TIMESTAMP '1969-12-31 23:59:59.999999'");
41364225
testTimestampCoercionOnCreateTableAsWithArrayType("TIMESTAMP '1969-12-31 23:59:59.9999994'", "TIMESTAMP '1969-12-31 23:59:59.999999'");
4226+
testCharCoercionOnCreateTableAsWithArrayType("CHAR 'ab '", "'ab '");
4227+
testCharCoercionOnCreateTableAsWithArrayType("CHAR 'A'", "'A'");
4228+
testCharCoercionOnCreateTableAsWithArrayType("CHAR 'é'", "'é'");
4229+
testCharCoercionOnCreateTableAsWithArrayType("CHAR 'A '", "'A '");
4230+
testCharCoercionOnCreateTableAsWithArrayType("CHAR ' A'", "' A'");
4231+
testCharCoercionOnCreateTableAsWithArrayType("CHAR 'ABc'", "'ABc'");
41374232
}
41384233

41394234
private void testTimestampCoercionOnCreateTableAsWithArrayType(@Language("SQL") String actualValue, @Language("SQL") String expectedValue)
@@ -4149,6 +4244,18 @@ private void testTimestampCoercionOnCreateTableAsWithArrayType(@Language("SQL")
41494244
assertTimestampNtzFeature(testTable.getName());
41504245
}
41514246
}
4247+
private void testCharCoercionOnCreateTableAsWithArrayType(@Language("SQL") String actualValue, @Language("SQL") String expectedValue)
4248+
{
4249+
try (TestTable testTable = new TestTable(
4250+
getQueryRunner()::execute,
4251+
"test_char_coercion_on_create_table_as_with_array_type",
4252+
"AS SELECT array[%s] vch".formatted(actualValue))) {
4253+
assertThat(getColumnType(testTable.getName(), "vch")).isEqualTo("array(varchar)");
4254+
assertThat(query("SELECT vch[1] FROM " + testTable.getName()))
4255+
.skippingTypesCheck()
4256+
.matches("VALUES " + expectedValue);
4257+
}
4258+
}
41524259

41534260
@Test
41544261
public void testTypeCoercionOnCreateTableAsWithMapType()
@@ -4179,6 +4286,12 @@ public void testTypeCoercionOnCreateTableAsWithMapType()
41794286
testTimestampCoercionOnCreateTableAsWithMapType("TIMESTAMP '1969-12-31 23:59:59.9999995'", "TIMESTAMP '1970-01-01 00:00:00.000000'");
41804287
testTimestampCoercionOnCreateTableAsWithMapType("TIMESTAMP '1969-12-31 23:59:59.999999499999'", "TIMESTAMP '1969-12-31 23:59:59.999999'");
41814288
testTimestampCoercionOnCreateTableAsWithMapType("TIMESTAMP '1969-12-31 23:59:59.9999994'", "TIMESTAMP '1969-12-31 23:59:59.999999'");
4289+
testCharCoercionOnCreateTableAsWithMapType("CHAR 'ab '", "'ab '");
4290+
testCharCoercionOnCreateTableAsWithMapType("CHAR 'A'", "'A'");
4291+
testCharCoercionOnCreateTableAsWithMapType("CHAR 'é'", "'é'");
4292+
testCharCoercionOnCreateTableAsWithMapType("CHAR 'A '", "'A '");
4293+
testCharCoercionOnCreateTableAsWithMapType("CHAR ' A'", "' A'");
4294+
testCharCoercionOnCreateTableAsWithMapType("CHAR 'ABc'", "'ABc'");
41824295
}
41834296

41844297
private void testTimestampCoercionOnCreateTableAsWithMapType(@Language("SQL") String actualValue, @Language("SQL") String expectedValue)
@@ -4195,6 +4308,19 @@ private void testTimestampCoercionOnCreateTableAsWithMapType(@Language("SQL") St
41954308
}
41964309
}
41974310

4311+
private void testCharCoercionOnCreateTableAsWithMapType(@Language("SQL") String actualValue, @Language("SQL") String expectedValue)
4312+
{
4313+
try (TestTable testTable = new TestTable(
4314+
getQueryRunner()::execute,
4315+
"test_char_coercion_on_create_table_as_with_map_type",
4316+
"AS SELECT map(array[%1$s], array[%1$s]) vch".formatted(actualValue))) {
4317+
assertThat(getColumnType(testTable.getName(), "vch")).isEqualTo("map(varchar, varchar)");
4318+
assertThat(query("SELECT * FROM " + testTable.getName()))
4319+
.skippingTypesCheck()
4320+
.matches("SELECT map(array[%1$s], array[%1$s])".formatted(expectedValue));
4321+
}
4322+
}
4323+
41984324
private void assertTimestampNtzFeature(String tableName)
41994325
{
42004326
assertThat(query("SELECT * FROM \"" + tableName + "$properties\""))

0 commit comments

Comments
 (0)