Skip to main content

Project structure

src/lib.rs native crate root -> mod extension;
src/wasm_lib.rs wasm crate root -> mod extension; (the same set of mods, mirrored)
src/extension/mod.rs -> duckfn_entrypoint!("duckfn_quantstats");
src/bin/duckfn.rs duckfn CLI entry -> #[path] mod extension; + duckfn::cli::run(...)
(only serves `just docs_csv`, not part of the extension runtime)

src/extension/functions/mod.rs -> mod aggregate_html; mod translation;
src/extension/functions/aggregate_html/
mod.rs the two SQL names / one signature each, and the mod list
html_returns.rs qs_html_reports (the return branch)
html_prices.rs qs_html_reports_by_prices (the price/NAV branch, differenced in the tail)
kind.rs one path's SQL-side name: the macro-generated `SQL_NAME`
series.rs the internal point type, series building, price differencing
slots.rs the argument slots: the symbol table plus "parse each symbol's options once"
report.rs the tail: render one report per (symbol, benchmark), translate it when asked,
persist, open the browser, fill in the path
naming.rs the report file name: `<time>-<strategy>-<benchmark>[-<random>].html`
(persistence and the temporary file share the stem)
storage.rs persistence: pick a free name, write with std::fs (skipped entirely on wasm)
browser.rs opening in the system default browser (the whole feature is ignored on wasm)
src/extension/functions/translation/
mod.rs the `lang` option's feature: what is translatable, and where the table lives
keys.rs the catalog: every translatable position's key, CSS selector and English text
builtin/ the six built-in languages (en carries notes only; the others a text plus a note)
table.rs the process-level table: install at load time, read, rewrite
set.rs qs_set_translation(lang, entries) -> BOOLEAN
list.rs qs_list_translations()
report.rs the one lol_html rewrite (DOM mapping, the date template, month casing)
src/extension/types/
html_report_options.rs the named STRUCT type `qs_html_report_options`
html_report.rs the result row type `QuantstatsHtmlReport` (no named type registered)
translation.rs `TranslationEntry` (the named type `qs_translation_entry`) and the list row

test/sql/quantstats/ SQLLogicTest files
demo/prices.csv the committed market snapshot
scripts/release.sh version bump, tag, development version
Justfile the everyday commands
docs/ this documentation site
community-extension/ the community-extension registration draft

Both crate roots attach the same module tree:

The two crate roots​

src/lib.rs and src/wasm_lib.rs both declare exactly one module, mod extension;, and extension/mod.rs attaches everything else. The official Rust template instead writes mod lib; and forwards it a second time from the wasm root, which breaks as soon as modules nest (error[E0583]: file not found for module …): there would be two copies of the same path set to keep in sync.

Adding a module therefore means editing extension/mod.rs (and the mod.rs of the layer below), never the crate roots.

The command-line bin​

src/bin/duckfn.rs compiles the extension a second time with #[path = "../extension/mod.rs"] mod extension; and calls duckfn::cli::run(...). It exists to export the function-description CSV (just docs_csv) and takes no part in the extension itself.

The #[path] attribute is not a shortcut, it is necessary: the documentation metadata behind #[duck_*] is collected by inventory's static constructors, which only fire for object files that are really linked into the final binary. With use duckfn_quantstats::… the linker may drop those modules and the exported CSV comes out empty — silently. See Function descriptions.

Naming rules​

RuleWhy
The extension name is duckfn_quantstats, and identical in five places.It is the entry-point symbol and the artifact file name; DuckDB looks the symbol up by the file name.
Every registered SQL name carries the qs_ prefix.DuckDB has no namespaces, and community extensions almost never put the package name into function names — but a shared prefix is what makes the two names findable in duckdb_functions(). See the conventions in AGENTS.md.
src/lib.rs and src/wasm_lib.rs always declare the same set of mods.Otherwise the wasm build fails to compile the module tree.
output_dir takes a directory, never a file path.The function names the files; with one instrument against several benchmarks a caller-built path would necessarily overwrite itself (see Design notes).
Temporary files (scripts, data, logs) go to target/.target/ is git-ignored and never pollutes the tracked tree.
Text files use LF.The repository stores LF.

Where the development notes are​

The design notes the docs site does not cover from the user's side — why the function groups by symbol, how the benchmark pairing works, which dependency carries which part — live in Design notes and Dependencies.

duckfn's own conventions (the entry-point chain, the standard procedure for adding a function, which source to consult before writing against the macros) are not repeated here: they live in the repository's AGENTS.md.

Since duckfn 0.0.11 its documentation, plus a runnable example extension and its SQLLogicTest files, ship inside the crate package, so they always match the version in Cargo.toml and need no clone of the duckfn repository:

# after any build: the sources cargo actually compiled against
ls -d ~/.cargo/registry/src/*/duckfn-*/
Path under that directoryWhat it is
docs/docs/**The user guide's text (English), including the chapter per registration kind.
docs/i18n/zh-Hans/…/current/**The same guide in Simplified Chinese.
src/extension/**The example extension: one file per registration kind, plus custom types and a combined demo.
test/sql/**SQLLogicTest files for the example, worth copying the structure of.

The rendered guide is also online — shijianjs.github.io/duckfn — but it may be newer than your dependency; the copy in the registry is what this project is compiled against.