Writing to Iceberg
The DuckDB iceberg extension supports writing to Iceberg tables when an Iceberg catalog is attached.
Writing to a table read directly from storage with iceberg_scan is not supported: the catalog is required to commit the new table metadata.
Supported Operations
The iceberg extension supports the following operations when used with a REST Catalog attached:
CREATE/DROP SCHEMACREATE/DROP TABLEINSERT INTOUPDATEDELETEMERGE INTOALTER TABLESELECT
Since these operations are supported, the following will also work:
COPY FROM DATABASE duckdb_db TO iceberg_datalake;-- OrCOPY FROM DATABASE iceberg_datalake TO duckdb_db;This functionality enables deep copies between Iceberg and DuckDB storage.
Creating Tables
Tables are created with the standard CREATE TABLE syntax. Iceberg table properties can be supplied with the WITH clause:
CREATE TABLE test_create_table (a INTEGER)WITH ( 'format-version' = '2', -- format version will be elevated to format-version when creating a table 'location' = 's3://path/to/data', -- location will be elevated to location when creating a table 'property1' = 'value1', 'property2' = 'value2');Table properties can also be inspected and modified after creation, see Table Properties Functions.
MERGE INTO
The MERGE INTO statement is the recommended way to express upserts against Iceberg tables, which do not have a primary key. You can apply a change set in a single statement, deciding per row whether to insert, update, or delete:
MERGE INTO iceberg_catalog.default.people AS target USING ( FROM (VALUES (1, 'John', 105_000.0), (3, 'Sarah', 95_000.0) ) t(id, name, salary) ) AS upserts ON (upserts.id = target.id) WHEN MATCHED THEN UPDATE WHEN NOT MATCHED THEN INSERT;You can also use WHEN MATCHED THEN DELETE to express a delete set in the same statement. As with UPDATE and DELETE, MERGE INTO uses merge-on-read semantics and writes positional deletes to the Iceberg table.
ALTER TABLE and Schema Evolution
The ALTER TABLE statement is supported against Iceberg tables, covering the most common schema-evolution operations:
-- Rename the tableALTER TABLE iceberg_catalog.default.simple_table RENAME TO renamed_table;
-- Add a columnALTER TABLE iceberg_catalog.default.renamed_table ADD COLUMN col3 DOUBLE;
-- Rename a columnALTER TABLE iceberg_catalog.default.renamed_table RENAME COLUMN col2 TO name;
-- Drop a columnALTER TABLE iceberg_catalog.default.renamed_table DROP COLUMN col3;
-- Set the format-versionALTER TABLE iceberg_catalog.default.renamed_table SET ('format-version' = 3);Each ALTER TABLE statement updates the current-schema-id of the Iceberg table, and the changes become visible to other Iceberg-aware engines the next time they query the LoadTableInformation endpoint. Iceberg schema evolution is metadata-only, so no data files are rewritten.
Partition Transforms
In addition to identity partitioning, DuckDB-Iceberg supports creating, inserting into, and updating tables that use the bucket and truncate partition transforms.
The bucket(N, col) transform hashes the column’s value into N buckets, which is useful for stable partitioning on a high-cardinality column. truncate(W, col) groups rows by the first W characters (or by the column’s value rounded down to a multiple of W for numeric columns), which is useful for prefix-based partitioning.
CREATE TABLE iceberg_catalog.default.events ( event_id BIGINT, user_id BIGINT, country VARCHAR, payload VARCHAR)PARTITIONED BY (bucket(16, user_id), truncate(2, country));Updates and deletes against bucket- and truncate-partitioned tables are also supported, using positional deletes under merge-on-read semantics.
Iceberg V3 Support
DuckDB-Iceberg supports the following Iceberg v3 specification features for both reads and writes:
VARIANTandTIMESTAMP_NSdata types- Schema-level default values for columns
- Binary deletion vectors
- Row lineage tracking
The biggest change in practice is binary deletion vectors. In v2 tables, DuckDB-Iceberg writes positional deletes as Parquet files; in v3 tables, the same information is encoded as a much more compact binary deletion vector (Puffin file). DuckDB picks the right format automatically based on the table’s format-version. You can create a v3 table by setting the format-version table property at creation time:
CREATE TABLE iceberg_catalog.default.v3_tableWITH ('format-version' = 3) AS FROM (VALUES (1, {'kind': 'click', 'x': 10}::VARIANT, TIMESTAMP_NS '2026-05-20 12:00:00.123456789'), (2, {'kind': 'view'}::VARIANT, TIMESTAMP_NS '2026-05-20 12:00:00.987654321') ) t(id, payload, event_time);Warning The
GEOGRAPHYandUNKNOWNtypes are not yet supported in DuckDB-Iceberg; they are planned for DuckDB v2.0.0.
Limitations for UPDATE and DELETE
The UPDATE and DELETE operations have the following limitations:
- They only work on tables that are not sorted. Attempting these operations on sorted tables.
- DuckDB-Iceberg only writes positional deletes (encoded as binary deletion vectors for v3 tables). Copy-on-write functionality is not yet supported.
- DuckDB-Iceberg only supports merge-on-read semantics. If a table has
write.update.modeorwrite.delete.modeproperties set to something other thanmerge-on-read, the operation fails.
Unsupported Operations
The following are not yet supported by the DuckDB iceberg extension:
- The
GEOGRAPHYandUNKNOWNdata types (planned for DuckDB v2.0.0) - Copy-on-write semantics for
UPDATE,DELETE, andMERGE INTO