编写扩展函数
扩展函数是用 C++ 编写的,并向 VEF 注册。包含一个标头以访问完整的 SDK:参数和结果类型
VDF 参数和结果作为类型安全的参数和结果类型传递。框架会从您的函数签名中检测它们并自动进行调整——make_func 注册语法保持不变。
参数类型: IntArg、RealArg、StringArg、CustomArg——每个都提供 is_null() 和 value()。对于参数化的自定义类型,CustomArgWith<P> 添加了一个 params() 访问器,该访问器返回缓存的解析参数结构(请参阅 参数化类型)。
结果类型: IntResult、RealResult、StringResult、CustomResult——每个都提供 set_null()、warning(msg) 和 error(msg)。标量结果还提供 set(value)。缓冲区结果提供 buffer() 和 set_length(len)。StringResult 此外还提供 set(std::string_view),它从视图中复制最多 buffer().size() 个字节,并在一次调用中设置长度。对于参数化的自定义类型,CustomResultWith<P> 添加了一个 params() 访问器。
Span 类型: 面向字节的参数和结果类型上的 value() 和 buffer() 返回一个 vsql::Span<T>——一个非所有权视图,用于连续的 T 序列,具有 data()、size()、empty()、begin()/end() 和 operator[]。在 C++20 中,它是 std::span<T> 的别名;在 C++17 中,此 SDK 提供了一个最小的兼容实现,因此相同的代码可以在任何标准中编译。它通过 <villagesql/vsql.h> 提供。
warning(msg) 会为该行返回 SQL NULL,并附加一个 SQL 警告。在严格模式 (STRICT_TRANS_TABLES) 下,MySQL 会将其升级为 INSERT/UPDATE 上的语句错误,因此它在严格上下文中表现得像 error(msg)。将其用于可恢复的错误输入,例如编码函数中的无法解析的字符串。对于已损坏的存储数据或任何继续操作不安全的情况,请使用 error(msg)。两种情况下的消息都会被截断以适应服务器的内部错误缓冲区(如果需要)。
标量示例——将两个整数相加:
StringResult 和 CustomResult,写入 buffer(),然后调用 set_length(),其中包含写入的字节数。buffer().size() 是最大容量。
对于返回自定义类型的 VDF (returns(CUSTOM(MYTYPE))),服务器会自动调整结果缓冲区的大小以匹配解析的返回类型的 persisted_length——扩展作者在此情况下不需要在函数生成器上声明 .buffer_size(...)。如果 prerun 进一步增大缓冲区,则会保留更大的大小。这使得,例如,SVECTOR::from_string('[…1024 floats…]') 可以在不使结果缓冲区耗尽空间的情况下编码一个大型向量。
您可以在同一扩展中的不同函数中使用不同的样式——每个函数的样式由其自身的签名决定。
聚合 VDF
聚合 VDF 在每个GROUP BY 组内的行中累积状态,并为每个组返回单个结果,类似于 SQL SUM 或 COUNT。使用 make_aggregate_func<State, &result_fn>("name") 注册一个。State 类型是每个组的累积缓冲区;prerun 和 postrun 是自动生成的,用于分配和删除它。
结果函数必须具有签名 void(const State&, ResultType),其中 ResultType 是 IntResult、RealResult、StringResult、CustomResult 或 CustomResultWith<P> 中的一个。调用 out.set(value) 以返回一个值,或者调用 out.set_null() 以返回 SQL NULL。
.clear<>() 和 .accumulate<>() 都是必需的。生成器在编译时(通过 build())强制执行这一点,并且服务器在 INSTALL EXTENSION 时再次验证它——clear 重置状态,accumulate 折叠行,结果函数读取最终状态。
make_aggregate_func<State, &result_fn>()自动生成prerun和postrun(值初始化并删除State)。.clear<&fn>()注册您的void(State&)重置函数。.accumulate<&fn>()注册您的void(State&, TypedArgs...)折叠函数。TypedArgs从函数签名中推断出来(IntArg、StringArg等)。- 结果类型 (
IntResult、RealResult等)从结果函数签名中推断出来。
StringResult 聚合 VDF 返回文本:结果报告 utf8mb4_bin 字符集和排序规则,因此客户端将其显示为字符而不是十六进制——与标量 VDF STRING 路径相同。它还以相同的方式支持 .max_result_length(n),调整物化聚合结果(GROUP BY/DISTINCT 临时表、CREATE TABLE ... SELECT 或 UNION)的大小,使其不会在参数宽度处被截断。有关调整大小的规则和上限,请参阅 自定义缓冲区大小。
每个语句的状态(Prerun 和 Postrun)
某些 VDF 需要跨单个查询触及的每一行都存在的状态——调用计数器、缓存结果、打开的资源。在 prerun 钩子中分配它,在 VDF 主体中访问它,并在 postrun 钩子中释放它。这两个钩子都为每个语句运行一次;VDF 主体为每一行运行一次。 使用.prerun<&Hook>() 和 .postrun<&Hook>() 注册它们。所需的签名是:
使用
PrerunResult::set_user_data(void*) 来存储状态;使用 PostrunArgs::delete_state<T>() 来释放它。如果 prerun 调用 set_user_data(new T{}),则 postrun 必须调用 delete_state<T>()——SDK 不会自动释放。
PrerunArgs::type_at(i) 公开了在读取任何行之前声明的每个参数的 SQL 类型;返回的 PrerunArgType 上的谓词 is_int()、is_real()、is_str()、is_custom() 镜像列类型。在 prerun 中使用它来验证参数类型或调用 PrerunResult::request_buffer_size(n) 以调整结果缓冲区的大小。
可变参数 VDF
varargs VDF 接受任意数量的任何 SQL 类型的参数。使用.varargs() 在函数生成器上声明一个,这与 .no_params() 和 .param(TYPE) 互斥。主体接收一个 vsql::VarArgs 参数,而不是通常的固定参数个数的参数类型。
框架无法验证 varargs VDF 的参数计数或类型。将每个 varargs 注册与 prerun 钩子配对,该钩子在输入无效时调用 PrerunResult::error(),或者调用 PrerunResult::request_buffer_size(n) 以调整结果缓冲区的大小。
使用 range-for 循环遍历参数。每个 AnyArg 元素都需要在读取其值之前进行类型检查:
在任何访问器之前检查
is_null()——所有四个访问器在 null 参数上都是未定义的。
VEF_GENERATE_REGISTRATION
VEF_GENERATE_REGISTRATION 创建一个内部 _vef_do_register() 辅助函数,该函数执行扩展注册,但不定义 extern "C" 入口点。当您需要自定义 vef_register 行为时使用它——例如,在测试构建中注册后修补描述符。对于常规扩展,请改用 VEF_GENERATE_ENTRY_POINTS。
自定义类型操作
有关完整的类型操作生成器参考——编码、解码、比较、哈希、内建默认值和参数化类型——请参阅 类型操作。预览功能
以下 VEF 功能作为选择加入的预览标头提供。ABI 和 API 仍在积极开发中;有关完整参考,请参阅 预览功能。- 扩展系统变量——预览功能 → 系统变量
- 扩展状态变量——预览功能 → 状态变量
- 密钥环访问——预览功能 → 密钥环访问
- 列存储——预览功能 → 列存储
检查扩展注册元数据
INFORMATION_SCHEMA.EXTENSION_REGISTRATION 将每个已加载扩展的内存 VEF 注册结构作为 JSON 文档公开。使用它来验证服务器在 INSTALL EXTENSION 之后是否已正确解析扩展的函数、类型和系统变量。
另请参阅
- 使用 C++ 创建扩展 — 端到端构建步骤、CMake 设置和安装
- C++ 测试 — 本地开发服务器、MTR 以及调试失败
- 类型操作 — 编码、解码、比较、哈希、参数化类型
- C++ API 参考 — VDF 合约、空值处理和缓冲区大小
- 扩展架构 — 生命周期、Victionary 缓存、性能模式和安全模型

