Skip to main content

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
Extensions are built using the VEF SDK (VillageSQL Extension Framework), which provides:
  • 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:
Function Resolution Order: When you call a function without qualification, VillageSQL resolves it in this order:
  1. System functions (built-in MySQL functions like NOW(), CONCAT())
  2. UDFs (traditional MySQL user-defined functions)
  3. VDFs (extension functions) - only if there’s exactly one function with that name
  4. Stored functions (created with CREATE FUNCTION)
When to Use Qualified Names:
  • Use extension.function_name when 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
Extensions can add:
  • 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, ensure you have:
  • 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
  • VillageSQL - Either:
    • Source build with VillageSQL_BUILD_DIR set to build directory, or
    • Binary installation with mysql_config in PATH
  • VillageSQL SDK headers - Included in VillageSQL source at villagesql/sdk/include/
  • 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:
  1. Visit the template repository on GitHub:
  2. Click the “Fork” button to create your own copy
  3. Clone your fork locally:
Alternatively, use the “Use this template” button on GitHub to create a new repository based on the template without forking history.

Step 2: Update the Manifest

Edit manifest.json to define your extension’s metadata:

manifest.json Schema

Validation rules:
  • name: Must be lowercase letters, numbers, and underscores only. No hyphens in manifest!
  • version: Must follow semantic versioning (e.g., 1.0.0, 0.2.1)
  • Invalid manifest will cause INSTALL EXTENSION to fail
Example:

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 the VEF_GENERATE_ENTRY_POINTS() macro to define your extension:
Function Builder Methods:
  • 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
  • .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.

Custom Buffer Sizes

For functions returning variable-length data, request a specific buffer size:
Check available buffer space before writing:
Request sufficient buffer size via .buffer_size() based on your function’s maximum output size.

Implement Wrapper Functions

Implementation functions use the VEF API signature:

Handling NULL Values

Check for NULL via the is_null flag and return NULL by setting the result type:
NULL handling options:
  • 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 error: result->type = VEF_RESULT_ERROR + error message in result->error_msg

Error Handling

Return errors with custom messages for validation failures or invalid input:
Result types:
  • VEF_RESULT_ERROR - Validation failure, invalid input, or runtime error
  • VEF_RESULT_NULL - NULL value
  • VEF_RESULT_VALUE - Success

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 and decode functions, and optionally compare/hash functions:

Register the Type

Type Conversion Functions

Expose your encode/decode functions as SQL functions for explicit type conversion:
Usage in SQL:
The .from_string<>() and .to_string<>() builders automatically create SQL-callable conversion functions using your encode/decode implementations.

Example: COMPLEX Type

Here’s a complete example implementing a COMPLEX number type:
After defining these operations, users can create tables with your custom type:
See vsql_complex Example for the complete implementation.

Step 5: Update Build Configuration

Edit CMakeLists.txt to build your extension as a VEB file:
Configuration notes:
  • VillageSQLExtensionFramework provides CMake helpers for building extensions
  • VEF_CREATE_VEB() packages your library, manifest, and metadata into a .veb archive
  • 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

Step 6: Create a Build Directory

Create a separate build directory:

Step 7: Build with CMake and Make

Configure and build your extension:
This creates:
  • Compiled shared library (.so file)
  • VEB package (.veb file) - a tar archive containing manifest and library

Verify the Build

Check the contents of your VEB file:
You should see:

Step 8: Install and Test

Option A: Install to VillageSQL Extensions Directory

Use the install target to copy the VEB to your VillageSQL installation:
This copies the .veb file to the directory configured via VillageSQL_VEB_INSTALL_DIR.

Option B: Manual Installation

Copy the VEB file manually:

Test Your Extension

  1. Connect to VillageSQL:
  2. Install the extension:
  3. Verify installation:
  4. Test your functions:

Creating Tests

Add test files to validate your extension works correctly:
  1. Create a test file in test/t/:
  2. Generate expected results:
  3. 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

COUNT(DISTINCT), MIN, and MAX operations work with custom types. We are investigating support for additional aggregate functions (SUM, AVG, etc.) for inclusion in a future release.
Supported aggregate operations with custom types:
Extension functions are called in a per-row execution model:
  • Each function call processes one row with its own result buffer (thread-safe)
  • prerun/postrun provide 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
Best practice: Design functions to be stateless for simplicity and safety.

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

Resources