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]),
}
| Variant | Rust type | Corresponding SQL type |
|---|
String(&str) | UTF-8 string slice | STRING / VARCHAR / TEXT |
Real(f64) | 64-bit float | REAL / DOUBLE / FLOAT |
Int(i64) | 64-bit signed integer | INT / BIGINT / TINYINT |
Null | — | SQL NULL for any type |
Custom(&[u8]) | Raw binary bytes | Any 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:
| Constructor | SQL 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)
| Argument | Description |
|---|
rust_fn | The 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_type | villagesql::Type::* or villagesql::custom!("name"). |
deterministic: true | Optional. 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::String | STRING |
villagesql::Type::Real | REAL |
villagesql::Type::Int | INT |
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",
)
| Field | Type | Description |
|---|
type_name | &str literal | SQL name for the type. Case-insensitive in SQL. Must be unique among all installed extensions. |
persisted_length | usize | Fixed byte length for on-disk storage. All encoded values must produce exactly this many bytes. |
max_decode_buffer_length | usize | Maximum byte length of the decoded string. Used to size the output buffer before calling decode. |
encode | fn(&str) -> Result<Vec<u8>, String> | Called at INSERT time. Converts a SQL string literal to binary. Return Err(msg) to reject the input. |
decode | fn(&[u8]) -> Result<String, String> | Called to display the value. Converts binary back to a string. |
compare | fn(&[u8], &[u8]) -> std::cmp::Ordering | Called for ORDER BY, MIN, MAX. Return Less, Equal, or Greater. |
hash | fn(&[u8]) -> usize | Optional. Called for COUNT(DISTINCT) and set operations. Values that compare Equal must hash to the same value. Recommended for indexed columns. |
default | &str literal | Optional. 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"
}
| Field | Required | Format | Description |
|---|
name | Yes | lowercase letters, digits, _, - | Extension identifier. Must match the INSTALL EXTENSION name. Use underscores — hyphens require backtick quoting in SQL. |
version | Yes | MAJOR.MINOR.PATCH | Semantic version. |
description | No | String | Shown in INFORMATION_SCHEMA.EXTENSIONS. |
author | No | String | Author name or organization. |
license | No | String | License 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.