Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
c24a229
docs(plans): #165-169 列级分析查询面实施计划(Momus 两轮审核通过)
c2j Sep 8, 2026
8478a91
feat(parser): 函数包裹列字面量过滤 transform 白名单 + WHERE/JOIN %ROWTYPE 记录字段跨表等值…
c2j Sep 8, 2026
242f3ae
feat(parser): PL IF/CASE 条件解析为表列谓词(branch-aware 提取器) (fix #167)
c2j Sep 8, 2026
99c645e
feat(store): procedure_predicates 侧表与版本链 v10→v12 (#167)
c2j Sep 8, 2026
02f0d97
feat(builder): TableAccess 诊断字段并集合并 + 过程谓词收集(归一化存储键) (fix #165)
c2j Sep 8, 2026
480db37
feat(graph): 按过程/包聚合 ColumnAnalysis 查询后端 (fix #165)
c2j Sep 8, 2026
73a2d32
feat(cli): codeweb columns / predicates 子命令 (fix #165, fix #167)
c2j Sep 8, 2026
25c788b
feat(serve,mcp): columns/lineage MCP 工具与 HTTP 端点,共享 lineage 目标解析 (fix…
c2j Sep 8, 2026
50320a1
docs: 补齐 lineage/columns/predicates 用户与开发者文档 (fix #166)
c2j Sep 8, 2026
351736d
fix(predicates): ELSIF/简单 CASE 采集 + 函数包裹操作数断链修复 (PR#170 review F1/F2)
c2j Sep 8, 2026
fafeaa2
fix(cli,serve,mcp): 谓词身份对齐 columns、歧义显式失败、空谓词语义化 (PR#170 review F3/F4…
c2j Sep 8, 2026
8a3f0c1
refactor: 精简谓词/提取器注释,去除 issue 叙事(PR#170 review F6)
c2j Sep 8, 2026
4fb3dc1
docs(plans): PR#170 评审修复计划(Momus 审核通过)
c2j Sep 8, 2026
2028d0b
fix(cli): columns --package 歧义子串显式失败,对齐 --procedure (PR#170 review fo…
c2j Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
475 changes: 475 additions & 0 deletions .sisyphus/plans/2026-09-08-issue-165-169-column-analysis-surface.md

Large diffs are not rendered by default.

105 changes: 105 additions & 0 deletions .sisyphus/plans/2026-09-08-pr170-review-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# PR #170 评审修复计划(#165–#169 跟随修复)

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** 修复 PR #170 六条评审意见(4 bug + 2 suggestion,全部已对照代码核实成立)。核心目标:#167 谓词在 ELSIF/简单 CASE/函数包裹裸列/SELECT INTO 变量形态下不漏报不错报;`columns` 与 `predicates` 的过程身份可 join;机器入口不静默错配。

**Architecture:** 全部改动位于 `src/parser/predicates.rs`、`src/main.rs`、`src/mcp/tools.rs`、`src/server/handlers.rs`、`src/graph/lineage.rs`、`src/parser/extractor.rs`(仅注释)。无 store 布局变更——**不需要 bump `STORE_VERSION`(保持 12)**:F1/F2 改的是提取逻辑而非已序列化结构;F3 只改 JSON 输出字段来源;F4/F5 是解析参数与错误语义。

**已核实的评审发现(全部接受,无争议项):**

| # | 发现 | 核实位置 |
|---|---|---|
| F1 | If 臂漏 `elsifs`;简单 CASE(`expression: Some`)把 WHEN 值当裸条件 | predicates.rs:277-287 |
| F2 | `condition_operand` 的 `expr_name(expr)?` 对 FunctionCall 断链,跳过 var_sources 与 sole-table fallback;Derived 臂硬编码 `transform: None` | predicates.rs:402, 409 |
| F3 | `cmd_predicates` 的 `procedure` 取自 NodeKey 展示串(包内过程得 `pkg.prc`),与 columns 的 `id.name`+`package` 不一致;无 `package` 字段 | main.rs:2026-2029 |
| F4 | 四处新调用点 `resolve_single_node(..., false, false)` → `Ambiguous` 臂不可达,多匹配静默取首个 | main.rs:1894/1990, tools.rs:715, handlers.rs:487 |
| F5 | resolved-but-empty 谓词非零退出,应返回 `predicates: []` | main.rs:2021-2025 |
| F6 | 注释复述控制流/带 issue 叙事;`cmd_lineage` 保留内联解析双份 | extractor.rs 多处, main.rs, lineage.rs |

---

## Fix 1 (F1): ELSIF 采集 + 简单 CASE 合成比较

**Files:** `src/parser/predicates.rs`(visitor 的 `PlStatement::If`/`PlStatement::Case` 臂);测试同文件 tests 模块 + `tests/regress_predicates.rs`。

**Step 1 (Red):**
- 单测 `elsif_conditions_collected_as_predicates`:`IF r.x = '1' THEN ... ELSIF r.x = '2' THEN ... ELSIF r.x = '3' THEN ...`(record ctx)→ 3 条谓词,全部 `PredicateKind::If`、High、同表 clauses,id 递增;ELSIF 的 line 取各自 span(若 span 可得,否则 0——与现行主条件取法一致)。
- 单测 `simple_case_synthesizes_expression_comparison`:`CASE r.x WHEN '1' THEN ... WHEN '2' THEN ...`(`expression: Some`)→ 每条 WHEN 产出 `column: x, op: Eq, value: '1'/'2'` 的 High 谓词(合成 `expression = when.condition`),而非裸字面量 Low。
- e2e:`tests/regress_predicates.rs` 增补 fixture 断言 ELSIF 数量与简单 CASE 的 clauses。

**Step 2 (Green):**
- If 臂:主条件 push 后遍历 `spanned.elsifs`,逐个 `push_condition(&elsif.condition, PredicateKind::If, elsif 行号)`。
- Case 臂:`spanned.expression` 为 `Some` 时,对每个 when 合成比较表达式(构造 `Expr::BinaryOp { left: expression.clone(), op: "=".into(), right: when.condition.clone() }` 或等价内部表示——以 `push_condition` 现有输入类型为准,必要时新增 `push_equality(expression, when_value)` 内部路径),`expression: None`(搜索型 CASE)保持现行为。
- 跑 F1 既有测试确认不回归(搜索型 CASE 测试 `case_when_yields_predicates` 必须保持绿、语义不变)。

## Fix 2 (F2): condition_operand 断链修复 + Derived 携带 transform

**Files:** `src/parser/predicates.rs`。

**Step 1 (Red):**
- `naked_column_substr_resolves_via_sole_table`:单游标表 ctx + `IF substr(stock_kind,1,2) = '05'` → High 谓词,clause 带 `transform: Some(substr[1,2])`(当前实际:Low 无谓词)。
- `select_into_var_substr_resolves_via_var_source`:`SELECT kind_id INTO v_kind FROM swh_all_kind ...; IF substr(v_kind,1,2) = '05'` → Derived clause 指向 `swh_all_kind.kind_id` 且 **transform 携带**(当前:断链 Low)。

**Step 2 (Green):**
- `expr_name(expr)?` 改为可失败但不提前中断:将 `var_sources` 查找的键改为 `expr_name(expr)` **或** `column_transform_of(expr)` 的目标列名(裸列名,小写);两键都查不到才落入 sole-table fallback(`names.len()==1 && tables.len()==1` 分支,现有 transform 透传已就绪)。
- Derived 臂的 `PredicateClause` 携带与 Direct 臂相同的 `transform`(删除硬编码 `None`;var_sources 命中的是变量名包裹形态时 transform 语义同样成立)。
- 注意 fallback 顺序保持:记录字段(`resolved_clause`)→ var_sources → sole-table;不改变记录字段路径的既有行为(`transformed_condition_clause_carries_transform` 等测试保持绿)。

## Fix 3 (F3): predicates 输出身份对齐 columns

**Files:** `src/main.rs`(`cmd_predicates` + `PredicatesResult`);`tests/regress_predicates.rs`。

**Step 1 (Red):** e2e `predicates_identity_matches_columns_for_packaged_procedure`:包内过程 fixture → `codeweb predicates --format json` 的 `procedure` == `columns` 的 `procedure`(均为裸名),且 predicates JSON 新增 `package` 字段 == 包名(columns 同名字段一致)。当前实际:`procedure == "pkg.prc"` 且无 package 字段 → FAIL。

**Step 2 (Green):**
- `PredicatesResult` 增 `#[serde(default, skip_serializing_if = "Option::is_none")] package: Option<String>`(纯 JSON 输出结构,非 bincode 持久化——skip 安全;仿 `AggregatedColumnAnalysis` 的 package 字段风格)。
- `cmd_predicates` 不再从 NodeKey 展示串 split:从图节点 `RoutineId` 取 `name` 与 `package`(对齐 `column_analysis_of_routine` 的取法)。
- 独立过程 `package: None`(JSON 省略),schema_version 不变。

## Fix 4 (F4): 歧义显式失败,消灭静默首匹配

**Files:** `src/main.rs`(`cmd_columns`/`cmd_predicates` 两处)、`src/mcp/tools.rs`(`resolve_node`)、`src/server/handlers.rs`(`resolve_node`);测试 `tests/regress_columns.rs`、`tests/regress_predicates.rs`、`tests/mcp_test.rs`、`tests/serve_api.rs`。

**Step 1 (Red):**
- e2e:同前缀双过程 fixture(如 `prc_order` / `prc_order_header`)→ `codeweb columns --procedure prc_order` 非零退出且 stderr 提示歧义(当前实际:静默返回首个 + exit 0);`codeweb predicates` 同理。
- serve_api:`GET /api/v1/columns?procedure=prc_order` → 409 或 400(按 handlers 既有错误约定选一个,报告所选);mcp_test:`codeweb_column_analysis` 歧义名返回 error JSON(区分 Empty 的 "No nodes matching" 文案)。

**Step 2 (Green):**
- 四处调用第 4 参 `fail_on_multiple` 改 `true`;`cmd_*` 的 `ResolveResult::Ambiguous` 臂从死代码变为可达(保留现有非零错误路径)。
- MCP `resolve_node` 返回区分 `Empty`("No nodes matching ...")与 `Ambiguous`("Ambiguous match: N candidates ...");HTTP 对应 404 vs 400(报告所选映射)。
- 不动 `trace`/`detail`/`impact` 等既有调用点的语义(它们本就交互式,首匹配+stderr 提示是既有契约)。

## Fix 5 (F5): resolved-empty 返回空数组

**Files:** `src/main.rs`;`tests/regress_predicates.rs`。

**Step 1 (Red):** `predicates_empty_branches_return_empty_array`:存在但无 IF/CASE 的过程 → exit 0、stdout 为 `{schema_version, procedure, predicates: []}`(当前实际:非零 + "No PL predicates found")。

**Step 2 (Green):** `cmd_predicates` 中 store 侧表 miss/resolved-empty 不再 `?` 报错,改输出空 `predicates`;非零保留给:名称未解析(Empty)、歧义(F4 后可达)。注意与 F4 的歧义错误路径不冲突。

## Fix 6 (F6): 注释卫生 + cmd_lineage 共享 parse_lineage_target

**Files:** `src/parser/extractor.rs`(仅注释)、`src/parser/predicates.rs`(仅注释)、`src/graph/lineage.rs`、`src/main.rs`。

**内容:**
- 精简复述控制流的注释;保留并压缩非显性 WHY(HardFilter/PredicateClause 的 bincode 固定字段数约束一句话足够);删除 issue 编号/评审轮次/"intentionally left untouched" 类叙事。
- `cmd_lineage` 改为调用 `graph::lineage::parse_lineage_target`(消除 T5 留下的内联双份及其叙事注释);行为必须逐字不变——`tests/regress_lineage_table_upstream.rs`、`tests/regress_column_lineage.rs`、`tests/regress_issue_154_lineage_targets.rs` 全套保持绿不动即验证。

---

## 执行顺序与门禁

```
F1 → F2(同文件连续 Red→Green) → F3 → F4 → F5 → F6(纯清理收尾)
```

每项独立 Red→Green;每完成两项跑一次:
```bash
cargo test --features full -- --skip test_path_mapping_applied --skip test_serve_
cargo clippy --features full -- -D warnings
cargo fmt --all -- --check
```
最终全量门禁 + `cargo build --features full`。

**Never 红线(AGENTS.md)**:不删/跳过/改写既有测试(F3/F4/F5 的新行为一律新增测试表达;若既有测试因 F4/F5 语义变化失败——如某测试断言了旧的静默首匹配——STOP 并报告,不得擅改);不引入依赖/feature/unsafe/`#[allow]`;不动 `STORE_VERSION`;F6 不改任何行为语义(仅注释与等价重构,行为守护靠既有套件全绿)。
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ codeweb merge -o full-graph.bincode my-project.bincode erp-store.bincode
| `codeweb files` | List analyzed files with node counts |
| `codeweb nodes` | List graph nodes with filtering |
| `codeweb trace-sql <sql>` | Search by SQL fragment and trace to Java methods |
| `codeweb lineage <target>` | Table-level and column-level lineage analysis |
| `codeweb columns --procedure <name>` | Aggregate column-analysis for a procedure/package (JSON) |
| `codeweb predicates --procedure <name>` | PL IF/CASE predicates resolved to table columns (JSON) |
| `codeweb query` | Execute declarative JSON QuerySpec |
| `codeweb import` | Import CGEF JSON graph file |
| `codeweb merge` | Merge multiple graph stores |
Expand Down Expand Up @@ -219,6 +222,8 @@ When built with `--features serve`, codeweb provides a RESTful API:
| GET | `/api/v1/nodes/:id/callees` | Downstream callees |
| GET | `/api/v1/nodes/search-sql` | Search nodes by SQL fragment |
| GET | `/api/v1/trace` | Bidirectional call chain tracing |
| GET | `/api/v1/lineage` | Table-level and column-level lineage |
| GET | `/api/v1/columns` | Aggregate column-analysis for a procedure/package |
| POST | `/api/v1/query` | Execute declarative QuerySpec |
| GET | `/api/v1/export` | Export graph (DOT/JSON/Mermaid) |

Expand Down Expand Up @@ -263,6 +268,8 @@ Add to `claude_desktop_config.json`:
| `codeweb_trace` | Bidirectional call chain tracing from a node name |
| `codeweb_search_sql` | Search nodes by SQL text content with scoring |
| `codeweb_query` | Execute declarative JSON QuerySpec for complex traversals |
| `codeweb_column_analysis` | Aggregate column-analysis for a procedure/package |
| `codeweb_lineage` | Table-level and column-level lineage analysis |

## Project Structure

Expand Down Expand Up @@ -501,6 +508,9 @@ codeweb merge -o full-graph.bincode my-project.bincode erp-store.bincode
| `codeweb files` | 列出已分析文件及节点数 |
| `codeweb nodes` | 列出图节点(支持过滤) |
| `codeweb trace-sql <sql>` | 按 SQL 片段搜索并追踪到 Java 方法 |
| `codeweb lineage <target>` | 表级与列级血缘分析 |
| `codeweb columns --procedure <name>` | 按过程/包聚合列级分析结果 (JSON) |
| `codeweb predicates --procedure <name>` | 解析 PL IF/CASE 条件为表列谓词 (JSON) |
| `codeweb query` | 执行声明式 JSON QuerySpec |
| `codeweb import` | 导入 CGEF JSON 图谱文件 |
| `codeweb merge` | 合并多个图谱存储 |
Expand Down Expand Up @@ -557,6 +567,8 @@ codeweb merge -o full-graph.bincode my-project.bincode erp-store.bincode
| GET | `/api/v1/nodes/:id/callees` | 下游被调用方 |
| GET | `/api/v1/nodes/search-sql` | 按 SQL 文本搜索节点 |
| GET | `/api/v1/trace` | 双向调用链追踪 |
| GET | `/api/v1/lineage` | 表级与列级血缘分析 |
| GET | `/api/v1/columns` | 按过程/包聚合列级分析结果 |
| POST | `/api/v1/query` | 执行声明式 QuerySpec |
| GET | `/api/v1/export` | 导出图谱(DOT/JSON/Mermaid) |

Expand Down Expand Up @@ -601,6 +613,8 @@ codeweb mcp --project /path/to/your/project
| `codeweb_trace` | 从节点名双向追踪调用链 |
| `codeweb_search_sql` | 按 SQL 文本搜索节点(含相关性评分) |
| `codeweb_query` | 执行声明式 JSON QuerySpec,支持复杂多步遍历 |
| `codeweb_column_analysis` | 按过程/包聚合列级分析结果 |
| `codeweb_lineage` | 表级与列级血缘分析 |

## 项目结构

Expand Down
29 changes: 29 additions & 0 deletions docs/DeveloperGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,31 @@ impl CodeGraph {
}
```

### ColumnAnalysis(列级分析模型)

`ColumnAnalysis` 结构体(以及通过 `codeweb columns` 导出的 `AggregatedColumnAnalysis`)承载了过程或语句级别的详细列约束面,其核心字段如下:

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `alias_map` | `BTreeMap<String, String>` | 表别名到实际表名的映射关系 |
| `column_refs` | `HashSet<ColumnRef>` | 语句中出现的所有列引用集合 |
| `join_conditions` | `Vec<JoinCondition>` | 等值关联条件。`JoinCondition` 包含左右表列、关联类型及来源 `source`(`ImplicitWhere` \| `ExplicitOn` \| `RecordField` — 其中 `RecordField` 表示由 `%ROWTYPE` 记录字段推导出的跨表等值键) |
| `hard_filters` | `Vec<HardFilter>` | 字面量过滤条件。`HardFilter` 包含表、列、操作符(Eq, Neq, Gt, Gte, Lt, Lte, Like, NotLike, In, Between, IsNull, IsNotNull)、字面量值,以及可选的 `transform`(函数包裹描述,如 `{"fn": "substr", "args": [...]}`) |
| `enum_mappings` | `Vec<EnumMapping>` | 基于 `CASE` / `DECODE` 的离散值枚举转换映射 |
| `select_into` | `Vec<SelectIntoMapping>` | `SELECT INTO` 赋值到 PL 变量的映射关系 |
| `column_mappings` | `Vec<ColumnMapping>` | 目标表列到源表列的血缘映射,区分 `Direct`(直接赋值)、`Derived`(表达式派生)、`Aggregated`(聚合函数)等种类 |
| `insert_columns` | `Vec<InsertColumnInfo>` | `INSERT` 语句写入的目标列集合 |
| `update_columns` | `Vec<UpdateColumnInfo>` | `UPDATE` 语句更新的目标列集合 |
| `read_tables` | `Vec<String>` | 语句或过程读取的源表列表 |

#### 自动化造数与 Mock 消费场景

`ColumnAnalysis` 的结构化输出是自动化测试数据生成(Mock 数据生成)的核心输入源:
1. **跨表键关联**:利用 `join_conditions`(特别是 `RecordField` 隐式推导键)可以自动构建跨表的主外键关联池,确保生成的 Mock 数据在多表 JOIN 时不会因关联落空而变成空结果。
2. **边界约束提取**:通过 `hard_filters` 提取出各表各列必须满足的字面量强约束(如 `status = '05'`),并结合 `transform` 逆向推导原始列的取值范围(如 `substr(kind,1,2)='05'` 要求 `kind` 前两位必须是 `'05'`)。
3. **有效值集合播种**:从 `enum_mappings` 中收集列的离散有效值边界,避免生成非法的业务状态码。
4. **靶向分支覆盖**:结合 `predicates`(PL 谓词解析)的条件约束与置信度,可以逆向推导触发特定 PL 分支(如特定的 `IF` 逻辑块)所需的数据特征,实现面向代码分支覆盖的靶向数据播种。

---

## GraphStore 存储层
Expand Down Expand Up @@ -205,6 +230,8 @@ HTTP API 通过 `axum` 框架提供,所有端点以 `/api/v1/` 为前缀,启
| GET | `/api/v1/nodes/:id/callees` | 节点下游被调用方(分页) |
| GET | `/api/v1/nodes/search-sql` | 按 SQL 文本搜索(`q` 参数) |
| GET | `/api/v1/trace` | 双向调用链追踪(`from`, `depth`, `max_nodes`) |
| GET | `/api/v1/lineage` | 表级与列级血缘分析(`target`, `direction`, `depth`) |
| GET | `/api/v1/columns` | 按过程/包聚合列级分析结果(`procedure`, `package`, `table`) |
| POST | `/api/v1/query` | 执行 QuerySpec 声明式查询 |
| GET | `/api/v1/export` | 导出图谱(`format` 参数:dot/json/mermaid) |
| GET | `/api/v1/graph` | 完整图谱 JSON 数据 |
Expand Down Expand Up @@ -331,6 +358,8 @@ codeweb 提供四种 MCP/外部集成方式:
| `codeweb_nodes` | `search`, `node_type`, `limit`, `offset` | 节点列表(搜索、类型过滤、分页) |
| `codeweb_node_detail` | `id` (usize) | 节点详情:属性 + callers + callees |
| `codeweb_trace` | `from`, `depth`, `max_nodes` | 双向调用链追踪 |
| `codeweb_column_analysis` | `procedure`, `package`, `table` | 按过程/包聚合列级分析结果 |
| `codeweb_lineage` | `target`, `direction`, `depth` | 表级与列级血缘分析 |
| `codeweb_search_sql` | `sql` | SQL 片段搜索 |
| `codeweb_query` | `spec` (QuerySpec JSON) | 声明式复杂遍历 |

Expand Down
30 changes: 30 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,36 @@ Key options:

✅ **Success**: Output shows the path(s) between your nodes with hop count and edge types.

### 4.6 Data lineage — "Where does this column come from?"

`lineage` traces data flow between tables and columns. It "looks inside" procedures to see how data moves.

```sql
-- Add this to your myapp.sql
CREATE TABLE t_src (id INT, amt NUMBER);
CREATE TABLE t_out (id INT, amt NUMBER);

CREATE OR REPLACE PROCEDURE proc_copy_amt AS
BEGIN
INSERT INTO t_out (id, amt)
SELECT id, amt FROM t_src;
END;
/
```

After `codeweb analyze`, run:

```bash
codeweb lineage t_out.amt --direction upstream
```

```
t_out.amt
← t_src.amt [direct] via proc:proc_copy_amt
```

✅ **Success**: codeweb correctly identified that `t_out.amt` is populated from `t_src.amt` via the `proc_copy_amt` procedure.

---

## 5. Visual exploration (going further)
Expand Down
30 changes: 30 additions & 0 deletions docs/getting-started_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,36 @@ codeweb inspect proc_main proc_helper --style tree

✅ **验证成功**:输出展示节点间的路径、跳数和边类型。

### 4.6 数据血缘分析 — "这个列的数据从哪来?"

`lineage` 命令追踪表与表、列与列之间的数据流转。它能“看穿”存储过程内部逻辑,识别数据搬运路径。

```sql
-- 在 myapp.sql 中追加以下内容
CREATE TABLE t_src (id INT, amt NUMBER);
CREATE TABLE t_out (id INT, amt NUMBER);

CREATE OR REPLACE PROCEDURE proc_copy_amt AS
BEGIN
INSERT INTO t_out (id, amt)
SELECT id, amt FROM t_src;
END;
/
```

执行 `codeweb analyze` 后,运行:

```bash
codeweb lineage t_out.amt --direction upstream
```

```
t_out.amt
← t_src.amt [direct] via proc:proc_copy_amt
```

✅ **验证成功**:codeweb 准确识别出 `t_out.amt` 的数据来源于 `t_src.amt`,且流转路径经过了 `proc_copy_amt` 存储过程。

---

## 5. 可视化探索(进阶)
Expand Down
Loading
Loading