DuckDB Extension
ADBC Scanner
Connect DuckDB to databases through Arrow Database Connectivity (ADBC) drivers.
On this page
Technical Overview
ADBC drivers, Arrow results, DuckDB SQL
Use a standard database-client API to connect DuckDB to different backends. ADBC exposes Arrow batches; each driver determines its underlying protocol and conversions.
Arrow at the client interface
ADBC drivers expose query results and bulk ingestion through Arrow. The extension reads batches into DuckDB without requiring CSV exports or intermediate files. This does not imply that every database speaks Arrow over the network.
- • One API, multiple databases: Use installed ADBC drivers for SQLite, PostgreSQL, Flight SQL, and other supported backends. Available features depend on the driver.
- • Native libraries: Resolve drivers through installed ADBC manifests or load a shared library by path. Drivers run inside DuckDB with the process's privileges.
Attached databases, one name for everything
ATTACH an ADBC database once; query its tables through the catalog and pass its alias to every adbc_* function. DuckDB's BEGIN, COMMIT, and ROLLBACK cover both.
- • Catalog queries: ATTACH with TYPE adbc exposes remote schemas and tables with supported projection/filter pushdown. The catalog pools connections for independent scans; READ_ONLY rejects every write, including adbc_execute and adbc_insert.
- • Runtime commands and transactions: Use standalone CALL adbc_execute('db', …). Inside BEGIN … COMMIT it commits or rolls back together with INSERT INTO db.… through the catalog. Side effects happen at execution, not during PREPARE or ordinary EXPLAIN.
- • Schema metadata: adbc_scan uses ExecuteSchema, or an explicit columns declaration when unsupported. adbc_scan_table and adbc_schema obtain named-table metadata through GetTableSchema.
Deep Dive
Technical Details
API version and migration
These examples target the attached-database API on the v1.5 branch, introduced in 7e5210c. An older community package may still expose the previous connection-handle API. Installing from the community repository alone does not establish the source revision.
Every adbc_* function now takes the alias of an ATTACH … (TYPE adbc) database instead of a BIGINT connection handle. adbc_connect, adbc_disconnect, adbc_commit, adbc_rollback, and adbc_set_autocommit are removed:
| Previous | Replacement |
|---|---|
SET VARIABLE conn = (SELECT adbc_connect({'driver': 'sqlite', 'uri': 'x.db'})) |
ATTACH 'x.db' AS db (TYPE adbc, driver 'sqlite') |
adbc_connect({'profile': 'mydb'}) |
ATTACH 'profile://mydb' AS db (TYPE adbc) |
adbc_connect({'secret': 's'}) / URI scope lookup |
ATTACH … (TYPE adbc, secret 's') / the ATTACH path’s URI |
adbc_scan(getvariable('conn')::BIGINT, sql) |
adbc_scan('db', sql) (likewise every adbc_* function) |
CALL adbc_set_autocommit(conn, false) … CALL adbc_commit(conn) |
BEGIN … COMMIT |
CALL adbc_rollback(conn) |
ROLLBACK |
CALL adbc_disconnect(conn) |
DETACH db |
SELECT adbc_execute(...) |
CALL adbc_execute('db', sql) |
SELECT adbc_clear_cache() |
CALL adbc_clear_cache() |
An unknown alias, or one naming a non-ADBC database, fails at bind with the alias in the message. Aliases resolve case-insensitively, like any catalog name. Scans, inserts, and metadata calls resolve the alias again at execution, so a prepared statement fails after DETACH and follows whatever transaction is current when it runs.
Runtime commands
adbc_execute and adbc_clear_cache are table functions run with a standalone CALL:
| Command | Result column |
|---|---|
CALL adbc_execute('db', sql) |
rows_affected BIGINT, or NULL when the driver does not report a count |
CALL adbc_clear_cache() |
cleared BOOLEAN, false when no ADBC catalogs are attached |
Commands run during execution. Binding, PREPARE, and ordinary EXPLAIN do not perform them; EXPLAIN ANALYZE executes the statement. Each prepared execution runs the command again. Keep commands as standalone statements: filters and joins can skip execution when commands are embedded in relational queries. This is not an exactly-once guarantee across network retries.
Transactions and connections
ATTACH 'users.sqlite' AS db (TYPE adbc, driver 'sqlite');CALL adbc_execute('db', 'CREATE TABLE IF NOT EXISTS users(id INTEGER, name TEXT)');BEGIN;CALL adbc_execute('db', 'INSERT INTO users VALUES (1, ''Alice'')');COMMIT;DETACH db;Inside an explicit BEGIN … COMMIT, adbc_execute and adbc_insert use the attachment’s write connection with autocommit disabled, so they commit or roll back together with writes made through the catalog (INSERT INTO db.…). Once the transaction has written, reads (adbc_scan, adbc_scan_table, the metadata functions) see its uncommitted writes. A driver that cannot disable autocommit fails the first write in the transaction rather than silently autocommitting. These transactions control the remote ADBC connection; local DuckDB writes and remote writes are not one distributed transaction.
Outside an explicit transaction, the adbc_* functions use the attachment’s own connection in autocommit, so session state such as a temporary table created by adbc_insert or a SET run by adbc_execute is visible to later calls. Writes to an attachment made with READ_ONLY are rejected, in or out of a transaction.
The write connection is separate from the attachment’s own, so a SQLite :memory: attachment gives a transaction an empty database of its own; use a file.
Only one operation may use an ADBC connection at a time, and overlap fails promptly rather than waiting on a lock. A query that needs two simultaneous adbc_* scans of one database, such as a self-join, should attach it twice. Scans of attached tables (db.schema.table) lease their own pooled connections and are not limited this way.
Result schemas without executing SQL during planning
adbc_scan asks the driver for AdbcStatementExecuteSchema metadata. It never falls back to executing the query merely to discover its columns. For drivers without that method, including the SQLite ADBC driver, supply columns:
ATTACH ':memory:' AS db (TYPE adbc, driver 'sqlite');SELECT * FROM adbc_scan( 'db', 'SELECT 1 AS id, ''Alice'' AS name', columns := {'id': 'BIGINT', 'name': 'VARCHAR'});DETACH db;Declare the complete result in its remote column order, even when an outer DuckDB query selects only a subset. SQLite integer results map to BIGINT. The extension validates the runtime column count and checks types before converting the first nonempty batch.
adbc_scan_table obtains table metadata through AdbcConnectionGetTableSchema, then executes a generated SELECT with supported projection and filter pushdown. It also accepts a columns override. adbc_schema('db', table_name) describes a table, not an arbitrary SQL query. Metadata discovery can contact the backend during planning.
Remote tables expose no rowid column. A query that needs no columns, such as count(*), reads the first real column instead.
Read-only catalogs and shared databases
Use ATTACH ... (TYPE adbc, READ_ONLY, ...) for read-only analytics. Schemas and tables appear under the attachment name. See the cookbook for a file-backed SQLite example.
READ_ONLY rejects writes through that attachment, including adbc_execute and adbc_insert. It does not revoke the underlying database user’s write permissions. Catalog write support without READ_ONLY is driver-dependent; CALL adbc_execute runs any remote SQL.
Each SQLite :memory: connection is its own database, and catalog scans use pooled connections separate from the attachment’s own. Use a file when catalog queries (db.main.users) need to see tables created with adbc_execute.
Fresh catalog queries can see another client’s committed changes according to the backend’s transaction isolation. Data changes do not require reattaching or a cache clear. After remote DDL changes tables or columns, run CALL adbc_clear_cache() in each DuckDB client whose catalog metadata needs refreshing. This clears schema/table metadata for attached ADBC catalogs; it does not reload driver libraries or flush connection pools.
DuckDB treats an http:// or https:// ATTACH path as a remote file and asks for the httpfs extension. Pass such URIs (Trino’s, for example) as the uri option instead: ATTACH '' AS tr (TYPE adbc, driver 'trino', uri 'http://host:8080').
Driver options and secrets
Pass driver options as ATTACH options. VARCHAR and BOOLEAN values use the string option setter, integers the int64 setter, FLOAT/DOUBLE the double setter, and BLOBs the bytes setter. Nested option structures are rejected. Use numeric SQL values for options that require numeric setters, rather than quoted numbers. ATTACH option names are lowercased, so a driver option whose name needs uppercase letters has to come from a secret’s EXTRA_OPTIONS or a connection profile.
Use an ADBC secret to reuse connection details. SCOPE may be omitted when the secret has a non-empty URI; the URI then becomes its lookup scope, so ATTACH '<that uri>' AS db (TYPE adbc) finds it. An explicit SCOPE can match a different or broader prefix, and a secret with neither is rejected. Its EXTRA_OPTIONS map contains string values; supply options requiring numeric or binary setters directly as ATTACH options. Passwords and values from EXTRA_OPTIONS are redacted in DuckDB’s secret display. Other fields, including uri, are not, so avoid embedding credentials there. Secret display redaction does not redact SQL text containing credentials from application logs or query history.
adbc_insert accepts driver-specific statement options through options := {...} (a STRUCT or MAP), applied after the target table and mode. Keys adbc_insert sets itself are rejected; unknown keys surface the driver’s error.
Drivers load as native shared libraries into DuckDB. Install a driver manifest to resolve names such as sqlite, or provide the library path and, when needed, its entrypoint. Installing a Python driver package alone does not guarantee that a manifest is available to DuckDB. Load only libraries you trust.
Arrow streaming and performance
ADBC presents results as Arrow batches, which the extension pulls into DuckDB. The driver’s underlying network protocol and conversion costs depend on the backend; ADBC does not promise an Arrow wire protocol or zero serialization end to end.
Arrow type support also varies by implementation. Our DuckDB VARIANT and Arrow compatibility article follows one such type across C++, Python, Go, Rust, and Java.
Catalog scans and adbc_scan_table can push supported projections and filters into generated SQL. For arbitrary adbc_scan queries, put remote filtering in the SQL you send. Batch-size hints and early stream termination depend on driver support. Use a result-returning scan, with the appropriate schema, when inspecting a remote EXPLAIN result; adbc_execute returns an affected-row count rather than query rows.
On the DuckDB 2.0 development branch, whole-query pushdown can go beyond individual scans. The PostgreSQL pushdown benchmark explains how supported joins and aggregates become one remote SQL statement, what we measured, and when the extension falls back to local execution.
adbc_insert and INSERT / CREATE TABLE AS into an attached catalog pass DuckDB query results to the driver’s Arrow ingestion API through a bounded producer queue. Stream binding and execution run together on the consumer thread, so drivers that read their input during BindStream can ingest without blocking. Ingestion modes and transaction behavior depend on that driver’s capabilities.
Install
INSTALL adbc_scanner FROM community;
LOAD adbc_scanner;
Quick Start
Attach a SQLite database
ATTACH ':memory:' AS db (TYPE adbc, driver 'sqlite');
DETACH db;
Query SQLite with an explicit result schema
ATTACH ':memory:' AS db (TYPE adbc, driver 'sqlite');
SELECT * FROM adbc_scan('db', 'SELECT 1 AS id', columns := {'id': 'BIGINT'});
DETACH db;
Execute remote DDL and DML with CALL
ATTACH ':memory:' AS db (TYPE adbc, driver 'sqlite');
CALL adbc_execute('db', 'CREATE TABLE users(id INTEGER, name TEXT)');
CALL adbc_execute('db', 'INSERT INTO users VALUES (1, ''Alice'')');
SELECT * FROM adbc_scan_table('db', 'users');
DETACH db;
Commit and roll back with DuckDB transactions
ATTACH 'users.sqlite' AS db (TYPE adbc, driver 'sqlite');
CALL adbc_execute('db', 'DROP TABLE IF EXISTS users');
CALL adbc_execute('db', 'CREATE TABLE users(id INTEGER, name TEXT)');
BEGIN;
CALL adbc_execute('db', 'INSERT INTO users VALUES (1, ''Alice'')');
COMMIT;
BEGIN;
CALL adbc_execute('db', 'INSERT INTO users VALUES (2, ''Bob'')');
ROLLBACK;
SELECT * FROM adbc_scan_table('db', 'users'); -- Alice only
DETACH db;
Reference
Extension Contents
Quick reference to all available functions and settings organized by category.
| Name | Type | Description |
|---|---|---|
|
ADBC Catalog
|
||
| adbc | Object type: Catalog | Attach an ADBC database as a DuckDB catalog with table discovery, supported projection/filter pushdown, and Arrow result streams. |
|
Connection
Inspect attached ADBC databases, list connection profiles, and refresh attached catalog metadata. Connect with ATTACH … (TYPE adbc) and disconnect with DETACH. |
||
| adbc_clear_cache() | Object type: Table function | Use CALL to invalidate schema and table metadata for attached ADBC catalogs after remote DDL. |
| adbc_info() | Object type: Table function | Return driver and server metadata for an attached ADBC database — vendor name, version strings, supported features. |
| adbc_profiles() | Object type: Table function | List locally discoverable ADBC connection profiles. |
|
Database Connection
|
||
| adbc | Object type: Secret | Stored credentials and connection details for ADBC drivers. |
|
Mutation
Execute remote DDL/DML with CALL adbc_execute, or ingest Arrow batches with adbc_insert. Both join an open BEGIN … COMMIT transaction. |
||
| adbc_execute() | Object type: Table function | Use standalone CALL to execute remote DDL or DML at runtime on an attached ADBC database. |
| adbc_insert() | Object type: Table function | Pass a DuckDB query's Arrow batches to the driver's bulk ingestion API. |
|
Query
Read arbitrary SQL results with adbc_scan or named tables with adbc_scan_table. |
||
| adbc_scan() | Object type: Table function | Run SQL on an attached ADBC database and stream its result as a DuckDB table. |
| adbc_scan_table() | Object type: Table function | Read a named table using GetTableSchema metadata and a generated SELECT with supported projection and filter pushdown. |
|
Schema
Inspect tables, columns, table types, and named-table Arrow schemas through driver metadata APIs. |
||
| adbc_columns() | Object type: Table function | Describe the columns of a table — name, type, nullability, default — pulled from the driver's catalog API. |
| adbc_schema() | Object type: Table function | Describe a named table through the driver's GetTableSchema metadata API. |
| adbc_table_types() | Object type: Table function | List the table-type categories the driver/server distinguishes (e.g. |
| adbc_tables() | Object type: Table function | List tables visible to an attached ADBC database. |
No extension contents match that search.
API Reference
Function Reference
Database Storage
Storage Extensions
Catalog implementations that attach external storage as a DuckDB database.
adbc
Description
Attach an ADBC database as a DuckDB catalog with table discovery, supported projection/filter pushdown, and Arrow result streams. The alias is also the first argument of every adbc_* function; DETACH disconnects. BEGIN … COMMIT groups catalog writes with adbc_execute and adbc_insert, and READ_ONLY rejects all of them. CALL adbc_clear_cache refreshes metadata after remote DDL. Pass http(s) URIs (e.g. Trino) as the uri option rather than the ATTACH path, which DuckDB would treat as a remote file.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
driver
|
VARCHAR
|
Optional | Driver name resolved through installed manifests, or shared-library path. Required unless supplied by a secret or profile. |
uri
|
VARCHAR
|
Optional |
Connection URI passed to the driver, overriding the ATTACH path. Use it for http:///https:// URIs.
|
entrypoint
|
VARCHAR
|
Optional |
Custom driver entrypoint function name. Only needed for drivers that don't follow the standard AdbcDriverInit symbol convention.
|
search_paths
|
VARCHAR
|
Optional |
Extra colon-separated directories to look in for ADBC driver manifests (*.toml) and connection profiles, in addition to the platform defaults.
|
profile
|
VARCHAR
|
Optional |
Name of an ADBC connection profile supplying the driver and options. Equivalent to an ATTACH path of profile://<name>.
|
use_manifests
|
VARCHAR
|
Optional |
Default: true
Set to 'false' to skip manifest resolution entirely (load the driver only by absolute path).
|
batch_size
|
INTEGER
|
Optional | Hint for the number of rows per Arrow batch when scanning. Larger values reduce per-batch overhead at the cost of memory. |
secret
|
VARCHAR
|
Optional | Name of an ADBC secret containing driver and connection options. Without it, a secret whose scope prefixes the ATTACH path's URI is used automatically. |
Examples
Attach a SQLite file, then commit and roll back remote writes
ATTACH 'users.sqlite' AS db (TYPE adbc, driver 'sqlite');
CALL adbc_execute('db', 'DROP TABLE IF EXISTS users');
CALL adbc_execute('db', 'CREATE TABLE users(id INTEGER, name TEXT)');
BEGIN;
CALL adbc_execute('db', 'INSERT INTO users VALUES (1, ''Alice'')');
COMMIT;
BEGIN;
CALL adbc_execute('db', 'INSERT INTO users VALUES (2, ''Bob'')');
ROLLBACK;
SELECT * FROM adbc_scan_table('db', 'users'); -- Alice only
DETACH db;
Attach PostgreSQL for analytics (requires a configured database and authentication)
ATTACH 'postgresql://reader@localhost/analytics' AS pg (
TYPE adbc, READ_ONLY, driver 'postgresql'
);
SELECT user_id, COUNT(*) AS events
FROM pg.public.activity
WHERE ts >= CURRENT_DATE - 7
GROUP BY user_id;
DETACH pg;
Read an existing SQLite application database (replace the path)
ATTACH '/data/app.db' AS local_app (TYPE adbc, READ_ONLY, driver 'sqlite');
SHOW TABLES FROM local_app.main;
DETACH local_app;
Attach through a connection profile (requires mydb.toml in a profile search path)
ATTACH 'profile://mydb' AS mydb (TYPE adbc);
SELECT * FROM adbc_info('mydb');
DETACH mydb;
Security
Secrets
DuckDB secrets for storing the credentials and keys used by the adbc scanner extension.
adbc
Description
Stored credentials and connection details for ADBC drivers. Reference a secret by name with ATTACH's secret option, or let URI scope lookup pick it when its scope prefixes the ATTACH path.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| Parameter scope | Type VARCHAR | Required Optional |
Description
URI prefix identifying the secret's scope. Defaults to URI when omitted; a secret with neither is rejected. Set it explicitly to match a different or broader prefix.
|
| Parameter driver | Type VARCHAR | Required Required |
Description
ADBC driver name (e.g. 'sqlite', 'postgresql') or path to a shared library.
|
| Parameter uri | Type VARCHAR | Required Optional | Description Connection URI passed to the driver. Driver-specific format. |
| Parameter username | Type VARCHAR | Required Optional | Description Database username. |
| Parameter password | Type VARCHAR | Required Optional | Description Database password. This field is redacted in DuckDB's secret display, not necessarily in SQL logs. |
| Parameter database | Type VARCHAR | Required Optional | Description Database name, when not encoded in the URI. |
| Parameter entrypoint | Type VARCHAR | Required Optional | Description Custom driver entrypoint function name (rarely needed). |
| Parameter extra_options | Type MAP(VARCHAR, VARCHAR) | Required Optional | Description Driver-specific string options. Values are redacted in secret display. Pass numeric or binary options directly as ATTACH options. |
Examples
Create and use a SQLite secret by name
CREATE SECRET my_sqlite (TYPE adbc, DRIVER 'sqlite', URI ':memory:');
ATTACH '' AS db (TYPE adbc, secret 'my_sqlite');
SELECT * FROM adbc_scan('db', 'SELECT 1 AS id', columns := {'id': 'BIGINT'});
DETACH db;
DROP SECRET my_sqlite;
Omit SCOPE: the secret's URI becomes its scope, so ATTACH finds it from the path
CREATE SECRET app_db (TYPE adbc, DRIVER 'sqlite', URI 'app.sqlite');
ATTACH 'app.sqlite' AS app (TYPE adbc);
SELECT * FROM adbc_scan('app', 'SELECT 1 AS id', columns := {'id': 'BIGINT'});
DETACH app;
DROP SECRET app_db;
Practical Examples
Cookbook
Real-world recipes and patterns for common use cases.
The examples below use the attached-database API described in Technical Details. Install the SQLite ADBC driver and its manifest before running examples with driver 'sqlite'; alternatively use its shared-library path.
Create and query a SQLite table
ATTACH ':memory:' AS db (TYPE adbc, driver 'sqlite');CALL adbc_execute('db', 'CREATE TABLE users(id INTEGER, name TEXT)');CALL adbc_execute('db', 'INSERT INTO users VALUES (1, ''Alice''), (2, ''Bob'')');
-- SQLite requires explicit result columns for arbitrary SQL.SELECT * FROM adbc_scan( 'db', 'SELECT id, name FROM users WHERE id = ?', params := row(1), columns := {'id': 'BIGINT', 'name': 'VARCHAR'});
-- Scanning a named table can obtain its schema from table metadata.SELECT * FROM adbc_scan_table('db', 'users');SELECT * FROM adbc_schema('db', 'users');DETACH db;Bulk insert from a DuckDB query
ATTACH ':memory:' AS db (TYPE adbc, driver 'sqlite');SELECT * FROM adbc_insert( 'db', 'users', (SELECT * FROM (VALUES (1::BIGINT, 'Alice'), (2::BIGINT, 'Bob')) AS source(id, name)), mode := 'create');SELECT * FROM adbc_scan_table('db', 'users');DETACH db;Commit and roll back
DuckDB’s own BEGIN, COMMIT, and ROLLBACK control the remote transaction. Use a file: the transaction writes on a separate connection, and each SQLite :memory: connection is its own database.
ATTACH 'users.sqlite' AS db (TYPE adbc, driver 'sqlite');CALL adbc_execute('db', 'DROP TABLE IF EXISTS users');CALL adbc_execute('db', 'CREATE TABLE users(id INTEGER, name TEXT)');BEGIN;CALL adbc_execute('db', 'INSERT INTO users VALUES (1, ''Alice'')');COMMIT;BEGIN;CALL adbc_execute('db', 'INSERT INTO users VALUES (2, ''Bob'')');ROLLBACK;SELECT * FROM adbc_scan_table('db', 'users'); -- Alice onlyDETACH db;Mix catalog writes and remote SQL in one transaction
Catalog writes (INSERT INTO app.main.…) and adbc_execute commit or roll back together. The seed row matters for SQLite: its driver reports column types from stored values, so an empty table’s TEXT column would describe itself as an integer.
ATTACH 'app.sqlite' AS app (TYPE adbc, driver 'sqlite');CALL adbc_execute('app', 'DROP TABLE IF EXISTS messages');CALL adbc_execute('app', 'CREATE TABLE messages (id INTEGER, body TEXT)');CALL adbc_execute('app', 'INSERT INTO messages VALUES (1, ''hello'')');BEGIN;CALL adbc_execute('app', 'INSERT INTO messages VALUES (2, ''pending'')');INSERT INTO app.main.messages VALUES (3, 'also pending');ROLLBACK; -- discards both writesSELECT * FROM app.main.messages; -- only the seed rowDETACH app;Attach an existing SQLite file for analytics
Replace the path with a SQLite file containing your application tables. Each :memory: connection has its own database; use a file when catalog queries need to see the same data.
ATTACH '/data/app.db' AS local_app (TYPE adbc, READ_ONLY, driver 'sqlite');SHOW TABLES FROM local_app.main;SELECT * FROM local_app.main.users;DETACH local_app;Platform Support
Compatibility
Extension availability may vary by platform and DuckDB version. Check below to ensure this extension supports your environment before installation.
Quick Facts
Platforms
- Linux x86_64 aarch64
- Linux (musl) Not available
- macOS Intel Apple Silicon
- Windows x86_64
- WASM Not available
Compiled binary sizes
| Platform | Architecture | Size |
|---|---|---|
| Linux | x86_64 | 11.14 MB |
| Linux | aarch64 | 9.87 MB |
| macOS | Intel | 8.47 MB |
| macOS | Apple Silicon | 7.46 MB |
| Windows | x86_64 | 7.64 MB |
Compressed download size from the Haybarn extension repository.
DuckDB & Haybarn
Release calendar- DuckDB v1.5.5 Haybarn 1.5.5-rc1 Supported