duckdb#
Idiomatic OCaml bindings to DuckDB, the in-process
OLAP database — what sqlite3 is to SQLite.
FFI mechanism: ctypes#
These bindings talk to DuckDB's C API through
ctypes (with ctypes-foreign
for dynamic linking). There is no hand-written C stub layer — ctypes
expresses the foreign function surface directly in OCaml.
The bindings are generated, not hand-written#
The low-level binding surface is generated from the DuckDB C API, not maintained by hand. This is the whole point of the project: when DuckDB ships a new release (e.g. the upcoming 2.x), we regenerate the bindings from its headers instead of hand-patching them.
The generator itself is a pure OCaml library, driven by a small CLI.
Project layout#
| Path | Role |
|---|---|
lib/duckdb_c/ |
Low-level ctypes FFI surface. Generated, not hand-written. |
lib/duckdb/ |
High-level, idiomatic OCaml API consumers program against. |
lib/duckdb_bindgen/ |
Pure code generator: DuckDB C API → ctypes bindings source. |
bin/duckdb_bindgen/ |
climate-based CLI that drives the generator. |
lib/duckdb_shell/ |
Pure shell domain for oduck: output renderers, settings, dot-command + SQL-statement parsing. |
lib/duckdb_cli/ |
climate-based CLI shell for oduck (argv, REPL) over duckdb_shell. |
bin/duckdb_cli/ |
The oduck binary — a DuckDB SQL shell mirroring the duckdb CLI. |
test/parity/ |
run.sh — diffs oduck output against the real duckdb CLI. |
test/ |
alcotest test harness (qcheck available for property tests). |
vendor/ |
Pinned, prebuilt DuckDB library + header (provisioned, not built). |
scripts/ |
fetch-duckdb.sh — downloads/verifies/unpacks the prebuilt. |
Vendored DuckDB library#
The bindings compile against duckdb.h and link against a prebuilt libduckdb
shared library. We do not vendor DuckDB source. Instead a single pinned,
checksum-verified prebuilt archive is downloaded into vendor/duckdb/ by
scripts/fetch-duckdb.sh, and lib/duckdb_c/dune splices the resulting
compile/link flags in via (:include ...).
The pinned version lives in exactly one place — vendor/duckdb.version — so a
future DuckDB 2.x bump is a one-line change. Supported platforms are Linux and
macOS; Windows is out of scope. See vendor/README.md for
the full vendoring and opam packaging strategy.
This is currently a scaffold: the libraries contain placeholder modules so the tree builds and links. Real bindings are not implemented yet.
Build & test#
The prebuilt DuckDB library must be provisioned first (one-time, re-run on a version bump):
./scripts/fetch-duckdb.sh # download + verify + unpack into vendor/duckdb/
dune build
dune runtest
dune exec duckdb-bindgen -- --help
oduck — a DuckDB SQL shell#
oduck is a command-line SQL shell built on these bindings, an OCaml twin of the
official duckdb CLI. It is a thin climate
shell (lib/duckdb_cli/) over a pure rendering/parsing domain
(lib/duckdb_shell/) over the bindings — the same DDD layering the project uses
elsewhere.
oduck -c "SELECT 42 AS answer" # run SQL and exit
oduck mydb.db -c "SELECT * FROM t" # against a file-backed database
oduck mydb.db # interactive REPL (in-memory if omitted)
echo "SELECT 1;" | oduck # read a script from stdin
oduck --csv -c "SELECT * FROM t" # choose an output mode
Output modes (--csv --json --line --markdown --list --table --column --box, default box) reproduce the corresponding duckdb mode, and the
interactive/piped REPL supports the common dot-commands (.mode, .headers,
.separator, .nullvalue, .maxrows, .tables, .schema, .databases,
.read, .output, .help, .quit).
Parity with the real CLI#
Correctness is defined as byte-for-byte agreement with the installed duckdb
CLI, which is used as the oracle. test/parity/run.sh feeds the same input to
both binaries across a corpus × output-mode matrix (and piped dot-command
scripts) and diffs stdout/stderr and exit codes:
bash test/parity/run.sh # skips gracefully if `duckdb` is not on PATH
All covered cases match exactly. Documented v1 gaps (reported by the harness's
informational section, not silently dropped): the duckbox row-elision footer
spacing past maxrows + 3, embedded control characters in markdown/table,
terminal-width column elision, codepoint-based (not East-Asian) display width,
and --csv-style flag spelling (vs duckdb's -csv). The interactive REPL is
line-oriented; a mosaic full-screen front-end is a planned enhancement.