DuckDB

12 min read • 2,536 words
Connect DuckDB with Amfelt: setup, required privileges, configuration reference, and type mapping.

Available 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  

FeatureSourceTarget
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: 1 on 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:

  • STRUCT and LIST — 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  

  1. In Amfelt, add a DuckDB target and select the DuckDB connector.
  2. 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.
  3. 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.duckdb

Adding DuckDB as a Source  

  1. In Amfelt, add a DuckDB source and select the DuckDB connector.
  2. 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.
  3. Choose Discover schema to read the source’s schemas, objects, and properties.
  4. 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  

ModeWhat it does
Full loadReloads all selected data on every run.
Incremental loadLoads only records changed since the last run, tracked by a watermark column (timestamp or sequence).

Target History Modes  

ModeWhat 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.
AppendAdds new records without updating or removing existing ones.
Deduplicated appendAppends new records, skipping duplicates already loaded.
SnapshotStores 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  

PropertyTypeRequiredDescription
databasestringYesPath to duckdb database file.
schemaintegerNoThe 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 typeAliasesApache ArrowODCS logicalDescription
bigintint8int64integersigned eight-byte integer
bitbitstringutf8stringfixed-length bit string
blobbytea, binary, varbinarybinaryobjectvariable-length binary data
booleanboolboolbooleanlogical Boolean (true/false)
date—date32datecalendar date (year, month, day)
decimalnumeric—numberexact numeric of selectable precision
doublefloat8float64numberdouble precision floating-point number (8 bytes)
floatfloat4float32numbersingle precision floating-point number (4 bytes)
hugeint—decimal256(39,0)integersigned sixteen-byte integer (DuckDB-specific)
integerint, int4, signedint32integersigned four-byte integer
interval—month_day_nano_intervalobjecttime span
json—utf8stringJSON data
smallintint2int16integersigned two-byte integer
timetime without time zonetimestamp_us_utcdatetime of day (no time zone)
timestampdatetime, timestamp without time zonetimestamp_us_utcdatedate and time (no time zone)
timestamptztimestamp with time zonetimestamp_us_utcdatedate and time, including time zone
timetztime with time zonetimestamp_us_utcdatetime of day, including time zone
tinyintint1int8integersigned one-byte integer
ubigint—uint64integerunsigned eight-byte integer
uinteger—uint32integerunsigned four-byte integer
usmallint—uint16integerunsigned two-byte integer
utinyint—uint8integerunsigned one-byte integer
uuid—utf8stringuniversally unique identifier
varchartext, string, char, bpchar, character varyingutf8stringvariable-length character string

Troubleshooting  

Another process holds the database file open for writing. DuckDB is single-writer: close other DuckDB shells, notebooks, or BI tools using the same file, and make sure only one job targets it at a time.
The agent’s service account cannot read or create the file at the configured path. Check directory existence and file-system permissions on the agent host.
DuckDB reclaims space lazily. Space from deleted or re-loaded data (e.g. replace mode) is reused internally; to shrink the file itself, run CHECKPOINT or copy the database with the DuckDB CLI during a maintenance window.
DuckDB storage is not always forward-compatible. If the file was created by a newer DuckDB than the connector ships, upgrade the connector; if it was created by a much older version, export and re-import via the DuckDB CLI.
A target written by an earlier release of the connector can have indexes that lost rows. Earlier releases left their writes in DuckDB’s write-ahead log, and a DuckDB tool that replayed that log could drop rows from the target’s indexes. A sync then fails with this error, and upgrading the connector does not repair the index. The rows themselves are intact, and rebuilding the indexes keeps both staging and history. Stop every sync that uses the file, open it with the DuckDB CLI (1.5.5 or 1.5.6), and list the indexes with 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.
A target written by an earlier release of the connector can also hold a write-ahead log that DuckDB 1.5.5 cannot replay. The DuckDB 1.5.5 CLI then cannot open the file and reports 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.
Before a load starts, the connector writes any write-ahead log an earlier run left into the database file, and after every load and deploy it writes its own changes there. If DuckDB cannot do that before a load, the sync fails with 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”.
A target written by an earlier release of the connector can still hold that release’s write-ahead log. The first sync after the upgrade writes it into the database file. Run that sync before you open the file in the DuckDB CLI, a notebook, or a BI tool: such a tool otherwise replays the log itself, which can damage the target’s indexes, with DuckDB 1.5.6 as well. If that has happened, or that first sync fails, see the entries above.

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.

PackageLicense
github.com/Masterminds/semver  MIT
github.com/apache/arrow-go/v18  Apache-2.0 AND BSD-3-Clause
github.com/apapsch/go-jsonmerge/v2  MIT
github.com/bmatcuk/doublestar/v4  MIT
github.com/duckdb/duckdb-go-bindings  MIT
github.com/duckdb/duckdb-go-bindings/lib/linux-amd64  MIT
github.com/duckdb/duckdb-go/v2  MIT
github.com/fsnotify/fsnotify  BSD-3-Clause
github.com/gabriel-vasile/mimetype  MIT
github.com/go-cmd/cmd  MIT
github.com/go-playground/locales  MIT
github.com/go-playground/universal-translator  MIT
github.com/go-playground/validator  MIT
github.com/go-playground/validator/v10  MIT
github.com/go-viper/mapstructure/v2  MIT
github.com/goccy/go-json  MIT
github.com/goccy/go-yaml  MIT
github.com/google/flatbuffers  Apache-2.0
github.com/google/uuid  BSD-3-Clause
github.com/hashicorp/errwrap  MPL-2.0
github.com/hashicorp/go-multierror  MPL-2.0
github.com/iancoleman/strcase  MIT
github.com/jackc/pgpassfile  MIT
github.com/jackc/pgservicefile  MIT
github.com/jackc/pgx/v5  MIT
github.com/jackc/puddle/v2  MIT
github.com/jinzhu/copier  MIT
github.com/klauspost/compress  Apache-2.0 AND BSD-3-Clause AND MIT
github.com/klauspost/cpuid/v2  MIT
github.com/leodido/go-urn  MIT
github.com/lmittmann/tint  MIT
github.com/mattn/go-isatty  MIT
github.com/mitchellh/go-wordwrap  MIT
github.com/oapi-codegen/runtime  Apache-2.0
github.com/pelletier/go-toml/v2  MIT
github.com/pierrec/lz4/v4  BSD-3-Clause
github.com/sagikazarmark/locafero  MIT
github.com/santhosh-tekuri/jsonschema/v6  Apache-2.0
github.com/spf13/afero  Apache-2.0
github.com/spf13/cast  MIT
github.com/spf13/cobra  Apache-2.0
github.com/spf13/pflag  BSD-3-Clause
github.com/spf13/viper  MIT
github.com/subosito/gotenv  MIT
github.com/xo/dburl  MIT
github.com/zeebo/xxh3  BSD-2-Clause
go.yaml.in/yaml/v3  MIT
golang.org/x/crypto  BSD-3-Clause
golang.org/x/exp  BSD-3-Clause
golang.org/x/net  BSD-3-Clause
golang.org/x/sync  BSD-3-Clause
golang.org/x/sys  BSD-3-Clause
golang.org/x/text  BSD-3-Clause
google.golang.org/genproto/googleapis/rpc  Apache-2.0
google.golang.org/grpc  Apache-2.0
google.golang.org/protobuf  BSD-3-Clause