This guide covers writing VDF implementations and running regression tests for VillageSQL extensions.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.
If you are contributing to the VillageSQL server itself (not building an extension), see Clone and Build from Source, which covers the full server developer workflow including running tests with
mysql-test-run.pl directly.Setting Up Your Environment
To develop and test extensions, you need a built VillageSQL server. Follow the Clone and Build from Source guide to compile the server binaries. Once you have a build, use thevillagesql CLI to manage a local dev server instance. Run all commands from the directory where VillageSQL was installed.
Starting a Local Dev Server
Initialize and start a server instance:--dir <path> before any command to manage multiple independent instances, or use --here to create a server directory in the current working directory:
Managing Extension Files
Before installing an extension via SQL, its.veb file must be present on the server. The CLI manages the server’s lib/veb/ directory:
.veb files placed in lib/veb/ before init are seeded automatically. After adding a file, install the extension via SQL:
Writing Extension Functions
Extension functions are written in C++ and registered with VEF. Include a single header to access the full SDK:Typed Wrappers (Recommended)
Typed wrappers provide a type-safe interface for VDF parameters and results. The framework detects wrapper types in your function signature and adapts automatically — themake_func registration syntax is unchanged.
Input wrappers: IntArg, RealArg, StringArg, CustomArg — each
provides is_null() and value(). For parameterized custom types,
CustomArgWith<P> adds a params() accessor that returns the cached
parsed params struct (see Parameterized Types).
Result wrappers: IntResult, RealResult, StringResult, CustomResult
— each provides set_null(), warning(msg), and error(msg). Scalar
results also provide set(value). Buffer results provide buffer() and
set_length(len). For parameterized custom types, CustomResultWith<P>
adds a params() accessor.
error(msg) sets the function result to a SQL error with the given message string. Use it for invalid input that should surface as an error to the caller, as opposed to set_null() which silently produces NULL. The message is truncated to fit the server’s internal error buffer if necessary.
Scalar example — add two integers:
StringResult and CustomResult, write into buffer(), then call
set_length() with the number of bytes written. buffer().size() is the
maximum capacity.
You can use different styles across functions in the same extension — each
function’s style is determined by its own signature.
Raw ABI Style (Deprecated)
Prefer typed wrappers for new code. The raw ABI style passes pointers to the underlying C structs directly:VDF Return Codes
Setresult->type (or call the corresponding typed-wrapper method) to
indicate the outcome of a VDF call:
| Code | Value | Typed wrapper method | Behavior |
|---|---|---|---|
VEF_RESULT_VALUE | 0 | set(v) / set_length(n) | Returns the computed value. |
VEF_RESULT_NULL | 1 | set_null() | Returns SQL NULL. |
VEF_RESULT_WARNING | 2 | warning(msg) | Returns NULL for this row and adds a SQL warning. Execution continues. In strict mode (STRICT_TRANS_TABLES), MySQL promotes the warning to an error on INSERT/UPDATE. |
VEF_RESULT_ERROR | 3 | error(msg) | Aborts statement execution immediately. |
VEF_RESULT_WARNING for recoverable bad input (e.g., an unparseable
string passed to an encode function). Use VEF_RESULT_ERROR for conditions
that make it unsafe to continue (e.g., corrupt stored data).
Type Operation Builders
Only needed if your extension defines a custom column type. If you’re writing functions only, skip ahead to Running Regression Tests. Custom types require four operations the engine calls internally: encode (string → binary), decode (binary → string), compare, and hash. Implement them against these C++ signatures (all defined infunc_builder.h):
Fixed-Length Types
make_type_encode, make_type_decode,
make_type_compare, or make_type_hash, passing the function pointer and
the type name constant. The type descriptor references them by name:
Parameterized Types
Variable-length types need the column’s declared parameters at encode, decode, compare, and hash time to determine allocation sizes and layout. Define a params struct with a parse function, register it on the type builder with.params<P, &ParseFunc>(), and use const P& as the first argument of
your type operation functions. The SDK caches the parse result per unique
parameter combination, so the parse function runs at most once per type
instantiation.
.params<>() on the type builder alongside the operation VDF names.
Both are required: .params<>() binds the parse function at startup;
make_type_encode (and the other make_type_* functions) detect const P&
and route through the cache automatically.
TypeEncodeWithParamsFunc<P>,
TypeDecodeWithParamsFunc<P>, TypeCompareWithParamsFunc<P>, and
TypeHashWithParamsFunc<P> — are defined in func_builder.h.
make_type_encode, make_type_decode, make_type_compare, and
make_type_hash accept both fixed-length and variable-length forms.
Running Regression Tests
Run extension regression tests using the MySQL Test Runner from your VillageSQL build directory.Running the Full Suite
To run all tests for your extension:Running Individual Tests
To run a single test case, specify the suite path and test name:Creating New Tests
When adding new features or fixing bugs, you should add corresponding regression tests.Test Location
Tests are located inmysql-test/suite/villagesql/. Choose the appropriate sub-directory based on the SQL statement or feature being tested.
- Test files end with
.testand go in thet/directory. - Expected result files end with
.resultand go in ther/directory.
Test File Conventions
VillageSQL tests often involve the customCOMPLEX type. We follow a specific pattern to ensure tests are skipped gracefully if the feature is not yet fully implemented:
Steps to Add a Test
- Create the
.testfile in the appropriatet/directory (e.g.,mysql-test/suite/villagesql/select/t/my_new_test.test). - Create an empty
.resultfile in the correspondingr/directory (e.g.,mysql-test/suite/villagesql/select/r/my_new_test.result). - Run the test with
--recordto generate the expected output: - Verify the output in the generated
.resultfile to ensure it matches your expectations.
Debugging Tests
If a test fails, the test framework provides detailed logs.- Test output: Check
mysql-test/var/log/my_new_test.log. - Server error log: Check
mysql-test/var/log/mysqld.1.err. - Diff: The framework outputs a diff between the actual output and the expected
.resultfile.

