拓冰建站拓冰建站
首页 / 资讯中心 / 正文

ScyllaDB UDF 中 CQL 与 Lua 的类型映射机制全解析

ScyllaDB UDF 中 CQL 与 Lua 的类型映射机制全解析【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladbScyllaDB 允许用户以 Lua 编写用户自定义函数UDF在 CQL 查询中以SELECT fn(...)的形式调用。本文以仓库文档 docs/dev/lua-type-mapping.md 为主体完整讲解每一类 CQL 类型在 UDF 参数与返回值中如何映射到 Lua 值并结合 lang/lua.cc 的实现源码说明类型转换的底层校验逻辑、边界行为如整数回绕以及出错时的具体行为帮助开发者编写可正确通过类型检查的 UDF。背景UDF 的执行环境与类型转换入口ScyllaDB 的 UDF 是纯函数Lua 脚本在创建函数时被 lang/lua.cc 中的lua::compile()编译成 Lua 字节码bitcode每次执行时在一个受限的lua_State中运行——内存分配受max_bytes/max_contiguous限制执行时间受timeout_in_ms限制且只加载 base、string、coroutine、table 四个标准库见 lang/lua.cc 中的loadedlibs。类型映射发生在两个方向CQL → Lua参数入栈由to_lua_visitorlang/lua.cc根据 CQL 类型把data_value压入 Lua 栈nil统一压为lua_pushnilLua → CQL返回值转换由from_lua_visitorlang/lua.cc根据声明的返回类型从栈顶弹出 Lua 值并构造data_value失败时抛出invalid_request_exception。构建系统通过 cmake/FindLua.cmake 绑定 Lua 5.3NAMES lua lua5.3 lua53这与文档中Lua 5.3 has native support for 64 bit integers的说明一致。仓库中的 test/cql/lua_test.cql 与 test/cql/lua_test.result 提供了这些转换行为的回归测试依据。字符串类型ASCII、TEXT、VARCHAR 与 BLOB文档约定ASCII / TEXT / VARCHAR转换为 Lua 时映射为 Lua string。反向转换时接受任何 Lua 能强制转换为 string 的值即数字、decimal 等但会按目标类型校验VARCHAR 校验合法 UTF-8ASCII 校验合法 ASCII。BLOB由于 Lua string 可保存任意二进制数据Lua 5.3 的 string 是 8 位字节序列而非 C 字符串BLOB 直接以 Lua string 双向转换。源码印证lang/lua.ccBLOB 入栈用lua_pushlstring长度安全的二进制推送出栈时bytes_type_impl的访问器直接把 Lua string 的字节按bytes原样返回VARCHAR 出栈时调用utils::utf8::validate_with_error_position逐字节校验 UTF-8失败会报value is not valid utf8, invalid character at byte offset NASCII 出栈时调用utils::ascii::validate失败报value is not valid ascii。需要注意反向转换的强制逻辑由visit_lua_valuelang/lua.cc统一实现——例如返回text的 UDF 返回整数 42会被格式化为字符串 42。整数类型TINYINT、SMALLINT、INT、BIGINT 与 COUNTER文档约定Lua 5.3 原生支持 64 位整数因此 TINYINT、SMALLINT、INT、BIGINT 全部映射为 Lua 原生 integerlua_pushinteger见 lang/lua.cc反向转换时接受任何 Lua 能强制为整数的值整数、整数值 double、可解析的字符串等如果值超出目标 CQL 类型的范围值会回绕wrap而不是报错COUNTER由于 UDF 是纯函数不能真正自增因此 counter 也简单地映射为一个 Lua 原生整数。回绕行为在 utils/big_decimal.cc 中实现uint64_t from_varint_to_integer(const utils::multiprecision_int varint) { // The behavior CQL expects on overflow is for values to wrap // around. ... we first mask the low 64 bits, convert to a // uint64_t, and then let c convert, with possible overflow, // to ToType. return static_castuint64_t(~static_castuint64_t(0) boost::multiprecision::cpp_int(varint)); }即先掩码取低 64 位再转换到目标宽度例如一个超出tinyint范围的返回值会被截断为 8 位。编写 UDF 时应注意返回tinyint的函数返回 300得到的将是 300 % 256 44而不是异常。浮点类型FLOAT 与 DOUBLE文档约定CQL → LuaFLOAT 和 DOUBLE 统一映射为 Lua 原生 doubleLua 的 number 浮点部分即 C double源码中floating_type_implT的入栈访问器执行lua_pushnumberlang/lua.ccLua → CQL数值会被舍入为目标 float 或 double 的最近可表示值。文档特别指出返回一个无法精确表示为 double 的超大整数是合法的——例如一个超出 double 精确范围的整数值会被舍入后返回而不是报错。反向转换经visit_lua_number完成接受 decimal userdata 和 double 两类来源lang/lua.cc。大数VARINT 与 DECIMAL 的 userdata 映射这是文档中最特殊的一类。由于 Lua 没有原生大数类型VARINT 和 DECIMAL 映射为userdata并携带一个名为Scylla.decimal的元表lang/lua.cc元表上注册了__gc、__add、__sub方法lang/lua.cc因此脚本里可以直接对两个 decimal 做加减法加法结果溢出 double 精度范围时仍保持精确的big_decimal语义。对齐分配细节见 lang/lua_scylla_types.hh 中的aligned_user_dataTlua_newuserdata只保证 8 字节对齐因此实现预留alignof(T) - 8的填充字节并对齐后再做 placement new保证big_decimal等 C 对象在 userdata 内正确析构__gc中调用std::destroy_at。类型强制规则文档原文示例源码在 lang/lua.cc对于需要大数语义的返回类型如声明返回bigintLua 值可以是整数 42浮点数 42.0字符串 42varint 42userdatadecimal 42userdata其实现路径是visit_lua_value的访问器double 若落在 64 位整数范围内且为整数值则转为精确整数字符串先尝试解析为big_decimal解析失败再退化为 Lua 的tonumberdecimal userdata 则经visit_decimal判断分子能否被分母整除——能整除则得到精确整数否则退回 double。日期与时间类型DATE、TIME、TIMESTAMPDATE文档约定CQL → Lua映射为自 epoch 的天数 2^31的整数加 2^31 偏移是为了让 1970 年之前的日期也有非负表示与 CQLdate的内部编码一致Lua → CQL 支持三种形式整数自 epoch 的天数 2^31CQL date 字面量字符串如1980-01-01Lua date table且只含year、month、day三个字段含hour/min/sec会报错date type has no hour, minute or second。源码中simple_date_return_visitor::operator()(const lua_table)正是按date::local_days(ymd) (1UL 31)计算偏移lang/lua.cc整数分支要求值能放进 32 位无符号整数。TIME文档约定 CQL → Lua 传自午夜起的秒数整数Lua → CQL 接受同语义的整数或 CQL time 字面量字符串如10:30:00见 lang/lua.cc 中time_type_impl的访问器整数分支要求值能放进 64 位有符号整数字符串分支走time_type_impl::from_string_view。需要说明的是从源码结构看time_type_impl在 types/concrete_types.hh 定义为simple_type_implint64_t入栈访问器的注释写的是nanoseconds since midnightlang/lua.cc。可以推断脚本实际拿到的原生整数的量纲以 CQL 内部编码自午夜起的纳秒为准文档中seconds的表述与源码注释存在出入编写依赖该量纲的 UDF 时建议以测试行为test/cql/lua_test.cql实测为准。TIMESTAMP文档约定CQL → Lua映射为自 epoch 的毫秒数整数源码中入栈访问器取time_since_epoch().count()lang/lua.ccLua → CQL 支持同语义整数、CQL timestamp 字面量字符串源码中还额外支持 date table[lang/lua.cc](https://link.gitcode.com/i/7ccfeefeae7fc111dfe9a4239ee4db80#L552-L569, L911-L918)year、month、day必填hour缺省 12、minute/sec缺省 0与 CQL 的语义一致并用boost::gregorian/boost::posix_time换算为毫秒。注意源码注释中提到 boost::gregorian 只支持 1400–9999 年date table 路径的年份实际受此限制。DURATION文档约定CQL → Lua生成表{ months v1, days v2, nanoseconds v3 }Lua → CQL接受同样格式的表或 CQL duration 字面量字符串如2mo1d。入栈实现见 lang/lua.cclua_createtable 三个lua_setfield。出栈校验lang/lua.cc逐条严格months、days必须能放进 32 位有符号整数否则报months/days doesnt fit in a 32 bit integernanoseconds必须能放进 64 位有符号整数出现其它键名报invalid duration field传入非表、非字符串的值报a duration must be of the form { months v1, days v2, nanoseconds v3 }。INET、UUID 与 TIMEUUID文档约定这三类均以字符串形式双向转换INET127.0.0.1或::1。源码中 INET 出栈直接from_string_view(get_string(...))lang/lua.cc即非法地址字符串会抛异常而非静默失败UUID / TIMEUUID标准 UUID 字符串表示经uuid_type_impl::from_string_view/timeuuid_type_impl::from_string_view解析lang/lua.cc。容器类型LIST、TUPLE、MAP、UDT、SET、VECTORLIST 与 TUPLE文档约定LIST表示为 Lua 序列array part如{foo, bar}。返回非序列的表是错误TUPLE与 LIST 表示方式相同但返回时会检查元素个数和各位置类型。源码印证LIST 出栈时先收集所有(下标, 值)对并按下标排序然后逐个验证下标是否恰好是1..n否则抛出table is not a sequencelang/lua.cc。这意味着稀疏表如{[1]a, [3]c}会被拒绝TUPLE 出栈时验证下标落在1..n内否则key X is not valid for a sequence of size n且每个位置都必须存在否则key X missing in sequence of size n值再按 tuple 的第 k 个类型递归转换lang/lua.cc。MAP文档约定MAP 与 Lua table 互转{foo 42}表示为{foo 42}。源码中入栈按{k1 v1, k2 v2, ...}构造lang/lua.cc出栈遍历表后按 key 排序构造 CQL maplang/lua.cc。由于 CQL map 在存储上有序而 Lua table 无序这个排序保证了同一 UDF 输出在 CQL 侧的确定性。UDT文档约定UDT 表示为与 map 类似的表但返回时检查三件事——没有意外的键、所有预期的键都在、各字段类型合法。源码实现lang/lua.cc完全对应先建立字段名 - (字段序, 字段类型)的映射遍历表时遇到未知字段抛invalid UDT field X字段值按声明类型递归转换类型错则报错最后逐一确认每个字段都有值缺失则抛key X missing in udt。SET文档约定SET 用表的键表示值为true即{[1] true, [2] true, [3] true}。返回时检查所有值必须是true且与其它 Lua table 一样表本身是无序的——转回 CQL 时会对元素排序lang/lua.cc非 true 值抛sets are represented with tables with true values随后std::sort。VECTOR文档约定VECTOR 与 LIST 表示方式相同但返回时会检查元素个数。源码中vector_type_impl的访问器lang/lua.cc先按表下标排序然后验证下标恰好为1..dimension缺失位置抛key X missing in sequence of size N。常见错误信息速查以下信息均来自 lang/lua.cc 中from_lua_visitor的实际异常文本可用于排查 UDF 返回类型不匹配问题场景错误信息返回值个数不是 1N values returned, expected 1lang/lua.cc返回非序列表给 listtable is not a sequencetuple/vector 下标越界或缺失key X is not valid for a sequence of size N/key X missing in sequence of size Nset 值非 truesets are represented with tables with true valuesduration 表字段非法invalid duration field: X、months/days doesnt fit in a 32 bit integerUDT 字段错误invalid UDT field X、key X missing in udt字符串非法value is not valid utf8/asciidate/time/timestamp 类型不符date/timestamp must be a string, integer or date table、time must be a string or an integer小结ScyllaDB 的 CQL↔Lua 类型映射设计原则可以概括为三点能用 Lua 原生类型就用原生类型string、integer、double、boolean、table原生类型不够就补 userdatavarint/decimal 配Scylla.decimal元表获得精确算术容器类型全部落到 table并在返回时做严格的形状与类型校验。与 CQL 语义一致的关键边界行为是整数越界回绕而非报错map/set 返回时排序保证确定性date 值携带2^31偏移。以上规则均有 lang/lua.cc 的实现与 test/cql/lua_test.cql 的测试行为可查证编写 UDF 时按文档中各类型的约定返回即可通过类型检查。【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门