Windmill PostgreSQL 脚本编写实战指南:从参数绑定到 S3 流式导出
【免费下载链接】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 官方 AI 编程技能write-script-postgresql(见 SKILL.md)为骨架,系统讲解如何在 Windmill 中编写、调试、部署 PostgreSQL 脚本:包括$1::{type}位置参数与注释命名、wmill script preview与wmill script run的正确选用、wmill generate-metadata元数据同步机制,以及(s3object)参数注入与-- s3指令流式导出两个高级场景。读完本文,你将掌握一套可直接落地的 PostgreSQL 脚本开发闭环:本地编写 → 本地预览 → 元数据同步 → 按需部署,并能安全地处理大结果集与文件型入参。
一、Windmill SQL 脚本的基本形态
在 Windmill 中,SQL 脚本(PostgreSQL、MySQL、Snowflake、MSSQL、BigQuery 等)与代码型脚本一样存放在工作区中,并通过同一套 CLI 工具链进行管理。与 Python/TypeScript 脚本必须导出main函数不同,PostgreSQL 脚本的"入口"就是 SQL 语句本身,参数通过占位符注入。
通用脚本原则(参见 script.md)同样适用于 PostgreSQL:
- 脚本返回值必须是 JSON 可序列化的,后续流程步骤可通过
results.step_id引用; - 凭据与配置存放在 resource(资源)中并通过参数传入,而非硬编码在脚本里;
- Windmill 会自动安装依赖,脚本内无需展示安装步骤。
二、PostgreSQL 参数绑定:$n::{type}与注释命名
PostgreSQL 脚本的参数直接在语句中以$1::{type}、$2::{type}等位置占位符获取:
SELECT * FROM users WHERE name = $1::TEXT AND age > $2::INT;其中::{type}是 PostgreSQL 的显式类型转换语法,Windmill 依据该类型声明生成输入表单并完成客户端绑定,TEXT、INT、BIGINT、TIMESTAMP、JSONB等均可使用。
为了让参数具备可读的名称(而不是只有$1、$2),需要在脚本开头用注释声明参数名,注意只写名称、不写类型(类型已由占位符中的::{type}决定):
-- $1 name1 -- $2 name2 = default_value SELECT * FROM users WHERE name = $1::TEXT AND age > $2::INT;该示例声明了两个参数:name1(对应$1,类型TEXT)与name2(对应$2,类型INT,默认值default_value)。带默认值的参数在运行时可以省略。命名后,Windmill 的自动生成参数 UI、脚本调度与流程编排中都将以name1、name2引用这两个入参,极大提升可维护性。
说明:不同 SQL 方言的占位符语法不同——MySQL/Snowflake 用
?,MSSQL 用@P1,BigQuery 用@name,而 PostgreSQL 使用$1、$2这种位置参数(见 SKILL.md 中对各方言占位符的说明)。Bash 脚本则使用$1、$2的位置参数,PowerShell 用param(...)。
三、CLI 工作流:preview、run与部署的正确取舍
脚本写完后,面对wmill script preview、wmill script run、git push/wmill sync push这几种操作,官方技能给出了明确的意图判定规则,核心原则是按意图选命令,而非按习惯:
| 命令 | 行为 | 适用场景 |
|---|---|---|
wmill script preview <script_path> | 运行本地文件,不部署 | 默认选择。本地迭代、试运行、验证"能不能跑"时使用 |
wmill script run <path> | 运行已部署到工作区的版本 | 用户明确要求"运行已部署的版本"/"运行服务器上的版本",或本地没有正在编辑的脚本 |
wmill generate-metadata | 仅重生成本地元数据(.script.yaml、.lock、wmill-lock.yaml哈希),不部署 | 编辑脚本内容后保持元数据同步 |
git push/wmill sync push | 将本地变更部署到工作区 | 用户明确要求部署/发布/推送时 |
3.1 何时用 preview,何时用 run
当用户说"run the script"、"try it"、"test it"、"does it work"且脚本文件存在本地未提交的编辑时,必须使用wmill script preview。不要先把脚本 push 到工作区再用script run验证——push 本身就是一次部署,用未经测试的变更覆盖工作区版本是危险的。
只有满足以下条件之一才使用script run:
- 用户明确说"运行已部署的版本"/"运行服务器上的版本";
- 本地没有正在编辑的脚本(只是调用一个已存在的脚本)。
wmill sync push只在用户明确要求部署/发布/推送,且 preview 已验证通过后使用。如果用户只说 "run"、"try"、"test",不要触发任何部署动作。
3.2 写完脚本后的测试引导
如果用户原始请求中没有要求测试/运行,写完脚本后用一句话主动提出下一步(例如"要不要我用示例参数跑一下wmill script preview?"),不要抛出一堆选项菜单。
如果用户在原始请求中已经要求测试/运行/试跑,则跳过询问直接执行:
wmill script preview <path> -d '<args>'参数取值应从脚本声明的参数中推断。注意wmill script preview虽然不部署,但仍会真实执行脚本代码并可能产生副作用,因此仅在用户要求测试/预览(或确认执行是有意为之)时才自行运行。
如果想做可视化预览(在开发页面中打开脚本而不是运行并打印结果),则使用preview技能(见 preview/SKILL.md)。
3.3 用wmill resource-type list --schema发现资源类型
编写需要数据库连接的脚本时,可用以下命令查看当前工作区可用的资源类型及其 schema,以确认连接参数的字段名:
wmill resource-type list --schema四、保持元数据同步:wmill generate-metadata深度解析
wmill-lock.yaml为每个条目维护一个内容哈希。编辑脚本内容——尤其是新增或删除 import、修改main的参数签名——会使哈希失效,导致.lock文件、.script.yaml输入 schema 与哈希记录三者不同步。此时需要运行wmill generate-metadata(限定在你改动的范围上),让解析后的锁文件、由.script.yaml驱动的自动生成参数 UI 以及wmill-lock.yaml与代码保持一致。否则会产生 git-sync 与 CI 中的虚假 diff。
4.1 只写本地文件,不是部署
wmill generate-metadata只写本地文件,不是部署。但它会重新解析依赖,因此可能升级未固定版本的依赖(与从 UI 部署行为一致,属预期而非 bug)。默认策略是:先提出建议,用户同意后再运行,而不是每次编辑后静默执行——除非项目的AGENTS.md明确选择自动运行元数据。无论哪种方式,命令由你来执行,而非用户。运行后应 diff 重新生成的.lock/.script.lock文件,并告知用户哪些依赖版本发生了变化(例如requests 2.31.0 → 2.32.0),以便在部署前发现意外升级——即使在Metadata: auto模式下也要告知,因为这是信息而非确认门槛。要固定版本,就在代码中显式 pin。
4.2 不带路径参数时的行为与依赖传播
不带路径参数运行时,generate-metadata只重新生成内容哈希漂移的条目,而不是全部。import 会传播:编辑一个被其他脚本 import 的脚本,会使所有 import 它的脚本也标记为过期——因此共享模块的一行改动可能触发大量锁文件重新生成(这是设计使然,它们的锁必须反映被 import 的代码)。若影响的文件超出预期,先用 dry-run 排查:
wmill generate-metadata --dry-run它会列出每个过期条目及原因(content changed或depends on <path>),且不修改任何文件。然后可用路径参数收窄范围:
wmill generate-metadata f/foo # 或使用文件夹边界约束 wmill generate-metadata --strict-folder-boundaries--strict-folder-boundaries保证本次运行只更新指定文件夹内的条目(要求同时提供文件夹参数),其实现细节可见 generate-metadata.ts,其中定义了--dry-run、--strict-folder-boundaries选项以及rehash子命令。
4.3 纯哈希刷新:generate-metadata rehash
如果磁盘上的.lock与.script.yaml已经正确,只是wmill-lock.yaml需要刷新哈希(哈希漂移,或引导缺失条目),使用rehash子命令——它从磁盘重新记录哈希,不做后端往返、不改变依赖:
wmill generate-metadata rehash该命令对应源码中的rehashOnly快速路径(见 generate-metadata.ts 中rehashCommand与rehashOnly),在返回任何后端调用之前即完成哈希重录。
4.4 三步闭环总结
- 编辑脚本(增删 import、改
main签名)→ 哈希失效; - 运行
wmill generate-metadata(必要时先--dry-run预览,再用路径参数/--strict-folder-boundaries收窄)→ 本地元数据一致; - 用户明确要求部署时,再
git push或wmill sync push。
五、高级场景一:以(s3object)接收文件型参数
PostgreSQL 脚本的某个参数可以声明为(s3object)类型。此时 Windmill 会为该参数渲染一个S3 文件选择器,运行时自动下载文件,并将其绑定为jsonb参数:
- Parquet / CSV文件由服务端解码为 JSON 记录数组;
- JSON / JSONL文件原样透传。
脚本内通过jsonb_to_recordset(或任何jsonbAPI)消费:
-- $1 file (s3object) SELECT * FROM jsonb_to_recordset($1::jsonb) AS r(id INT, name TEXT);该示例把 S3 中的文件内容(如一个含id、name列的 CSV/Parquet)解构成关系表,随后可直接参与 JOIN、WHERE 等常规 SQL 操作。
这一机制在 Windmill 后端有完整的实现支撑:pg_executor.rs(见 pg_executor.rs)中通过materialize_s3object_args对每个(s3object)参数下载文件、调用sql_s3_input::fetch_s3object_as_json_text将其物化为 JSON 文本,并重新绑定为jsonb;当物化发生时空值溢出错误会被改写为更友好的 jsonb 相关错误信息。MySQL、MSSQL、BigQuery、DuckDB 执行器也采用同样的(s3object)物化模式,说明这是 Windmill SQL 脚本的通用能力。
六、高级场景二:-- s3指令流式导出查询结果
对于大结果集,在脚本顶部添加-- s3指令即可将查询结果流式写入 S3,而不是作为脚本返回值在内存中缓冲。Windmill 负责写文件,并把生成的S3Object作为脚本结果返回:
-- s3 prefix=exports/users format=parquet SELECT id, name FROM users;指令中的所有键均为可选:
| 键 | 含义 | 取值 / 默认值 |
|---|---|---|
prefix | 对象键前缀 | 可选,如exports/users |
storage | 命名的存储配置 | 可选,省略时使用工作区默认存储 |
format | 导出格式 | json(默认)、parquet、csv |
使用场景:导出数千万行报表数据、生成下游数仓的 Parquet 分片等。因为行数据直接流式写入 S3,内存占用恒定,不会因结果集过大导致脚本 OOM 或响应体超限;脚本的返回值从"整包数据"变成"一个S3Object引用",非常适合与流程后续步骤或 git-sync 工作流配合。
七、实践要点与源码指引
- 参数即注释、类型即转换:PostgreSQL 脚本参数一律
$n::{type}声明类型,注释行只给名称与默认值,两条规则缺一不可。 - 本地验证优先:有本地编辑时默认
wmill script preview;只有用户点名要跑已部署版本才wmill script run;只有明确要求部署才git push/wmill sync push。 - 元数据即契约:编辑后运行
wmill generate-metadata,异常扩散用--dry-run排查、用路径参数或--strict-folder-boundaries收窄、纯哈希刷新用rehash。 - 文件型入参用
(s3object):自动文件选择器 + 服务端解码 +jsonb_to_recordset消费,避免在 SQL 中手写文件解析。 - 大结果集用
-- s3:流式直写 S3,返回S3Object而非内存缓冲,prefix/storage/format三键均可选。
想继续深入,可阅读以下仓库文件:
- 技能原文:write-script-postgresql/SKILL.md
- 脚本编写总纲(含各语言参数绑定约定):system_prompts/auto-generated/script.md
- 元数据生成命令实现(
--dry-run、--strict-folder-boundaries、rehash):cli/src/commands/generate-metadata/generate-metadata.ts - PostgreSQL 执行器(
(s3object)物化、jsonb 绑定):backend/windmill-worker/src/pg_executor.rs - S3 文件输入物化工具:
backend/windmill-worker/src/sql_s3_input.rs - 部署与仓库接线说明:
wmill sync push与git push的选择取决于仓库如何接线,详见项目级AGENTS.wmill.md中的Deploying一节(该文件位于部署工作区根目录,非本仓库内置文件)
【免费下载链接】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),仅供参考