DuckDB
12 min read • 2,536 wordsAvailable v2.15.0 Released October 31, 2026
Overview
DuckDB is supported as both a source and a target. As a target, Amfelt loads and historizes data into an embedded DuckDB database file — a fast, zero-administration analytical warehouse that lives entirely inside your infrastructure. As a source, Amfelt reads an existing DuckDB file, so a warehouse Amfelt built can be read back and a DuckDB file produced elsewhere can be brought in. It suits development and test environments, edge and single-node deployments, and lightweight analytical marts that do not warrant a server-based warehouse.
DuckDB is an embedded, in-process database: there is no server to connect to. The Amfelt agent opens the database file directly, so the agent and the warehouse always run on the same host, and DuckDB’s single-writer model applies (Amfelt caps the connector at one connection automatically).
Capabilities
| Feature | Source | Target |
|---|---|---|
| Full load | ||
| Incremental load | — | |
| Change Data Capture (CDC) | — | — |
| Schema drift | — |
Prerequisites
Before connecting DuckDB:
- A writable location on the Amfelt agent’s host for the database file
(e.g.
/data/warehouse.duckdb). For a target, the agent creates the file if it does not exist. - Disk space for the warehouse: staging, history, and bridge layers all live in the one file.
- Single writer: DuckDB allows one writing process at a time. Amfelt enforces
max_connections: 1on the target; no other process may hold the file open for writing during a load. - No server, no credentials: DuckDB is embedded — there is no network endpoint and no login role. Access control is the file system’s.
- Read-only source access: a DuckDB source opens the database read-only. The file must already exist — a path that does not is an error, not a newly created empty database — and the source can never modify, or upgrade the format of, the file it reads.
- Sources and targets cannot share a file concurrently: DuckDB’s read-write lock is exclusive, so an extract cannot run while a load holds the same database file. Schedule jobs that read and write one file apart from each other.
Unsupported Source Column Types
A DuckDB source cannot currently extract these column types. A table containing one fails at extraction rather than returning partial data:
STRUCTandLIST— DuckDB’s nested types are not mapped yet. Support for them is planned; until it lands, flatten nested columns in a view and extract that instead.
Targets are unaffected: both write normally. Only extraction is blocked.
INTERVAL was listed here until the platform gained a logical category for the type.
Interval columns now extract, and their months, days and nanoseconds are fingerprinted
as separate components — a month is not a fixed number of days, so folding them into one
scalar would make INTERVAL '1 month' and INTERVAL '30 days' indistinguishable and
change detection would miss a row edited from one to the other.
JSON Numeric Precision
JSON columns extract as text, but the DuckDB driver decodes JSON numbers through a
64-bit float before Amfelt sees them. An integer larger than 2^53 inside a JSON document
loses precision — {"id":9007199254740993} extracts as {"id":9007199254740992}. Store
large integers as their own typed column, not inside a JSON document, if exact values
matter.
Connect
- In Amfelt, add a DuckDB target and select the DuckDB connector.
- Provide the database property: the path to the database file on the agent host (see Configuration reference). The agent creates the file on first use.
- Validate the connection and deploy the warehouse layers.
A connection resolves to an ODCS server descriptor like:
servers:
- server: duckdb-local
type: duckdb
database: /data/warehouse.duckdbAdding DuckDB as a Source
- In Amfelt, add a DuckDB source and select the DuckDB connector.
- Provide the database property: the path to an existing database file on the agent host. Unlike the target, the source does not create the file — it opens the database read-only, so a path that does not exist is reported as an error.
- Choose Discover schema to read the source’s schemas, objects, and properties.
- Select the objects to extract and deploy the job.
The source performs full extracts and incremental extracts. An incremental extract reads
only the rows whose cursor column is at or after the lower bound of the run. The cursor is
a timestamp (a TIMESTAMP or TIMESTAMP WITH TIME ZONE column) or a sequence (a
SMALLINT, INTEGER or BIGINT column); a cursor on a column of any other type fails the
run instead of reading the object in full. A TIMESTAMP cursor is compared as a UTC wall
clock, whatever the time zone of the agent host. There is no change-data-capture path for
DuckDB.
Authentication
DuckDB is an embedded database file: there are no credentials and no vault entry. Protect the warehouse by restricting file-system access to the database file and the agent’s service account.
Single-Writer Scheduling
Only one connection writes at a time (max_connections: 1 on the target, enforced by
the platform). Schedule jobs that target the same DuckDB file apart from each other.
State Schemas
A target deploy that creates the history layer also creates the state schema <prefix>_sys
in the database file. It holds what each history apply commits with its rows: the watermark
of each object and the ledger of the files loaded. The first quarantined file of an object
creates <prefix>_qrn, when it does not exist yet, and the object’s quarantine table in it,
which holds the quarantined rows until they are purged. No bridge view reads either schema.
DuckDB has no roles or grants, so whoever can open the file can read these schemas, as every
other schema in it.
Sync Modes
| Mode | What it does |
|---|---|
| Full load | Reloads all selected data on every run. |
| Incremental load | Loads only records changed since the last run, tracked by a watermark column (timestamp or sequence). |
Target History Modes
| Mode | What it does |
|---|---|
| SCD Type 2 (full history) | Keeps the full history. Each change writes a new versioned record with validity dates. |
| SCD Type 1 (overwrite) | Overwrites in place, keeping only the latest value, no history. |
| Append | Adds new records without updating or removing existing ones. |
| Deduplicated append | Appends new records, skipping duplicates already loaded. |
| Snapshot | Stores a full point-in-time copy of the source on each run. |
| Replace (truncate & load) | Replaces the entire target with the latest load on each run. |
Configuration Reference
| Property | Type | Required | Description |
|---|---|---|---|
database | string | Yes | Path to duckdb database file. |
schema | integer | No | The name of the schema. |
Type Mapping
Each DuckDB physical type maps to an Apache Arrow type (the data-plane intermediate) and then to an ODCS logical type. See the official DuckDB data type reference .
| DuckDB type | Aliases | Apache Arrow | ODCS logical | Description |
|---|---|---|---|---|
bigint | int8 | int64 | integer | signed eight-byte integer |
bit | bitstring | utf8 | string | fixed-length bit string |
blob | bytea, binary, varbinary | binary | object | variable-length binary data |
boolean | bool | bool | boolean | logical Boolean (true/false) |
date | — | date32 | date | calendar date (year, month, day) |
decimal | numeric | — | number | exact numeric of selectable precision |
double | float8 | float64 | number | double precision floating-point number (8 bytes) |
float | float4 | float32 | number | single precision floating-point number (4 bytes) |
hugeint | — | decimal256(39,0) | integer | signed sixteen-byte integer (DuckDB-specific) |
integer | int, int4, signed | int32 | integer | signed four-byte integer |
interval | — | month_day_nano_interval | object | time span |
json | — | utf8 | string | JSON data |
smallint | int2 | int16 | integer | signed two-byte integer |
time | time without time zone | timestamp_us_utc | date | time of day (no time zone) |
timestamp | datetime, timestamp without time zone | timestamp_us_utc | date | date and time (no time zone) |
timestamptz | timestamp with time zone | timestamp_us_utc | date | date and time, including time zone |
timetz | time with time zone | timestamp_us_utc | date | time of day, including time zone |
tinyint | int1 | int8 | integer | signed one-byte integer |
ubigint | — | uint64 | integer | unsigned eight-byte integer |
uinteger | — | uint32 | integer | unsigned four-byte integer |
usmallint | — | uint16 | integer | unsigned two-byte integer |
utinyint | — | uint8 | integer | unsigned one-byte integer |
uuid | — | utf8 | string | universally unique identifier |
varchar | text, string, char, bpchar, character varying | utf8 | string | variable-length character string |
Troubleshooting
SELECT schema_name, index_name, sql FROM duckdb_indexes();. Keep the sql column: it holds each index’s definition. Drop every index with DROP INDEX "<schema_name>"."<index_name>";, run CHECKPOINT;, re-create each index by running its saved definition, and run CHECKPOINT; again. The next sync then runs normally. Current releases of the connector write every change into the database file before they finish, which prevents this.Failure while replaying WAL followed by Attempted to access index N within vector of size N, and a sync with an earlier release fails or stops responding. Upgrade the connector: current releases use DuckDB 1.5.6, which replays such a log and writes it into the database file on the next sync, and the DuckDB 1.5.6 CLI opens the file. If a sync with the current release still fails, and the DuckDB plugin’s output shows SIGSEGV: segmentation violation, DuckDB cannot write that log into the file as it stands. The sync’s own error then only reports that the connection to the plugin was lost, for example WriteRecords send: rpc error: code = Unavailable desc = error reading from server: EOF (agents before v4.18.6 report WriteRecords send: EOF), and the DuckDB CLI likewise exits without a message when it runs CHECKPOINT; on the file. Rebuild the indexes as described under “Failed to delete all rows from index”, with the DuckDB 1.5.5 or 1.5.6 CLI, and the next sync runs normally.checkpoint failed, followed by DuckDB’s reason, and the load does not start, so staging and history keep their previous rows. Retrying does not make the file harder to repair. Fix the cause DuckDB names and run the sync again. If DuckDB names an internal error, rebuild the indexes as described under “Failed to delete all rows from index”.Changelog
Release history for the DuckDB connector, newest first.
Features
- Keep the state schema and the file ledger
Features
- Cursor binder for postgres and duckdb sources
Bug Fixes
- Preserve configured database file paths
Features
- Publish plugin releases on the connectors repo
Bug Fixes
- Move connector module paths to github.com/amfeltlabs
Bug Fixes
- Count scd1 overwrites as updates
- Scaffold rdbms connectors on pgx; require conformance v0.5.0
Features
- Open PostgreSQL with pgx; core v3.41.2, plugin-sdk v5.17.4
Features
- Write the amf_ metadata columns and name Amfelt in what connectors ship
Bug Fixes
- Bump golang.org/x/net to v0.60.0 (GO-2026-6617)
Features
- Ship third-party notices with the vendored-code addendum
Bug Fixes
- Checkpoint every write so no target replays a write-ahead log
Bug Fixes
- Load non-string values into varchar columns as exact text
Features
- Record third-party marks per connector
Features
- Compare scope in the postgres and duckdb templates
Bug Fixes
- ship plugin-sdk v5.12.2 to keep credentials out of exported contracts
Features
- Postgres and duckdb sources answer the source connection test
Bug Fixes
- Require plugin-sdk v5.9.3 so a read-only-default role is not reported ok
Features
- Answer the target connection test
Features
- Pass the declared override surface to the renderer (ADR-117)
Features
- Declare the override surface and govern the declared tier (ADR-117)
Features
- Publish darwin/amd64 and windows/amd64
Features
- Add the source connector
Bug Fixes
- Carry hugeint’s full range into Arrow
Bug Fixes
- Pin arrow_type across the datatypes map
Breaking Changes
- Supersede infusal/target-duckdb
Features
- Target connector on plugin-sdk /v5
Licensing
Third-party open-source packages bundled by this connector, with their licenses. Attribution only: informational, not an evaluation of the connector.