ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Turso SDK Kit 指南:基于 Rust 内核与 C ABI 的语言绑定开发实战

Turso SDK Kit 指南:基于 Rust 内核与 C ABI 的语言绑定开发实战 Turso SDK Kit 指南基于 Rust 内核与 C ABI 的语言绑定开发实战【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso本文面向希望为 TursoRust 编写、SQLite 兼容的数据库引擎构建新语言 SDK / 语言绑定的开发者。Turso SDK Kit 把数据库核心逻辑实现在 Rust 层并通过一个轻量、可移植的 C ABIturso.h暴露给外部因此你可以用 rust-bindgen 等工具为任意语言生成绑定而无需重新实现任何数据库语义。读完本文你将掌握 SDK Kit 的架构分层、turso_database_config_t全部配置项、调用者驱动caller-driven异步 I/O 模型、语句生命周期与参数绑定规则以及用于兼容旧版加密数据库的外部页编解码器page codec扩展机制。一、SDK Kit 是什么写给绑定开发者的低层 API在深入代码之前先明确一个容易混淆的点SDK Kit 不是给最终 Rust 应用使用的 SDK。sdk-kit/README.md 开头就给出了明确建议——如果你在用 Rust 构建应用程序请直接使用tursocrate本 crate 的定位是用于构建语言绑定的低层 C ABI。这一定位决定了它的设计取向核心逻辑在 Rust接口是 C所有数据库语义SQL 解析、执行、事务、存储都实现在 Rust 的turso_core中SDK Kit 只是在其之上包裹一层最小化、可移植的 C ABI让任何能调用 C 的语言Python、Go、Java、.NET、Node.js、React Native 等都能以统一方式接入单一头文件导出sdk-kit/turso.h是这个 crate 导出的唯一 C 头文件它可以直接用 rust-bindgen 翻译成各语言的绑定代码对应sdk-kit/src/capi.rs中include!(bindings.rs)的生成机制把绑定这件事变得可预测C 消费方看到的 API 面极小——打开数据库、连接、prepare、bind、step/execute、读取行、finalize外加显式的所有权与生命周期约定。从源码结构看SDK Kit 由三层组成见 sdk-kit/src/lib.rsrsapi模块sdk-kit/src/rsapi.rs对turso_core的类型化 Rust 封装提供TursoDatabase、TursoConnection、TursoStatement、TursoStatusCode、TursoError等高层句柄capi模块sdk-kit/src/capi.rs把rsapi的 Rust 方法逐个翻译为#[no_mangle] extern C导出函数并通过#[signature(c)]宏来自 sdk-kit-macros/src/lib.rs与turso.h中的声明保持一致turso.h头文件sdk-kit/turso.h对外唯一的 C 接口契约也是各语言绑定的生成源。此外sdk-kit/Cargo.toml 显示该 crate 以crate-type [lib, cdylib, staticlib]三种形式产出既可作为 Rust 库链接也可编译为动态/静态 C 库供其他语言使用默认特性为encryption与pure-rust-crypto并可选fts特性。二、核心设计思想五个关键约定README 总结了 SDK Kit 的五条核心设计理解它们是使用任何语言绑定的前提1. 异步 I/O默认由库驱动可选调用者驱动库驱动library-drivenasync_io false时step/execute内部遇到需要 I/O 的情况会自动把 I/O 跑完再返回调用方永远看不到Io状态调用者驱动caller-drivenasync_io true时step/execute遇到 I/O 需求会直接返回Io状态码由调用方显式调用run_io()推进一轮 I/O 后端循环。这非常适合接入事件循环event loop或 io_uring 等现代存储后端。2. 清晰的状态码没有隐藏阻塞也没有异常step的返回被收敛为三个显式状态Done执行完毕、Row产生了一行结果、Io需要推进 I/O。任何错误都通过显式的错误指针/错误对象返回而不是靠异常或隐式阻塞。3. 最小 API 面打开数据库 → 连接 → prepare → bind → step/execute → 读行 → finalize全部核心操作就这些绑定层的工作量因此被压到最低。4. 所有权与生命周期对 C 消费方显式化每个句柄database / connection / statement都有对应的_new/_deinit或_deinit/finalize配对行值引用仅在下一次 step/reset/finalize 之前有效字符串返回值需要调用turso_str_deinit释放。5. 可选的外部页编解码器page codecturso_database_config_t.page_codec允许调用方注入一个同步的页变换回调表turso_page_codec_v1_t在磁盘原始字节与明文 SQLite 页之间做转换从而在不把加密库链接进 Turso 核心的情况下兼容旧版 SQLite 加密格式。这一节我们会在后文展开。三、配置总览turso_database_config_t 逐字段解析无论是 Rust 的TursoDatabaseConfigsdk-kit/src/rsapi.rs还是 C 的turso_database_config_tsdk-kit/turso.h配置字段一一对应。下表列出全部字段及其语义字段类型说明pathconst char *数据库文件路径或:memory:使用纯内存数据库async_iouint64_t非零表示启用调用者驱动异步 I/Ostep/execute可能返回TURSO_IO为零则由库自行驱动 I/Oexperimental_featuresconst char *逗号分隔的实验特性列表可为 NULL见下文特性表vfsconst char *显式指定文件系统后端memory、syscall、io_uring仅 Linux、experimental_win_iocp仅 Windows实验性可为 NULL 使用默认encryption_cipherconst char *本地加密算法实验性需同时在experimental_features中启用encryptionencryption_hexkeyconst char *加密密钥的十六进制字符串encryption_cipher与encryption_hexkey必须同时设置或同时不设置page_codecconst turso_page_codec_v1_t *可选的外部页编解码器回调表用于兼容旧版加密格式open_flagsuint32_t打开标志位掩码TURSO_DATABASE_OPEN_DEFAULT 0或TURSO_DATABASE_OPEN_READONLY 1experimental_features 的合法取值experimental_features在 Rust 层通过TursoDatabaseConfig::database_opts()sdk-kit/src/rsapi.rs翻译为类型化的DatabaseOpts。从源码可以看出它支持以下 token逗号分隔前后空白会被 trim未知 token 与strict会被忽略——strict 表始终开启保留该 token 仅为向后兼容Token效果views启用视图index_method启用索引方法custom_types启用自定义类型autovacuum启用自动 VACUUMvacuum启用 VACUUMencryption启用加密设置encryption_cipher/encryption_hexkey的前提否则报错attach启用 ATTACHgenerated_columns启用生成列multiprocess_wal启用多进程 WAL开启时自动附加NoLock打开标志without_rowid启用 WITHOUT ROWID 表mvcc_passive_checkpoint启用实验性 MVCC 被动检查点对应行为均有单元测试覆盖例如database_opts_maps_experimental_features验证了每个 token 与开关的映射关系以及 views , strict , unknown_one 这类带空白、未知 token 的容错处理sdk-kit/src/rsapi.rs。加密与 page codec 的互斥约束从TursoDatabaseConfig::from_capisdk-kit/src/rsapi.rs与open()的校验逻辑sdk-kit/src/rsapi.rs可以看到三条硬性规则encryption_cipher与encryption_hexkey必须成对出现只设置其中一个会返回Misuse内置加密encryption特性 密钥与外部page_codec不能同时使用二者互斥加密是实验特性未在experimental_features中声明encryption就传入密钥会直接报错。四、外部页编解码器External Page Codecs深入README 用专门一节介绍了turso_page_codec_v1_t这是 SDK Kit 最独特的扩展点用于为旧版 SQLite 加密格式提供兼容层——加密实现由应用自己提供Turso 核心完全不感知具体算法。回调表结构turso_page_codec_v1_tsdk-kit/turso.h是一个带版本号的回调表字段如下字段类型说明abi_versionuint32_t必须为1否则from_capi返回Misuseunsupported page codec ABI versionctxvoid *回调上下文绑定层需自行管理其生命周期reserved_spaceuint8_t每页保留字节数codec_id[16]uint8_t稳定且非保密的编解码器配置标识只要页变换可能产生不同字节就必须改变见下destroyvoid (*)(void *ctx)销毁回调数据库句柄释放时调用以回收上下文probe_headerint32_t (*)(...)可选。在 Turso 解析第 1 页之前检查原始前 512 字节报告页大小与保留字节decode_pageint32_t (*)(...)必填。把磁盘原始页解码为明文 SQLite 页encode_pageint32_t (*)(...)必填。把明文页编码回磁盘格式probe_header与decode_page/encode_page共享同一个变换回调签名turso_page_codec_transform_tsdk-kit/turso.h其参数为(ctx, page_no, location, input, input_len, output, output_len, error)。六条硬性规则README 明确列出的 codec 规则如下回调必须是同步的不得执行 Turso I/O也不得重入同一个数据库probe_header可以检查原始前 512 字节并在 Turso 解析第 1 页前报告页大小 / 保留字节decode_page/encode_page接收页号与位置TURSO_CODEC_LOCATION_DATABASE 0或TURSO_CODEC_LOCATION_WAL 1且必须恰好写出一页到输出缓冲区codec_id必须是稳定、非保密的标识且只要页变换可能产生不同字节就必须改变例如换了密钥或保留字节数回调上下文与函数指针必须保持有效直到 codec 的destroy回调被调用回调失败会以数据库打开 / 读 / 写错误的形式浮出水面。源码层面的验证与约束CApiPageCodec::from_capisdk-kit/src/rsapi.rs在构造时即校验abi_version 1、decode_page/encode_page非空、codec_id非全零不满足则直接拒绝Drop for CApiPageCodecInnersdk-kit/src/rsapi.rs保证数据库释放时调用destroy回调把上下文回收责任交还给绑定层测试page_codec_is_applied_to_sdk_connectionssdk-kit/src/rsapi.rs演示了完整的端到端流程用 XOR 编解码器写入secret_datacheckpoint 后直接读原始文件确认前 16 字节不再是SQLite format 3\0魔数再换用同一个 codec 重新打开并成功读回数据——证明编解码发生在磁盘字节层面测试page_codec_id_tracks_full_test_codec_configurationsdk-kit/src/rsapi.rs证明codec_id会随掩码与保留字节数的变化而改变约束方面page_codec_rejects_multiprocess_wal_through_sdk_opensdk-kit/src/rsapi.rs验证了外部 page codec不能与实验性多进程 WAL 组合否则打开数据库直接报错测试capi_page_codec_does_not_read_c_struct_paddingsdk-kit/src/rsapi.rs则关注 FFI 细节逐字段写入MaybeUninit结构体避免读取 C 结构体未初始化填充字节。对于语言绑定实现者建议的接入方式是用绑定语言各自的生命周期管理机制包裹turso_page_codec_v1_t保证ctx与函数指针的存活期覆盖整个数据库生命周期并在destroy中完成回收。五、Rust 示例逐行解读README 给出的 Rust 示例对应turso_sdk_kit::rsapi演示了完整的最小工作流use turso_sdk_kit::rsapi::{ TursoDatabase, TursoDatabaseConfig, TursoStatusCode, Value, ValueRef, }; fn main() - Result(), Boxdyn std::error::Error { // Create the database holder (not opened yet). let db TursoDatabase::create(TursoDatabaseConfig { path: :memory:.to_string(), experimental_features: None, io: None, // When true, step/execute may return Io and you should call run_io() to progress. async_io: true, }); // Open and connect. db.open()?; let conn db.connect()?; // Prepare, bind, and step a simple query. let mut stmt conn.prepare_single(SELECT :greet || Turso)?; stmt.bind_named(greet, Value::Text(Hello.into()))?; loop { match stmt.step()? { TursoStatusCode::Row { // Read current row value. Valid until next step/reset/finalize. match stmt.row_value(0)? { ValueRef::Text(t) println!({}, t.as_str()), other println!(row[0] {:?}, other), } } TursoStatusCode::Io { // Drive one iteration of the I/O backend. stmt.run_io()?; } TursoStatusCode::Done break, _ unreachable!(unexpected status), } } // Finalize to complete the statement cleanly (may also return Io). match stmt.finalize()? { TursoStatusCode::Io { // If needed, drive IO and finalize again. stmt.run_io()?; let _ stmt.finalize()?; } _ {} } Ok(()) }结合 sdk-kit/src/rsapi.rs 的源码这个示例背后有几个值得注意的实现细节两阶段打开TursoDatabase::new/create只是创建一个持有者ArcSelf此时并不初始化真正的open()是一个异步状态机TursoDatabaseOpenPhase::Init → Opening → Done见 sdk-kit/src/rsapi.rs。在Init阶段先解析 VFS、计算DatabaseOpts、打开文件Opening阶段调用Database::open_async若async_io false遇到IOResult::IO会内部wait否则原样返回给调用方驱动sdk-kit/src/rsapi.rs。状态码语义TursoStatusCode只有Done / Row / Io三态sdk-kit/src/rsapi.rs。在step_inner中StepResult::IO | Yield | Sleep在async_io true时被转换为Io状态返回否则内部_io().step()后继续循环sdk-kit/src/rsapi.rs——这就是没有隐藏阻塞的落地实现。行值有效期row_value返回的是当前行的引用/值只在下一次step/reset/finalize之前有效示例中的注释即是这一约定。finalize 也可能返回 Iofinalize在语句仍在运行时execution_state().is_running()会先推进执行直到完成期间同样可能返回Io因此示例中做了二次驱动处理sdk-kit/src/rsapi.rs。并发保护ConcurrentGuardsdk-kit/src/lib.rs基于原子compare_exchange保证同一语句不会被并发执行违例返回Misuse(concurrent use forbidden)测试test_db_concurrent_usesdk-kit/src/rsapi.rs专门验证了并发时会产生Misuse错误。语句跟踪与关闭TursoConnection维护一个语句注册表stmts: StmtRegistryclose()会把所有未 finalize 的语句置为None打断Statement → ArcConnection → ArcDatabase的引用链避免数据库文件改名后残留陈旧句柄sdk-kit/src/rsapi.rs对应回归测试见test_stale_registry_with_live_sdk_handles与test_close_finalizes_outstanding_statementssdk-kit/src/rsapi.rs。六、C 示例逐行解读与状态码速查README 的 C 示例展示了同一流程在 C ABI 下的完整写法turso_database_create→turso_database_open→turso_database_connect→turso_connection_prepare_single→turso_statement_bind_named→ 循环step/run_io→finalize→ 三个deinit清理句柄。对照 sdk-kit/src/capi.rs 的导出实现C 层 API 与 Rust 层一一对应C 函数Rust 后端说明turso_database_new/turso_database_open/turso_database_connectTursoDatabase::new/open/connect句柄创建与打开turso_connection_prepare_singleprepare_single准备单条语句turso_connection_prepare_firstprepare_first解析首条语句并返回尾部偏移用于实现多语句executeturso_statement_bind_positional_{null,int,double,text,blob}bind_positional按 1-based 位置绑定turso_statement_named_positionnamed_position命名参数 → 位置必须带前缀如:name、name、$name、?1turso_statement_step/execute/run_io/reset/finalizestep/execute/run_io/reset/finalize执行驱动turso_statement_row_value_{kind,int,double,bytes_ptr,bytes_count}row_value按类型读取行值turso_statement_column_{count,name,decltype}及column_kind、column_base_type、column_array_dimensions、column_declared_namecolumn_count/column_name/column_decltype/column_type_info结果集元数据含自定义类型 / 数组维度信息turso_statement_{n_change,parameters_count,parameter_name}同名方法影响行数与参数信息turso_str_deinitc_string_to_str释放由 C API 返回的字符串状态码速查turso_status_code_tsdk-kit/turso.h完整定义如下绑定层应完整透传而非自行映射值名称含义0TURSO_OK成功1TURSO_DONE语句执行完毕2TURSO_ROW产生一行结果3TURSO_IO需要调用run_io()推进 I/Oasync_io true时4TURSO_BUSY数据库被锁5TURSO_INTERRUPT语句被中断6TURSO_BUSY_SNAPSHOT快照过期需回滚并重试事务127TURSO_ERROR通用错误128TURSO_MISUSEAPI 误用如并发访问、越界绑定、finalize 后继续使用129TURSO_CONSTRAINT约束违反130TURSO_READONLY只读数据库写入131TURSO_DATABASE_FULL数据库已满132TURSO_NOTADB文件不是数据库134TURSO_IOERRI/O 错误错误消息通过可选的error_opt_out参数返回const char **用完后必须调用turso_str_deinit释放。Rust 侧的TursoError::to_capi_codesdk-kit/src/rsapi.rs是这些码的权威映射来源其中LimboError::StatementsInProgress被映射为Busy语义提示调用方应自行 finish/reset 语句而非等待sdk-kit/src/rsapi.rs。C 示例中的两个细节示例在bind_named时注释强调省略名称中的前导冒号传greet而非:greet而 Rust 层的named_position/parameter_name则要求必须带前缀测试test_named_position_requires_prefixed_name验证了named_position(new_name)会报错sdk-kit/src/rsapi.rs。两个语言入口对命名参数的约定不同绑定层需要按各自入口文档处理。错误路径上示例采用了逐层清理的惯例任一环节失败都会deinit已经创建的所有句柄避免资源泄漏。七、语句生命周期与参数绑定规则绑定实现必读生命周期状态机一个TursoStatement的生命周期是prepare创建 → 可选的多次bind→ 反复step或一次性execute→finalize或reset后重新绑定复用。finalize 之后句柄内部被置为None任何后续操作都返回Misuse(statement has been finalized)FINALIZED_ERR常量sdk-kit/src/rsapi.rstest_finalize_disposes_statementsdk-kit/src/rsapi.rs逐一验证了 finalize 后step/execute/reset/run_io/bind_positional全部报错、而n_change/column_count/parameters_count返回 0 的行为。参数绑定的 SQLite 兼容规则从源码与测试可以总结出以下绑定规则绑定层需原样暴露位置参数 1-basedbind_positional的索引从 1 开始越界返回Misusetest_bind_positional_rejects_out_of_bounds_index命名参数必须带前缀named_position接受:name、name、$name三种前缀也接受?1形式的位置索引别名test_named_and_indexed_alias_share_slot证明:v与?1指向同一槽位时绑定一次两个列都可见稀疏位置索引SELECT ?3的parameters_count是 3可绑定 1 和 3绑定 4 则Misuse——与 SQLite 行为一致test_sparse_positional_index_*混合占位符UPDATE ... SET email ?, age :new_age WHERE name ?这类混合写法中槽位按 SQL 中出现顺序编号named_position与bind_positional可以混用test_execute_update_with_mixed_placeholdersn_change反映行数execute返回的TursoExecutionResult.rows_changed取自stmt.n_change()INSERT/UPDATE/DELETE 后可用于判断实际影响行数。上述规则在 C 层同样成立参见 capi 测试test_db_stmt_bind_positional_out_of_bounds、test_db_stmt_sparse_positional_slot_range_matches_sqlite、test_db_stmt_named_position_requires_prefixed_namesdk-kit/src/capi.rs。行值读取C 层按类型提供turso_statement_row_value_kind返回TURSO_TYPE_NULL/INTEGER/REAL/TEXT/BLOB/UNKNOWN、_int、_double、_bytes_ptr、_bytes_count等取值函数其中_bytes_ptr/_bytes_count只对 TEXT/BLOB 有效其他类型分别返回 NULL 与 -1。Rust 层的ValueRef同样区分Null / Numeric(Integer|Float) / Text / Blob。八、附加能力扩展函数、聚合、排序规则与更多SDK Kit 不止提供基础的 prepare/step 流程还通过 sdk-kit/src/rsapi.rs 与对应 C 导出暴露了一组可选的扩展接口供绑定层按需接入外部标量函数register_external_scalar_functionC:turso_connection_register_scalar_function支持注册带context/value_destructor生命周期管理的自定义标量函数外部聚合函数register_external_aggregate_functionC:turso_connection_register_aggregate_function提供init/step/finalize三阶段回调可自定义聚合逻辑排序规则register_external_collation/unregister_external_collation允许覆盖列的排序比较扩展加载set_load_extension_enabled与load_extension非 wasm 目标用于加载 C 扩展连接控制set_busy_timeoutC:turso_connection_set_busy_timeout_ms、interrupt跨线程安全语义对应sqlite3_interrupt、set_query_timeout/get_query_timeout语句级最大墙钟时间Duration::ZERO表示禁用、last_insert_rowid、get_auto_commit语句缓存prepare_cached会把 prepared program 缓存起来并在连接 schema 兼容时复用cached_statements缓存 program.is_compatible_with(connection)校验sdk-kit/src/rsapi.rs多语句执行支持prepare_first返回第一条语句与尾部偏移方便绑定层实现顺序执行整段 SQL的execute(...)语义sdk-kit/src/rsapi.rs。九、构建与接入指引SDK Kit 作为工作区成员见根目录 Cargo.toml随 Turso 主仓库一起构建rust 版本约束见 rust-toolchain.toml。典型接入路径Rust 绑定开发者直接依赖turso_sdk_kitcratecrate-type包含lib、cdylib、staticlib使用rsapi模块编写绑定逻辑再用capi模块或自行#[no_mangle]导出 C 接口C/其他语言绑定开发者编译出cdylib/staticlib后链接turso.h或直接以turso.h为契约生成绑定代码SDK Kit 的构建脚本使用bindgen版本见 sdk-kit/Cargo.toml 的[build-dependencies]自动生成bindings.rs各语言绑定同样可以借助 rust-bindgen 复用这一模式参考实现仓库 bindings 目录下的 Go / Java / JavaScript / Python / Rust / React Native / C / .NET / Tcl 等语言绑定均是以 SDK Kit 为基础构建的现成范例可作为实现自己语言绑定的蓝本。十、总结Turso SDK Kit 用Rust 实现语义 单一 C 头文件契约的方式把数据库绑定的复杂度收敛到了最小架构上rsapi类型化 Rust API→capiextern C导出→turso.h唯一头文件三层清晰分离绑定只需翻译头文件执行模型上Done/Row/Io三态状态码让异步 I/O 完全透明——库驱动适合简单场景调用者驱动适合事件循环与 io_uring 等现代后端扩展能力上外部页编解码器机制允许在不把加密算法链接进核心的前提下兼容旧版加密数据库且通过codec_id与 ABI 版本校验保证了配置可追踪、接口可演进安全与健壮性上并发守卫、finalize 后的统一Misuse错误、连接关闭时的语句注册表清理、以及加密/多进程 WAL/page codec 之间的互斥约束都通过大量单元测试固化了下来。对任何想为 Turso 添加一门新语言 SDK 的团队来说正确的起点不是从零实现 SQL 语义而是理解 sdk-kit/README.md 所定义的这份 C ABI 契约再参考 bindings 目录中的既有实现完成绑定。【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表