
Windmill 中的 DuckDB 脚本开发指南参数、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导读DuckDB 是 Windmill 中一类一等公民的脚本语言你可以在脚本编辑器中直接用纯 SQL 编写可复用的数据任务通过注释声明参数、通过ATTACH挂接 DuckLake 数据湖、连接外部 Postgres/MySQL 等数据库资源并直接读写工作区 S3 存储上的 CSV、Parquet、JSON 文件。本文以 system_prompts/languages/duckdb.md 为基础结合仓库源码系统讲解在 Windmill 中编写 DuckDB 脚本的参数体系、DuckLake 集成、外部数据库连接与 S3 文件操作的完整姿势读完即可上手写出可参数化、可调度、可回放的 DuckDB 数据脚本。一、参数定义注释声明 $name引用Windmill 的 DuckDB 脚本遵循一套“注释即签名”的参数协议参数用注释声明在 SQL 中用$name语法引用。脚本头部的一组--注释会被解析成脚本的输入参数签名并在前端 UI 中自动渲染为表单。-- $name (text) default -- $age (integer) SELECT * FROM users WHERE name $name AND age $age;要点说明类型标注括号内的text、integer等即参数类型Windmill 的类型体系text、integer、boolean、datetime 等均可使用参数声明了类型后前端会按类型渲染对应的输入控件并做校验。默认值用 default给出。当调用方未显式传值时执行器会回落到默认值。从 backend/windmill-worker/src/duckdb_executor.rs 的执行逻辑看每个签名参数在运行时都会按“调用方传入值 → 参数默认值 → null”的优先级解析job_args.remove(sig_arg.name).or_else(|| sig_arg.default)因此带默认值的参数可以安全地省略。$name引用声明后即可在 SQL 中直接以$name使用执行器在运行前会对脚本做参数插值源码中对应sanitize_and_interpolate_unsafe_sql_args把$age这类占位符替换为实际的参数值。与其它语言一致这套注释签名语法与 Windmill 的 Python、TypeScript 等脚本语言完全一致都通过注释声明name (type) defaultDuckDB 脚本因此可以与其它语言的脚本共享同一套参数传递、调度触发和 API 调用机制。从源码结构看DuckDB 脚本的签名解析由windmill_parser_sql::parse_duckdb_sig见 backend/windmill-worker/src/duckdb_executor.rs 第 1361 行负责脚本在每次运行时都会被重新解析签名——这意味着修改头部注释后无需重新发布 SDK保存脚本即可生效。二、DuckLake 集成把数据湖挂进 DuckDBDuckLake 是 Windmill 内置的、基于 DuckDB 引擎的数据湖能力是脚本“写库”与“版本化”的承载层详见 docs/ducklake-materialization.md。在 DuckDB 脚本中通过ATTACH即可挂接-- 默认 DuckLake ATTACH ducklake AS dl; -- 命名 DuckLake ATTACH ducklake://my_lake AS dl; -- 挂接后即可查询 SELECT * FROM dl.schema.table;这里有几层需要理解别名即命名空间AS dl之后dl就成为一个 catalog 别名后续所有查询都通过dl.schema.table三段式引用与 DuckDB 原生的多 catalog 语义完全一致。底层是真实连接的重写你写的ATTACH ducklake://name AS dl并不是原样执行的。从 backend/windmill-worker/src/duckdb_executor.rs 的transform_attach_ducklake实现约第 2451、2550 行可以看到执行器会把ducklake://形式的 ATTACH 重写为携带真实凭据与数据路径的完整 ATTACH例如ATTACH ducklake:postgres:... AS dl (DATA_PATH s3://..., OVERRIDE_DATA_PATH TRUE, AUTOMATIC_MIGRATION TRUE);即 DuckLake 的目录元数据存放在 Postgres、数据文件存放在工作区 S3 存储上这些细节对脚本作者完全透明——你只需要写ATTACH ducklake://...。写库的正式姿势普通脚本对 DuckLake 的常规写入并不是裸INSERT而是通过// materialize ducklake://name/table注释声明托管写入策略replace / merge / append / SCD2由 Windmill 生成幂等、带快照的写入 DDL。这一部分属于“管道”能力详见 docs/ducklake-materialization.md 的注解语法一节。三、外部数据库连接通过资源ATTACH除了 DuckLakeDuckDB 脚本还能借助 Windmill 的**资源Resource**体系连接外部数据库。资源是 Windmill 中集中管理、带权限控制的连接凭据载体在 SQL 中用$res:前缀引用资源路径即可ATTACH $res:path/to/resource AS db (TYPE postgres); SELECT * FROM db.schema.table;关键点资源路径path/to/resource是工作区内资源的路径可以带文件夹层级$res:前缀告诉执行器“这是一个资源引用”脚本作者永远不需要把主机名、用户名、密码写进 SQL。(TYPE ...)指定方言TYPE postgres指明连接目标类型。从执行器的 ATTACH 转换逻辑parse_attach_db_resource/transform_attach_db_resource_query位于 backend/windmill-worker/src/duckdb_executor.rs可以确认这一机制会读取对应资源中的连接配置主机、端口、凭据等并展开为 DuckDB 可执行的连接串BigQuery 类型的资源还会有额外的凭据文件处理路径源码中UseBigQueryCredentialsFile分支。密码脱敏执行器在转换资源 ATTACH 时会收集真实凭据并在日志与错误信息中对密码做脱敏处理hidden_passwords机制避免敏感信息泄漏到运行日志。别名的后续使用AS db之后db.schema.table即该外部库的表引用可参与 JOIN、子查询等任意 SQL 运算。四、S3 文件操作读 CSV / Parquet / JSONDuckDB 的原生 httpfs 扩展允许直接从对象存储读取数据。在 Windmill 中s3://URI 有统一的映射规则读写工作区 S3 存储-- 默认存储 SELECT * FROM read_csv(s3:///path/to/file.csv); -- 命名存储 SELECT * FROM read_csv(s3://storage_name/path/to/file.csv); -- Parquet 文件 SELECT * FROM read_parquet(s3:///path/to/file.parquet); -- JSON 文件 SELECT * FROM read_json(s3:///path/to/file.json);理解这套 URI 的规则很重要默认存储用双斜杠s3:///path/to/file.csv中“主机”位置为空表示使用工作区的默认 S3 存储。执行器的transform_s3_urisbackend/windmill-worker/src/duckdb_executor.rs 第 2686 行起会把s3:///xxx规范化为s3://_default_/xxx_default_即默认存储的内部名对应源码中的DEFAULT_STORAGE随后统一经工作区的 S3 代理完成读写。仓库自带的单元测试同文件第 4027 行起明确断言了这一行为read_parquet(s3:///path/to/file.parquet)会被改写成read_parquet(s3://_default_/path/to/file.parquet)。命名存储s3://storage_name/path中的storage_name是工作区中配置的具名存储对应 Windmill 的存储资源URI 会原样保留并路由到对应存储。读取函数全家桶read_csv、read_parquet、read_json是 DuckDB 原生的表函数因此它们可以出现在SELECT * FROM位置与普通表无差别地参与过滤、聚合与多表 JOIN。五、以(s3object)参数接收 S3 文件当脚本需要由调用方指定要读取哪个文件时把参数类型声明为(s3object)即可获得完整的文件选择体验-- $file (s3object) SELECT * FROM read_parquet($file);这套机制是这样运作的前端渲染文件选择器声明为(s3object)后Windmill 的脚本编辑器会在参数表单中渲染一个S3 文件选择器运行时可从工作区存储中挑选文件或目录。参数以裸 URI 绑定运行时执行器把这个参数绑定为不带引号包装的s3://storage/keyURI见 backend/windmill-worker/src/duckdb_executor.rs 第 1426–1447 行的s3object分支s3://{storage}/{s3}未指定存储时回落到DEFAULT_STORAGEDuckDB 的读取函数可以直接消费该 URI。适用于任何 DuckDB 读取函数read_csv($file)、read_json($file)等一律适用不止read_parquet——因为它绑定的是裸 URI 字符串凡是能接受s3://路径的 DuckDB 函数都能直接用。这样脚本作者既不需要硬编码文件路径调用方也能在界面上可视化地选择文件同时脚本本身保持“输入即路径、读取即分析”的纯粹函数形态。六、写回 S3用 DuckDB 原生的COPY ... TO把查询结果写回 S3DuckDB 提供了原生的导出语句COPY (SELECT * FROM users) TO s3:///exports/users.parquet (FORMAT PARQUET);目标路径同样遵循上文提到的s3://规则s3:///...双斜杠为默认存储s3://storage_name/...为具名存储执行器会做同样的 URI 规范化。(FORMAT PARQUET)指定导出格式DuckDB 原生支持 Parquet、CSV、JSON 等多种COPY ... TO目标格式并可带PARTITION_BY、COMPRESSION等导出选项配合SELECT子查询可以完成“查询 → 落盘”一步到位。为什么这里不用-- s3流式指令Windmill 的其它 SQL 方言如 Postgres 脚本提供-- s3流式指令用于把查询结果以流式方式写回 S3。DuckDB 脚本不提供该指令原因在于 DuckDB 本身就内置了完整的 S3 写入能力COPY ... TO s3://...无需再叠加一层流式导出——直接用原生语法即可这也是文档明确建议的替代方案。写作时请以COPY ... TO为准不要尝试在 DuckDB 脚本中使用-- s3指令。七、底层原理速览一条 DuckDB 脚本的运行时旅程为了更可靠地使用上面的语法了解执行器做了什么会很有帮助全部逻辑集中在 backend/windmill-worker/src/duckdb_executor.rs 的do_duckdb中解析签名与注解先用parse_duckdb_sig解析头部注释中的-- $name (type)参数声明同时解析// materialize、// macros、// use等管道注解。参数插值将调用方传入的参数或默认值按签名顺序绑定为$name引用的值其中(s3object)类型被特判翻译为裸s3://URI。ATTACH 转换逐块扫描脚本语句把ducklake://、datatable://、$res:资源形式的ATTACH重写为携带真实凭据与数据路径的连接指令并完成 S3 URI 规范化transform_s3_uris。进程内执行DuckDB 以**进程内in-process**方式运行在 worker 中通过 FFI 动态库见 backend/windmill-duckdb-ffi-internal 所在的 worker 目录及 docs/duckdb-isolation.md因此它不能像其它语言那样被 nsjail 子进程隔离在启用隔离的 worker 上隔离策略会在连接层面施加SET disabled_filesystemsLocalFileSystem等设置——这也解释了为什么脚本中的外部访问S3、DuckLake、资源连接全部要走 Windmill 的托管通道而不是脚本自行访问本地文件系统。宏库注入若脚本调用工作区宏库// use声明的CREATE MACRO库执行器会从注册表拉取被调用的宏以CREATE OR REPLACE TEMP MACRO形式按依赖顺序注入到语句流中见 backend/parsers/windmill-parser/src/duckdb_macros.rs。理解这条链路后就能明白为什么“参数声明要写注释、外部访问要走资源/DuckLake/S3 托管通道”——它们不是约定俗成的风格而是执行器真实处理的分界点。八、这份指南从哪来Language System Prompts本文内容是 Windmill 系统提示词中 DuckDB 语言专属指令system_prompts/languages/duckdb.md的完整展开。该目录是前端 Copilot 与 CLI 引导共用的“单一事实来源”见 system_prompts/README.mdlanguages/下的语言指令会被打包进脚本生成的系统提示词指导 AI 助手按照上述语法编写符合规范的 DuckDB 脚本。因此本文所述语法也正是 Windmill 的 AI 助手生成 DuckDB 脚本时所遵循的规范——你完全可以把它当作与 Copilot 协作时的共同语言。小结参数-- $name (type) default声明$name引用(s3object)获得文件选择器并绑定裸s3://URI。DuckLakeATTACH ducklake AS dl或ATTACH ducklake://my_lake AS dl随后以dl.schema.table查询底层由执行器重写为真实连接。外部数据库ATTACH $res:path/to/resource AS db (TYPE postgres)凭据集中管理、自动脱敏。S3 读取read_csv/read_parquet/read_json消费s3:///...默认存储或s3://storage/...具名存储URI。S3 写入使用 DuckDB 原生COPY ... TO s3:///... (FORMAT PARQUET)不要使用其它方言的-- 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创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考