examples/ directory also carries one example per supported preview capability.
Source: examples/ in vsql-rust-sdk
vsql_rot13 — Function-Only Extension
The simplest possible Rust extension: one VDF that takes a STRING and returns a STRING. Usage:Directory Structure
Implementation
File:src/lib.rs
- VDFs receive
&[InValue]and returnVdfReturn— both are safe Rust enums - NULL is a first-class variant on both sides; pattern-match it directly
- The
extension!macro generates the C entry points the server calls at load time func!declares the SQL signature; argument and return types usevillagesql::Type::*
Manifest
File:manifest.json
vsql_rational — Custom Type with Arithmetic
A complete custom type: rational numbers stored as(numerator, denominator) in reduced form, with arithmetic functions, ordering, and hashing.
Usage:
Binary Storage Format
rational stores 16 bytes (little-endian):
- Bytes 0–7: numerator (
i64) - Bytes 8–15: denominator (
i64)
Type-System Functions
File:src/lib.rs
The type registers four operations: encode (string → bytes), decode (bytes → string), compare (for ORDER BY), and hash (for indexing).
VDF Implementations
VDFs that take a custom type receiveInValue::Custom(&[u8]) and decode the bytes themselves:
Registration
Theextension! macro registers both the type and its functions in a single declaration:
villagesql::custom!("name")references a custom type as an argument or returncustom_type!registers the type alongside its encode/decode/compare/hash functionsdefault: "0/1"is the intrinsic default — the server callsencode()on this string at type initialization, so it must be a valid valuepersisted_lengthmust match the byte lengthencode()returnsdeterministic: truelets the optimizer fold constant calls
vsql_agg_sum — Aggregate Function
An aggregate VDF that reimplementsSUM over an INT column. It shows the three hooks an aggregate needs — clear, accumulate, and the result function — and how each one sees the same accumulator.
Usage:
Accumulator Lifecycle
The accumulator is one value per statement, reused across every group. The server drives it in a fixed order:clear resets the accumulator at the start of each group. A field it forgets to reset leaks from the previous group.
The server calls accumulate for every row, including rows where the argument is NULL. Skipping NULLs is the function’s job: match only the variant you want and ignore the rest.
Implementation
File:src/lib.rs
seen flag is what distinguishes a group that summed to zero from a group with nothing to sum. Without it, an empty or all-NULL group would return 0 where built-in SUM returns NULL.
Registration
- The first identifier is the result function, not the row function — an aggregate’s per-row work lives in
accumulate: state:names the accumulator type, which must implementDefault- The declared parameter list is the per-row argument list:
[villagesql::Type::Int]is whataccumulatereceives, and the return type is what the result function produces agg_func!also acceptsbuffer_size:anddeterministic:afteraccumulate:, supplied together, in that order
vsql_varargs — Varargs Function
Four VDFs that each take any number of arguments. Together they cover the three registration formsvarargs_func! supports — stateful with a prerun, prerun-only, and bare — plus validation of custom-typed arguments.
Usage:
#1 prefix is the per-statement call counter, which climbs across the rows of one statement:
Prerun Owns All Varargs Validation
For a varargs function the server does no argument checking at all — not the count, not the types. A declared signature is what normally makes the server reject a bad call before your code runs, and a varargs function has none. Whatever the prerun hook does not reject reaches the row function. Prerun rejects the call, once, before any row: it sees the argument types the optimizer resolved and fails the statement. The row function still has to handle each value, because a column whose type passed validation can still carry NULL on any given row. A prerun rejection fails statement initialization:arg_count is registered bare, so a zero-argument call is legal:
Implementation
File:src/lib.rs
A prerun receives PrerunArgs and a PrerunResult<T> whose T matches the state type. PrerunArgs::len() is the argument count, and type_at(i) returns the type of argument i as an ArgType:
request_buffer_size, scaled to args.len() — a fixed buffer_size can’t grow with the argument count.
ArgType exposes four predicates — is_int(), is_real(), is_str(), and is_custom() — so a prerun can accept a heterogeneous call as long as every argument is one of the shapes the row function handles. describe accepts any mix of the three scalars and rejects anything else:
() because this prerun keeps nothing: it validates and sizes the buffer, and never calls set_state.
Custom-Typed Varargs
is_custom() alone only says the argument is some custom type. custom_name() returns which one, so a prerun can restrict a varargs call to a single type. The extension registers a point2d custom type and accepts a variable number of point2d values:
InValue::Custom(b) and decodes the bytes itself, the same as any other custom-typed VDF.
Registration
describe, point_path, and point2d’s encode/decode/compare follow the same InValue-matching and byte-encoding patterns already shown for str_join and rational above — see examples/vsql_varargs/src/lib.rs in the Rust SDK repo for their full source.
Key patterns:
[..]in place of a parameter list is what marks the function varargs- Three forms, each with a different row-function signature:
state:+prerun:givesfn(&mut State, &[InValue]) -> VdfReturn;prerun:alone and the bare form both givefn(&[InValue]) -> VdfReturn - Only the
state:form allocates and drops per-statement state - The return type is still declared, so only the argument list is variable
- Each form also accepts
buffer_size:anddeterministic:as a trailing pair point2dregisters nohash, which is optional —comparealone is enough forORDER BY
Key Implementation Patterns
Testing
All four examples use MTR (the MySQL Test Runner) just like C++ extensions:--record.
Next Steps
Creating Extensions in Rust
SDK installation, build, and the extension! macro
Rust Custom Types
Deep dive on encode, decode, compare, and hash
Rust API Reference
InValue, VdfReturn, and the macro surface
Example Source
Complete source for all four examples

