@guandata/guanetl 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## @guandata/guanetl 0.1.16 - 2026-06-17
4
+
5
+ - `save --dry-run` 增加保存影响预览基础能力,帮助在提交前检查关键配置变化。
6
+ - `run` 会在执行前提示上游数据集失败状态,降低基于异常上游继续执行的风险。
7
+ - `schedule` 修复上游触发调度的默认输入处理,减少调度配置误差。
8
+ - `export` / `save` 增强输入字段类型校验,并在 `preview` 中提示 LEFT JOIN 桥接列全空样本。
9
+
10
+ ## @guandata/guanetl 0.1.15 - 2026-06-15
11
+
12
+ - `save` 增强输出数据集保护,保留级联相关配置,并对追加写入场景的行数据结构提前校验。
13
+ - `preview` 对 `LEFT_OUTER` JOIN 的右表桥接列增加全 null 样本告警,提示检查 join 键值域是否匹配。
14
+ - 补充多输出数据集和追加写入保存流程说明,降低 ETL 保存时误改输出配置的风险。
15
+
3
16
  ## @guandata/guanetl 0.1.14 - 2026-06-09
4
17
 
5
18
  - 移除 `delete` 命令。ETL / 数据集删除属于高风险操作,后续不再通过 guanetl 暴露。
package/README.md CHANGED
@@ -25,7 +25,7 @@ guanetl save --dir <work_dir>
25
25
 
26
26
  标准 ETL 写入闭环:`create/edit → export → preview → save → run --wait`。
27
27
 
28
- > **触发成功 ≠ ETL 执行成功**:`run` 返回"执行已触发"仅表示后端接受了请求。使用 `run --wait` 等待终态,FAILED 时会展示真实错误消息。
28
+ > **触发成功 ≠ ETL 执行成功**:`run` 返回"执行已触发"仅表示后端接受了请求。使用 `run --wait` 等待终态,FAILED 时会展示真实错误消息。触发前会检查直接上游数据集状态;若发现上游处于失败态,会先输出警告但继续执行,可用 `--skip-upstream-check` 跳过检查。
29
29
 
30
30
  新建 ETL 时注意目录树不同:`create --parent-dir` 使用 ETL 目录树 id,输出数据集目录使用 DATA_SET 目录树 id。可用 `guancli etl tree` / `guancli ds tree` 分别查询,或用 `guanetl mkdir-pair` 成对创建。
31
31
 
@@ -39,6 +39,18 @@ guanetl install-skill
39
39
 
40
40
  ## 版本更新
41
41
 
42
+ ### @guandata/guanetl 0.1.16
43
+
44
+ - `save --dry-run` 增加保存影响预览基础能力,帮助在提交前检查关键配置变化。
45
+ - `run` 会在执行前提示上游数据集失败状态,降低基于异常上游继续执行的风险。
46
+ - `schedule` 修复上游触发调度的默认输入处理,减少调度配置误差。
47
+ - `export` / `save` 增强输入字段类型校验,并在 `preview` 中提示 LEFT JOIN 桥接列全空样本。
48
+
49
+ ### @guandata/guanetl 0.1.15
50
+
51
+ - `save` 增强输出数据集保护,保留级联相关配置,并对追加写入场景的行数据结构提前校验。
52
+ - 补充多输出数据集和追加写入保存流程说明,降低 ETL 保存时误改输出配置的风险。
53
+
42
54
  ### @guandata/guanetl 0.1.14
43
55
 
44
56
  - 移除 `delete` 命令。ETL / 数据集删除属于高风险操作,后续不再通过 guanetl 暴露。
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guandata/guanetl",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "观远 ETL 本地开发工具 - 拉取、编辑、导出、预览、保存 ETL",
5
5
  "bin": {
6
6
  "guanetl": "bin/run.js"
@@ -133,13 +133,18 @@ guanetl edit <etl_id> --dir <work_dir>
133
133
  ```bash
134
134
  guanetl export --dir <work_dir>
135
135
  guanetl preview <node_id> --dir <work_dir>
136
+ guanetl save --dir <work_dir> --dry-run
136
137
  guanetl save --dir <work_dir>
137
138
  guanetl run <etl_id> --wait # 可选:保存后触发执行并等待完成
138
139
  ```
139
140
 
140
141
  **再次修改已 save 过的 ETL 时**:即使本地还保留着上次的工作目录,也必须重新执行 `guanetl edit <etl_id> --dir <新目录>` 拉取服务端最新版本,不要在旧工作目录上直接改了再 save。原因:服务端版本可能已经变化,直接复用旧 base 会导致合并冲突(如"输出数据集目录中存在同名文件")。
141
142
 
142
- 如果 `save` 在 direct-save 前提示输出数据集绑定风险,按提示处理,不要手动删除旧输出数据集。常见原因是本地改动把已有 `OUTPUT_DATASET` 节点的 dsId/dataSource 绑定丢掉、新建了同目录同名输出节点,或用 `guands dataset rename` 改了输出数据集名但 `etl.go` 的 `outputDsName` 没同步。修复优先级:重新 `edit` 到新目录、保持原输出节点 id、同步 `outputDsName`,确实要新输出时改 `outputDsName` 或 `parentDirId`。
143
+ 如果 `save` 在 direct-save 前提示输出数据集绑定风险,按提示处理,不要手动删除旧输出数据集。常见原因是本地改动把已有 `OUTPUT_DATASET` 节点的 dsId/dataSource 绑定丢掉、新建了同目录同名输出节点,或用 `guands dataset rename` 改了输出数据集名但 `etl.go` 的 `outputDsName` 没同步。修复优先级:重新 `edit` 到新目录、保持原输出节点 id、同步 `outputDsName`;确实要新建独立输出时,同时更换 `OUTPUT_DATASET` 节点 id,并设置新的 `outputDsName` 或 `parentDirId`。
144
+
145
+ 只追加输出列是安全例外:新增列、不改名、不删已有列、不改已有列类型,并保持原 `OUTPUT_DATASET` 节点 id 和 `outputDsName` 时,可以原地 `save`,保存前仍需 `preview` 目标输出确认结果。改列名、删列、改类型、换输入数据集、重接输出链路或更换输出节点 id 都按下游绑定风险处理。
146
+
147
+ 保存前优先执行 `save --dry-run` 查看影响报告;`--format json` 可用于自动化检查。dry-run 不调用 direct-save,若报告出现阻断风险,先修 `etl/` 后重新 `export -> preview -> save --dry-run`。
143
148
 
144
149
  ### 新建 ETL
145
150
 
@@ -183,6 +188,8 @@ guanetl create --name "ETL名称" --dir <work_dir> --parent-dir <etl_dir_id> --o
183
188
  3. 将本地修改合并到服务端版本
184
189
  4. 调用 direct-save API 写入
185
190
 
191
+ `save --dry-run` 只执行到合并和保存影响检查,不执行第 4 步。
192
+
186
193
  用户不需要手写服务端保存 payload。只要 `etl/` 修改正确、`export` 通过,`save` 就能完成服务端保存。
187
194
 
188
195
  ### run --wait 与任务状态
@@ -191,6 +198,7 @@ guanetl create --name "ETL名称" --dir <work_dir> --parent-dir <etl_dir_id> --o
191
198
 
192
199
  - 不加 `--wait` 时,`run` 只触发并返回 `taskId`。
193
200
  - 加 `--wait` 后,CLI 会轮询任务状态直到终态(FINISHED / FAILED / CANCELED),FAILED 时会展示真实错误消息。
201
+ - 触发前,`run` 会检查直接上游数据集状态;若发现上游处于 `FAILED`/`失败` 态,会先输出警告但继续触发执行。确认不需要检查时可加 `--skip-upstream-check`。
194
202
  - 如果不加 `--wait` 后想查状态:`task status <taskId>` 或 `task wait <taskId>`。
195
203
 
196
204
  ### 排查执行失败
@@ -248,12 +256,16 @@ guanetl schedule <etl_id> --disable # 关闭调
248
256
 
249
257
  ```bash
250
258
  # 任一上游数据集更新后执行
259
+ guanetl schedule <etl_id> --trigger upstream
251
260
  guanetl schedule <etl_id> --trigger upstream --inputs <dsId1>,<dsId2>
252
261
 
253
262
  # 全部上游数据集更新后执行
263
+ guanetl schedule <etl_id> --trigger upstream-all
254
264
  guanetl schedule <etl_id> --trigger upstream-all --inputs <dsId1>,<dsId2>
255
265
  ```
256
266
 
267
+ 不指定 `--inputs` 时,`guanetl` 会读取 ETL 当前输入数据集并默认全部监听;指定 `--inputs` 时只监听列出的 dsId。
268
+
257
269
  ### 定时调度类型说明
258
270
 
259
271
  | cron-type | 参数 | 说明 |
@@ -293,6 +305,7 @@ guanetl task cancel <t1> <t2> # 批量取消多个任务
293
305
  - 输入字段 schema 优先维护在 `BasicInputDataset(..., []Field{...})` 中。
294
306
  - 新版 ETL 默认以输入字段 `alias` / 展示名作为算子列名;字段存在别名时,SQL 和直接列名算子优先使用展示名,raw name 只作为底层数据集元数据理解。
295
307
  - ETL 执行时以真实数据集字段元数据为准;如果需要设置或确认展示名,先用 `guands dataset fields <dsId>` / `guands dataset alias <dsId> --fd-id <fdId> --alias "展示名"`,不要只在 `etl.go` 手写 alias 后假设服务端会采用。
308
+ - 本地字段解析优先级为 `alias -> displayName/showName/title -> name`;遇到同一来源下有效字段名重复,先消除歧义再继续写算子。
296
309
  - 新增 SQL 节点时,要同步新增对应 `.sql` 文件。
297
310
  - 对 `edit` 导入出来的 ETL,优先局部修改,不要为“重构”大面积推翻。
298
311
  - **文件访问边界**:只允许访问以下路径,不要主动探索或读取用户未明确授权的其他目录:
@@ -311,7 +324,7 @@ guanetl task cancel <t1> <t2> # 批量取消多个任务
311
324
  | 节点函数 | 用途 | 何时使用 | 关键约束 |
312
325
  |---|---|---|---|
313
326
  | `BasicInputDataset` | 引入数据集 | 每个 ETL 至少一个 | 必须提供 `[]Field` schema |
314
- | `BasicOutputDataset` | 输出数据集 | 每个 ETL 至少一个(叶子节点) | `DefineETL()` 返回值 |
327
+ | `BasicOutputDataset` | 输出数据集 | 每个 ETL 至少一个,可有多个(叶子节点) | `DefineETL()` 返回值;多输出共享一次 save/run/schedule |
315
328
  | `BasicSelectColumns` | 选列/重命名 | 只需要部分字段或改名 | - |
316
329
  | `BasicFilterRows` | 行筛选 | 按条件过滤数据 | combineType: `AND`/`OR` |
317
330
  | `BasicJoinData` | 等值 JOIN | 两表关联 | **优先用这个,不要写 SQL JOIN** |
@@ -62,12 +62,48 @@ orders := BasicInputDataset("id_1001", "订单", "ds_orders", []Field{
62
62
  - `meta.json` 主要保留 ETL 名称等元信息。
63
63
  - 字段相关报错时,优先检查 `[]Field`,不要先怀疑 `meta.json`。
64
64
 
65
+ ## ODS 展示格式字符串先解析再声明数值
66
+
67
+ `guancli ds get` 只能告诉你字段声明类型;ODS / mock / 平台导出表可能把 UI 展示值原样存成 `STRING`,例如 `"13小时31分"`、`"27.69%"`、`"90"`。写 DWD 前先执行 `guancli ds preview <dsId>` 看样本;如果 preview 提示 STRING 样本像展示格式,不能在 `BasicInputDataset` 里直接把该字段声明成 `DOUBLE` / `LONG` 后聚合。
68
+
69
+ `guanetl export` 会通过数据集详情接口校验输入字段 schema:服务端字段是 `STRING` 时,`BasicInputDataset` 不能把它声明成 `DOUBLE` / `LONG` / `DATE` 等非 STRING 类型。
70
+
71
+ 推荐先用 `BasicCalculator` 或 SQL 显式解析成新的数值列,再让下游聚合引用新列:
72
+
73
+ ```go
74
+ calc := BasicCalculator("id_1002", "解析展示格式度量", inputA, []Formula{
75
+ {
76
+ Name: "下单转化率_数值",
77
+ Type: "DOUBLE",
78
+ Expr: "CAST(REPLACE([下单转化率], \"%\", \"\") AS DOUBLE) / 100",
79
+ Key: "formula_order_rate_value",
80
+ },
81
+ {
82
+ Name: "营业时长_分钟",
83
+ Type: "DOUBLE",
84
+ Expr: "CAST(IF(REGEXP_EXTRACT([营业时长], \"([0-9.]+)小时\", 1) = \"\", \"0\", REGEXP_EXTRACT([营业时长], \"([0-9.]+)小时\", 1)) AS DOUBLE) * 60 + CAST(IF(REGEXP_EXTRACT([营业时长], \"([0-9.]+)分\", 1) = \"\", \"0\", REGEXP_EXTRACT([营业时长], \"([0-9.]+)分\", 1)) AS DOUBLE)",
85
+ Key: "formula_open_minutes",
86
+ },
87
+ {
88
+ Name: "店铺分_数值",
89
+ Type: "DOUBLE",
90
+ Expr: "CAST([店铺分] AS DOUBLE)",
91
+ Key: "formula_store_score_value",
92
+ },
93
+ }, Position{X: 320, Y: 100})
94
+ ```
95
+
96
+ - 百分比字符串先去 `%`,再确认业务语义是 `27.69` 还是 `0.2769`。
97
+ - 中文时长要统一成明确单位,例如分钟或秒。
98
+ - 纯数字字符串可能是编码 / ID;确认不是维度编码后再 cast。
99
+
65
100
  ## 字段 name / alias / showName 规则
66
101
 
67
102
  后端 ETL 的展示名语义主要来自字段 `alias`,有些接口或前端上下文会叫 `displayName` / `showName`。
68
103
 
69
104
  - 新版 ETL 默认使用输入字段别名:如果数据集字段有 `alias`,进入 ETL 算子后的列名通常是 `alias`;没有 `alias` 时才是原始 `name`。
70
105
  - 输入字段别名的事实源是数据集字段元数据;可用 `guands dataset fields <dsId>` 回读,用 `guands dataset alias <dsId> --fd-id <fdId> --alias "展示名"` 更新。`BasicInputDataset(..., []Field{...})` 中的字段 schema 主要用于导入/导出和本地 lint,不会把一个未设置过数据集 alias 的字段强制变成展示名列。
106
+ - 本地校验按 `alias -> displayName/showName/title -> name` 解析有效字段名;同一来源下两个字段解析成相同有效名会被视为歧义,建模前应先改名或明确上游输出。
71
107
  - SQL 节点使用上游表 `input1`、`input2` 的当前列名。字段有中文展示名时,SQL 里应写展示名并用反引号,例如 ``SELECT `门店ID` FROM input1``。
72
108
  - `SELECT_COLUMNS`、`FILTER_ROWS`、`REMOVE_DUPLICATES`、`GROUP_BY` 等直接列名算子,也以当前上游列名为准。
73
109
  - 底层数据集读取和数据集元数据仍保留原始 `name`,所以 `guancli` / `guands` 输出字段时要同时关注 `name` 和 `alias`。
@@ -242,6 +278,52 @@ func DefineETL() []Node {
242
278
  }
243
279
  ```
244
280
 
281
+ ### 多输出 ETL
282
+
283
+ `DefineETL()` 可以返回多个叶子输出节点。适合从同一批输入派生多张下游表,例如同时产出明细表和汇总表。
284
+
285
+ ```go
286
+ package main
287
+
288
+ import . "guanetl/internal/framework"
289
+
290
+ func DefineETL() []Node {
291
+ orders := BasicInputDataset("id_1001", "订单", "ds_orders", []Field{
292
+ BasicField("门店", "STRING"),
293
+ BasicField("订单号", "STRING"),
294
+ BasicField("销售额", "DOUBLE"),
295
+ }, Position{X: 100, Y: 120})
296
+
297
+ detail := BasicSelectColumns("id_1002", "订单明细", orders, []ColumnSetting{
298
+ {Name: "门店"},
299
+ {Name: "订单号"},
300
+ {Name: "销售额"},
301
+ }, Position{X: 340, Y: 40})
302
+
303
+ summary := BasicGroupBy(
304
+ "id_1003",
305
+ "门店汇总",
306
+ orders,
307
+ []GroupByColumn{
308
+ BasicGroupByColumn("门店", "STRING"),
309
+ },
310
+ []AggregationColumn{
311
+ BasicAggregationColumnWithAlias("销售额", "DOUBLE", "SUM", "销售额合计"),
312
+ },
313
+ Position{X: 340, Y: 200},
314
+ )
315
+
316
+ outDetail := BasicOutputDatasetInDir("id_1004", "输出明细", detail, "订单明细表", "ds_dir_id", Position{X: 600, Y: 40})
317
+ outSummary := BasicOutputDatasetInDir("id_1005", "输出汇总", summary, "门店销售汇总表", "ds_dir_id", Position{X: 600, Y: 200})
318
+
319
+ return []Node{outDetail, outSummary}
320
+ }
321
+ ```
322
+
323
+ - 每个输出都要有独立的 `OUTPUT_DATASET` 节点 id 和独立的 `outputDsName`。
324
+ - 多输出共享同一个 ETL 的 `save`、`run` 和调度配置;任何一个输出分支变更后,都应重新验收本 ETL 的全部输出。
325
+ - 如果多个输出需要独立调度、独立发布、独立回滚,或希望隔离某个输出分支的变更影响,应拆成多个单输出 ETL。
326
+
245
327
  ## 每次改完都检查
246
328
 
247
329
  1. `DefineETL()` 返回的是叶子节点吗?
@@ -258,7 +340,8 @@ func DefineETL() []Node {
258
340
 
259
341
  1. `guanetl export --dir <work_dir>`
260
342
  2. 需要看结果时:`guanetl preview <node_id> --dir <work_dir>`
261
- 3. 确认无误后:`guanetl save --dir <work_dir>`
343
+ 3. 保存前先看影响:`guanetl save --dir <work_dir> --dry-run`
344
+ 4. 确认无误后:`guanetl save --dir <work_dir>`
262
345
 
263
346
  如果 `export` 没过,不要直接 `save`。
264
347
 
@@ -267,11 +350,13 @@ func DefineETL() []Node {
267
350
  `save` 会先拉取服务端当前 ETL,再把本地 `_exported.json` 合并进去。修改已保存 ETL 时,输出节点必须保留服务端已有输出数据集绑定,否则 direct-save 可能把它当成“创建新输出数据集”,触发“输出数据集目录中存在同名文件”。
268
351
 
269
352
  - 再次修改已保存 ETL,优先重新执行 `guanetl edit <etl_id> --dir <新目录>`,不要长期复用旧工作目录。
270
- - 修改已有输出 schema 时,保持原 `OUTPUT_DATASET` 节点 id
353
+ - 只追加输出列时,可以原地 `save`:新增列,不改名、不删已有列、不改已有列类型,并保持原 `OUTPUT_DATASET` 节点 id 和 `outputDsName`。
354
+ - 修改已有输出 schema 时,保持原 `OUTPUT_DATASET` 节点 id;改列名、删列、改已有列类型、换输入数据集或重接输出链路都可能影响下游绑定,保存前必须重新 `preview` 并评估下游。
271
355
  - 如果用 `guands dataset rename` 改过 ETL 输出数据集名称,必须同步 `etl.go` 中 `BasicOutputDataset(..., outputDsName, ...)` 或 `BasicOutputDatasetInDir(..., outputDsName, ...)` 的名称。
272
356
  - 不要为了绕过同名错误手动删除旧输出数据集;旧输出通常仍被 ETL 依赖。
273
- - 如果确实要创建新输出数据集,必须改 `outputDsName` 或 `parentDirId`,避免与旧输出同目录同名。
357
+ - 如果确实要创建新输出数据集,必须同时更换 `OUTPUT_DATASET` 节点 id,并设置新的 `outputDsName` 或 `parentDirId`;只改名称会被视为已有输出绑定风险。
274
358
  - `save` 如果在 direct-save 前提示输出绑定风险,先修本地 `etl.go` / `meta.json`,再重新 `export -> preview -> save`。
359
+ - `save --dry-run` 只生成保存影响报告,不调用 direct-save;需要机器可读结果时加 `--format json`。报告里出现阻断级输出绑定风险时,必须先修本地定义,不能继续真实 `save`。
275
360
 
276
361
  ## Appendix: Framework Surface
277
362
 
@@ -385,3 +470,5 @@ APPEND_ROWS unionType:
385
470
  - `INCLUDE_SHARED`
386
471
  - `INCLUDE_ALL`
387
472
  - `INCLUDE_FROM`
473
+
474
+ `BasicAppendRows` 会按列名对齐各输入源。参与拼接的同名列类型必须一致;例如一侧为 `DATE`、另一侧为 `LONG` 会在 `export` 阶段报错。遇到冲突时,先用 `BasicCalculator` / `BasicSelectColumns` 把各源字段统一类型或移除不用的冲突列,再执行行拼接。