Skip to main content

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples in this guide work on VillageSQL. Install Now →
VillageSQL extensions can be written in Rust using the villagesql crate. The SDK handles all FFI marshaling — you write ordinary Rust, and the extension! macro generates the C entry points the server calls at load time.

When to Choose Rust

Rust is the right choice when:
  • You prefer Rust and don’t need a C++ codebase
  • You want memory safety without a garbage collector
  • You’re building a new extension and have no existing C++ dependency
If you have an existing C++ codebase or need the most complete custom-type builder API available today, see Writing C++ Extensions.
The Rust SDK is at version 0.0.1. The API is stable for extension development but the custom-type builder API is still maturing.

Prerequisites

You need:
  • Rust stable toolchain — install at rustup.rs
  • cargo-vsql — the Cargo subcommand for packaging, installing, and testing
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

Set Up a New Crate

Create a library crate and configure it:
cargo new --lib vsql_my_extension
cd vsql_my_extension
Edit Cargo.toml:
[package]
name = "vsql_my_extension"
version = "0.1.0"
edition = "2021"

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

[dependencies]
villagesql = "0.0.1"
Add manifest.json alongside Cargo.toml:
{
  "name": "vsql_my_extension",
  "version": "0.1.0",
  "description": "My VillageSQL extension",
  "author": "Your Name",
  "license": "GPL-2.0"
}

Write a Function

The Building Extensions in Rust page has the full walkthrough with the ROT-13 example. The short version: implement fn(&[InValue]) -> VdfReturn, register it with func!, and wrap everything in extension!:
use villagesql::{InValue, VdfReturn};

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

villagesql::extension! {
    funcs: [
        villagesql::func!(my_func_impl, "my_func", [villagesql::Type::String] -> villagesql::Type::String),
    ]
}
For custom types (binary storage, ordering, hashing), see Custom Types in Rust.

Test Your Extension

Write a test in mysql-test/t/basic.test:
INSTALL EXTENSION vsql_my_extension;
SELECT my_func('hello');
SELECT my_func(NULL);
UNINSTALL EXTENSION vsql_my_extension;
Set your VillageSQL build directory, generate expected results, then run:
export VillageSQL_BUILD_DIR=/path/to/villagesql/build
cargo vsql test --record
cargo vsql test
A passing run prints [ pass ] for each test file.

Install in SQL

Package and install:
cargo vsql install
Then in SQL:
INSTALL EXTENSION vsql_my_extension;
SELECT * FROM INFORMATION_SCHEMA.EXTENSIONS WHERE EXTENSION_NAME = 'vsql_my_extension';
SELECT my_func('hello');
-- → HELLO

See also