Skip to main content
The Rust SDK is at version 0.0.1.
If you prefer C++, see Creating Extensions 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

You need:
  • Rust stable toolchain — install at rustup.rs
  • 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
Install cargo-vsql from the SDK repo:
git clone https://github.com/villagesql/vsql-rust-sdk
cd vsql-rust-sdk
cargo install --path cargo-vsql
Verify it’s available:
cargo vsql --help

Create a new crate

Create a new Rust library crate:
cargo new --lib vsql_rot13
cd vsql_rot13
Edit Cargo.toml to set the crate type and add the villagesql dependency:
[package]
name = "vsql_rot13"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
villagesql = "0.0.1"
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:
use villagesql::{InValue, VdfReturn};

fn rot13_impl(args: &[InValue]) -> VdfReturn {
    match args.first() {
        Some(InValue::String(s)) => VdfReturn::string(rot13(s)),
        Some(InValue::Null) | None => VdfReturn::null(),
        _ => VdfReturn::error("vsql_rot13: expected a STRING argument"),
    }
}

fn rot13(s: &str) -> String {
    s.chars()
        .map(|c| match c {
            'a'..='m' | 'A'..='M' => (c as u8 + 13) as char,
            'n'..='z' | 'N'..='Z' => (c as u8 - 13) as char,
            _ => c,
        })
        .collect()
}

villagesql::extension! {
    funcs: [
        villagesql::func!(rot13_impl, "vsql_rot13", [villagesql::Type::String] -> villagesql::Type::String),
    ]
}
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:
villagesql::func!(rot13_impl, "vsql_rot13", [villagesql::Type::String] -> villagesql::Type::String, deterministic: true)

Add manifest.json

Create manifest.json in the crate root (alongside Cargo.toml):
{
  "name": "vsql_rot13",
  "version": "0.1.0",
  "description": "ROT-13 encoding for VillageSQL",
  "author": "Your Name",
  "license": "GPL-2.0"
}
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:
cargo vsql package
This produces dist/vsql_rot13.veb. To package and copy the VEB directly to your VillageSQL build directory:
export VillageSQL_BUILD_DIR=/path/to/villagesql/build
cargo vsql install
You should see the VEB copied to the extensions directory. Verify it’s there:
ls "$VillageSQL_BUILD_DIR/veb_output_directory/"
# vsql_rot13.veb

Test

Write a test file in mysql-test/t/rot13_basic.test:
INSTALL EXTENSION 'vsql_rot13';
SELECT vsql_rot13('Hello');
SELECT vsql_rot13('');
SELECT vsql_rot13(NULL);
UNINSTALL EXTENSION 'vsql_rot13';
Generate the expected results:
cargo vsql test --record
Run the suite:
cargo vsql test
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:
INSTALL EXTENSION 'vsql_rot13';
Verify:
SELECT * FROM INFORMATION_SCHEMA.EXTENSIONS WHERE EXTENSION_NAME = 'vsql_rot13';
Call it:
SELECT vsql_rot13('Hello, World!');
-- → Uryyb, Jbeyq!

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.