diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index 796e64b35f64..c0352f8d88f4 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -297,10 +297,13 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DateTimeEncoding.unpackMillisUtc; import static io.trino.spi.type.DateType.DATE; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.TimeType.TIME_MICROS; import static io.trino.spi.type.TimestampType.TIMESTAMP_MICROS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MICROS; import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_MILLISECOND; +import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.Math.floorDiv; import static java.lang.String.format; @@ -953,6 +956,9 @@ public Optional getSupportedType(ConnectorSession sessio private io.trino.spi.type.Type coerceType(io.trino.spi.type.Type type) { + if (type == TINYINT || type == SMALLINT) { + return INTEGER; + } if (type instanceof TimestampWithTimeZoneType) { return TIMESTAMP_TZ_MICROS; } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java index 728de653a232..31a1f9bc0250 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java @@ -5185,11 +5185,6 @@ protected TestTable createTableWithDefaultColumns() protected Optional filterDataMappingSmokeTestData(DataMappingTestSetup dataMappingTestSetup) { String typeName = dataMappingTestSetup.getTrinoTypeName(); - if (typeName.equals("tinyint") - || typeName.equals("smallint")) { - // These types are not supported by Iceberg - return Optional.of(dataMappingTestSetup.asUnsupported()); - } if (typeName.equals("char(3)")) { // Use explicitly padded literal in char mapping test due to whitespace padding on coercion to varchar return Optional.of(new DataMappingTestSetup(typeName, "'ab '", dataMappingTestSetup.getHighValueLiteral())); @@ -8169,6 +8164,8 @@ protected Optional filterTypeCoercionOnCreateTableAsSelec private List typeCoercionOnCreateTableAsSelectData() { return ImmutableList.builder() + .add(new TypeCoercionTestSetup("TINYINT '127'", "integer", "INTEGER '127'")) + .add(new TypeCoercionTestSetup("SMALLINT '32767'", "integer", "INTEGER '32767'")) .add(new TypeCoercionTestSetup("TIMESTAMP '1970-01-01 00:00:00'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.000000'")) .add(new TypeCoercionTestSetup("TIMESTAMP '1970-01-01 00:00:00.9'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.900000'")) .add(new TypeCoercionTestSetup("TIMESTAMP '1970-01-01 00:00:00.56'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.560000'")) @@ -8230,6 +8227,20 @@ private List typeCoercionOnCreateTableAsSelectData() // TODO Add test case for MAP type with ARRAY keys once https://github.com/trinodb/trino/issues/1146 is resolved .add(new TypeCoercionTestSetup("CAST(ROW('a') AS ROW(x CHAR))", "row(x varchar)", "CAST(ROW('a') AS ROW(x VARCHAR))")) .add(new TypeCoercionTestSetup("CAST(ROW(ROW('a')) AS ROW(x ROW(y CHAR)))", "row(x row(y varchar))", "CAST(ROW(ROW('a')) AS ROW(x ROW(y VARCHAR)))")) + // tinyint -> integer + .add(new TypeCoercionTestSetup("ARRAY[TINYINT '127']", "array(integer)", "ARRAY[127]")) + .add(new TypeCoercionTestSetup("ARRAY[ARRAY[TINYINT '127']]", "array(array(integer))", "ARRAY[ARRAY[127]]")) + .add(new TypeCoercionTestSetup("MAP(ARRAY[TINYINT '1'], ARRAY[TINYINT '10'])", "map(integer, integer)", "MAP(ARRAY[1], ARRAY[10])")) + .add(new TypeCoercionTestSetup("MAP(ARRAY[TINYINT '1'], ARRAY[ARRAY[TINYINT '10']])", "map(integer, array(integer))", "MAP(ARRAY[1], ARRAY[ARRAY[10]])")) + .add(new TypeCoercionTestSetup("CAST(ROW(127) AS ROW(x TINYINT))", "row(x integer)", "CAST(ROW(127) AS ROW(x INTEGER))")) + .add(new TypeCoercionTestSetup("CAST(ROW(ROW(127)) AS ROW(x ROW(y TINYINT)))", "row(x row(y integer))", "CAST(ROW(ROW(127)) AS ROW(x ROW(y INTEGER)))")) + // smallint -> integer + .add(new TypeCoercionTestSetup("ARRAY[SMALLINT '32767']", "array(integer)", "ARRAY[32767]")) + .add(new TypeCoercionTestSetup("ARRAY[ARRAY[SMALLINT '32767']]", "array(array(integer))", "ARRAY[ARRAY[32767]]")) + .add(new TypeCoercionTestSetup("MAP(ARRAY[SMALLINT '1'], ARRAY[SMALLINT '10'])", "map(integer, integer)", "MAP(ARRAY[1], ARRAY[10])")) + .add(new TypeCoercionTestSetup("MAP(ARRAY[SMALLINT '1'], ARRAY[ARRAY[SMALLINT '10']])", "map(integer, array(integer))", "MAP(ARRAY[1], ARRAY[ARRAY[10]])")) + .add(new TypeCoercionTestSetup("CAST(ROW(32767) AS ROW(x SMALLINT))", "row(x integer)", "CAST(ROW(32767) AS ROW(x INTEGER))")) + .add(new TypeCoercionTestSetup("CAST(ROW(ROW(32767)) AS ROW(x ROW(y SMALLINT)))", "row(x row(y integer))", "CAST(ROW(ROW(32767)) AS ROW(x ROW(y INTEGER)))")) .build(); } @@ -8251,6 +8262,9 @@ public TypeCoercionTestSetup withNewValueLiteral(String newValueLiteral) @Test public void testAddColumnWithTypeCoercion() { + testAddColumnWithTypeCoercion("tinyint", "integer"); + testAddColumnWithTypeCoercion("smallint", "integer"); + testAddColumnWithTypeCoercion("timestamp with time zone", "timestamp(6) with time zone"); testAddColumnWithTypeCoercion("timestamp(0) with time zone", "timestamp(6) with time zone"); testAddColumnWithTypeCoercion("timestamp(1) with time zone", "timestamp(6) with time zone"); @@ -8301,6 +8315,14 @@ public void testAddColumnWithTypeCoercion() testAddColumnWithTypeCoercion("array(char(10))", "array(varchar)"); testAddColumnWithTypeCoercion("map(char(20), char(30))", "map(varchar, varchar)"); testAddColumnWithTypeCoercion("row(x char(40))", "row(x varchar)"); + + testAddColumnWithTypeCoercion("array(tinyint)", "array(integer)"); + testAddColumnWithTypeCoercion("map(tinyint, tinyint)", "map(integer, integer)"); + testAddColumnWithTypeCoercion("row(x tinyint)", "row(x integer)"); + + testAddColumnWithTypeCoercion("array(smallint)", "array(integer)"); + testAddColumnWithTypeCoercion("map(smallint, smallint)", "map(integer, integer)"); + testAddColumnWithTypeCoercion("row(x smallint)", "row(x integer)"); } private void testAddColumnWithTypeCoercion(String columnType, String expectedColumnType) @@ -8342,6 +8364,7 @@ protected Optional filterSetColumnTypesDataProvider(SetColum return Optional.of(setup.withNewValueLiteral("TIMESTAMP '2020-02-12 14:03:00.123000 +00:00'")); } switch ("%s -> %s".formatted(setup.sourceColumnType(), setup.newColumnType())) { + case "tinyint -> smallint": case "bigint -> integer": case "decimal(5,3) -> decimal(5,2)": case "varchar -> char(20)": @@ -8367,13 +8390,14 @@ protected void verifySetColumnTypeFailurePermissible(Throwable e) { assertThat(e).hasMessageMatching(".*(Failed to set column type: Cannot change (column type:|type from .* to )" + "|Time(stamp)? precision \\(3\\) not supported for Iceberg. Use \"time(stamp)?\\(6\\)\" instead" + - "|Type not supported for Iceberg: char\\(20\\)).*"); + "|Type not supported for Iceberg: smallint|char\\(20\\)).*"); } @Override protected Optional filterSetFieldTypesDataProvider(SetColumnTypeSetup setup) { switch ("%s -> %s".formatted(setup.sourceColumnType(), setup.newColumnType())) { + case "tinyint -> smallint": case "bigint -> integer": case "decimal(5,3) -> decimal(5,2)": case "varchar -> char(20)": @@ -8408,7 +8432,7 @@ protected void verifySetFieldTypeFailurePermissible(Throwable e) { assertThat(e).hasMessageMatching(".*(Failed to set field type: Cannot change (column type:|type from .* to )" + "|Time(stamp)? precision \\(3\\) not supported for Iceberg. Use \"time(stamp)?\\(6\\)\" instead" + - "|Type not supported for Iceberg: char\\(20\\)" + + "|Type not supported for Iceberg: smallint|char\\(20\\)" + "|Iceberg doesn't support changing field type (from|to) non-primitive types).*"); }