Skip to main content
This guide covers writing VDF implementations and running regression tests for VillageSQL extensions.
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 the villagesql 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:
To set a root password on init:
Pass --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 provide a type-safe interface for VDF parameters and results. The framework detects wrapper types in your function signature and adapts automatically — the make_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:
Binary example — transform a custom type buffer in place:
For 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:
Registration is the same as with typed wrappers.

VDF Return Codes

Set result->type (or call the corresponding typed-wrapper method) to indicate the outcome of a VDF call: Use 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 in func_builder.h):

Fixed-Length Types

Register each operation with 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.
Register .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.
The parameterized variants — 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 in mysql-test/suite/villagesql/. Choose the appropriate sub-directory based on the SQL statement or feature being tested.
  • Test files end with .test and go in the t/ directory.
  • Expected result files end with .result and go in the r/ directory.

Test File Conventions

VillageSQL tests often involve the custom COMPLEX 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

  1. Create the .test file in the appropriate t/ directory (e.g., mysql-test/suite/villagesql/select/t/my_new_test.test).
  2. Create an empty .result file in the corresponding r/ directory (e.g., mysql-test/suite/villagesql/select/r/my_new_test.result).
  3. Run the test with --record to generate the expected output:
  4. Verify the output in the generated .result file 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 .result file.
To run a test with extra debug information: