villagesql crate API. For the getting started tutorial, see Creating Extensions in Rust. For custom types, see Custom Types in Rust.
InValue
InValue is the enum the server passes for each function argument. Your function receives args: &[InValue] and must check each argument before using its value.
Always match on
Null explicitly. Calling .unwrap() or pattern-matching only the value variants is a bug — SQL NULL is a normal input, not an error.
VdfReturn
VdfReturn is what your function returns to the server. Construct it with one of the associated functions:
Warning vs error:
Use
warning for user-input validation failures where continuing with the rest of the result set makes sense. In strict mode, MySQL promotes warnings to errors on INSERT and UPDATE. Use error for conditions where proceeding is unsafe — corrupt stored data, internal invariant violations. A fatal error aborts the entire statement.
extension! macro
extension! generates the VEF entry points the server calls when loading your VEB file. It must appear exactly once in the crate.
types: and requires: are optional on their own, but funcs: must always be present — write funcs: [] for a type-only extension. A pure-function extension omits types:. An extension! block with funcs: [] and no types is valid but produces an extension that does nothing.
requires: declares the preview capabilities the extension uses, as references to static capability objects. It must come last, after a funcs: section — include funcs: [] if the extension registers no functions.
func! macro
func! declares a SQL-callable function. Six forms — four without per-statement state (no parameters, buffer_size only, deterministic only, both), and two that attach per-statement state through a prerun function:
The
buffer_size parameter requires the villagesql crate 0.0.2 or
later. The current crates.io release
(0.0.1) doesn’t expose it — until 0.0.2 ships, use the forms without
buffer_size.
Type constants for use in
func!:
Per-statement state
Some functions need state that spans every row of a single statement — a call counter, an accumulator. Declare the state type withstate: and a setup function with prerun:. The prerun function runs once, before the first row; the row function then runs once per row with &mut access to that state.
A prerun function has the signature fn(PrerunArgs, PrerunResult<T>), and the row function it feeds takes the state first: fn(state: &mut T, args: &[InValue]) -> VdfReturn. T is the type named by state:, and the compiler checks that the prerun and the row function agree on it.
PrerunArgs::len() is the number of arguments each row will receive, and PrerunArgs::is_empty() is true when the function was called with no arguments.
You must not free the state yourself: func! generates the postrun that drops it when the statement ends. This is the opposite of the C++ SDK, where your postrun has to call delete_state<T>() — see Per-Statement State.
The
state and prerun parameters are not in a published release yet. The
current crates.io release (0.0.1)
doesn’t expose them.call_index() runs three times and returns 1, then 2, then 3 — one value per row. SUM adds those three values, which gives 6.
The second SELECT returns the same total as the first, not a larger one: the counter is allocated for one statement and dropped when it finishes.
agg_func! macro
agg_func! declares an aggregate SQL function — SUM/COUNT-style, called over the rows of each group rather than once per row. Two forms:
agg_func! is not in a published release yet. The current
crates.io release (0.0.1) doesn’t
expose it.agg_func! generates both the prerun that creates it and the postrun that drops it, so you never write either. clear_fn is what gives you per-group behavior: with GROUP BY, the same accumulator is reused across groups, so any field that must not leak between groups has to be reset there.
A complete SUM-equivalent aggregate — the vsql_agg_sum example in the SDK repo:
accumulate matching only InValue::Int is what skips NULLs, matching built-in SUM. The seen flag is what makes an all-NULL group and an empty group return NULL rather than 0:
varargs_func! macro
varargs_func! declares a VDF that accepts any number of arguments, of any type. The parameter list is written [..] — a required literal, not the [] used for a zero-arity func!.
Six forms — three shapes, each with a shorthand and a full form that adds buffer_size and deterministic together (never singly):
The bare form has no validation and accepts a zero-argument call — a legitimate choice for a function that’s total over every input, but it means the row function alone is responsible for every input it can be handed. Only the
state: form allocates and drops per-statement state; the prerun-only form uses PrerunResult<()> and stores nothing, so there is no postrun for it — such a prerun uses PrerunResult only for error and request_buffer_size, never set_state.
Inspecting argument types in a prerun
Because the server validates nothing, a varargs prerun needs to see the argument types before the first row runs.PrerunArgs::type_at provides that view, alongside the len()/is_empty() and the PrerunResult methods described under Per-statement state.
Pair
is_custom() with custom_name() to accept exactly one custom type: is_custom() alone accepts every custom type in the server.
varargs_func! and PrerunArgs::type_at are not in a published release
yet. The current crates.io release
(0.0.1) doesn’t expose them.vsql_varargs example in the SDK repo declares one function per form. A stateful varargs function, validated in prerun and carrying a per-statement call counter:
str_join still matches on InValue in the row function even though the prerun proved every argument is a string: prerun sees declared types, not values, and a STRING column can carry NULL on any given row.
describe (a prerun-only function that rejects zero arguments and non-scalar arguments, then formats a heterogeneous argument list) and point_path (which validates with is_custom() and custom_name()). See examples/vsql_varargs/src/lib.rs in the Rust SDK repo.
custom_type! macro
custom_type! registers a new column type. type_name, persisted_length, max_decode_buffer_length, encode, decode, and compare are required. hash and default are optional but recommended.
The
default field is not a column default value — it’s a startup probe. The server calls encode(default) when loading the extension to verify the callback works. If encode returns Err for the default, the extension fails to load.
custom! macro
villagesql::custom!("type_name") references a custom type by name in a func! declaration:
villagesql::Type::* would appear in a parameter list or return type position. The string must match the type_name declared in the corresponding custom_type!.
manifest.json fields
Every extension needs amanifest.json alongside its Cargo.toml:
name validation rules: must start with a letter, end with a letter or digit, max 64 characters. An invalid manifest causes INSTALL EXTENSION to fail.
