Skip to main content
本页介绍 Rust SDK 仓库中的四个参考扩展——一个仅包含函数的最小示例,一个包含算术运算、排序和哈希的完整自定义类型,一个聚合函数,以及一个变长参数函数。该仓库的 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
值始终以约分形式(GCD = 1)存储,且分母为正。

类型系统函数

文件: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。它展示了聚合所需的三个钩子——clearaccumulate 和结果函数——以及每个钩子如何看到同一个累加器。 用法:
添加一个全为 NULL 的分组可以看出,累加器在分组之间会被重置,而不是被带到下一个分组:

累加器生命周期

累加器是每个语句一个值,并在所有分组之间复用。服务器按固定顺序驱动它: 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 拒绝会使语句初始化失败:
省略 prerun 意味着接受每一次调用。arg_count 以裸形式注册,因此零参数调用是合法的:

实现

文件:src/lib.rs prerun 接收 PrerunArgs 和一个 T 与状态类型匹配的 PrerunResult<T>PrerunArgs::len() 是参数数量,type_at(i)ArgType 的形式返回参数 i 的类型:
对于变长参数,请在 prerun 中使用 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 相同。

注册

describepoint_path 以及 point2dencode/decode/compare 遵循上文已为 str_joinrational 展示过的同样的 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 以及宏接口

示例源码

全部四个示例的完整源码