examples/ 目录还为每一项受支持的预览功能提供了一个示例。
源码: vsql-rust-sdk 中的 examples/
vsql_rot13 — 仅包含函数的扩展
最简单的 Rust 扩展:一个接受 STRING 并返回 STRING 的 VDF。 用法:目录结构
实现
文件:src/lib.rs
- VDF 接收
&[InValue]并返回VdfReturn——两者都是安全的 Rust 枚举 - NULL 在两端都是一等变体;直接对其进行模式匹配
extension!宏生成服务器在加载时调用的 C 入口点func!声明 SQL 签名;参数和返回类型使用villagesql::Type::*
清单
文件:manifest.json
vsql_rational — 带算术运算的自定义类型
一个完整的自定义类型:以约分形式(numerator, denominator) 存储有理数,并带有算术函数、排序和哈希。
用法:
二进制存储格式
rational 存储 16 字节(小端序):
- 字节 0–7:分子(
i64) - 字节 8–15:分母(
i64)
类型系统函数
文件:src/lib.rs
该类型注册了四个操作:编码(字符串 → 字节)、解码(字节 → 字符串)、比较(用于 ORDER BY)和哈希(用于索引)。
VDF 实现
接受自定义类型的 VDF 会接收InValue::Custom(&[u8]),并自行解码这些字节:
注册
extension! 宏在单个声明中同时注册类型及其函数:
villagesql::custom!("name")用于以参数或返回值的形式引用自定义类型custom_type!注册类型及其 encode/decode/compare/hash 函数default: "0/1"是内建默认值——服务器在类型初始化时会对该字符串调用encode(),因此它必须是一个有效值persisted_length必须与encode()返回的字节长度匹配deterministic: true让优化器可以折叠常量调用
vsql_agg_sum — 聚合函数
一个重新实现对INT 列求 SUM 的聚合 VDF。它展示了聚合所需的三个钩子——clear、accumulate 和结果函数——以及每个钩子如何看到同一个累加器。
用法:
累加器生命周期
累加器是每个语句一个值,并在所有分组之间复用。服务器按固定顺序驱动它:clear 在每个分组开始时重置累加器。它忘记重置的字段会从上一个分组泄漏过来。
服务器会对每一行调用 accumulate,包括参数为 NULL 的行。跳过 NULL 是该函数自己的职责:只匹配您想要的变体,忽略其余部分。
实现
文件:src/lib.rs
seen 标志用于区分求和结果为零的分组和没有内容可求和的分组。没有它,空分组或全为 NULL 的分组会返回 0,而内置 SUM 返回的是 NULL。
注册
- 第一个标识符是结果函数,而不是行函数——聚合的按行工作位于
accumulate:中 state:命名累加器类型,该类型必须实现Default- 声明的参数列表就是按行的参数列表:
[villagesql::Type::Int]是accumulate接收的内容,返回类型则是结果函数生成的内容 agg_func!还接受buffer_size:和deterministic:,位于accumulate:之后,必须一起提供,并按此顺序
vsql_varargs — 变长参数函数
四个各自接受任意数量参数的 VDF。它们合起来覆盖了varargs_func! 支持的三种注册形式——带 prerun 的有状态形式、仅 prerun 形式和裸形式——外加对自定义类型参数的验证。
用法:
#1 前缀是每个语句的调用计数器,它会随着一条语句的各行递增:
prerun 负责变长参数的全部验证
对于变长参数函数,服务器完全不做参数检查——既不检查数量,也不检查类型。通常正是已声明的签名让服务器在您的代码运行之前拒绝错误的调用,而变长参数函数没有签名。凡是 prerun 钩子没有拒绝的内容都会到达行函数。 prerun 对调用进行拒绝,在任何行之前只执行一次:它看到优化器解析出的参数类型,并使语句失败。行函数仍然必须处理每个值,因为类型通过了验证的列在任何一行上仍可能携带 NULL。 prerun 拒绝会使语句初始化失败:arg_count 以裸形式注册,因此零参数调用是合法的:
实现
文件:src/lib.rs
prerun 接收 PrerunArgs 和一个 T 与状态类型匹配的 PrerunResult<T>。PrerunArgs::len() 是参数数量,type_at(i) 以 ArgType 的形式返回参数 i 的类型:
request_buffer_size、按 args.len() 的比例确定缓冲区大小——固定的 buffer_size 无法随参数数量增长。
ArgType 公开四个谓词——is_int()、is_real()、is_str() 和 is_custom()——因此只要每个参数都是行函数能处理的形态之一,prerun 就可以接受一次混合类型的调用。describe 接受三种标量类型的任意组合,并拒绝其他任何类型:
(),因为这个 prerun 不保存任何内容:它只做验证并确定缓冲区大小,从不调用 set_state。
自定义类型的变长参数
单独使用is_custom() 只能说明该参数是某个自定义类型。custom_name() 返回具体是哪一个,因此 prerun 可以将变长参数调用限制为单一类型。该扩展注册了一个 point2d 自定义类型,并接受任意数量的 point2d 值:
InValue::Custom(b) 并自行解码这些字节,与任何其他自定义类型 VDF 相同。
注册
describe、point_path 以及 point2d 的 encode/decode/compare 遵循上文已为 str_join 和 rational 展示过的同样的 InValue 匹配和字节编码模式——完整源码请参阅 Rust SDK 仓库中的 examples/vsql_varargs/src/lib.rs。
关键模式:
- 用
[..]取代参数列表,正是将函数标记为变长参数的方式 - 三种形式各有不同的行函数签名:
state:加prerun:给出fn(&mut State, &[InValue]) -> VdfReturn;仅prerun:和裸形式都给出fn(&[InValue]) -> VdfReturn - 只有
state:形式会分配和丢弃每个语句的状态 - 返回类型仍然要声明,因此只有参数列表是可变的
- 每种形式还接受作为尾部成对参数的
buffer_size:和deterministic: point2d没有注册hash,它是可选的——仅compare就足以支持ORDER BY
关键实现模式
测试
这四个示例都像 C++ 扩展一样使用 MTR(MySQL Test Runner):--record 生成或更新预期结果。
后续步骤
在 Rust 中创建扩展
SDK 安装、构建以及 extension! 宏
Rust 自定义类型
深入了解 encode、decode、compare 和 hash
Rust API 参考
InValue、VdfReturn 以及宏接口
示例源码
全部四个示例的完整源码

