Overview
VillageSQL’s extension framework (VEF) lets you add custom functionality to the database server. This guide walks through building an extension in C++ using the C++ SDK and the extension template. For writing VDF implementations in depth — argument and result types, aggregates, system variables, parameterized types — see the Development Guide.If you prefer Rust, see Creating Extensions in Rust.
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 argument and result types
- Builder pattern for extension definition
VDFs vs Traditional UDFs: Functions registered through VEF are called VDFs (VillageSQL Defined Functions). VillageSQL also supports traditional MySQL UDFs registered via
CREATE FUNCTION ... SONAME, but VDFs are 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, you need the VillageSQL Extension SDK, and there are two ways to point CMake at one. If you have a VillageSQL build tree, pass-DVillageSQL_BUILD_DIR=/path/to/villagesql/build — the Build from Source guide produces that tree. If you have an unpacked SDK directory — one containing include/villagesql/vsql.h — that is enough on its own: pass -DVillageSQL_SDK_DIR=/path/to/sdk. The extension template README covers how to get an SDK without building the server. Only the build-tree form sets a VEB install directory, so on the SDK-only path copy the built VEB file yourself into the directory SHOW VARIABLES LIKE 'veb_dir' reports.
You also need:
- Git - For cloning and version control
- CMake 3.18 or higher - Build system
- C++ Compiler - With C++17 support: the template sets
CMAKE_CXX_STANDARD 17, and the SDK’s CMake package rejects a lower standard - 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
Validation rules:
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 the C++ SDK
The C++ 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 C++ SDK headers:
<villagesql/vsql.h> header pulls in the type builder, function builder, and
extension builder, and re-exports commonly used symbols into the vsql namespace.
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.max_result_length(size_t)- Size the result column for a STRING return so a materialized result isn’t truncated at the argument width. Values aboveVEF_MAX_RESULT_LENGTH(16 MiB) are capped by the server. STRING only; call.returns(STRING)first. Requires Protocol 4 (dev ABI)..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 argument and result types 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.
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:buffer() and set_length(), never write
past buffer().size() — that overflows memory. But do not fail on a value that is
too large either: report the size you would have written and let the server grow
the buffer and call you again.
out.set(sv) follows an snprintf-style overflow contract: it
copies as many bytes as fit in out.buffer(), but reports the value’s full
size. The server detects that the reported length exceeds the buffer, grows the
buffer, and calls the function a second time — so a value larger than
.buffer_size() is returned whole rather than truncated. One retry always
suffices, because the reported length says exactly how large the buffer must be.
Growth is bounded by max_allowed_packet. A value larger than that is returned
as NULL, not truncated.
Writing into out.buffer() and calling out.set_length() yourself participates
in the same contract: report the full size you would have written, not the number
of bytes that fit. Reporting the truncated length defeats the retry and silently
drops the remainder.
That contract applies at row time. A materialized STRING result — in a
GROUP BY/DISTINCT temp table, CREATE TABLE ... SELECT, or UNION — still
truncates at the argument width unless the function declares
.max_result_length(n), which sizes the result column (in characters, capped at
VEF_MAX_RESULT_LENGTH, 16 MiB):
Request sufficient buffer size via
.buffer_size() based on your function’s maximum output size. Sizing the buffer correctly avoids the cost of a grow-and-retry round trip.Before implementing your functions, review the C++ API Reference for the complete VDF contracts: null checking, result types, buffer sizing, and error handling.
Step 4: Creating Custom Types
Custom types let you define new column types — likeCOMPLEX, UUID, or
VECTOR — that work with ORDER BY, indexes, and aggregate functions.
If your extension only registers functions, skip to Step 5.
See Custom Types in C++ for the full
implementation walkthrough. If your type takes parameters (e.g., VECTOR(1536)),
see Parameterized Types.
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
mysql-test/t/: -
Generate expected results:
-
Run tests:
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
Development Guide
Argument and result types, aggregates, system variables, and testing
Extension Architecture
Lifecycle, caching, performance, and security model
Build from Source
Build VillageSQL from source code

