Turso sqltest 测试运行器架构解析Backend 特质系统、结果比较与并行执行原理【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso导读本文围绕 Turso一个用 Rust 实现的 SQLite 兼容数据库仓库中testing/sqltest测试运行器的架构文档展开系统讲解该测试框架的四大核心组件——DSL 解析器Parser、后端抽象Backend Trait、结果比较Comparison与并行调度Runner并深入到testing/sqltest/src的真实实现说明每种比较模式、错误处理与执行流水线的源码级细节。读完本文你将理解 sqltest 如何把同一份.sqltest测试文件跑在 Rust 绑定、CLI、JavaScript、PostgreSQL 四种后端上掌握其可扩展后端设计与结果比较机制能够读懂甚至为 Turso 添加新的测试后端。一、总体架构为可扩展性而设计的测试运行器sqltest 测试运行器的设计目标始终是可扩展性。其核心组件架构文档为四个Parser解析器将.sqltest文件解析为 AST语法细节见 DSL 规范Backend Trait后端特质对“面向不同目标执行 SQL”的抽象目标包括 CLI、SDK 等Comparison比较将实际结果与期望结果进行比对Runner运行器以并行方式编排测试执行。各组件的关系如下架构文档中的原始示意┌─────────────────────────────────────────────────────────────┐ │ Test Runner │ │ ┌─────────┐ ┌──────────┐ ┌───────────┐ ┌─────────────┐ │ │ │ Parser │──│ Executor │──│ Comparison│──│ Output │ │ │ └─────────┘ └────┬─────┘ └───────────┘ └─────────────┘ │ │ │ │ │ ┌─────┴─────┐ │ │ │ Backend │ │ │ └─────┬─────┘ │ │ ┌────────────┼────────────┐ │ │ ▼ ▼ ▼ │ │ ┌───────┐ ┌────────┐ ┌────────┐ │ │ │ CLI │ │ Python │ │ Go │ ... │ │ └───────┘ └────────┘ └────────┘ │ └─────────────────────────────────────────────────────────────┘在真实源码中这一架构对应为 src/lib.rs 下的模块划分backends/后端抽象与四种实现、parser/词法/语法分析、AST、comparison/比较算法、runner/调度与执行、snapshot/快照测试、matrix_oracle.rs矩阵差分测试。二、Backend Trait 系统一份测试四种执行目标后端抽象是整个框架“可扩展性”的落点。它由两层 trait 组成全部定义在 src/backends/mod.rs。2.1SqlBackendTrait执行目标的抽象架构文档给出了核心定义#[async_trait] pub trait SqlBackend: Send Sync { /// Name of this backend (for filtering and display) fn name(self) - str; /// Create a new isolated database instance async fn create_database(self, config: DatabaseConfig) - ResultBoxdyn DatabaseInstance; }实际源码在 src/backends/mod.rs 中进一步补充了三个关键方法fn backend_type(self) - Backend返回后端类型枚举供测试按后端过滤fn capabilities(self) - HashSetCapability声明该后端支持的能力集合fn supports_snapshots(self) - bool默认返回false只有 Rust 后端开启用于 EXPLAIN 输出的快照比较fn is_sqlite(self) - bool标识是否为 sqlite3 CLI 后端。Backend枚举定义在 src/parser/ast.rs当前支持四种后端变体显示名说明RustrustRust 绑定后端ClicliCLI 后端tursodb或sqlite3JsjsJavaScript 绑定后端PgpgPostgreSQL 线协议后端tursopg server每种后端一个实现文件cli.rs、js.rs、pg.rs、rust.rs。2.2DatabaseInstanceTrait一次数据库会话#[async_trait] pub trait DatabaseInstance: Send Sync { /// Execute SQL and return results async fn execute(mut self, sql: str) - ResultQueryResult; /// Close and cleanup the database async fn close(self: BoxSelf) - Result(); }源码版src/backends/mod.rs增加了一个重要的默认方法execute_setup用于执行 setup SQL对内存数据库setup SQL 会被缓冲、延迟到正式查询时合并执行对文件型数据库setup SQL 立即执行若返回错误则包装为BackendError::Execute。close在源码中返回DatabaseFileHandle而非()该句柄持有底层临时文件NamedTempFile保证PRAGMA integrity_check交叉校验等后续操作期间数据库文件仍存活句柄被 drop 时临时文件才被清理。2.3QueryResult标准化的结果格式pub struct QueryResult { /// Rows returned, each row is a vector of string-formatted columns pub rows: VecVecString, /// Error message if the query failed pub error: OptionString, }该结构的实际实现src/backends/mod.rs还提供了便利构造器与工具方法QueryResult::success(rows)构造成功结果QueryResult::error(message)构造错误结果is_error()判断是否出错filter_setup_output()处理 CLI 类后端时会通过SETUP_END_MARKER__SETUP_END_MARKER_7f3a9b2c__定义于 src/backends/mod.rs把 setup 阶段输出与查询输出分离开——找到标记行后删除其之前的所有行保证比较只针对真正查询的结果。2.4BackendError后端错误分类src/backends/mod.rs 定义了全部后端失败模式基于thiserror派生CreateDatabase(String)创建/打开数据库失败Execute(String)执行 SQL 失败Close(String)关闭数据库失败NotAvailable(String)后端未安装或未配置如缺少对应运行时Timeout(Duration)查询执行超时CLI 后端默认超时 30 秒见 cli.rs。2.5 Capability能力声明与测试门控为了让测试文件能声明依赖Backend需要上报能力集src/parser/ast.rsCapability含义Trigger支持CREATE TRIGGERStrict支持 STRICT 表MaterializedViews支持物化视图实验性CustomTypes支持CREATE TYPE/DROP TYPE实验性测试可用requires capability reason声明依赖运行器在 runner/mod.rs 的check_skip_conditions中检查global_requires与用例级requires若后端能力不足则跳过并附上原因。CLI 后端还通过 cli.rs 的TURSO_CLI_EXPERIMENTAL_FLAGS常量把视图、自定义类型、attach、索引方法、生成列、vacuum、without-rowid 等实验性特性以--experimental-*参数透传给tursodb二进制。三、比较模式Comparison Modes四种断言方式运行器根据期望类型Expectation选择不同的比较算法。入口函数是 src/comparison/mod.rs 的compare(actual, expectation)它依据Expectation枚举分派到各实现模块。若实际结果是错误而期望成功会直接产生 “expected success but got error” 的 mismatch。3.1 Exact Match精确匹配pub fn compare_exact(actual: [VecString], expected: [String]) - ComparisonResult规则exact.rs逐行、逐列比较行顺序必须一致期望格式管道符分隔的列每行一条如1|Alice空期望 期望空结果内部先通过format_rows行以|连接、行间以换行连接与parse_expected_rows归一化再整体比较字符串不匹配时借助similarcrate 的TextDiff::from_lines生成统一 diffunified diff输出。3.2 Pattern Match正则匹配pub fn compare_pattern(actual: [VecString], pattern: str) - ComparisonResult规则pattern.rs使用 Rustregexcrate 的RegexBuilder正则针对格式化后的完整输出进行匹配行以换行连接、列以管道符连接适合随机值、时间戳等无法精确断言的场景非法正则表达式会作为 “invalid regex pattern” mismatch 报告而不是 panic。3.3 Unordered Match无序集合匹配pub fn compare_unordered(actual: [VecString], expected: [String]) - ComparisonResult规则unordered.rs将实际与期望行各自转为HashSetString行格式化为|连接的字符串做集合比较行序无关每条期望行必须恰好出现一次去重语义注意重复行的判定按集合处理不匹配时分别报告 Missing rows缺失行与 Extra rows多余行便于定位适用于ORDER BY不确定或结果顺序无关紧要的查询。3.4 Error Match错误断言pub fn compare_error(actual_error: Optionstr, expected_pattern: Optionstr) - ComparisonResult规则src/comparison/mod.rs期望错误但查询成功 → mismatch“expected error but query succeeded”pattern 为None接受任意错误pattern 为Some错误消息必须包含该 patterncompare_error在 pattern.rs 中实现为大小写不敏感的正则匹配匹配前会先经normalize_whitespace把连续空白折叠为单个空格并剔除│字符使跨 CLI 排版差异的错误文本也能灵活匹配。3.5 Diff 输出一眼定位差异精确比较不匹配时输出类似架构文档示例与 exact.rs 的生成逻辑一致--- expected actual 1|Alice -2|Bob 2|Charlie 3|Dave-表示期望有而实际缺失的行表示实际多出的行 表示一致行。四、结果类型ComparisonResult 与 TestResult4.1ComparisonResultpub enum ComparisonResult { /// Results match Match, /// Results dont match Mismatch { reason: String }, }src/comparison/mod.rs 提供了is_match()与mismatch(reason)辅助方法reason携带 diff 文本或缺失/多余行明细直接用于测试失败报告。4.2TestResult与TestOutcomepub struct TestResult { /// Name of the test pub name: String, /// Outcome of the test pub outcome: TestOutcome, /// Duration of the test pub duration: Duration, } pub enum TestOutcome { Passed, Failed { reason: String }, Skipped { reason: String }, Error { message: String }, }运行器中的真实定义src/runner/mod.rs更丰富TestResult额外携带file源文件与database数据库配置TestOutcome在四种基础状态之外增加了快照测试专用状态SnapshotNew { content }生成了新快照SnapshotUpdated { old, new }快照被更新更新模式下手动/自动刷新SnapshotMismatch { expected, actual, diff }快照内容不一致。RunSummarysrc/runner/mod.rs负责汇总SnapshotUpdated计入 passedSnapshotMismatch计入 failed整体is_success()要求 failed 与 errors 均为 0。五、测试执行流程四阶段流水线架构文档定义了每个测试用例的标准生命周期Setup Phase准备阶段创建全新数据库实例按顺序执行 setup SQL 块Execution Phase执行阶段执行测试 SQL捕获结果或错误Comparison Phase比较阶段将实际结果与期望比对应用对应的比较模式Cleanup Phase清理阶段关闭数据库实例报告结果。对应的流程图架构文档原始示意┌─────────────────────────────────────────────┐ │ For each test: │ │ ┌─────────┐ │ │ │ Create │ │ │ │ DB │ │ │ └────┬────┘ │ │ ▼ │ │ ┌─────────┐ │ │ │ Run │ (for each setup decorator) │ │ │ Setups │ │ │ └────┬────┘ │ │ ▼ │ │ ┌─────────┐ │ │ │Execute │ │ │ │ Test │ │ │ └────┬────┘ │ │ ▼ │ │ ┌─────────┐ │ │ │Compare │ │ │ │ Result │ │ │ └────┬────┘ │ │ ▼ │ │ ┌─────────┐ │ │ │ Close │ │ │ │ DB │ │ │ └─────────┘ │ └─────────────────────────────────────────────┘5.1 源码级实现Runnable与run_single流水线在 src/runner/mod.rs 的run_single中落地。先看它依赖的Runnabletraitsrc/runner/mod.rsTestCase、SnapshotCase和矩阵展开用例都实现它name()/skip()/setups()元数据访问check_skip_conditions()能力门控expects_error()/cross_check_integrity()供交叉校验决策queries_to_execute()返回要执行的 SQL 列表——测试用例返回[sql]快照用例在eqp_only时为[EXPLAIN QUERY PLAN sql]否则为[EXPLAIN QUERY PLAN sql, EXPLAIN sql]evaluate_results()对执行结果求值并产出TestOutcome。run_single的实际执行顺序与文档一致且额外做了三件事跳过检查依次检查用例级skip与文件级全局skip。skip支持条件SkipCondition::Mvcc在 MVCC 模式开启时跳过、SkipCondition::Sqlite在 sqlite3 CLI 后端跳过见 src/parser/ast.rspanic 防护用AssertUnwindSafe(...).catch_unwind()包裹整个执行将后端或测试代码中的 panic 转成TestOutcome::Error { message: panic: ... }避免单个用例崩溃拖垮整个测试进程交叉完整性校验当测试带cross-check-integrity且通过、数据库可写、不期望错误、非 MVCC 模式、且配置了交叉校验二进制时用另一二进制对生成的数据库文件跑PRAGMA integrity_check内存数据库会被自动升级为临时文件库以支持该能力maybe_upgrade_memory_to_temp见 src/runner/mod.rs。5.2 并行执行与调度框架以并行为默认。TestRunnerB: SqlBackendsrc/runner/mod.rs持有tokio::sync::Semaphore限制并发数默认max_jobs num_cpus::get()RunnerConfig::default。每个用例被tokio::spawn为独立任务经FuturesUnordered聚合每个任务先acquire_owned获取并发许可再执行run_single。每条测试用例还会为文件中的每个database声明各跑一遍。RunnerConfig支持按名称过滤filter为 glob 模式、MVCC 开关、快照更新模式与快照名称过滤等配置项src/runner/mod.rs更多并行细节见 并行执行文档。5.3 快照测试与矩阵差分测试除普通测试外框架还支持两类进阶用例见 DSL 规范 与 快照测试文档Snapshot 用例对EXPLAIN QUERY PLAN以及默认的EXPLAIN字节码输出做快照比较仅在 Rust 后端启用快照文件按命名规则存放在examples/snapshots/下如 snapshot_example__query-plan-by-id.snapMatrix 用例一条带$var模板的 SQL按各变量取值做笛卡尔积展开见 ast.rs 的expand展开后的每个用例在运行时由内置 SQLite oraclematrix_oracle.rs做差分验证Turso 与 SQLite 必须返回相同行同序或双方都拒绝该语句否则视为失败。六、错误处理四类错误的分类与定位架构文档将错误分为四类Parse Errors解析错误测试文件语法无效。运行器在load_single_file中捕获并生成名为parse的TestResultsrc/runner/mod.rsBackend Errors后端错误创建数据库或执行 SQL 失败对应BackendError的CreateDatabase/Execute/Close/NotAvailable/TimeoutComparison Failures比较失败结果与期望不符产生Mismatch { reason }Test Errors测试异常执行期间的意外异常含 panic。所有错误都会携带上下文文件、测试名、行号用于调试AST 中TestCase、SnapshotCase、MatrixCase均保存了name_span名称在源码中的Rangeusize字节区间解析错误能据此精确定位运行期错误则通过TestResult.fileTestResult.name关联到具体文件与用例。加载测试文件时目录会被 glob 展开为**/*.sqltestsrc/runner/mod.rs解析失败的文件不会中断其余文件的运行而是单独收集到errors中统一上报。七、如何扩展新增一个 Backend得益于 trait 抽象接入新目标只需两步详见 adding-backends.md 与 CLI 后端示例实现SqlBackend提供name()、backend_type()在Backend::ALL中加入新变体、capabilities()并实现create_database()返回Boxdyn DatabaseInstance实现DatabaseInstance提供execute_setup()文件型直接执行内存型可缓冲、execute()返回QueryResult注意对 CLI 类输出调用filter_setup_output()剥离 setup 输出、close()返回保持文件存活的DatabaseFileHandle。参考现有实现 cli.rs 可以观察到 CLI 后端的完整模式通过tokio::process::Command派生子进程、用timeout()包裹实现 30 秒超时、以--experimental-*旗标开启实验特性、按二进制名前缀sqlite自动识别 sqlite3 兼容模式而 rust.rs 则是进程内直接调用 Rust 绑定。新增后端后在src/backends/mod.rs中pub mod声明并接入Backend枚举的解析FromStrsrc/parser/ast.rs即可被--backend参数选用。结语sqltest 通过“Parser → Executor → Comparison → Output”的组件化设计配合SqlBackend/DatabaseInstance双层 trait 与统一的QueryResult结果格式让同一套.sqltest用例可以无缝跑在 Rust、CLI、JavaScript、PostgreSQL 四种后端上并以精确、正则、无序、错误四种比较模式覆盖绝大多数断言需求。架构文档之外运行器模块、比较模块 与 DSL 规范 构成了理解这套 SQL 测试体系的三份关键材料而仓库中 basic.sqltest、joins.sqltest 与 snapshot_example.sqltest 等示例文件则提供了最直接的阅读与学习起点。【免费下载链接】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),仅供参考
