Curious why the API looks the way it does? Read Happy Path, Escape Hatch,
and the Space Between for the
design philosophy behind the typed argument/result API and lower-level hooks like
prerun() and varargs.VDF Function Contracts
These contracts govern how VDF implementation functions interact with the VEF runtime. Every function registered viamake_func<> must follow them.
The types referenced below are available via #include <villagesql/vsql.h>.
1. VDF implementation functions are void — they never return a value.
out.set(...) / out.set_length(n), out.set_null(),
out.warning(msg), or out.error(msg).
2. Check input.is_null() before calling input.value().
If is_null() returns true, calling value() is undefined behavior.
out.buffer() and call out.set_length(n). Check out.buffer().size() before writing.
out.buffer()returns aSpan<char>over the server-managed buffer.out.set_length(n)records how many bytes were written.out.buffer().size()is the maximum capacity. Always check it before writing.
out.error(msg). The message is truncated to VEF_MAX_ERROR_LEN (512 bytes) if necessary.
out.error(msg) accepts a std::string_view. It copies the message into a
server-managed buffer and sets the result state to error in one call.
Implement Functions
Implementation functions use typed argument and result types:Handling NULL Values
Check for NULL viais_null() and return NULL by calling set_null().
NULL handling options:
- Input NULL check:
input.is_null() - Return NULL:
out.set_null() - Return value:
out.set(v)(numeric/custom) orout.set_length(n)after writing intoout.buffer()(string) - Return warning:
out.warning(msg)— returns NULL for this row, adds a SQL warning, continues execution; in strict mode, MySQL promotes this to an error on INSERT/UPDATE. Call instead ofout.set(), not in addition to it. - Return error:
out.error(msg)— aborts statement execution
Error Handling
Return errors with custom messages for validation failures or invalid input:Per-Statement State with Prerun/Postrun
Register hooks with.prerun<>() and .postrun<>(). The required signatures are:
PrerunArgs and PostrunArgs method details, see
Per-Statement State
in the Development guide.
Most extensions don’t need prerun/postrun hooks. The C++ SDK automatically
handles common cases like type checking and result buffer sizing — for both
STRING-returning and CUSTOM-returning VDFs, the result buffer is grown to fit
the resolved return type before the VDF body runs. Use prerun/postrun only when
you need expensive per-statement setup (like opening connections) that
shouldn’t happen per-row.If you find you need prerun/postrun for your use case, share your scenario on
the VillageSQL Discord — the team may be able
to add C++ SDK support to handle it automatically.
Aggregate Functions
Built-in aggregates COUNT(DISTINCT), MIN, MAX, and GROUP_CONCAT work with custom types out of the box. MIN and MAX require a compare function registered on the type. Custom aggregate VDFs are also supported. Register one withmake_aggregate_func<State, &result_fn>("name"), then chain .returns(),
.param(), .clear<>(), and .accumulate<>() before calling .build().
Both .clear<>() and .accumulate<>() are required. See
Aggregate VDFs for the
builder API and callback signatures.
Built-in aggregate operations with custom types:
- Each function call processes one row with its own result buffer (thread-safe)
prerun/postrunprovide per-statement setup/teardown- Avoid global state - use function parameters and return values instead
- If you must use global state, protect it with mutexes/locks
Window Functions
The following window functions work with custom types:Temporary Tables
Custom types work in temporary tables.CREATE TEMPORARY TABLE, INSERT,
and ALTER TABLE behave the same as with permanent tables.
Preview APIs
Some VEF capabilities are available as opt-in headers undervillagesql/preview/ in the C++ SDK include tree. The ABI and API are still
under active development and may change without notice.
To opt in, add the include to your extension source. For example:
<villagesql/vsql.h>; you must include it
directly when you opt in.
Namespace layout under vsql::preview is per-capability — there is no single
universal pattern. The keyring API uses vsql::preview_keyring::KeyringCapability;
the thread worker API uses vsql::preview_thread_worker::ThreadWorkerCapability;
the SQL query API uses vsql::preview_sql_query::SqlQueryCapability and must be
opened from a background worker thread handle (vef_thread_handle_t *).
Check each header for the exact namespace and class name it defines.
For the full preview API documentation, see Preview Capabilities.
Triggers
Triggers fire on tables with custom type columns. The trigger body can reference non-custom-type columns fromNEW and OLD. Accessing custom
type column values inside a trigger body is not yet supported.

