Skip to main content
This page is a reference for C++ extension authors. For the step-by-step tutorial, see Creating Extensions in C++. For custom column types, see Custom Types in C++.
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 via make_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.
Communicate success, NULL, warning, or error by calling one terminal method on the result type: 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.
3. For string results, write into out.buffer() and call out.set_length(n). Check out.buffer().size() before writing.
  • out.buffer() returns a Span<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.
4. Pass error messages to 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 via is_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) or out.set_length(n) after writing into out.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 of out.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:
For 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 with make_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:
Extension functions are called in a per-row execution model:
  • Each function call processes one row with its own result buffer (thread-safe)
  • prerun/postrun provide 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
Best practice: Design functions to be stateless for simplicity and safety.

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 under villagesql/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:
None of these headers are pulled in by <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.
Preview headers are not stable. An extension built against them may break when the server is updated. When a feature stabilizes, its headers move to a versioned stable C++ SDK path.

Triggers

Triggers fire on tables with custom type columns. The trigger body can reference non-custom-type columns from NEW and OLD. Accessing custom type column values inside a trigger body is not yet supported.