Documentation Index
Fetch the complete documentation index at: https://villagesql.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
Overview
VillageSQL’s extension framework allows you to add custom functionality to the database server. Build custom extensions using the VEF SDK and extension template.What is a VillageSQL Extension?
A VillageSQL extension is packaged as a VEB file (VillageSQL Extension Bundle) containing:- Manifest - Metadata about the extension (name, version, description)
- Shared library - Compiled C++ code implementing the functionality
- Optional metadata - Additional resources or configuration
- C++ API for defining types and functions
- Automatic registration without SQL scripts
- Type-safe function wrappers
- Builder pattern for extension definition
VDFs vs Traditional UDFs: Functions registered through the VEF SDK are called VDFs (VillageSQL Defined Functions). VillageSQL also supports traditional MySQL UDFs registered via
CREATE FUNCTION ... SONAME, but the VEF SDK approach is recommended for new extensions.Calling VDFs in SQL
VDFs can be called with or without the extension prefix:- System functions (built-in MySQL functions like
NOW(),CONCAT()) - UDFs (traditional MySQL user-defined functions)
- VDFs (extension functions) - only if there’s exactly one function with that name
- Stored functions (created with
CREATE FUNCTION)
- Use
extension.function_namewhen multiple extensions provide functions with the same name - Use unqualified names for cleaner code when there’s no ambiguity
- Qualification is never required if only one extension provides that function name
- Custom functions (VDFs) - SQL functions with automatic type checking and validation
- Custom data types - New column types like COMPLEX, UUID, or VECTOR that work with ORDER BY and indexes
- Type operations - Encode, decode, compare, and hash functions for custom types
Prerequisites
Before you begin, build VillageSQL from source — extensions link against the server’s SDK headers and build tree. Follow the Clone and Build from Source guide first. You also need:- Git - For cloning and version control
- CMake 3.16 or higher - Build system
- C++ Compiler - GCC 8+, Clang 8+, or MSVC 2019+ with C++11 support
- Basic C++ knowledge - Understanding of C++ and function pointers
Step 1: Get the Extension Template
You can start with the extension template in two ways:Option A: Use Template from VillageSQL Source
If you have VillageSQL source code, the template is included:Option B: Fork from GitHub
Start by forking the VillageSQL extension template repository:-
Visit the template repository on GitHub:
- Click the “Fork” button to create your own copy
-
Clone your fork locally:
Step 2: Update the Manifest
Editmanifest.json to define your extension’s metadata:
$schema field is optional but enables IDE autocomplete and inline validation
for all manifest fields.
manifest.json Schema
| Field | Required | Format | Description |
|---|---|---|---|
name | ✅ Yes | letters, digits, _, - | Unique identifier. Must match INSTALL EXTENSION name. |
version | ✅ Yes | MAJOR.MINOR.PATCH | Semantic version string |
description | No | String | Brief explanation of functionality |
author | No | String | Author name or organization |
license | No | String | License identifier (GPL-2.0 recommended) |
name: Must start with a letter and end with a letter or digit. May contain lowercase letters, digits, underscores, and hyphens. Max 64 characters. Use underscores — hyphens require backtick quoting in SQL.version: Must follow semantic versioning (e.g., 1.0.0, 0.2.1)- Invalid manifest will cause
INSTALL EXTENSIONto fail
Step 3: Implement Your Extension with VEF SDK
The VEF SDK provides a C++ API for defining extensions using a fluent builder pattern:- Type-safe function definitions with compile-time checking
- Automatic argument validation and type conversion
- Support for custom types with compare/hash functions (enables ORDER BY and indexes)
Include VillageSQL Headers
Create your main extension file (e.g.,src/extension.cc) and include the VEF SDK:
Define Your Extension
Use theVEF_GENERATE_ENTRY_POINTS() macro to define your extension:
make_func<&impl>("name")- Create function with implementation pointer.returns(type)- Set return type (STRING, INT, REAL, or custom type name).param(type)- Add parameter (maximum 8 parameters).buffer_size(size_t)- Request specific output buffer size for STRING/CUSTOM returns.deterministic(bool = true)- Declare that this function always returns the same output for the same inputs and has no side effects. Default is non-deterministic..prerun<func>()- Set per-statement setup function (optional).postrun<func>()- Set per-statement cleanup function (optional).build()- Finalize function registration
Parameter Limit: Functions support a maximum of 8 parameters (defined by
kMaxParams). If you need more, consider using structured types or multiple functions.VDFs with Custom Type Arguments and Return Values
A VDF can take and return custom type values using.param(TYPE_NAME) and
.returns(TYPE_NAME) in the builder. The implementation uses CustomArg
for input and CustomResult for output — the same wrappers used for type
operations:
.param(COMPLEX) and .returns(COMPLEX):
CustomArg/CustomResult API, including CustomArgWith<P>
and CustomResultWith<P> for parameterized types.
Deterministic Functions
By default, VDFs are registered as non-deterministic. A non-deterministic function is blocked from three SQL contexts: generated columns, CHECK constraints, and expression default values (DEFAULT (expr) on a column) — using any of these features with a non-deterministic VDF returns an error. If your function always produces the same output for the same inputs and has no side effects, you can declare it deterministic by adding .deterministic() to the builder chain.
The optimizer may use this information to evaluate the function once per statement and reuse the value across rows, rather than calling it per row. Incorrectly marking a non-deterministic function as deterministic can therefore cause the server to return the same result for inputs that should produce different outputs. Only add .deterministic() when your function truly has no dependency on external state, randomness, or time. See the MySQL UDF Characteristics documentation for more details on why and when determinism is required.
Builder signature: .deterministic(bool d = true) — the zero-argument form defaults to true.
Example:
complex_add is declared deterministic, it can be used in a generated column definition:
Custom Buffer Sizes
For functions returning variable-length data, request a specific buffer size:Request sufficient buffer size via
.buffer_size() based on your function’s maximum output size.Critical API 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 defined in #include <villagesql/extension.h>.
Part A: VDF Function Contracts
1. VDF implementation functions arevoid — they never return a value.
The server calls your function through this typedef:
result->type.
2. Set result->type to exactly one of the four result constants.
Four constants exist in vef_return_value_type_t:
| Constant | Value | Meaning |
|---|---|---|
VEF_RESULT_VALUE | 0 | Success — the output is in the appropriate union field |
VEF_RESULT_NULL | 1 | The result is SQL NULL |
VEF_RESULT_WARNING | 2 | Row-level warning — execution continues, NULL is returned for this row, and a SQL warning is added. In strict mode, MySQL promotes this to an error on INSERT/UPDATE. |
VEF_RESULT_ERROR | 3 | Fatal error — statement execution is aborted; message is in result->error_msg |
VEF_RESULT_VALUE is the single success
constant for strings, integers, reals, and custom types alike. The type of
output is determined by which union field you write to (str_buf,
int_value, real_value, bin_buf).
3. Check input->is_null before reading any other field on an input argument.
If is_null is true, every other field on that vef_invalue_t is undefined.
Accessing str_value, int_value, or any other field without checking
is_null first is undefined behavior.
result->str_buf and set result->actual_len. Check result->max_str_len before writing.
result->str_bufis a server-managed buffer. Write your output here.result->actual_lenmust be set to the number of bytes written. There is no field namedstr_lenon the result struct.result->max_str_lenis the size ofstr_bufin bytes. Always check it before writing. Do not usesizeof(result->str_buf).
result->error_msg, not to result->str_buf. Use VEF_MAX_ERROR_LEN (512 bytes) as the snprintf size limit.
error_msg is a separate, caller-provided buffer dedicated to error text.
It is independent of str_buf. The maximum size including the null
terminator is VEF_MAX_ERROR_LEN (512).
Part B: Encode/Decode Return Convention
The bool return convention applies only to encode and decode function pointers used by custom types. It does not apply to VDF-name type operations (which use the standard VDFvoid convention) or to VDF implementation functions.
bool where:
false= success (the operation completed without error)true= error (the conversion failed)
Implement Wrapper Functions
Implementation functions use the VEF API signature:Handling NULL Values
Check for NULL via theis_null flag and return NULL by setting the result type:
- Input NULL check:
if (input->is_null) - Return NULL:
result->type = VEF_RESULT_NULL - Return value:
result->type = VEF_RESULT_VALUE+ write to type-specific buffer - Return warning:
result->type = VEF_RESULT_WARNING+ warning message inresult->error_msg(returns NULL for this row, adds SQL warning, continues execution; in strict mode, MySQL promotes this to an error on INSERT/UPDATE) - Return error:
result->type = VEF_RESULT_ERROR+ error message inresult->error_msg
Error Handling
Return errors with custom messages for validation failures or invalid input:VEF_RESULT_VALUE- SuccessVEF_RESULT_NULL- NULL valueVEF_RESULT_WARNING- Row-level warning (returns NULL, adds SQL warning, continues execution; strict mode promotes to error on INSERT/UPDATE)VEF_RESULT_ERROR- Fatal error (aborts statement execution)
Step 4: Creating Custom Types
Custom types allow you to define new column types that work seamlessly with SQL operations like ORDER BY, GROUP BY, and indexes.Define Type Operations
Every custom type needs encode, decode, and compare operations, and optionally a hash operation. There are two ways to provide these: VDF names (VEF_PROTOCOL_2 or later, recommended) or function pointers. For each operation, provide one or the other — not both. VDF names — reference VDFs from the same extension by name (see VDF-Name Type Operations):false = success, true = error — see Part B):
Register the Type
Using VDF names (VEF_PROTOCOL_2 required; see VDF-Name Type Operations):.func() in the same extension.
Using function pointers:
VDF-Name Type Operations
Each VDF named via.encode(), .decode(), .compare(), or .hash() must be registered with the matching make_type_* builder in the same extension:
| Operation | Builder | VDF SQL Signature |
|---|---|---|
encode | make_type_encode<&f>("name", TYPE) | (STRING) -> CUSTOM(this type) |
decode | make_type_decode<&f>("name", TYPE) | (CUSTOM(this type)) -> STRING |
compare | make_type_compare<&f>("name", TYPE) | (CUSTOM(this type), CUSTOM(this type)) -> INT |
hash | make_type_hash<&f>("name", TYPE) | (CUSTOM(this type)) -> INT |
bool return convention (false = success, true = error), same as function-pointer encode/decode. See development.mdx — Type Operation Builders for the full C++ signatures.
ALTER TABLE and Custom Types
ALTER TABLE ... MODIFY COLUMN and CHANGE COLUMN enforce these rules when custom types are involved:
| From | To | Result |
|---|---|---|
| Non-custom | Custom | Error: Cannot convert column 'col' to custom type 'MYTYPE' |
| Custom | String type | Allowed |
| Custom | Non-string type | Error: Cannot convert custom type column 'col' to non-string type |
| Custom | Different custom type | Error if incompatible: Cannot convert between incompatible custom types 'A' and 'B' |
Type Conversion Functions
Expose your encode/decode functions as SQL functions for explicit type conversion:The
.from_string<>() and .to_string<>() builders automatically create SQL-callable conversion functions using your encode/decode implementations.INSERT INTO t (val) VALUES ('(1.0,2.0)') works without an explicit call. But
expressions that resolve to STRING type — CASE expressions, CONCAT, and
similar — are not implicitly coerced. Wrap them with the from_string VDF:
Example: COMPLEX Type
Here’s a complete example implementing a COMPLEX number type:VDFs in Generated Columns
VDFs can be used in generated column expressions. The VDF must be declared.deterministic() in the extension builder — the server blocks non-deterministic functions in this context.
complex_abs must be registered with .deterministic(). Traditional MySQL UDFs are not permitted in generated columns.VDFs in Functional Indexes
VDFs can be used in functional index expressions. The same.deterministic()
requirement from generated columns applies here because MySQL implements
functional indexes as hidden generated columns.
WHERE,
ORDER BY, or GROUP BY. Cast the comparison value to the VDF’s return type
so the optimizer matches the expression:
Step 5: Update Build Configuration
EditCMakeLists.txt to build your extension as a VEB file:
VillageSQLExtensionFrameworkprovides CMake helpers for building extensionsVEF_CREATE_VEB()packages your library, manifest, and metadata into a.vebarchive- The framework automatically detects MySQL/VillageSQL build flags
- Library target name is typically
extension(can be anything) - VEB name must match your manifest.json name
- By default, extensions build against the stable ABI headers. Set
-DVSQL_USE_DEV_ABI=ONto build against the unstable dev headers instead
Step 6: Create a Build Directory
Create a separate build directory:Step 7: Build with CMake and Make
Configure and build your extension:- Compiled shared library (
.sofile) - VEB package (
.vebfile) - a tar archive containing manifest and library
Verify the Build
Check the contents of your VEB file:Step 8: Install and Test
Option A: Install to VillageSQL Extensions Directory
Use the install target to copy the VEB to your VillageSQL installation:.veb file to the directory configured via VillageSQL_VEB_INSTALL_DIR.
Option B: Manual Installation
Copy the VEB file manually:Test Your Extension
-
Connect to VillageSQL:
-
Install the extension:
-
Verify installation:
-
Test your functions:
Creating Tests
Add test files to validate your extension works correctly:-
Create a test file in
test/t/: -
Generate expected results:
-
Run tests:
Advanced Topics
Per-Statement State with Prerun/Postrun
For VEF SDK functions that need setup/cleanup per SQL statement (not per row):Most extensions don’t need prerun/postrun hooks. The VEF SDK automatically handles common cases like type checking and buffer allocation. 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, please share your scenario on the VillageSQL Discord - the team may be able to add SDK support to handle it automatically.
Aggregate Functions
Supported 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.
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.
Troubleshooting
Extension Won’t Load
Check the error log and verify the VEB contents:Function Not Found
Verify installation and registration:Build Errors
Example Extensions
Learn from existing VillageSQL extensions:vsql_complex
Complex number data type implementation
vsql_extension_template
Minimal template for creating extensions
Next Steps
Using Extensions
Learn how to install and manage extensions
Clone and Build from Source
Build VillageSQL from source code
GitHub
Contribute to VillageSQL Server
MySQL UDF API
MySQL UDF API reference documentation

