Skip to main content
The Rust SDK is in alpha — expect breaking API changes between releases. Function-only extensions and custom types (encode, decode, compare, hash) are supported. Aggregates, prerun(), VarArgs, system and status variables, keyring access, and the column storage ABI are C++-only today — use the C++ SDK if you need any of those.
If you prefer C++, see Creating Extensions in C++ for the C++ SDK walkthrough.
VillageSQL extensions can be written in Rust using the villagesql crate. The SDK handles all FFI marshaling — you work in ordinary Rust types and the extension! macro generates the C entry points the server calls at load time.

Prerequisites

Before you begin, build VillageSQL from source — extensions link against the server’s build tree. Follow the Build from Source guide first. You also need:
  • Rust stable toolchain — install at rustup.rs
  • Git — for cloning the SDK and your extension repo
  • cargo-vsql — the Cargo subcommand for packaging, installing, and testing extensions
  • VillageSQL build directory — set VillageSQL_BUILD_DIR to your server build path for cargo vsql install and cargo vsql test
  • Basic Rust knowledge — comfort with Cargo, enums, and pattern matching
Install cargo-vsql from the SDK repo:
Verify it’s available:

Create a new crate

Create a new Rust library crate:
Edit Cargo.toml to set the crate type and add the villagesql dependency:
The cdylib crate type tells Cargo to produce a shared library (.so on Linux, .dylib on macOS) that the server can load.

Write your first function

Replace src/lib.rs with a complete extension:
The func! macro wires rot13_impl to the SQL name vsql_rot13 with a STRING -> STRING signature. The function signature is fn(&[InValue]) -> VdfReturn. InValue is an enum over the SQL types the server passes in. VdfReturn is what you send back. Check args.first() to handle both the NULL case and the wrong-type case before touching the value. If your function always returns the same output for the same inputs, declare it deterministic — the optimizer can then cache results for identical inputs:

Add manifest.json

Create manifest.json in the crate root (alongside Cargo.toml):
The server reads this at install time. The name field must match what you pass to INSTALL EXTENSION.

Build and install

Run cargo vsql package, cargo vsql install, and cargo vsql test from inside the extension directory (where Cargo.toml and manifest.json live), not from the workspace root.
Package the extension into a .veb file:
This produces dist/vsql_rot13.veb. To package and copy the VEB directly to your VillageSQL build directory:
To patch a dependency to a local checkout during development, pass --config KEY=VALUE (repeatable):
You should see the VEB copied to the extensions directory. Verify it’s there:

Test

Write a test file in mysql-test/t/rot13_basic.test:
Generate the expected results:
Run the suite:
After modifying your function’s behavior, re-run cargo vsql test --record to update the expected results, then cargo vsql test to confirm.

Install in SQL

Once the VEB is in the extensions directory, install it:
Verify:
Call it:

Next steps

Custom Types in Rust

Define new column types with binary storage, ordering, and hashing.

Rust API Reference

InValue, VdfReturn, extension!, func!, and custom_type! — all fields.

C++ SDK (Creating Extensions)

The C++ path — typed wrappers, builder API, and CMake setup.

Extension Architecture

How VEB files load, lifecycle hooks, and symbol isolation.

Testing Network-Dependent Extensions

MTR port patterns for extensions that spawn HTTP servers or external listeners — applies equally to Rust and C++ extensions.