Skip to main content
Beyond the basic lifecycle, the Appender supports targeting specific catalogs, feeding arbitrary queries, projecting a subset of columns, and bulk-ingesting whole data chunks. It also has strict error semantics worth understanding.

Catalog & query appenders

Address [catalog.]schema.table explicitly. None catalog uses DuckDB’s default.
A query appender feeds appended rows into an arbitrary INSERT/UPDATE/DELETE/MERGE, referring to the appended data by table_name (default "appended_data"):
Rows appended here feed query. types gives the appended columns’ logical types; column_names optionally names them (default col1, col2, …).
LogicalType::of::<T>() builds a logical type from a Rust type.

Column introspection

Both reflect the appender’s active column list (the table’s columns, unless projected):
  • column_count() -> u64.
  • column_type(col_idx: u64) -> Option<LogicalType>.

Projected columns & default rows

Supply only some columns and let the rest take their DEFAULT. Projection must be configured before the first row (otherwise an error):
  • add_column(name) — add name to the active column list (pre-first-row only).
  • clear_columns() — restore the full column set (pre-first-row only).
  • append_default_row() — append one row where every column takes its DEFAULT (a column with no DEFAULT becomes NULL); opens and closes the row safely.

Raw chunk ingestion

Append whole data chunks in one call rather than value-by-value. Always available (operate on the internal DataChunk fetched from a query result):
  • append_chunk(&DataChunk) -> Result<()> — append every row of a DataChunk. The chunk is only read; the column count must match (validated Rust-side).
  • append_default_to_chunk(&mut DataChunk, col: u64, row: u64) — write a column’s DEFAULT into a raw chunk cell.
With the udf / columnar feature, the DataChunkHandle counterparts:
  • append_chunk_handle(&DataChunkHandle) -> Result<()> — append a chunk you built column-by-column. The shape check is strict — column count and each column’s type id must match — and runs entirely before the chunk reaches DuckDB, so a rejected chunk leaves the appender usable.
  • set_chunk_default(&mut DataChunkHandle, col: usize, row: usize) — write a column’s DEFAULT into a built chunk cell.

Poisoning & error handling

DuckDB invalidates an appender the instant a flush, row-end, or close fails — the buffered data is gone and no further append/flush/close is legal. A failed append / save therefore poisons the Appender: every subsequent operation fails fast (an Error::Engine carrying the poison message) without re-entering DuckDB, and drop skips the flush entirely. (Re-flushing an invalidated appender can deadlock DuckDB on indexed / PRIMARY KEY tables, which is exactly what this prevents.)
  • Engine-side failures (e.g. a deferred CHECK constraint caught at flush) surface as Error::Engine with DuckDB’s typed classification.
  • A Rust-side value conversion failure mid-row also poisons the appender but returns the original conversion error unchanged.
  • The originating connection stays usable after an appender poisons.
Use finish() (not drop) when you need the flush/close error reported.

See also