>_EXECUTIVE SUMMARY
stryke-duckdb is one of the opt-in connector packages in the stryke ecosystem. Embedded DuckDB SQL engine for stryke. Direct-query parquet / CSV / JSON from disk or URL without loading, persistent .duckdb files when you need them, full standard SQL on top. Opt-in package, kept out of the stryke core binary so the daily-driver install stays slim; ships as a Rust cdylib that stryke dlopens in-process on first use DuckDB.
DuckDB is an in-process analytical SQL engine; the duckdb crate with bundled compiles libduckdb from source into one static cdylib — no system .so dep at runtime. Single-shot opt-in install.
~ARCHITECTURE
In-process cdylib design: the stryke side is a thin .stk wrapper that calls FFI symbols on the dlopen'd library. No subprocess, no pipe — calls return on the same thread that made them.
| Layer | Implementation |
|---|---|
stryke wrappers (lib/*.stk) | Thin .stk wrapper (DuckDB); exposes typed helpers that serialize args to JSON and parse the response |
cdylib (libstryke_duckdb.{dylib,so}) | Single Rust cdylib crate; every public surface fn is an extern "C" fn duckdb__<verb>(*const c_char) -> *mut c_char |
| Process model | Library is dlopen'd once per stryke session on first use DuckDB; caches one duckdb::Connection per (db, session, read_only) tuple in OnceCell<Mutex<HashMap>>; lives until the runtime exits |
| Build | Cargo with [lib] crate-type = ["cdylib"]; publish = false; the package itself ships via s pkg install -g . |
| Install path | ~/.stryke/store/duckdb@<version>/ after make install; use DuckDB from any stryke script resolves it |
| Tests | zunit-style under t/ with live-service variants when applicable |
| CI | GitHub Actions .github/workflows/ci.yml — cargo fmt + clippy + test, plus stryke pkg install verification |
$WHY OPT-IN (NOT BUILTIN)
DuckDB is an in-process analytical SQL engine; the duckdb crate with bundled compiles libduckdb from source into one static binary. Single-shot opt-in install.
The trade-off is intentional. The core stryke binary stays under ~40 MB precisely because each connector ships separately. Daily-driver work (one-liners, awk replacement, data scripting) doesn't need MongoDB drivers, AWS SDKs, or Spark Connect bindings linked in.
&FFI ENVELOPE
JSON in / JSON out across the FFI boundary. Each DuckDB::* wrapper serializes its args to a JSON dict and calls the matching duckdb__* symbol on the dlopen'd libstryke_duckdb.{dylib,so}; the cdylib returns a JSON CString the wrapper parses. catch_unwind on every call converts panics to JSON errors. The returned CString is freed via the cdylib-exported stryke_free_cstring, wired automatically by rust_ffi::load_cdylib.
# request shape (built by the .stk wrapper)
{"verb": "<op>", "...": ...}
# response shape
{"result": ...} # success — per-fn shape
{"error": "<msg>"} # failure — wrapper die()s with the message
Per-verb success shapes returned by the cdylib (the .stk wrapper unwraps each into the stryke-native type):
| Verb | Wire shape |
|---|---|
query, dump | {"columns": [...], "rows": [{col: val, ...}, ...]} |
execute | {"affected": <n>} |
exec | {"ok": true} |
import | {"table": ..., "rows": <n>} |
export | {"path": ..., "kind": ...} |
tables | {"tables": [...]} |
schema | {"table": ..., "columns": [{name, type, nullable}, ...]} |
attach | {"alias": ..., "attach_path": ..., "read_only": <bool>} |
detach | {"alias": ..., "detached": true} |
copy_from | {"table": ..., "path": ..., "copied": <n>} |
create_index | {"index": ..., "table": ..., "columns": [...]} |
drop | {"dropped": ..., "kind": ...} |
appender_columns | {"table": ..., "columns": [...], "appended": <n>} |
indexes, constraints, table_info, database_size, columns, schemas, memory, describe_query | {"columns": [...], "rows": [...]} |
checkpoint | {"ok": true, "force": <bool>} |
inspect, ping | {...} |
| any error | {"error": "<msg>"} — the wrapper dies with it |
+SURFACE INVENTORY
The package exposes 91 public DuckDB::* functions from lib/DuckDB.stk. Of these, 33 cross the FFI boundary to a duckdb__* symbol in the cdylib; the remainder are pure-stryke helpers composed over query / execute (CRUD, analytics, quote/parse). The cdylib's extern "C" exports are declared in stryke.toml under [ffi].exports and matched by extern "C" fn duckdb__<verb> definitions in src/lib.rs.
| Category | Surface |
|---|---|
| Read paths | query, query_stream, query_one, query_col, query_scalar, dump |
| DDL / DML | execute, exec_file, explain, insert_many, appender, appender_columns, import, export, copy_from, create_index, drop, drop_index, attach, detach, update, delete, truncate, upsert |
| Transactions | begin, commit, rollback, transaction |
| Metadata | version, server_version, ping, inspect, tables, databases, views, functions, settings, extensions, schema, count, exists, table_exists, table_info, indexes, constraints, database_size, all_columns, schemas, memory_usage, describe_query |
| Analytics & readers | describe, columns, column_types, summarize, head, sample, distinct, aggregate, sum_, avg_, min_, max_, group_count, create_table_as, read_parquet, read_csv, read_json, copy_to, install_extension, load_extension, pragma, set, get_setting, vacuum, checkpoint |
| Quote / parse (pure) | quote_ident, unquote_ident, quote_identifier, quote_literal, unquote_literal, quote_like, unquote_like, quote_qualified_ident, parse_qualified_ident, format_list, parse_list, format_in_list, parse_in_list, format_struct, parse_struct, format_map |
The full per-function signature listing (return shapes, option keys) is in the Docs API reference and the README's API reference section.
=CONNECTION CACHE
The cdylib caches one duckdb::Connection keyed on the (db, session, read_only) tuple in a OnceCell<Mutex<HashMap>> that lives for the life of the stryke process. Two consequences:
:memory:databases persist across calls within a session — the v1 helper binary got a fresh empty:memory:on every fork; the in-process cdylib does not.- Statements issued with the same
%optsride the same handle, sobegin/commit/rollback/transactionwork without extra FFI bookkeeping — connection affinity is implicit in the cache key.
Named sessions (session => "name") give distinct in-memory instances; the default session name is "_default". A file-backed db => "app.duckdb" shares its cached connection across every call with that same path.
%CI GATES
GitHub Actions .github/workflows/ci.yml runs: cargo check / fmt --check / clippy -D warnings / test (ubuntu + macOS), cargo doc -D warnings, a cross-target release build (linux x86_64, macOS x86_64 + aarch64), and a stryke-test job that downloads the latest strykelang release, syntax-checks every .stk file, installs the package, and runs s test t/. Documentation is pinned by shell gates under tests/ (docs HTML structure, target=_blank/rel=noopener, no inline handlers, no http:// links, no placeholder hrefs, final-newline, README/man-page structure).
/SCOPE
See the README's "Why this is a package" + "API reference" sections for the authoritative scope. The package is intentionally narrower than its underlying SDK / driver — the goal is "useful from a shell pipeline", not "complete API coverage".
#PROJECT METADATA
| Item | Value |
|---|---|
| License | MIT |
| Author | MenkeTechnologies |
| Repository | github.com/MenkeTechnologies/stryke-duckdb |
| Parent language | strykelang |
| Meta umbrella | MenkeTechnologiesMeta |
| Issues | github.com/MenkeTechnologies/stryke-duckdb/issues |