duckfn 0.0.18

Write DuckDB extensions in plain Rust: attribute macros that turn ordinary functions into scalar/aggregate/table functions, SQL macros and nested LIST/MAP/ARRAY/STRUCT types.
Documentation
//! 宿主文件系统(DuckDB 的 VFS)访问,以及基于它的便捷文件读写。
//!
//! Host file system (DuckDB's VFS) access, plus convenience file reads and writes on top of it.
//!
//! 这里的所有读写都经 DuckDB 的**虚拟文件系统**,而不是 `std::fs`:本地磁盘、内存文件系统、
//! `httpfs` 的 `s3://` / `http(s)://` 走同一条通路,`wasm32-unknown-emscripten` 下也能落到宿主
//! 真正的文件系统上。
//!
//! Everything here goes through DuckDB's **virtual file system**, not `std::fs`: local disk,
//! in-memory file systems and `httpfs`' `s3://` / `http(s)://` take the same path, and under
//! `wasm32-unknown-emscripten` it reaches the host's real file system.
//!
//! # 两层接口 / Two layers
//!
//! - **底层**:[`with_file_system`] / [`file_system`] / [`client_context`] 现取 DuckDB 的
//!   [`FileSystem`] / [`ClientContext`]([`DuckFileSystem`] / [`DuckClientContext`] 是持有连接的
//!   guard),自己决定打开选项与读写节奏。
//! - **便捷层**:[`read`] / [`read_string`] / [`read_lines`] / [`write()`] / [`write_string`] /
//!   [`append_string`] / [`size`] / [`exists`] 等一行式接口,调用方只表达意图(覆盖 / 不许覆盖 /
//!   追加,文本 / 字节,见 [`WriteMode`])。
//!
//! - **Low level**: [`with_file_system`] / [`file_system`] / [`client_context`] take DuckDB's
//!   [`FileSystem`] / [`ClientContext`] as needed ([`DuckFileSystem`] / [`DuckClientContext`] are
//!   guards that keep the connection alive), leaving open options and I/O pacing to the caller.
//! - **Convenience**: [`read`] / [`read_string`] / [`read_lines`] / [`write()`] / [`write_string`] /
//!   [`append_string`] / [`size`] / [`exists`] and friends — callers state intent (replace / fail if
//!   exists / append, text or bytes; see [`WriteMode`]).
//!
//! # 为什么需要「注册期捕获」/ Why registration time matters
//!
//! DuckDB 的 C API 只在部分回调里交出客户端上下文,**聚合函数一个都没有**(没有 bind 回调,也没有
//! `duckdb_aggregate_function_get_client_context`);而注册期那条连接是借来的,注册一结束就被断开。
//! 所以 duckfn 在扩展加载时([`crate::register_all_duckfn`])打开一条**自有长连接**存进进程级静态,
//! 之后任何回调都能从它现取句柄。详见 [`DuckFileSystem`]。
//!
//! DuckDB's C API hands out a client context in some callbacks only, and **aggregate functions have
//! none** (no bind callback, no `duckdb_aggregate_function_get_client_context`); the connection the
//! entry point receives is borrowed and disconnected as soon as registration returns. duckfn
//! therefore opens an **owned, long-lived connection** at extension load
//! ([`crate::register_all_duckfn`]) and stores it in a process-level static, so any later callback
//! can take fresh handles from it. See [`DuckFileSystem`].
//!
//! # 调用方不必关心的事 / What callers do not have to care about
//!
//! - **覆盖一个更长的旧文件**:C API 没有 truncate(只有 「需要时新建」,映射到 `O_TRUNC` /
//!   `CREATE_ALWAYS` 的那个标志只在 C++ 侧)。[`WriteMode::Replace`] 的语义是「文件内容精确等于这次
//!   写进去的字节」,内部必要时先用一条零行 `COPY ... TO` 清零,再写正文。
//! - **路径转 C 字符串**:含 NUL 字节的路径在入口就被拒绝。
//! - 这些实现细节将来会随 DuckDB C API 的完善而整体替换,**对外行为不变**。
//!
//! - **Overwriting a longer file**: the C API has no truncate (only "create if needed"; the flag
//!   that maps to `O_TRUNC` / `CREATE_ALWAYS` exists on the C++ side only). [`WriteMode::Replace`]
//!   means "the file holds exactly the bytes written this time": internally a longer file is zeroed
//!   with a zero-row `COPY ... TO` first, then the contents are written.
//! - **Paths become C strings**: a path containing a NUL byte is rejected at the entry point.
//! - These internals will be swapped out wholesale once DuckDB's C API grows the pieces; the
//!   observable behaviour stays the same.
//!
//! # 前置条件与限制 / Prerequisites and limits
//!
//! - 需要 DuckDB 1.5.0+ 与本 crate 的 `owned-connection` feature(它自己依赖 `duckdb-1-5`)。
//! - 进程级只有一份入口,以**首个完成注册的实例**为准;需要按实例隔离时,手写适配器并自持连接。
//! - **没有删除**:C API 既没有 remove 也没有 move,DuckDB 也没有 `remove_file` 函数。
//! - 每次取用都会拿那条自有连接上的互斥锁:**同一线程别嵌套取用**(会死锁),并发取用之间串行。
//! - **不能用在「同一个线程里同步执行 DuckDB」的运行时里**:注册期就在当前线程上打开连接并重入
//!   引擎,那类运行时(单线程 wasm、DuckDB-Wasm 的 Node blocking 绑定)会直接死锁,扩展 LOAD /
//!   注册卡住不动。Node 的 worker 模式、浏览器与原生 CLI 都不受影响。
//! - **浏览器 / DuckDB-Wasm 上文件系统不可靠**(实测):对任意**不存在**的路径,DuckDB-Wasm 的 FS
//!   都返回一条 1 字节 `\0` 的幻影条目 —— 连 DuckDB 自带的 `glob` / `read_text` / `file_size` 都
//!   谎报其存在,故 `exists()`(以「能否只读打开」判定)在 wasm 上恒真,且没有任何 FS 原语能区分
//!   「不存在」(不是 duckfn 能修的 bug)。duckfn 裸 `FileHandle` 的 create/write 偏移也坏(写出的文件
//!   多一字节 / 错位)。DuckDB 的 `COPY … TO` 只能把查询结果按格式(CSV/JSON/parquet)导出 —— 它能
//!   往返,但**存不了一个任意长字符串(如 HTML)的原样字节**(CSV 会加引号/换行),所以 wasm 上本就
//!   没有通用的“把内容写进文件”路子。因此 duck_vfs 已从 `all` 移除,**只在 native 需要时开 `owned-connection`**。
//!   native CLI 与真实磁盘上语义全部正确(见 `test/sql/functions/duck_vfs.test`)。
//!
//! - Requires DuckDB 1.5.0+ and this crate's `owned-connection` feature (which itself needs
//!   `duckdb-1-5`).
//! - One process-level entry, first instance to finish registering wins; for per-instance isolation,
//!   hand-write an adapter and keep your own connection.
//! - **No delete**: the C API offers neither remove nor move, and DuckDB has no `remove_file`.
//! - Every take-up locks the mutex on that owned connection: **never nest it on one thread** (that
//!   deadlocks), and concurrent take-ups serialize.
//! - **Must not be used in a runtime that executes DuckDB synchronously on one thread**: the
//!   connection is opened, and the engine re-entered, on the current thread during registration, so
//!   such a runtime (single-threaded wasm, DuckDB-Wasm's Node blocking bindings) deadlocks and the
//!   extension LOAD just hangs. Node's worker mode, the browser and the native CLI are unaffected.
//! - **The file system is unreliable under the browser / DuckDB-Wasm build** (measured): for ANY
//!   non-existent path DuckDB-Wasm's FS returns a phantom one-byte `\0` entry — even DuckDB's own
//!   `glob` / `read_text` / `file_size` report it as present — so [`exists`] (which decides by
//!   "can it be opened read-only") is always true there and no FS primitive can tell "absent"
//!   apart (a platform limit, not something the extension can fix). duckfn's raw `FileHandle`
//!   create/write offset is also broken on wasm (the written file gains / misorders a byte). DuckDB's
//!   `COPY … TO` only exports query results in a format (CSV/JSON/parquet) — it round-trips, but it
//!   **cannot store an arbitrary string (e.g. HTML) as its raw bytes** (CSV adds quoting/newlines),
//!   so there is no general "write this content to this file" path on wasm at all. Hence duck_vfs is
//!   no longer in `all` — enable `owned-connection` on native when you actually need it. On the
//!   native CLI and a real disk everything is correct (see `test/sql/functions/duck_vfs.test`).

mod capture;
mod file;

// 注册期捕获:由 `register_all_duckfn` 调用,不对用户公开。
//
// Registration-time capture: called by `register_all_duckfn`, not part of the public API.
pub(crate) use capture::capture;

pub use capture::{
    DuckClientContext, DuckFileSystem, client_context, file_system, with_file_system,
};
pub use file::{
    WriteMode, append, append_string, exists, read, read_lines, read_string, read_string_lossy,
    size, write, write_string, write_string_with, write_with,
};

// 出现在上面这些签名与用法里的 quack-rs 类型:从这里再导出,下游不必直接依赖 quack-rs。
//
// The quack-rs types appearing in those signatures and usages, re-exported here so downstream code
// does not have to depend on quack-rs directly.
pub use quack_rs::client_context::ClientContext;
pub use quack_rs::error_data::ErrorData;
pub use quack_rs::file_system::{FileFlag, FileHandle, FileOpenOptions, FileSystem};