Skip to main content
预览功能是服务器提供的功能,在最终确定其 API 之前,会将其暴露给扩展程序。声明了预览功能的扩展程序需要 vsql_allow_preview_extensions = ON 才能安装(请参阅启用预览层)——不使用预览功能的扩展程序无论此设置如何都会正常安装。
预览功能 API 并不稳定。基于预览功能构建的扩展程序在服务器更新后可能无法加载。当某个功能稳定后,其头文件将移动到版本化的稳定 SDK 路径。

启用预览层

在安装任何使用预览功能的扩展程序之前,使用 SET PERSIST 设置 vsql_allow_preview_extensions = ON
对于此变量,SET GLOBAL 会被拒绝——服务器需要 SET PERSIST,以便设置在重启后仍然有效。具有预览功能的扩展程序在启动时加载,因此在服务器启动时,该变量必须为 ON。 如果您直接启动 mysqld(例如,从安装脚本中启动服务器),请在命令行中传递该标志,而不是使用 mysqld-auto.cnf(因为此时 mysqld-auto.cnf 尚未存在,无法保存持久化值):
要禁用:
如果当前安装了任何使用预览功能的扩展程序,则此操作将失败。首先卸载这些扩展程序,然后关闭该设置。

功能索引

注册模式

要使用预览功能,请在文件范围内声明一个功能对象,并将其按引用传递给 make_extension() 中的 .with()。服务器将在注册期间填充对象的 abi 指针:
.with(capability) 告诉服务器扩展程序需要哪些功能。如果扩展程序在安装时 vsql_allow_preview_extensions 为 OFF,则服务器会拒绝安装,并显示一个包含功能名称的错误。
扩展程序中声明的每个功能对象都必须精确地传递给 .with() 一次。在加载时,服务器会交叉检查每个声明的功能实例与 .with() 接收到的内容,如果违反了该规则,则 INSTALL EXTENSION 将失败:
  • 已声明但从未传递给 .with() capability '<Type>' was declared but never passed to .with(); every CapabilityBase-derived static must be registered via .with(cap) in the extension builder
  • 同一实例多次传递给 .with() capability '<Type>' passed to .with() more than once
  • 传递给 .with() 的对象不是功能: .with() received an object that does not inherit vsql::detail::CapabilityBase; not a registered capability
完整的错误消息为:Failed to load VEF extension '<name>': vef_register returned an error: <message above>

密钥环访问

密钥环功能 (vsql::preview::keyring) 允许扩展程序读取和写入存储在 MySQL 密钥环组件中的密钥。扩展程序可将其用于 API 密钥、加密密钥或其他不应存储在 SQL 表中的密钥。 功能名称 VEF_PREVIEW_KEYRING_NAME"vsql::preview::keyring" 为了使读取和写入成功,必须在 MySQL 服务器上安装密钥环组件。如果没有,则操作将返回 KeyringCapability::Status::UNAVAILABLE

状态值

KeyringCapability::Status 是一个范围枚举,由 read()(在 ReadResult 中)和 write() 返回:

声明功能

包含头文件,在文件范围内声明一个功能对象,并将其传递给 .with()
服务器将在加载时填充 g_keyring 对象。如果未安装密钥环组件,则 read()write() 方法在运行时将返回 Status::UNAVAILABLE——请在每次调用时检查该状态,而不是基于单独的可用性探测。

读取和写入

data_id 是密钥标识符。auth_id 是所有者用户——传递一个空字符串(或在 read 上省略它,read 默认使用 {})以读取或写入与特定用户无关的内部密钥。 read 返回一个 ReadResult。使用结构化绑定将其绑定:
对于任何状态(Status::OK 除外),value 都是空的。 write 直接返回 Status,并将 data 存储在 data_id / auth_id 下。

完整示例

这是与服务器一起提供的 vsql_keyring_reader 测试扩展程序的简化版本。它注册了 2 个 VDF:keyring_readkeyring_store

状态变量

status_var 功能 (vsql::preview::status_var) 允许扩展程序将 long longdouble 计数器作为 MySQL 状态变量公开。扩展程序拥有存储空间并写入其中;服务器每次查询状态变量时,都会通过指针读取。 使用 vsql::preview_status_var::make_capability() 构建功能,并传递来自 make_int(name, value_ptr)make_double(name, value_ptr) 的大括号列表。模板从大括号列表中推断计数,因此不需要显式大小。

完整示例

make_int 需要一个 long long *make_double 需要一个 double *。这是支持的两种类型。

从 SQL 访问

INSTALL EXTENSION my_ext 之后,该变量将以扩展程序名称作为前缀显示:
来自多个查询线程的并发递增(使用非原子 ++)可能会偶尔丢失;这对于通过 SHOW STATUS 公开的近似调用计数器是可以接受的。

系统变量

sys_var 功能 (vsql::preview::sys_var) 允许扩展程序注册由扩展程序拥有的存储空间支持的 MySQL 系统变量。支持三种类型:BOOL (bool *)、INT (long long *) 和 STR (char **)。INT 描述符还携带 min_valmax_val 边界;所有描述符都携带默认值和注释。 使用 vsql::preview_sys_var::make_capability() 和相应的工厂函数 make_boolmake_intmake_str 构建功能。功能对象还公开 get()set(),以便从扩展程序代码中进行编程访问。两者都返回 false 表示成功。 要响应值更改,请在描述符上链接 .on_change<&fn>()。回调将接收一个 sv::SysVarChange,其中包含 var_name() 和类型化的访问器 (as_int()as_real()as_str())。 功能对象必须具有静态存储持续时间。当用户设置变量时,MySQL 会直接写入存储指针。

完整示例

从 SQL 访问

安装 INSTALL EXTENSION my_ext 后,可以使用扩展名作为组件前缀来访问变量:

从扩展代码读取和写入

对于 INT 和 BOOL 变量,直接读取全局存储指针——MySQL 以原子方式更新这些变量。要通过 MySQL 更新变量(以便服务器处理锁定、范围验证和持久性),请调用 SYS_VARS.set(extension_name, var_name, scope, value)setget 都成功时返回 false
scope 参数控制持久性:

线程工作器

线程工作器功能 (vsql::preview::thread_worker) 允许扩展在服务器驱动的后台线程中运行。线程通过服务器注册的控制系统变量在扩展加载时启动和停止;服务器在定期计时器上、在文件描述符准备就绪时,或响应启用/禁用事件时调用扩展的工作函数。 功能名称 VEF_PREVIEW_THREAD_WORKER_NAME"vsql::preview::thread_worker"

声明功能

包含头文件,在文件范围内声明一个实例化了工作函数的 ThreadWorkerCapability,并将其传递给 .with()
工作函数作为非类型模板参数提供 (ThreadWorkerCapability<&my_work>),因此它必须是一个具有以下签名的函数。第一个构造函数参数是线程名称后缀;可选的第二个参数覆盖控制系统变量名称。

工作函数签名

reason 指示服务器调用该函数的原因。thread 是服务器拥有的此工作器的句柄(在初始 VEF_WAKEUP_ENABLE 调用时为 NULL——参见下文)。arg 是在描述符上注册的不透明指针;它会保持不变地传递。

唤醒生命周期

服务器使用以下四个原因之一调用工作函数: 当原因是 VEF_WAKEUP_ENABLE 时,thread 参数为 NULL,因为此时线程句柄尚不存在。对于其他三个原因,thread 不为 NULL。

唤醒返回值

工作函数返回一个 vef_next_wakeup_t,以更新下一个唤醒配置。任何字段中的零值都表示“保持当前设置”——返回一个值初始化的结构体 (return {};) 以保持两者不变。 要设置新的轮询文件描述符,请返回其值(必须大于零)。要清除现有的轮询文件描述符,请在 poll_fd 中返回 -1 当原因是 VEF_WAKEUP_DISABLE 时,返回值将被忽略。

线程名称和控制变量

描述符上的两个字段控制命名:
  • suffix——线程名称后缀。服务器将其与扩展名称连接起来,生成线程名称,如 my_ext/monitor
  • var_name——可选。如果非 NULL,服务器将注册此确切名称作为控制系统变量。如果为 NULL,服务器将使用默认模式 {suffix}_enabled
控制变量是服务器注册的系统变量。使用 SET GLOBAL {suffix}_enabled = ON 启用工作器;将其设置为 OFF 以停止它。

完整示例

一个带有单个周期性工作器的最小扩展,该工作器在每个计时器周期中递增一个心跳计数器。
安装此扩展(并且 vsql_allow_preview_extensions = ON)后,服务器将注册一个 heartbeat_enabled 系统变量。使用以下命令启用工作器:

SQL 查询

sql_query 功能 (vsql::preview::sql_query) 允许扩展从后台线程执行 SQL 语句。查询在服务器内部通过功能 vtable 运行——扩展不链接到任何 MySQL 客户端库。 功能名称 VEF_PREVIEW_SQL_QUERY_NAME"vsql::preview::sql_query"

声明功能

包含头文件,在文件范围内声明一个 SqlQueryCapability,并将其传递给 .with()。它通常与 ThreadWorkerCapability 一起注册,因为会话是从工作器回调中打开的:
g_sql.open(handle) 返回一个 Session。在使用之前,使用 operator bool 检查它;无效的 Session 表示功能 vtable 未绑定或服务器无法分配会话。Session 是只移动的,并在销毁时自行关闭。

执行查询

Session 通过 session.sql(sv) 生成一个 SqlQuery。查询可以以两种模式运行:
  • execute()——运行该语句并将完整的結果集缓冲到 Result 中。通过调用调用方自己的速度调用 next() 来迭代行。
  • for_each(fn)——运行该语句,并为生成的每一行调用 fn,而不进行缓冲。返回的 Result 仅包含诊断信息(不包含行)。
两者都返回一个 Result。非 NULL 的 Result 并不意味着该语句已成功——调用 has_error() 以确定。 缓冲 (execute):
column_str() 返回一个 string_view,它仅在下一次 next() 调用或 Result 销毁时有效。如果需要更长的生命周期,请复制它。string_view 具有 data() == nullptr 表示 SQL NULL。 流式 (for_each):
传递给回调的 Row 仅在调用期间有效——不要存储对它的引用,以便跨行使用。for_each 返回的 Result 不包含缓冲的行;在其上调用 next() 不会产生数据。仅将其用于 has_error()error()warning_count()warning(i)

诊断

execute()for_each() 都通过返回的 Result 提供诊断信息。一个诊断信息是一个 Diag
Result 暴露:
error() 在语句成功时返回一个默认构造的 Diag (errno_ == 0)。warning(i)i >= warning_count() 时返回一个默认构造的 Diag sqlstatemessage 视图指向由 Result 拥有的存储,并在 Result 销毁时失效——如果需要超出其生命周期,请复制它们。

完整示例

一个工作线程,在每个周期执行一个带缓冲的查询和一个流式查询,并记录来自两者的诊断信息:

列式存储

列式存储允许扩展直接向 InnoDB 注册自定义的磁盘布局,用于其自定义类型之一,而不是将该类型的数据通过行的 VARBINARY 负载进行路由。当您的类型需要一种 VARBINARY 无法表达的磁盘布局时,请使用它——例如,必须存储在专用页中的打包浮点数组。这是一个功能特性:它启用了新的存储布局,而不是用于现有布局的调整旋钮。
列式存储是一个预览版 ABI——正在积极开发中,并且可能在不同版本之间发生变化。它目前仅涵盖行级持久性;对自定义存储列进行索引尚未可用。

声明功能

两个预览功能协同工作:
  • vsql::preview::storage — 开放对 InnoDB 存储基础设施的访问(微事务、段、页)。在文件范围内声明一个 StorageCapability
  • vsql::preview::column_store — 将每个类型的存储实现绑定到扩展的自定义类型之一。在文件范围内使用 make_column_store<Ctx>(TYPE).…build() 声明一个 ColumnStoreCapability
两者都必须传递给 make_extension() 上的 .with()
make_column_store<Ctx>(MY_TYPE) 将实现绑定到在同一扩展上注册的一个自定义类型。所有七个槽位都必须在 build() 时提供,因为每个槽位都映射到列生命周期中的一个不同点,InnoDB 在正常操作期间会到达该点。

七个存储函数

每个函数都接受 storage::Column::StorageCtx<MyCtx>*,其 user() 访问器返回扩展的每个列状态,并且其 arena() 提供服务器管理的辅助对象分配。每个函数在成功时返回 false,在出错时返回 true,并将消息写入 error_msg(容量 error_msg_len),以便将故障传递到 SQL 客户端。
mark_deletepurge 是不同的,因为 InnoDB MVCC 要求已删除的行在 purge 运行之前对旧的快照保持可读。

每个列的上下文和 Arena

SDK 在调用 createload 之前,默认构造 MyCtx——在进入您的函数时,ctx->user() 已经填充。MyCtx 必须是默认可构造的;SDK 使用不带参数的 T() 调用。 直接使用 ctx->user() 来初始化状态。不要调用 ctx->arena().construct<MyCtx>()——这将分配第二个、未使用的实例,并且 ctx->user() 不指向它。
load 遵循相同的模式——ctx->user() 预先填充,并且 storage_ref 携带由 ctx->set_ref()create 中存储的打包值:
仅使用 ctx->arena() 来分配辅助对象,这些对象太大或太动态,无法直接嵌入到 MyCtx 中。SDK 在 drop 返回后,无论 drop 是否成功,都会自动销毁 arena 并调用 ~MyCtx()

InnoDB 访问实用程序

包含 <villagesql/preview/storage_api.h> 以获取 InnoDB 原语。所有页面的读取和写入都必须在微事务中进行:
提交微事务会释放页面锁,并写入重做日志记录,以使更改持久化。 create 时保留——有关完整的设置模式,请参见上面“每个列的上下文”中的 createload 示例。在 DML 操作期间,从根页面获取段引用以分配新页面:
页面 使用共享锁读取,使用独占锁写入。将 mtr_ref 传递给写入调用,以便 InnoDB 记录更改:
页面布局常量: 在标头或尾部区域内读取或写入会损坏页面——InnoDB 使用这些字节范围用于其自身的簿记和校验和。