diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableProperties.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableProperties.java index 9de78f2280e1..87076a3d64a5 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableProperties.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableProperties.java @@ -19,6 +19,8 @@ import io.trino.spi.TrinoException; import io.trino.spi.session.PropertyMetadata; import io.trino.spi.type.ArrayType; +import io.trino.spi.type.MapType; +import io.trino.spi.type.TypeManager; import java.util.List; import java.util.Map; @@ -45,13 +47,15 @@ public class IcebergTableProperties public static final String FORMAT_VERSION_PROPERTY = "format_version"; public static final String ORC_BLOOM_FILTER_COLUMNS = "orc_bloom_filter_columns"; public static final String ORC_BLOOM_FILTER_FPP = "orc_bloom_filter_fpp"; + public static final String EXTRA_PROPERTIES = "extra_properties"; private final List> tableProperties; @Inject public IcebergTableProperties( IcebergConfig icebergConfig, - OrcWriterConfig orcWriterConfig) + OrcWriterConfig orcWriterConfig, + TypeManager typeManager) { tableProperties = ImmutableList.>builder() .add(enumProperty( @@ -107,6 +111,24 @@ public IcebergTableProperties( orcWriterConfig.getDefaultBloomFilterFpp(), IcebergTableProperties::validateOrcBloomFilterFpp, false)) + .add(new PropertyMetadata<>( + EXTRA_PROPERTIES, + "Extra table properties", + new MapType(VARCHAR, VARCHAR, typeManager.getTypeOperators()), + Map.class, + null, + true, // These properties are not listed in SHOW CREATE TABLE + value -> { + Map extraProperties = (Map) value; + if (extraProperties.containsValue(null)) { + throw new TrinoException(INVALID_TABLE_PROPERTY, format("Extra table property value cannot be null '%s'", extraProperties)); + } + if (extraProperties.containsKey(null)) { + throw new TrinoException(INVALID_TABLE_PROPERTY, format("Extra table property key cannot be null '%s'", extraProperties)); + } + return extraProperties; + }, + value -> value)) .build(); } @@ -169,4 +191,9 @@ private static void validateOrcBloomFilterFpp(double fpp) throw new TrinoException(INVALID_TABLE_PROPERTY, "Bloom filter fpp value must be between 0.0 and 1.0"); } } + + public static Optional> getExtraProperties(Map tableProperties) + { + return Optional.ofNullable((Map) tableProperties.get(EXTRA_PROPERTIES)); + } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java index 6a196cab870a..69df66cf63aa 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java @@ -110,6 +110,7 @@ import static io.trino.plugin.iceberg.IcebergTableProperties.ORC_BLOOM_FILTER_FPP; import static io.trino.plugin.iceberg.IcebergTableProperties.PARTITIONING_PROPERTY; import static io.trino.plugin.iceberg.IcebergTableProperties.SORTED_BY_PROPERTY; +import static io.trino.plugin.iceberg.IcebergTableProperties.getExtraProperties; import static io.trino.plugin.iceberg.IcebergTableProperties.getOrcBloomFilterColumns; import static io.trino.plugin.iceberg.IcebergTableProperties.getOrcBloomFilterFpp; import static io.trino.plugin.iceberg.IcebergTableProperties.getPartitioning; @@ -656,7 +657,24 @@ public static Transaction newCreateTableTransaction(TrinoCatalog catalog, Connec propertiesBuilder.put(TABLE_COMMENT, tableMetadata.getComment().get()); } - return catalog.newCreateTableTransaction(session, schemaTableName, schema, partitionSpec, sortOrder, targetPath, propertiesBuilder.buildOrThrow()); + Map baseProperties = propertiesBuilder.buildOrThrow(); + + // Add properties set via "extra_properties" table property. + Map extraProperties = getExtraProperties(tableMetadata.getProperties()) + .orElseGet(ImmutableMap::of); + Set illegalExtraProperties = Sets.intersection(baseProperties.keySet(), extraProperties.keySet()); + if (!illegalExtraProperties.isEmpty()) { + throw new TrinoException( + INVALID_TABLE_PROPERTY, + "Illegal keys in extra_properties: " + illegalExtraProperties); + } + + Map properties = ImmutableMap.builder() + .putAll(baseProperties) + .putAll(extraProperties) + .buildOrThrow(); + + return catalog.newCreateTableTransaction(session, schemaTableName, schema, partitionSpec, sortOrder, targetPath, properties); } /** diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/RegisterTableProcedure.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/RegisterTableProcedure.java index a3b3efb385fb..659c62f48a92 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/RegisterTableProcedure.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/RegisterTableProcedure.java @@ -238,7 +238,7 @@ private static boolean locationEquivalent(String a, String b) private static String normalizeS3Uri(String tableLocation) { - // Normalize e.g. s3a to s3, so that table can be registed using s3:// location + // Normalize e.g. s3a to s3, so that table can be registered using s3:// location // even if internally it uses s3a:// paths. return tableLocation.replaceFirst("^s3[an]://", "s3://"); }