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.

