
Windmill 的 SQLx 离线查询缓存安全更新指南从cargo sqlx prepare到update-sqlx技能全解析【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill导读本文围绕 Windmill 仓库中的update-sqlx技能文档SKILL.md展开系统讲解在 Windmill 的 Rust 后端中安全更新 SQLx 离线查询缓存的完整方法论。文章涵盖缓存的作用机制、触发时机、备份与恢复流程、测试目标缓存、EE 缓存保护、常见陷阱与最终验证并结合 sqlx-cache.sh 脚本、update_sqlx.sh 脚本以及 AGENTS.md 中的工作树开发约定为读者提供一份可直接落地、经源码验证的实操手册。读完本文你将掌握如何在 CI 的SQLX_OFFLINEtrue模式下安全完成查询缓存的增删改而不破坏企业版EE缓存。什么是 SQLx 离线查询缓存为什么 Windmill 需要它Windmill 的 Rust 后端大量使用 SQLx 的编译期宏sqlx::query!/sqlx::query_as!。这些宏在编译时会解析 SQL 字符串并对查询结果进行类型检查从而在编译期发现 SQL 错误与类型不匹配。然而编译期的类型检查需要连接数据库获取表结构与列类型这在纯离线构建环境下不可行。为此SQLx 提供了offline 模式将查询的元数据数据库名、SQL 文本、列信息、参数类型、可空性、hash预先缓存在backend/.sqlx/query-*.json文件中。当设置了环境变量SQLX_OFFLINEtrue时宏不再访问数据库而是直接读取这些缓存文件。Windmill 的 CI 中即采用SQLX_OFFLINEtrue见 SKILL.md因此所有sqlx::query!/sqlx::query_as!宏都必须在backend/.sqlx/中存在对应的缓存数据。任何一个缓存文件缺失CI 都会直接失败报错信息形如error: SQLX_OFFLINEtrue but there is no cached data for this query以仓库中实际的缓存文件 query-0010ef26da16facd1c2c832601ac687c4c27de46a90f45496b8446af1a9d0578.json 为例其结构包含db_name数据库类型如PostgreSQLquery被缓存的 SQL 原始文本describecolumns列名、类型信息、可空性、parameters参数类型、nullable等由数据库返回的描述信息hash查询的哈希值也是文件名的一部分。文件名query-hash.json中的 hash 与文件内容中的hash字段对应SQLx 通过该 hash 在离线模式下定位查询的元数据。何时需要更新缓存什么操作必须运行 prepareSKILL 文档给出了清晰的触发时机必须运行当你在 Rust 源码中新增或修改了一条 SQL 查询时。此时旧的缓存文件与新的 SQL 不匹配必须重新生成缓存否则 CI 在SQLX_OFFLINEtrue下会报 no cached data 错误。禁止运行当你的改动只删除了查询时。此时缓存对 CI 而言已经完整剩余的是孤儿条目orphaned entries它们只是装饰性的永远不会破坏构建。运行prepare去清理这些孤儿条目反而冒着销毁整个缓存的风险得不偿失。对于纯删除场景SKILL 文档给出的建议是离线删除孤儿条目对每个.sqlx/query-*.json将它的query字段规范化去掉\行续接符、折叠空白然后检查该规范化后的 SQL 是否仍出现在任何.rs文件中。需要特别注意的是这种检测器在 CE checkout 中会报告约 48 个误报——因为 EE 查询位于*_ee.rs符号链接中检测器无法读取这些文件。因此只过滤到你的改动所涉及的表并只删除这些条目。运行任何命令之前备份与 DATABASE_URL 校验SKILL 文档强调了两个前置步骤缺一不可1. 备份缓存cargo sqlx prepare会先删除.sqlx/目录再重新生成因此任何编译失败都会让缓存被掏空。SKILL 文档记录了真实观察到的后果缓存从 2350 条骤降到 142 条。仓库提供了官方备份脚本 sqlx-cache.sh其子命令设计如下见脚本头部注释sqlx-cache.sh backup # 快照 backend/.sqlx sqlx-cache.sh newq # 显示 prepare 之后新增的条目并将其暂存 sqlx-cache.sh restore # 恢复快照并将暂存的新增条目嫁接回去使用方式bash .agents/skills/update-sqlx/sqlx-cache.sh backup脚本内部使用set -euo pipefail严格模式repo_root$(git rev-parse --show-toplevel)定位仓库根缓存路径为$repo_root/backend/.sqlx。其状态目录为${TMPDIR:-/tmp}/wm-sqlx-cache/$(basename $repo_root)即按工作树目录名隔离注意是目录名而非分支名。这意味着状态是每个工作树per-worktree独立的并行的兄弟工作树同时运行prepare不会互相覆盖备份list_entries函数特意用 glob 循环而非ls *.json以保证在缓存为空即prepare失败后时脚本不会在set -e下直接失败——这正好对应失败后缓存被掏空的状态。2. 将DATABASE_URL指向当前工作树的数据库prepare会针对实时数据库编译每一条sqlx::query!。如果你指向了另一个工作树的数据库那个数据库缺少你的迁移migrations所有涉及新表的查询都会失败并连带摧毁缓存。典型症状是relation your_new_table does not exist——这表示DATABASE_URL配错了而不是查询本身有问题。关于如何确认工作树自己的端口与数据库参见仓库根目录的 AGENTS.md 中的 Per-worktree ports and database 一节在 webmux 工作树中权威值位于$(git rev-parse --git-dir)/webmux/runtime.env包含BACKEND_PORT、FRONTEND_PORT、DATABASE_URL、CARGO_FEATURES、WM_DB_NAME在普通 checkout 中回退到根目录的.env/.env.local与backend/.env数据库以工作树目录名命名windmill_ 目录 basename 中的-替换为_Postgres 截断到 63 字符所以分支hugo/win-2340-...位于目录win-2340-…时其数据库是windmill_win_2340_...——不要手工重建名字直接从runtime.env取WM_DB_NAME。AGENTS.md 还特别警告DATABASE_URL指向另一个工作树的数据库会静默摧毁 sqlx 缓存。cargo run与cargo sqlx prepare都会针对实时数据库编译宏错误数据库会以relation ... does not exist失败而prepare在失败之前就删除了整个.sqlx/目录。这再次印证了先备份、再确认DATABASE_URL的铁律。测试内的查询需要--all-targets而它在 CE checkout 中会失败这是 SKILL 文档中一个非常实战化的坑prepare只缓存它编译过的代码中的查询而--workspace单独使用不会编译测试目标test targets。因此tests/*.rs中的sqlx::query!不会生成缓存条目CI 在测试目标上仍会报 no cached data 错误即使库本身编译干净。复现该问题的方式正是SQLX_OFFLINEtrue cargo check --workspace --all-targets加入--all-targets可以缓存这些查询——但在 CE checkout 中会在中途中止backend/tests/otel.rs导入了windmill_common::otel_ee而该符号只存在于privatefeature 之后。于是编译在prepare已经清空.sqlx/之后死亡。SKILL 文档记录缓存从 2435 条骤降到 4 条报错error: cargo check failed with status: exit status: 101。处理策略不要与它对抗——这个中止是既有的 EE 缺口pre-existing EE gap不是你的改动造成的。正确做法是取走你需要的条目然后把备份放回去bash .agents/skills/update-sqlx/sqlx-cache.sh backup cd backend DATABASE_URLthis worktrees db \ cargo sqlx prepare --workspace -- --workspace --features all_sqlx_features --all-targets # 预期会失败但它在死前已经写入了它得到的条目 cd .. bash .agents/skills/update-sqlx/sqlx-cache.sh newq # 打印每个新增的查询 bash .agents/skills/update-sqlx/sqlx-cache.sh restore # 恢复备份并嫁接新增条目关键纪律在运行restore之前务必阅读newq打印的内容。它显示每个新增条目的query字段每个都应该是你自己的。这个集合很小每条新测试查询对应一个条目如果里面出现任何其他东西说明这次运行比你以为的走得更远需要停下来核查。sqlx-cache.sh的实现细节印证了这一点newq用comm -13 before.txt after.txt计算备份后新增的文件列表逐个复制到$added暂存目录并打印文件名与query字段优先用jq回退到sedrestore先恢复备份再把$added中的条目逐一复制回缓存目录。然后必须同时验证两个目标——库通过并不代表测试通过SQLX_OFFLINEtrue cargo check --workspace --features all_sqlx_features # lib SQLX_OFFLINEtrue cargo check -p your-crate --all-targets # tests核心问题prepare 会删除并重生成全部缓存cargo sqlx prepare --workspace的行为是删除所有现有缓存文件然后只重生成当前编译中发现的那些。如果你没有使用每一个 feature flag尤其是 EE 文件所需的private你会在不知情的情况下删除 EE 查询缓存从而破坏企业版测试。仓库中的标准脚本 update_sqlx.sh 试图用全部 features 编译#!/usr/bin/env bash set -e if [[ $(uname) Darwin ]]; then echo Running on macOS - substituting samael... # macOS 下替换 samael 依赖版本 - git sed -i s/^samael { version0.0.14, features \[xmlsec\] }/#samael { version0.0.14, features [xmlsec] }/ Cargo.toml sed -i s/^# \(samael { githttps:\/\/github.com\/njaremko\/samael, rev464d015e3ae393e4b5dd00b4d6baa1b617de0dd6, features \[xmlsec\] }\)/\1/ Cargo.toml cargo sqlx prepare --workspace -- --workspace --all-targets --features all_sqlx_features,private,deno_core_mac,deno_core,enterprise,mcp else cargo sqlx prepare --workspace -- --workspace --all-targets --features all_sqlx_features,ee,deno_core,private,enterprise,mcp fi该脚本在 macOS 与 Linux 上分别启用all_sqlx_features,private,deno_core_mac,deno_core,enterprise,mcpmacOS与all_sqlx_features,ee,deno_core,private,enterprise,mcpLinux。all_sqlx_features在 backend/Cargo.toml 中定义为[all_languages, enterprise, enterprise_saml, embedding, parquet, prometheus, flow_testing, ...]等组合含对windmill-git-sync/all_sqlx_features的传递启用而sqlx依赖本身指向https://github.com/windmill-labs/sqlx的一个固定 rev见 backend/Cargo.toml即 Windmill 维护的 SQLx fork。问题在于即使如此该脚本仍常常在本地失败——因为 EE 符号链接可能与main分支不同步。一旦失败prepare已经删除了整个缓存目录后果是灾难性的。安全流程保留来自 origin/main 的 EE 缓存SKILL 文档给出了绝对安全的推荐工作流其核心思想是始终保留来自origin/main的 EE 缓存。cd backend # 1. 从 main 恢复完整缓存包含 EE 缓存 git checkout origin/main -- .sqlx/ # 2. 用 OSS features 运行 prepare这是本地能编译的 # 重新生成与你的代码改动匹配的 OSS 缓存 cargo sqlx prepare --workspace -- --workspace --features all_sqlx_features # 3. 恢复第 2 步中被删除的 EE 缓存 # 这些是 origin/main 中存在但 prepare 之后缺失的文件 git ls-tree origin/main backend/.sqlx/ \ | awk {print $4} | sed s|backend/\.sqlx/|| | sort /tmp/main_files.txt find backend/.sqlx -name *.json -printf %P\n | sort /tmp/current_files.txt comm -23 /tmp/main_files.txt /tmp/current_files.txt /tmp/missing_files.txt while read f; do git show origin/main:backend/.sqlx/$f backend/.sqlx/$f done /tmp/missing_files.txt # 4. 验证没有丢失任何 main 中的文件 find backend/.sqlx -name *.json -printf %P\n | sort /tmp/current_files.txt comm -23 /tmp/main_files.txt /tmp/current_files.txt | wc -l # 应输出0各步骤要点恢复完整缓存从origin/maincheckout.sqlx/确保 EE 缓存齐全运行 prepare仅 OSS features用本地能够编译的 feature 集重新生成 OSS 缓存以匹配你的代码改动补齐被删的 EE 缓存用git ls-tree列出origin/main中的全部缓存文件与当前目录比对comm -23找出缺失文件逐个用git show从origin/main恢复零丢失验证再次比对并统计缺失数必须为 0。步骤 4 之所以与origin/main比较是因为步骤 1 就是从它恢复的两者一致。如果你没有运行步骤 1例如单独审计某个分支的缓存则应改为与git merge-base HEAD origin/main比较——因为origin/main会持续推进其更新的条目在你的分支上会被误判为丢失。如果 EE 本地能编译完整脚本如果恰好你的 EE 仓库处于同步状态可以直接使用完整脚本更快cd backend ./update_sqlx.sh但一旦它因 EE 编译错误而失败就回退到上面的安全流程。绝不做什么六条红线SKILL 文档用专门的 What NOT to Do 一节列出了不可违反的纪律这里逐条展开绝不要只用 OSS features 运行cargo sqlx prepare --workspace并提交结果——它会删除 EE 缓存绝不要为本地cargo sqlx prepare设置SQLX_OFFLINEtrue——本地必须使用实时数据库按 CLAUDE.md 的约定。CI 才使用SQLX_OFFLINEtrue这正是缓存必须完整的原因绝不要在没有.sqlx备份的情况下运行prepare或对着一个你未确认属于当前工作树的DATABASE_URL运行绝不要仅为删除查询的改动运行prepare绝不要跳过验证步骤上述步骤 4绝不要在--all-targets运行中止后把它的输出留在原地——那是一个近乎空的缓存。要恢复备份并且只嫁接你已验证过的条目。提交后验证diff 应呈现的形态提交之后与origin/main的差异应当符合以下形态见 SKILL.md少量新增缓存文件对应你改动的查询少量删除缓存文件对应已不存在的旧查询从 EE 缓存集合来看零净删除。git diff origin/main --stat backend/.sqlx/这既是自检清单也是评审时最直观的核对依据。从技能到脚本update-sqlx 在仓库中的落地形态update-sqlx技能在仓库中有一份规范文档与一份可执行脚本二者互为补充规范文档SKILL.md由 .claude/skills/update-sqlx/SKILL.md 符号链接指向保证 Claude Code 等工具自动发现同一份规范可执行脚本sqlx-cache.sh提供backup/newq/restore三个子命令覆盖失败前备份、失败后只取新增、再恢复的完整闭环。而 AGENTS.md 作为仓库开发约定的总纲在多处与技能呼应Per-worktree ports and database 一节明确把先cp -r backend/.sqlx tmp/sqlx_backup见 update-sqlx 技能列为错误数据库操作前的强制动作AGENTS.md该节同时解释了为什么DATABASE_URL必须逐工作树确认数据库以工作树目录名命名克隆与迁移由 post-create hook 从零构建与主开发实例并不相同。因此当你或你的 Agent在 Windmill 仓库中新增或修改任何 Rust 侧 SQL 查询时标准的动作序列是备份 → 确认本工作树DATABASE_URL→ 运行prepare视改动选择--all-targets→newq核对新增 →restore恢复并嫁接 → 双目标验证 → diff 核对 → 提交。这套流程在保障 CI 绿色通过的同时也保护了 EE 查询缓存这一极易被静默摧毁的资产。【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考