diff --git a/src/catalog/rest/api/iceberg_create_table_request.cpp b/src/catalog/rest/api/iceberg_create_table_request.cpp index 1842382fe..2484293b1 100644 --- a/src/catalog/rest/api/iceberg_create_table_request.cpp +++ b/src/catalog/rest/api/iceberg_create_table_request.cpp @@ -229,6 +229,9 @@ IcebergCreateTableRequest::CreateIcebergColumn(const ColumnDefinition &column_de throw InvalidInputException("non-null DEFAULT values are not supported for initial_default = make_uniq(val); + if (iceberg_version >= 3) { + iceberg_column_def->write_default = make_uniq(val); + } } return iceberg_column_def; } diff --git a/src/catalog/rest/catalog_entry/schema/iceberg_schema_entry.cpp b/src/catalog/rest/catalog_entry/schema/iceberg_schema_entry.cpp index 5f9194d91..b40f1b613 100644 --- a/src/catalog/rest/catalog_entry/schema/iceberg_schema_entry.cpp +++ b/src/catalog/rest/catalog_entry/schema/iceberg_schema_entry.cpp @@ -281,8 +281,8 @@ IcebergColumnDefinition &ResolveColumn(T &alter_table_info, const shared_ptrGetMutableFromPath({column_name}, nullptr); if (!column_p) { - throw CatalogException("Column with name '%s' does not exist on the table '%s'", column_name, - alter_table_info.GetAlterEntryData().name); + throw BinderException("Binder Error: Table \"%s\" does not have a column with name \"%s\"", + alter_table_info.GetAlterEntryData().name, column_name); } auto &column = *column_p; return column; @@ -461,13 +461,13 @@ void IcebergSchemaEntry::Alter(CatalogTransaction transaction, AlterInfo &info) auto column_p = new_schema->GetMutableFromPath({column_name}, nullptr); if (!column_p) { - throw CatalogException("Column with name '%s' does not exist on the table '%s', RENAME COLUMN failed", - column_name, table_entry.name); + throw BinderException("Column with name '%s' does not exist on the table '%s', RENAME COLUMN failed", + column_name, table_entry.name); } auto collision_column_p = new_schema->GetMutableFromPath({new_name}, nullptr); if (collision_column_p) { - throw CatalogException("Column with name '%s' already exists on the table '%s', RENAME COLUMN failed", - new_name, table_entry.name); + throw BinderException("Column with name '%s' already exists on the table '%s', RENAME COLUMN failed", + new_name, table_entry.name); } auto &column = *column_p; column.name = new_name; @@ -561,17 +561,20 @@ void IcebergSchemaEntry::Alter(CatalogTransaction transaction, AlterInfo &info) auto column_p = new_schema->GetMutableFromPath({column_name}, nullptr); if (!column_p) { - throw CatalogException("Column with name '%s' does not exist on the table '%s', SET DEFAULT failed", - column_name, table_entry.name); + throw BinderException("Binder Error: Table \"%s\" does not have a column with name \"%s\"", + table_entry.name, column_name); } auto &column = *column_p; - if (updated_table.table_metadata.iceberg_version < 3) { - throw NotImplementedException("SET DEFAULT is not supported on tables < V3"); - } IcebergDefaultBinder binder(context); auto default_constant_value = binder.Evaluate(expression.get(), column.type); - column.write_default = make_uniq(default_constant_value); + if (updated_table.table_metadata.iceberg_version < 3 && !default_constant_value.IsNull()) { + throw InvalidInputException("non-null DEFAULT values are not supported for = 3) { + column.write_default = make_uniq(default_constant_value); + } auto new_schema_id = new_schema->schema_id; diff --git a/src/common/iceberg_default.cpp b/src/common/iceberg_default.cpp index e3e225269..da0f215f6 100644 --- a/src/common/iceberg_default.cpp +++ b/src/common/iceberg_default.cpp @@ -1,5 +1,6 @@ #include "common/iceberg_default.hpp" #include "duckdb/execution/expression_executor.hpp" +#include "catalog/rest/api/iceberg_type.hpp" namespace duckdb { @@ -13,10 +14,30 @@ Value IcebergDefaultBinder::Evaluate(optional_ptr expr, } auto expr_copy = expr->Copy(); auto bound_expr = constant_binder.Bind(expr_copy, nullptr); + if (!bound_expr->IsFoldable()) { throw NotImplementedException("Only foldable expressions are allowed as DEFAULT values"); } - return ExpressionExecutor::EvaluateScalar(context, *bound_expr, false).DefaultCastAs(type); + auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr, false).DefaultCastAs(type); + + auto type_id = type.id(); + switch (type_id) { + case LogicalTypeId::SQLNULL: + case LogicalTypeId::VARIANT: + // case LogicalTypeId::GEOGRAPHY: + case LogicalTypeId::GEOMETRY: { + if (!val.IsNull()) { + //! SPEC: All columns of unknown, variant, geometry, and geography types must default to null. Non-null + //! values for initial-default or write-default are invalid. + throw InvalidInputException("Non-null DEFAULT values are not accepted for columns of type %s", + IcebergTypeHelper::LogicalTypeToIcebergType(type)); + } + break; + } + default: + break; + }; + return val; } } // namespace duckdb diff --git a/test/configs/lakekeeper.json b/test/configs/lakekeeper.json index 63d525907..1f13e13ff 100644 --- a/test/configs/lakekeeper.json +++ b/test/configs/lakekeeper.json @@ -38,6 +38,7 @@ "test/sql/local/catalog_test_config_setup/catalog_agnostic/merge/merge_multi_partition_timestamp.test", "test/sql/local/catalog_test_config_setup/catalog_agnostic/create/default/test_create_default_blob.test", "test/sql/local/catalog_test_config_setup/catalog_agnostic/test_basic_variant.test", + "test/sql/local/catalog_test_config_setup/catalog_agnostic/create/test_create_geo.test", "test/sql/local/catalog_test_config_setup/catalog_agnostic/insert/iceberg_types/test_geometry_type.test", "test/sql/local/catalog_test_config_setup/catalog_agnostic/insert/iceberg_types/test_geometry_type_nested.test", "test/sql/local/catalog_test_config_setup/catalog_agnostic/insert/iceberg_types/test_write_geometry_bounds.test", diff --git a/test/configs/nessie.json b/test/configs/nessie.json index 1077a1776..9b2bdf86d 100644 --- a/test/configs/nessie.json +++ b/test/configs/nessie.json @@ -58,6 +58,7 @@ "test/sql/local/catalog_test_config_setup/catalog_agnostic/merge/merge_into_default.test", "test/sql/local/catalog_test_config_setup/catalog_agnostic/merge/merge_into_multiple_update_delete_actions.test", "test/sql/local/catalog_test_config_setup/catalog_agnostic/other_engines/test_create_partitioned_tables.test", + "test/sql/local/catalog_test_config_setup/catalog_agnostic/create/test_create_geo.test", "test/sql/local/catalog_test_config_setup/catalog_agnostic/insert/test_create_geometry_type.test", "test/sql/local/catalog_test_config_setup/catalog_agnostic/insert/iceberg_types/test_geometry_type.test", "test/sql/local/catalog_test_config_setup/catalog_agnostic/insert/iceberg_types/test_geometry_type_nested.test", diff --git a/test/sql/local/catalog_test_config_setup/catalog_agnostic/alter/alter_rename_column.test b/test/sql/local/catalog_test_config_setup/catalog_agnostic/alter/alter_rename_column.test index 56c4c716d..8865f1a4e 100644 --- a/test/sql/local/catalog_test_config_setup/catalog_agnostic/alter/alter_rename_column.test +++ b/test/sql/local/catalog_test_config_setup/catalog_agnostic/alter/alter_rename_column.test @@ -38,7 +38,7 @@ insert into my_datalake.default.tbl VALUES ('hello world', true); statement error ALTER TABLE my_datalake.default.tbl RENAME a TO b; ---- -Catalog Error: Column with name 'b' already exists on the table 'tbl', RENAME COLUMN failed +Binder Error: Column with name 'b' already exists on the table 'tbl', RENAME COLUMN failed # Rename to same name (second alter before commit) diff --git a/test/sql/local/catalog_test_config_setup/catalog_agnostic/create/test_create_default.test b/test/sql/local/catalog_test_config_setup/catalog_agnostic/create/test_create_default.test index ba16f4a8d..345d2bc86 100644 --- a/test/sql/local/catalog_test_config_setup/catalog_agnostic/create/test_create_default.test +++ b/test/sql/local/catalog_test_config_setup/catalog_agnostic/create/test_create_default.test @@ -71,7 +71,20 @@ CREATE TABLE my_datalake.default.tbl( balance VARIANT DEFAULT [42, 'hello'::VARIANT, 'world', {'a': true}] ) WITH ('format-version'='3'); ---- -Not implemented Error: DEFAULT values for VARIANT are not supported yet +Invalid Input Error: Non-null DEFAULT values are not accepted for columns of type variant + +# VARIANT DEFAULT NULL is allowed +statement ok +DROP TABLE IF EXISTS my_datalake.default.tbl_variant_null; + +statement ok +CREATE TABLE my_datalake.default.tbl_variant_null( + item_id int, + v VARIANT DEFAULT NULL +) WITH ('format-version'='3'); + +statement ok +DROP TABLE my_datalake.default.tbl_variant_null; # DEFAULT value of unsupported type statement error diff --git a/test/sql/local/catalog_test_config_setup/catalog_agnostic/create/test_create_geo.test b/test/sql/local/catalog_test_config_setup/catalog_agnostic/create/test_create_geo.test new file mode 100644 index 000000000..344d3f35b --- /dev/null +++ b/test/sql/local/catalog_test_config_setup/catalog_agnostic/create/test_create_geo.test @@ -0,0 +1,49 @@ +# name: test/sql/local/catalog_test_config_setup/catalog_agnostic/create/test_create_geo.test +# group: [create] + +require-env CATALOG_TEST_CONFIG_SETUP + +require avro + +require parquet + +require iceberg + +require httpfs + +# Do not ignore 'HTTP' error messages! +set ignore_error_messages + +statement ok +set enable_logging=true + +statement ok +set logging_level='debug' + +statement ok +CREATE SCHEMA IF NOT EXISTS my_datalake.default; + +statement ok +DROP TABLE IF EXISTS my_datalake.default.tbl; + +# Iceberg spec: geometry columns must default to null +statement error +CREATE TABLE my_datalake.default.tbl( + item_id int, + g GEOMETRY DEFAULT 'POINT(0 0)'::GEOMETRY +) WITH ('format-version'='3'); +---- +Invalid Input Error: Non-null DEFAULT values are not accepted for columns of type geometry(ogc:crs84) + +# GEOMETRY DEFAULT NULL is allowed +statement ok +DROP TABLE IF EXISTS my_datalake.default.tbl_geo_null; + +statement ok +CREATE TABLE my_datalake.default.tbl_geo_null( + item_id int, + g GEOMETRY DEFAULT NULL +) WITH ('format-version'='3'); + +statement ok +DROP TABLE my_datalake.default.tbl_geo_null;