Windmill 中编写 DuckDB 脚本实战指南:CLI 工作流、Ducklake 数据湖与 S3 集成
Windmill 中编写 DuckDB 脚本实战指南CLI 工作流、Ducklake 数据湖与 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 开源仓库中的write-script-duckdb技能文档system_prompts/auto-generated/skills/write-script-duckdb/SKILL.md为核心系统讲解在 Windmill 中编写、预览、测试和部署 DuckDB 脚本的完整流程。你将掌握如何用wmill script preview/wmill script run/wmill generate-metadata正确驱动本地脚本迭代与元数据同步如何用$name参数语法定义 DuckDB 脚本入参以及如何通过ATTACH接入 Ducklake 数据湖、外部数据库PostgreSQL 等和 S3 对象存储实现从查询到写回的数据管道。为什么 Windmill 中的 DuckDB 值得单独一套工作流Windmill 是一个把脚本变成 Webhook、工作流和 UI 的开源开发者平台其脚本语言支持非常广泛。DuckDB 在 2025 年 5 月被正式纳入后端支持这一点可以从数据库迁移脚本中找到直接证据backend/migrations/20250515084520_duckdb_support.up.sql 中通过ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS duckdb;将duckdb注册为一种原生脚本语言并同时把duckdb标签加入默认 worker 的worker_tags意味着 DuckDB 脚本由专门的 worker 标签调度执行。DuckDB 与其他 SQL 方言PostgreSQL、MySQL、Snowflake 等在 Windmill 中的差异主要体现在三处参数绑定语法$name而非$1、?等、数据源接入方式ATTACH而非--指令以及S3 写回方式原生COPY ... TO而非其他方言的-- s3流式指令。理解这些差异是写出可在 Windmill 中稳定运行的 DuckDB 脚本的前提。CLI 命令总览本地编辑与远程部署的分界SKILL.md 开篇就给出了一条核心纪律先写脚本、放好文件然后根据用户意图选择对应的 CLI 命令。四条主要命令的分工如下命令作用是否改动工作区远程wmill script preview script_path运行本地文件不部署。本地迭代脚本时的默认选择否wmill script run path运行已部署到工作区的脚本否运行的是远端已有版本wmill generate-metadata重新生成.script.yaml输入 schema与.lock解析后的依赖刷新wmill-lock.yaml中的内容哈希。只写本地文件否git push或wmill sync push将本地变更部署到工作区是唯一会变更远端状态的步骤其中wmill script preview的实现位于 cli/src/commands/script/script.ts它会读取磁盘上的脚本内容而非工作区版本并支持从本地解析相对导入——也就是说当你本地同时改了多个互相 import 的脚本时preview 会优先使用本地未部署的版本而不是远端旧版本。该函数还会拒绝指向.script.json/.script.yaml元数据文件的路径明确要求传入脚本内容文件.py、.ts、.go、.sh等。Preview vs run按意图选择而非按习惯SKILL.md 特别强调当用户说运行脚本 / 试试 / 测一下能不能用、而脚本文件存在本地未部署的编辑时一律使用wmill script preview。绝对不要先把脚本push上去再用wmill script run测试——push 本身就是一次部署用未经测试的本地改动覆盖工作区版本是危险的。wmill script run只在两种情况下使用用户明确说运行已部署的版本 / 运行服务器上的那个本地根本没有正在编辑的脚本只是调用一个已存在的脚本。wmill sync push或git push只在用户明确要求部署/发布/推送时使用且 preview 已验证过变更。写完后主动提供测试而非被动等待如果用户没有在原始请求中要求运行/测试写完脚本后应给出一句话的下一步建议例如需要我用示例参数运行wmill script preview吗不要抛出多选项菜单。如果用户原本就要求测试/运行则直接执行wmill script preview path -d args参数-d传入脚本声明的参数。参数值的写法因语言而异代码类语言用main(...)SQL 方言用各自的占位符PostgreSQL 用$1MySQL/Snowflake 用?MSSQL 用P1BigQuery 用nameBash 用位置参数$1、$2PowerShell 用param(...)。DuckDB 属于前者范畴详见下文参数语法一节。需要补充说明的是wmill script preview虽然不部署但仍会实际执行脚本代码可能产生副作用wmill generate-metadata则只写本地文件锁文件、schema、哈希不执行代码。只有当用户明确要求部署/发布/push 时才会触发git push或wmill sync push修改远端状态。编辑后保持元数据同步generate-metadata 的深层机制Windmill 的 CLI 工作区wmill.yaml 驱动的同步仓库中wmill-lock.yaml为每个条目记录一个内容哈希。当你增删 import或修改main的参数时该哈希失效导致.lock、.script.yaml输入 schema 和哈希记录三者全部过期。过期状态会在 git-sync 和 CI 中产生虚假 diff所以编辑后应运行wmill generate-metadata该命令的实现位于 cli/src/commands/generate-metadata/generate-metadata.ts核心逻辑是遍历本地脚本、flow 文件夹与 app 条目walkLocalScripts、walkLocalFlowFolders、walkLocalAppItems对比内容哈希找出过期项StaleItem再逐项重新解析依赖、生成 schema。它只写本地文件不是部署但因为它会重新解析依赖所以可能升级未固定版本的依赖与从 UI 部署的行为一致属预期而非 bug。因此 SKILL.md 给出的默认策略是主动提出并征得用户同意后运行而不是每次编辑后静默执行——除非项目的AGENTS.md明确选择了自动运行元数据参考Keeping metadata in sync偏好设置。无论哪种模式命令由你Agent执行。运行后要 diff 重新生成的.lock/.script.lock文件并告知用户哪些依赖版本变了例如requests 2.31.0 → 2.32.0以便在部署前发现意外的版本升级——即使处于Metadata: auto模式下也应如此告知因为这是信息而非确认门槛。要固定版本就在代码里显式 pin。不带路径参数时的增量语义不带路径参数运行generate-metadata时它只重新生成内容哈希漂移的条目而非全部。Import 会传播编辑一个被其他脚本 import 的脚本会标记所有 importer 为过期因此对共享模块的一行改动可能触发大量锁文件重新生成——这是设计使然它们的锁必须反映被 import 的代码。如果影响范围超出预期先用 dry-run 检查wmill generate-metadata --dry-run它只列出每个过期条目及原因content changed或depends on path不做任何修改。然后可以用路径参数收窄范围wmill generate-metadata f/foo或使用--strict-folder-boundaries限制在文件夹边界内。rehash 子命令仅刷新哈希如果磁盘上的.lock和.script.yaml已经正确只是wmill-lock.yaml的哈希需要刷新哈希漂移或引导缺失条目使用wmill generate-metadata rehash它只从磁盘重新记录哈希不做后端往返、不改变依赖。DuckDB 脚本参数语法注释定义 $name引用DuckDB 脚本在 Windmill 中的参数定义方式与其他语言不同参数用注释声明脚本体内用$name语法引用-- $name (text) default -- $age (integer) SELECT * FROM users WHERE name $name AND age $age;-- $name (text) default声明了一个类型为text、默认值为default的参数$name-- $age (integer)声明了无默认值的整数参数$age。这些注释由解析器读取生成.script.yaml输入 schema进而在 Windmill UI 中自动渲染出参数表单。该 schema 正是上文wmill generate-metadata负责再生成的对象。Ducklake 集成一行 ATTACH 接入数据湖Ducklake 是 Windmill 的托管数据湖层DuckDB 脚本可以通过ATTACH直接挂载。SKILL.md 给出的两种形式-- Main ducklake ATTACH ducklake AS dl; -- Named ducklake ATTACH ducklake://my_lake AS dl; -- Then query SELECT * FROM dl.schema.table;第一行挂载主 Ducklake 实例第二行挂载命名实例my_lake之后即可用dl.schema.table三段式命名查询任意表。Ducklake 相关能力在仓库中有大量配套基础设施包括建表迁移 backend/migrations/20250724084100_ducklake.up.sql、实例设置迁移、以及物化写入支持——在 backend/parsers/windmill-parser/src/sql_materialize.rs 中可以看到 DuckDB 物化写入会通过保留别名_wm_target解析真实的ATTACH ducklake:…语句并在写入后通过ducklake_snapshots(_wm_target)捕获快照 ID说明 Ducklake 具备快照级的物化能力。连接外部数据库通过资源ResourceATTACHDuckDB 可以借助 Windmill 的资源Resource体系连接外部数据库资源中保存连接凭证与地址脚本内用$res:前缀引用ATTACH $res:path/to/resource AS db (TYPE postgres); SELECT * FROM db.schema.table;$res:path/to/resource会解析为工作区中已配置的资源路径(TYPE postgres)声明数据库类型。你可以在工作区内通过wmill resource-type list --schema查看可用的资源类型及其 schema从而为 DuckDB 准备合适的连接资源。这种ATTACH形式属于被 worker 的 transform 阶段统一重写的受管 ATTACH$res:、ducklake:等前缀在 backend/parsers/windmill-parser/src/duckdb_macros.rs 的is_managed_attach中有完整清单。S3 文件操作read_csv / read_parquet / read_jsonDuckDB 的 S3 读取使用其原生 reader 函数路径遵循 Windmill 的 S3 命名约定-- Default storage SELECT * FROM read_csv(s3:///path/to/file.csv); -- Named storage SELECT * FROM read_csv(s3://storage_name/path/to/file.csv); -- Parquet files SELECT * FROM read_parquet(s3:///path/to/file.parquet); -- JSON files SELECT * FROM read_json(s3:///path/to/file.json);注意双斜杠与单斜杠的区别s3:///三个斜杠表示默认存储桶s3://storage_name/表示命名存储。支持read_csv、read_parquet、read_json等全部 DuckDB reader 函数可根据数据格式自由选择。以 S3Object 作为脚本参数UI 文件选择器当脚本需要接收一个 S3 文件时把参数类型声明为(s3object)。Windmill 会在 UI 中为它渲染一个 S3 文件选择器并在运行时把参数绑定为裸的s3://storage/keyURI——DuckDB 的 reader 函数可以直接消费这个 URI-- $file (s3object) SELECT * FROM read_parquet($file);这适用于任何 DuckDB readerread_csv($file)、read_json($file)等均可用。查询结果写回 S3COPY ... TODuckDB 通过原生COPY ... TO写回 S3无需额外扩展COPY (SELECT * FROM users) TO s3:///exports/users.parquet (FORMAT PARQUET);务必注意SKILL.md 明确指出应使用COPY ... TO代替其他 SQL 方言支持的-- s3流式指令——该指令在 DuckDB 中不可用。深入底层DuckDB 宏库与 worker 侧的注入机制虽然 SKILL.md 聚焦于参数与数据源但仓库源码揭示了 DuckDB 脚本在 Windmill 中更深层的能力工作区级宏库。一个// macros标注的脚本可以包含CREATE [OR REPLACE] [TEMP] MACRO语句及ATTACH/INSTALL/LOAD/SET/PRAGMA等 setup 语句部署时被解析进宏注册表运行时 worker 将传递闭包内被调用的宏以CREATE OR REPLACE TEMP MACRO块注入消费脚本——宏体以**逐字verbatim**方式存取不做 AST 往返任何 DuckDB 接受的表达式都能原样存活。由于 DuckDB 在 CREATE 时即对宏体做绑定检查注入定义必须按依赖顺序输出因此实现了topo_order_macrosKahn 算法名称排序保证确定性循环依赖报错。同时为避免工作区宏静默遮蔽 DuckDB 内置函数backend/parsers/windmill-parser/src/duckdb_builtins.rs 维护了完整的内置函数名表做校验宏名被限定为[A-Za-z_][A-Za-z0-9_]*的简单标识符。CLI 侧还提供了等价的 TypeScript 移植版cli/src/commands/pipeline/duckdbMacros.ts与后端保持逐行对齐。此外DuckDB 也作为 dbt 的已知适配器被支持backend/windmill-worker/src/dbt_profiles.rs脚本内可通过-- use lib强制引入整库宏。完整工作流从编写到部署的推荐顺序综合 SKILL.md 与源码实现一个标准的 DuckDB 脚本迭代流程如下编写脚本将.sql或.py、.ts等文件放入同步仓库的脚本目录用注释声明参数、用$name引用按需加入ATTACHDucklake / 外部数据库 / S3语句。本地验证运行wmill script preview script_path -d args直接执行本地文件若想用可视化方式在开发页打开脚本预览而非运行打印结果使用preview技能。同步元数据编辑了 import 或main参数后运行wmill generate-metadata必要时先用--dry-run评估影响范围并 diff 锁文件确认没有意外的依赖版本升级仅需刷新哈希时用wmill generate-metadata rehash。征得同意后部署当用户明确要求发布/推送时按仓库的接线方式通过git push或wmill sync push部署到工作区——部署章节的细节以仓库中 AGENTS.md 与 cli/AGENTS.md 为准。这条链路保证了本地编辑永不污染工作区已部署版本.script.yaml输入 schema 与锁文件始终与代码一致git-sync 与 CI 不会产生虚假 diff而真正的远端变更只发生在用户明确授权部署的那一刻。【免费下载链接】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),仅供参考