Skip to main content
The Rust SDK is at version 0.0.1.
This page is a reference for the villagesql crate API. For the getting started tutorial, see Building Extensions in Rust. For custom types, see Custom Types in Rust.

InValue

InValue is the enum the server passes for each function argument. Your function receives args: &[InValue] and must check each argument before using its value.
pub enum InValue<'a> {
    String(&'a str),
    Real(f64),
    Int(i64),
    Null,
    Custom(&'a [u8]),
}
VariantRust typeCorresponding SQL type
String(&str)UTF-8 string sliceSTRING / VARCHAR / TEXT
Real(f64)64-bit floatREAL / DOUBLE / FLOAT
Int(i64)64-bit signed integerINT / BIGINT / TINYINT
NullSQL NULL for any type
Custom(&[u8])Raw binary bytesAny custom type registered via custom_type!
Always match on Null explicitly. Calling .unwrap() or pattern-matching only the value variants is a bug — SQL NULL is a normal input, not an error.

VdfReturn

VdfReturn is what your function returns to the server. Construct it with one of the associated functions:
ConstructorSQL effect
VdfReturn::null()Returns SQL NULL for this row
VdfReturn::string(s)Returns a String value; s is impl Into<String>
VdfReturn::real(v)Returns an f64 value
VdfReturn::int(v)Returns an i64 value
VdfReturn::binary(bytes)Returns binary bytes for a custom type column; bytes is Vec<u8>
VdfReturn::warning(msg)Returns NULL for this row, adds a SQL warning, execution continues
VdfReturn::error(msg)Aborts the statement with a fatal error
Warning vs error: Use warning for user-input validation failures where continuing with the rest of the result set makes sense. In strict mode, MySQL promotes warnings to errors on INSERT and UPDATE. Use error for conditions where proceeding is unsafe — corrupt stored data, internal invariant violations. A fatal error aborts the entire statement.
fn validate_impl(args: &[InValue]) -> VdfReturn {
    match args.first() {
        Some(InValue::Int(n)) if *n >= 0 => VdfReturn::int(*n),
        Some(InValue::Int(_)) => VdfReturn::warning("value must be non-negative"),
        Some(InValue::Null) | None => VdfReturn::null(),
        _ => VdfReturn::error("validate: expected an INT argument"),
    }
}

extension! macro

extension! generates the VEF entry points the server calls when loading your VEB file. It must appear exactly once in the crate.
villagesql::extension! {
    funcs: [
        // One or more villagesql::func!(...) declarations
    ],
    types: [
        // One or more villagesql::custom_type!(...) declarations
    ]
}
Both sections are optional. A pure-function extension omits types:; a type-only extension omits funcs:. An empty extension! block (no funcs, no types) is valid but produces an extension that does nothing.

func! macro

func! declares a SQL-callable function. Positional arguments:
villagesql::func!(rust_fn, "sql_name", [param_types] -> return_type)
villagesql::func!(rust_fn, "sql_name", [param_types] -> return_type, deterministic: true)
ArgumentDescription
rust_fnThe Rust function implementing the VDF. Signature: fn(&[InValue]) -> VdfReturn.
"sql_name"The SQL function name as a string literal. This is what users call from SQL.
[param_types]Comma-separated list of villagesql::Type::* or villagesql::custom!("name") values. Use [] for zero-arity functions.
return_typevillagesql::Type::* or villagesql::custom!("name").
deterministic: trueOptional. Declares the function deterministic — same inputs always produce the same output, no side effects. The optimizer can cache results for identical inputs. Only set this when it’s true.
Type constants for use in func!:
villagesql::Type::*SQL type
villagesql::Type::StringSTRING
villagesql::Type::RealREAL
villagesql::Type::IntINT

custom_type! macro

custom_type! registers a new column type. type_name, persisted_length, max_decode_buffer_length, encode, decode, and compare are required. hash and default are optional but recommended.
villagesql::custom_type!(
    type_name: "sql_type_name",
    persisted_length: N,
    max_decode_buffer_length: M,
    encode: encode_fn,
    decode: decode_fn,
    compare: compare_fn,
    hash: hash_fn,
    default: "default_string",
)
FieldTypeDescription
type_name&str literalSQL name for the type. Case-insensitive in SQL. Must be unique among all installed extensions.
persisted_lengthusizeFixed byte length for on-disk storage. All encoded values must produce exactly this many bytes.
max_decode_buffer_lengthusizeMaximum byte length of the decoded string. Used to size the output buffer before calling decode.
encodefn(&str) -> Result<Vec<u8>, String>Called at INSERT time. Converts a SQL string literal to binary. Return Err(msg) to reject the input.
decodefn(&[u8]) -> Result<String, String>Called to display the value. Converts binary back to a string.
comparefn(&[u8], &[u8]) -> std::cmp::OrderingCalled for ORDER BY, MIN, MAX. Return Less, Equal, or Greater.
hashfn(&[u8]) -> usizeOptional. Called for COUNT(DISTINCT) and set operations. Values that compare Equal must hash to the same value. Recommended for indexed columns.
default&str literalOptional. A valid string the server encodes at type initialization to verify the callback works. Must encode to exactly persisted_length bytes.
The default field is not a column default value — it’s a startup probe. The server calls encode(default) when loading the extension to verify the callback works. If encode returns Err for the default, the extension fails to load.

custom! macro

villagesql::custom!("type_name") references a custom type by name in a func! declaration:
villagesql::func!(
    my_fn,
    "my_sql_func",
    [villagesql::custom!("mytype")] -> villagesql::custom!("mytype"),
    deterministic: true
)
Use it anywhere a villagesql::Type::* would appear in a parameter list or return type position. The string must match the type_name declared in the corresponding custom_type!.

manifest.json fields

Every extension needs a manifest.json alongside its Cargo.toml:
{
  "name": "vsql_my_extension",
  "version": "0.1.0",
  "description": "Brief description of what the extension does",
  "author": "Your Name",
  "license": "GPL-2.0"
}
FieldRequiredFormatDescription
nameYeslowercase letters, digits, _, -Extension identifier. Must match the INSTALL EXTENSION name. Use underscores — hyphens require backtick quoting in SQL.
versionYesMAJOR.MINOR.PATCHSemantic version.
descriptionNoStringShown in INFORMATION_SCHEMA.EXTENSIONS.
authorNoStringAuthor name or organization.
licenseNoStringLicense identifier. GPL-2.0 recommended for open-source extensions.
name validation rules: must start with a letter, end with a letter or digit, max 64 characters. An invalid manifest causes INSTALL EXTENSION to fail.