Skip to main content
本页是 C++ 类型操作构建器的参考。有关自定义类型的入门级介绍,请参阅 C++ 中的自定义类型 自定义类型需要引擎在内部调用的三个操作:编码(字符串到二进制)、解码(二进制到字符串)和比较。哈希是可选的。请针对以下这些 C++ 签名实现它们(全部通过 <villagesql/vsql.h> 提供):

固定长度类型

使用 vsql::make_type<kTypeName>() 注册这些操作。类型名称作为非类型模板参数 (NTTP) 传递——一个 static constexpr const char[] 数组。构建器会根据这个 NTTP 以 TYPE::method 格式(例如 "MYTYPE::from_string")自动生成 VDF 名称,因此无需手动匹配字符串。将构建好的类型对象传递给扩展构建器上的 .type();类型操作不需要单独的 .func() 调用。
类型名称必须是 static constexpr const char[] 变量——字符串字面量不能用作非类型模板参数。直接传递 "MYTYPE" 会产生类似以下的编译器错误:
如下所示,请将名称声明为具名数组。
如果缺少 from_stringto_stringcomparebuild() 将在编译时失败。每个模板方法都通过 static_assert 检查函数指针签名。

内建默认值

NOT NULL 自定义类型列在 IGNORE 模式下接收到 NULL 时(例如 INSERT IGNOREUPDATE IGNORE),服务器会调用内建默认值来生成一个回退值,而不是引发错误。内建默认值提供一个字符串表示;服务器使用该类型的 from_string 函数将其转换为二进制。
如果您同时省略 .intrinsic_default_str().intrinsic_default_vdf(),服务器会调用 from_string("") 作为回退。这发生在该类型首次使用时(在创建表时),而不是在 INSTALL EXTENSION 时。如果您的编码函数拒绝空字符串——或者将其编码为错误的字节数——类型初始化将失败,并在 SQL 客户端中显示一个错误:
对于固定长度类型,默认字符串必须编码为恰好 persisted_length 个字节。对于任何空字符串不是有效输入的类型,请设置一个显式默认值。
字符串字面量:.intrinsic_default_str() 对于常量默认值,请直接在类型构建器上传递字符串(如上面固定长度示例中的 .intrinsic_default_str("0") 所示)。 基于 VDF:.intrinsic_default_vdf() + make_intrinsic_default 当默认值依赖于类型参数时,请针对以下签名之一实现一个函数(通过 <villagesql/vsql.h> 提供):
重大变更IntrinsicDefaultFuncIntrinsicDefaultWithParamsFunc 现在返回 std::string 而不是 const char*。 请更新任何现有的内建默认值实现,使其直接返回 std::string
返回默认值的 std::string 表示。出错时,向 error_msg 写入一条消息并返回任意值(SDK 通过检查 error_msg[0] != '\0' 来检测错误)。使用 make_intrinsic_default<&fn>("vdf_name")(一个参数:VDF 名称)进行注册,并在类型构建器上通过 .intrinsic_default_vdf() 引用该名称。下面的参数化类型示例展示了完整的注册模式。

参数化类型

参数化类型需要在编码、解码、比较和哈希时获取列声明的参数,以确定分配大小和布局。定义一个带有解析函数和逆向 to_strings 函数的参数结构体,在类型构建器上通过 .params<P, &ParseFunc, &ToStringsFunc>() 注册两者,并将 const P& 用作类型操作函数的第一个参数。SDK 会为每个唯一的参数组合缓存解析结果,因此解析函数每个类型实例化最多运行一次。to_strings 函数是 parse 的逆函数:它将一个类型化的 P 写回规范的键/值字符串形式,以便服务器可以按照 parse 消费的相同形状发布推断出的参数。
在类型构建器上注册 .params<>()。使用 .int_to_params<&mytype_int_to_params_fn>() 处理 MYTYPE(N) 整数语法,使用 .resolve_params<&mytype_resolve_params_fn>() 来验证参数并计算存储大小。调用 .max_persisted_length(N) 并传入所有有效参数化下持久化字节大小的上界;服务器仅在类型参数推断路径上使用它,此时它尚未推断出参数,因此无法查询 resolve_params 来确定编码缓冲区的大小。对于基于 VDF 的内建默认值,请使用带有 VDF 名称的 .intrinsic_default_vdf(),并通过 make_intrinsic_default<&mytype_default>() 单独注册该 VDF。
.max_persisted_length() 需要 VEF 协议 3 或更高版本。使用它的类型无法被协议 3 之前的服务器加载。
参数化变体——TypeEncodeWithParamsFunc<P>TypeDecodeWithParamsFunc<P>TypeCompareWithParamsFunc<P>TypeHashWithParamsFunc<P>——以及 ParamsToStringsFunc<P> (void fn(const P&, std::map<std::string,std::string>&)) 均通过 <villagesql/vsql.h> 提供。 vsql::make_type 模板方法会检测 params 参数并自动通过 params 缓存进行路由。编码函数将 vsql::MaybeParams<P> & 作为第一个参数;is_known() 在运行时始终为 true,value() 返回 const P&。解码、比较和哈希变体接受 vsql::CustomArgWith<P>,其 params() 访问器返回 const P& 在 SQL 中提供参数。 有两种语法可以到达 resolve_params
  • 整数——MYTYPE(N)。服务器将 N 通过 int_to_params 路由以构建参数映射。需要 .int_to_params<>()
  • 字符串——MYTYPE('key=value,...')。服务器规范化该字符串并直接调用 resolve_params;不涉及 int_to_params。只要注册了 .resolve_params<>() 即可使用——无需额外的构建器调用。
仅注册了 .resolve_params<>() 的类型接受字符串形式并拒绝 MYTYPE(N)SHOW CREATE TABLE 会保留写入时所用的形式。
int_to_params 生成并由 resolve_params 消费的序列化 key=value,... 参数字符串上限为 VEF_MAX_TYPE_PARAMS_STRING_LEN (1024 字节)。如果某个参数化配置的规范字符串会超过该限制,将被拒绝并返回一个明确定义的错误,而不是被静默截断——请将单个类型的参数名称与值的组合保持在 1024 字节以内。

重写参数并提供默认值

resolve_params 还有第二个会修改参数的重载:它以非常量引用的方式接收参数映射,以便类型可以重写它——通常是为了填充作者省略的默认值。以相同的方式注册它(.resolve_params<&fn>() 接受任一形式;只注册其中一个):
重写后的映射会成为服务器持久化并由 SHOW CREATE TABLE 打印的规范参数字符串,因此重写必须是幂等的。声明(MYTYPE,无长度或参数)现在会以一个空映射调用 resolve_params,而不是跳过它,因此提供默认值的类型会为每一列都赋予显式参数——vsql_bitfield_testBITFIELD 会将一个裸列解析为 max_number_of_bits=4096

可变长度类型

可变长度自定义类型按每个值决定其持久化大小,而不是使用单一的固定占用空间。通过在类型构建器上调用 .variable_length_type() 来声明一个可变长度类型,该调用会设置类型的 variable_length 标志。
.variable_length_type() 将类型所需的协议提升至 VEF 协议 4。服务器仅在协议 4 或更高版本读取 variable_length 标志。请针对选择加入的开发 ABI 标头进行构建 (-DVSQL_USE_DEV_ABI=ON);旧版服务器不会读取该标志。
可变长度类型还必须调用 .max_persisted_length(N)。如果省略它,build() 将在编译时失败——服务器需要这个上界来为底层字段分配缓冲区。 .variable_length_type() 是单调的:在协议 3 的设置器(max_persisted_length()params()int_to_params())之前或之后调用它,都不会将协议要求降回到协议 4 以下。
与每个自定义类型一样,可变长度类型必须生成一个可用的 内建默认值。默认值被编码到字段的最大容量中,并且任何 1 到 max_persisted_length 字节之间的非空结果都会被接受。如果某个类型对空字符串编码的结果为字节——比如空数组或空位集——它就没有可用的默认值,因此请声明一个编码结果非空的显式默认值:
否则,当一个 NOT NULL 列首次引用该类型时(在 CREATE TABLE 处),该类型将无法初始化——这与无法编码 from_string("") 的固定长度类型相同。

存储过程中的自定义类型

自定义扩展类型可用作存储过程的参数类型,也可用于 DECLARE 变量声明中。服务器在例程执行时使用已安装扩展的类型元数据来解析自定义类型。

另请参阅