{"id":51121797,"url":"https://github.com/tegmentum/ducklink-extension","last_synced_at":"2026-06-25T03:30:30.484Z","repository":{"id":366930965,"uuid":"1277584140","full_name":"tegmentum/ducklink-extension","owner":"tegmentum","description":"Run duckdb:extension WebAssembly components inside DuckDB (community extension)","archived":false,"fork":false,"pushed_at":"2026-06-23T22:37:31.000Z","size":57,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-24T00:21:25.622Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tegmentum.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-23T02:45:23.000Z","updated_at":"2026-06-23T22:37:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tegmentum/ducklink-extension","commit_stats":null,"previous_names":["tegmentum/ducklink-extension"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/tegmentum/ducklink-extension","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tegmentum%2Fducklink-extension","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tegmentum%2Fducklink-extension/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tegmentum%2Fducklink-extension/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tegmentum%2Fducklink-extension/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tegmentum","download_url":"https://codeload.github.com/tegmentum/ducklink-extension/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tegmentum%2Fducklink-extension/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34758773,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-25T03:30:29.165Z","updated_at":"2026-06-25T03:30:30.476Z","avatar_url":"https://github.com/tegmentum.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ducklink (DuckDB community extension)\n\nRun WebAssembly **component** extensions inside DuckDB.\n\nA `duckdb:extension` component is built once and runs unmodified on every\nplatform DuckDB supports — no per-platform native extension builds. `ducklink`\nembeds [wasmtime](https://wasmtime.dev), loads the component, runs its `load()`\nto discover the functions it registers, and bridges them into DuckDB's catalog.\n\n```sh\n# Name the component(s) to expose, then LOAD ducklink registers their functions.\nDUCKLINK_COMPONENTS=sample=/path/sample_extension.wasm \\\n  duckdb -unsigned -c \"LOAD 'ducklink.duckdb_extension'; SELECT sample_plus_one(41);\"\n```\n\nCatalog registration happens at extension-load time (DuckDB's model), so the\ncomponents are named up front via `DUCKLINK_COMPONENTS` (a `:`-separated list of\n`name=path` or `path`) rather than an in-query `CALL`.\n\n## Three deployment scenarios, one component\n\nThe same `duckdb:extension` component artifact, built once, runs unmodified in\nthree deployments:\n\n1. **Native DuckDB + the `ducklink` extension** (*this crate*) — native DuckDB\n   loads `ducklink`, which embeds the wasmtime WebAssembly runtime and runs the\n   component inside the native process. Lets a single portable component extend\n   DuckDB on any platform without per-platform native extension builds.\n2. **Standalone WebAssembly DuckDB** — the `ducklink` host runs\n   DuckDB-compiled-to-WebAssembly and loads components alongside it, as a native\n   CLI/server. WebAssembly throughout, no native DuckDB.\n3. **WebAssembly DuckDB in a web browser** — the same WebAssembly DuckDB build,\n   running extension components directly in-browser (the `web/` build). Extensions\n   ship and run client-side with zero install.\n\nScenario 1 is \"embed WebAssembly into native DuckDB\"; scenarios 2 and 3 are\n\"run a WebAssembly DuckDB that hosts WebAssembly extensions\" — natively and in\nthe browser respectively.\n\nAll three share the\n[`ducklink-runtime`](https://github.com/tegmentum/ducklink/tree/main/crates/ducklink-runtime)\nengine crate (consumed here as a pinned git dependency): the `duckdb:extension`\nwasmtime bindings, the neutral `reg::*` registration model, and the callback\nregistry. A component therefore loads identically in every scenario.\n\n## Layout\n\n- `src/engine.rs` — the direction-agnostic engine glue: `Engine2::load` loads a\n  component, runs its `load()`, and returns the functions it registered;\n  `dispatch_scalar_batch` / `dispatch_table` / `dispatch_aggregate` route a DuckDB\n  invocation back into the component through the shared callback registry. Depends\n  only on `ducklink-runtime` + wasmtime, so it builds and is checked **without**\n  the DuckDB toolchain.\n- `src/reg_duckdb.rs` — the DuckDB sink (behind the `duckdb-api` feature): turns\n  the functions a component registered into real DuckDB scalar / table / aggregate\n  functions and marshals each call across the WIT boundary. Scalars and tables use\n  the safe duckdb-rs `VScalar` / `VTab` APIs; aggregates use the raw C aggregate\n  API (duckdb-rs has no safe wrapper). Every FFI entry point is wrapped so a panic\n  surfaces as a query error rather than aborting the host process.\n- `src/lib.rs` — the `loadable` module (behind the `loadable` feature): the\n  `ducklink_init_c_api` entry point plus a built-in `ducklink_version()` scalar.\n- `tests/` — `bridge_coverage.rs` (end-to-end against an in-process DuckDB) and\n  `scenario1_corpus.rs` (the prebuilt component corpus).\n- `benches/` — criterion benchmarks of the scalar dispatch hot path\n  (`scalar_dispatch`, `scalar_query`).\n\n## Build\n\nThe `loadable` feature is on by default, so a plain release build produces the\nloadable artifact for the **native** host triple via the DuckDB Rust C Extension\nAPI (`build: cargo`) — exactly what the community-extensions CI runs:\n\n```\ncargo build --release\n```\n\nTo check just the direction-agnostic engine glue against `ducklink-runtime` —\nwithout the DuckDB toolchain — disable the default feature:\n\n```\ncargo check --no-default-features    # engine.rs only, no DuckDB\n```\n\nThe community-extensions CI builds it with the `rust` and `python3` toolchains.\nIt is excluded from the `wasm_*` platforms (it embeds a JIT) and from the\nstatic-musl / mingw triples.\n\n## Status\n\nWorking and verified end-to-end against a real in-process DuckDB (the `bundled`\ntest): `LOAD ducklink` loads each `DUCKLINK_COMPONENTS` entry, registers its\nfunctions, and `SELECT fn(x)` dispatches every row into the wasm component\n(`SELECT sample_plus_one(41)` → 42, computed in wasm). `SELECT ducklink_version()`\nis a built-in that needs no component, so it confirms the extension loaded.\n\nCoverage:\n- **Scalar functions** — any arity, all logical types\n  (`INT64`/`UINT64`/`DOUBLE`/`BOOLEAN`/`VARCHAR`/`BLOB`). One dynamic `WasmScalar`\n  serves every signature (the per-function signature is fed to the static\n  `VScalar::signatures()` via a thread-local set during registration). NULL\n  inputs follow SQL semantics — a row with any NULL argument yields NULL — and the\n  chunk is marshalled column-major into a reused buffer, so steady-state\n  evaluation allocates no per-row memory.\n- **Table functions** — `SELECT * FROM sample_emit_sequence(5)` streams rows from\n  the component through a `VTab` bridge.\n- **Aggregate functions** — bridged through the raw C aggregate API\n  (init/update/combine/finalize over per-group state). The loadable entry point\n  takes the `duckdb_database` DuckDB hands it and opens a raw sibling connection\n  on it, so aggregates register database-wide alongside scalars and tables. NULL\n  inputs are skipped, per SQL aggregate semantics.\n\nThe bridge is covered by a bundled test suite (per-type marshalling, NULL\npropagation, multi-chunk evaluation, multi-component registration, concurrency,\nand the aggregate path) and dispatch benchmarks:\n\n```\ncargo test  --no-default-features --features bundled        # full suite\ncargo bench --no-default-features --bench scalar_dispatch   # hot-path micro-bench\n```\n\nPackaging the `loadable` cdylib (which exports `ducklink_init_c_api`) as a\nloadable `.duckdb_extension` — the metadata footer + a DuckDB-version-matched\nbuild — is handled by the community-extensions CI.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftegmentum%2Fducklink-extension","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftegmentum%2Fducklink-extension","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftegmentum%2Fducklink-extension/lists"}