Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

adbc-driver-quack

An Apache Arrow ADBC driver for DuckDB's Quack remote protocol.

PyPI PyPI downloads Python versions Go module CI GitHub Repo License: MIT

Returns Apache Arrow RecordBatches directly from a remote DuckDB server speaking Quack. Supports the standard ADBC bulk-ingest path (Statement.BindStreamAPPEND_REQUEST) for fast column-oriented loads.

Distributed as:

  • a Go module — github.com/gizmodata/adbc-driver-quack
  • a pip install adbc-driver-quack wheel for Python (macOS / Linux / Windows × x64 / arm64)

Status: Alpha — v0.1.0-alpha.1 is the first release. The companion gizmodata/quack-jdbc JDBC driver is the same protocol from the JVM and is at v0.1.0-alpha.1 on Maven Central.

Quickstart

1. Start a Quack server (any DuckDB v1.5.3+)

-- in any DuckDB session — Quack is a core extension as of v1.5.3,
-- so no `core_nightly` repository or `-unsigned` flag is needed.
INSTALL quack;
LOAD quack;
CALL quack_serve('quack:localhost:9494', token=>'my-secret-token');

The server stays running until the DuckDB session exits. Press Ctrl-C in the DuckDB REPL to stop it.

Note: quack_serve accepts shorter forms — 'quack:localhost' uses the default port, and a bare quack_serve() with no first arg uses localhost as the host. We keep the explicit localhost:9494 form throughout this README so the client-side URI maps obviously to what the server is bound to.

If localhost ever gives you a connection refused (rare, but it can happen on a system whose /etc/hosts is set up such that the server binds one address family and the client dials the other), use 127.0.0.1 on both sides.

2. Install the driver

Python:

pip install adbc-driver-quack

Go:

go get github.com/gizmodata/adbc-driver-quack@latest

3. Connect and query

import adbc_driver_quack.dbapi as quack
import pyarrow

with quack.connect(
    uri="quack://localhost:9494",
    db_kwargs={"adbc.quack.token": "my-secret-token"},
) as conn, conn.cursor() as cur:
    cur.execute("SELECT 42 AS answer, 'hello duckdb' AS greeting")
    table: pyarrow.Table = cur.fetch_arrow_table()
    print(table)

The result is a real pyarrow.Table — pass it straight to Polars, Pandas, DuckDB-in-process, ibis, or anything else that consumes Arrow:

import polars as pl
df = pl.from_arrow(table)

Alternative: drive adbc_driver_manager directly

If you prefer the adbc-quickstarts idiom — passing the driver to adbc_driver_manager.dbapi.connect rather than going through our wrapper — point at the bundled shared library via _driver_path():

from adbc_driver_manager import dbapi
import adbc_driver_quack

with dbapi.connect(
    driver=adbc_driver_quack._driver_path(),
    entrypoint="QuackDriverInit",
    db_kwargs={
        "uri": "quack://localhost:9494",
        "adbc.quack.token": "my-secret-token",
    },
) as conn, conn.cursor() as cur:
    cur.execute("SELECT 42 AS answer")
    table = cur.fetch_arrow_table()

Both styles work the same on the wire — pick whichever reads better for your codebase.

Streaming large result sets

Cursor.fetch_record_batch() returns a pyarrow.RecordBatchReader that pulls one server-side DataChunk per read_next_batch() call. Memory stays bounded by the server's chunk size (~2k rows) even when the result is millions of rows:

with conn.cursor() as cur:
    cur.execute("SELECT * FROM lineitem")  # arbitrary size
    reader = cur.fetch_record_batch()
    for batch in reader:
        process(batch)  # one ~2k-row Arrow batch at a time

Bulk ingest (Arrow → DuckDB)

import pyarrow as pa
import adbc_driver_quack.dbapi as quack

table = pa.table({"id": [1, 2, 3], "name": ["alice", "bob", "carol"]})
with quack.connect(
    uri="quack://localhost:9494",
    db_kwargs={"adbc.quack.token": "my-secret-token"},
    autocommit=True,  # ADBC connections are autocommit-OFF by default;
                      # opt in here so the ingest persists on close
) as conn, conn.cursor() as cur:
    # create_append: create "customers" from the Arrow schema if it
    # doesn't exist, then append. One APPEND_REQUEST per RecordBatch.
    cur.adbc_ingest(table_name="customers", data=table, mode="create_append")

Heads-up — autocommit is off by default. Per the Python DB-API, quack.connect() opens connections inside a transaction. Without the autocommit=True above (or an explicit conn.commit()), the CREATE + append run in a transaction that is rolled back when the connection closesadbc_ingest still returns the row count it sent, but nothing persists. Prefer explicit transactions? Drop autocommit=True and call conn.commit() after adbc_ingest():

with quack.connect(uri="quack://localhost:9494",
                    db_kwargs={"adbc.quack.token": "my-secret-token"}) as conn, conn.cursor() as cur:
    cur.adbc_ingest(table_name="customers", data=table, mode="create_append")
    conn.commit()  # without this, the ingest is rolled back on close

mode accepts the four standard ADBC ingest modes:

mode behavior
create create the table (errors if it already exists), then append — this is the default when mode is omitted
append append to an existing table (no DDL; errors if missing)
replace CREATE OR REPLACE the table, then append
create_append create the table if it doesn't exist, then append

Table DDL for the create-family modes is generated from the Arrow schema. Pass db_schema_name=... to target a non-default schema.

Transactions (autocommit off)

import adbc_driver_quack.dbapi as quack

with quack.connect(
    uri="quack://localhost:9494",
    db_kwargs={"adbc.quack.token": "..."},
    autocommit=False,
) as conn, conn.cursor() as cur:
    cur.execute("INSERT INTO orders VALUES (1, 'pending')")
    cur.execute("INSERT INTO order_items VALUES (1, 'widget', 2)")
    conn.commit()  # both inserts persist atomically

Connection URL

quack://host[:port]
Option Default Notes
adbc.uri Required. Pass as the uri= kwarg to quack.connect.
adbc.quack.token (none) Authentication token. Server-side token=> argument to quack_serve().
adbc.quack.token_env (none) Environment variable to read the token from. Option only — rejected on the URL.
adbc.quack.token_file (none) Local file to read the token from. Option only — rejected on the URL.
adbc.quack.tls false true → use https:// for the underlying HTTP transport.
adbc.quack.rpc.timeout_seconds.connect 10 HTTP connect timeout, as seconds or a Go duration like 1.5s.
adbc.quack.rpc.timeout_seconds.request 60 Per-request HTTP timeout, as seconds or a Go duration like 90s.
adbc.quack.http.header.<Name> (none) Extra HTTP header sent with every request (proxy/LB auth). Repeatable; empty value clears. Option only — rejected on the URL.

Token precedence matches quack-jdbc: an explicit adbc.quack.token (or password) wins, then adbc.quack.token_env, then adbc.quack.token_file. The env/file indirections are accepted only as ADBC options, never as quack://...?tokenEnv=... URL query parameters — a pasted URL must not be able to read a local secret and send it to whatever host the URL names.

The URI is its own kwarg; everything else goes through db_kwargs:

import adbc_driver_quack.dbapi as quack

quack.connect(
    uri="quack://localhost:9494",
    db_kwargs={
        "adbc.quack.token": "my-secret-token",
        "adbc.quack.tls": "false",
    },
)

Extra HTTP headers

Gateways and load balancers in front of a Quack server often need their own auth. adbc.quack.http.header.<Name> options add static headers to every request the driver makes (mirroring the EXTRA_HTTP_HEADERS parameter of DuckDB's own quack secret):

quack.connect(
    uri="quack://gateway.example.com:443",
    db_kwargs={
        "adbc.quack.tls": "true",
        "adbc.quack.token": "my-secret-token",
        "adbc.quack.http.header.X-Proxy-Authorization": "Bearer abc123",
    },
)

Like the token indirections, header options are rejected as URL query parameters — a pasted URL cannot inject headers into your requests. The protocol-owned headers (Content-Type, Accept, Host, Content-Length) are reserved and cannot be overridden.

Connection profiles & driver manifests

ADBC connection profiles (adbc-driver-manager ≥ 1.11) let you keep a connection's driver + options in a reusable TOML file instead of code. Profiles resolve the driver by name, which requires a driver manifest on the search path. Install ours once per environment:

$ python -m adbc_driver_quack install-manifest
Wrote ADBC driver manifest: .../etc/adbc/drivers/quack.toml

(Inside a virtualenv/conda env this targets the environment's auto-searched etc/adbc/drivers/; otherwise the per-user ADBC config directory. --user, --venv, and --dir PATH override; the same is available programmatically as adbc_driver_quack.install_manifest().)

With the manifest in place, the driver manager finds the driver by name — no import of adbc_driver_quack needed:

from adbc_driver_manager import dbapi

# Resolve by URI scheme alone:
conn = dbapi.connect(uri="quack://localhost:9494")

And a profile bundles the whole connection. Drop this in ~/.config/adbc/profiles/quack_prod.toml (Linux; ~/Library/Application Support/ADBC/Profiles/ on macOS, or any directory named in ADBC_PROFILE_PATH):

profile_version = 1
driver = "quack"

[Options]
uri = "quack://prod.example.com:9494"
"adbc.quack.tls" = true
"adbc.quack.token" = "{{ env_var(QUACK_TOKEN) }}"

then connect from any ADBC driver-manager binding:

conn = dbapi.connect(profile="quack_prod")

The {{ env_var(...) }} substitution keeps secrets out of the file; options set explicitly in code still override profile values.

Why ADBC and not JDBC?

Both drivers speak the same protocol to the same kind of server. Pick the one that fits your runtime:

You're using Reach for
A JVM tool (DBeaver, IntelliJ, Spark, dbt-jdbc, plain java.sql) quack-jdbc
Python (pip install), Go, Rust, R, anything via ADBC C ABI this driver
You want zero-copy Arrow data end-to-end this driver

Repo layout

adbc-driver-quack/
├── go.mod, go.sum
├── internal/
│   ├── codec/       — BinaryReader/Writer for DuckDB BinarySerializer
│   ├── quacktype/   — Logical / physical / extra type system + codec
│   ├── message/     — DataChunk, DecodedVector, MessageCodec, VectorCodec
│   └── transport/   — QuackURI parser + net/http transport (IPv4/IPv6 fallback)
├── driver/quack/    — pure-Go ADBC Driver/Database/Connection/Statement impl
├── pkg/quack/       — cgo c-shared wrapper (produces libadbc_driver_quack.{so,dylib,dll})
├── python/          — Python wheel sources (adbc_driver_quack)
└── .github/         — CI: go test, python tests, cibuildwheel matrix, PyPI publish

The internal/ layer is a clean-room Go port of the matching Java packages in quack-jdbc.

Credits

License

MIT — see LICENSE for full attribution.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

adbc_driver_quack-0.1.0a9-py3-none-win_amd64.whl (8.6 MB view details)

Uploaded Python 3Windows x86-64

adbc_driver_quack-0.1.0a9-py3-none-macosx_26_0_universal2.whl (4.3 MB view details)

Uploaded Python 3macOS 26.0+ universal2 (ARM64, x86-64)

File details

Details for the file adbc_driver_quack-0.1.0a9-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for adbc_driver_quack-0.1.0a9-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 a3cad3dc0b0dfe856b11e99538a2056c9b5264df26c2e77faf8c70bbd779ec1e
MD5 c982d01706c8e1fc1ab803b6a0457c63
BLAKE2b-256 d13a1f547e9476e4a560a1bdeadbe666a1be6ef1044399d8173b054158f8da47

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_quack-0.1.0a9-py3-none-win_amd64.whl:

Publisher: python.yml on gizmodata/adbc-driver-quack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file adbc_driver_quack-0.1.0a9-py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for adbc_driver_quack-0.1.0a9-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 3ce5482e04db54ef75a6d024269a36467f1803d2fba3245387810ee5985609ff
MD5 317f7087325c7eb4c725b7e9f8415eec
BLAKE2b-256 d8775e6dd7ed13039e6bdcdd968838717300017516789febb86e6302bf207207

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_quack-0.1.0a9-py3-none-manylinux2014_x86_64.whl:

Publisher: python.yml on gizmodata/adbc-driver-quack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file adbc_driver_quack-0.1.0a9-py3-none-manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for adbc_driver_quack-0.1.0a9-py3-none-manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 d7d8d0e6dcc7ca8d07869d93526b28e937b5e00750fabe50135ffe0dc6b6847f
MD5 9725317906ef24a9d54379a76b8da7cc
BLAKE2b-256 1a9384d3ef04495685d0aed549724178124c802c069ba91483414c3e3fea7cd2

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_quack-0.1.0a9-py3-none-manylinux2014_aarch64.whl:

Publisher: python.yml on gizmodata/adbc-driver-quack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file adbc_driver_quack-0.1.0a9-py3-none-macosx_26_0_universal2.whl.

File metadata

File hashes

Hashes for adbc_driver_quack-0.1.0a9-py3-none-macosx_26_0_universal2.whl
Algorithm Hash digest
SHA256 e9c8e27f7dad000055f081655ef6107820ff29779aded29f864f546b374e848d
MD5 ccfc7c48d3e833f3a977170d022330c4
BLAKE2b-256 d4d5e0ea2a54dd6d47bcf49fa785c7f8a4fe2397312f24125467d54c099526be

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_quack-0.1.0a9-py3-none-macosx_26_0_universal2.whl:

Publisher: python.yml on gizmodata/adbc-driver-quack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page