Files
cobol-java-v3/docs/detailed-design/02-orchestrator-db.md
T

575 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DB 管道编排器 - 详细设计文档
> 模块路径: `orchestrator_db.py`
> 版本: V3 (2026技术大赛)
> 行数: 1973
> 核心类: `GixsqlOrchestrator`
---
## 1. 模块概述
### 1.1 职责
`orchestrator_db.py` 是 DB 型 COBOL 程序的 6 步端到端测试管道编排器,负责:
1. **环境整备** - gixpp 预处理 + cobc 编译(Step 1
2. **输入数据生成** - 测试数据生成 + 平面文件输出 + DB 初始化(Step 2)
3. **COBOL 执行** - 调用编译后的 COBOL 程序(Step 3
4. **中间数据提取** - SQLite DB → Java 中介 JSONStep 4
5. **Java 执行** - 调用 Java 转换程序(Step 5
6. **结果验证** - Java 输出与 COBOL 期望值比较(Step 6
### 1.2 边界
| 在范围内 | 不在范围内 |
|---------|-----------|
| DB 型 COBOL 程序 6 步管道编排 | Flat-file 型 COBOL 程序处理 |
| SQLite 数据库初始化与种子注入 | 分支树构建(由 cobol_testgen/ 处理) |
| 多场景(多轮)执行调度 | 覆盖率报告生成(可选附件,非管道核心) |
| 测试数据合成与 PK 冲突注入 | Java 程序内部逻辑 |
### 1.3 依赖关系
```
orchestrator_db.py
+-- config.py <- 项目配置
+-- config/program_schema.py <- YAML 程序定义
+-- cobol_testgen/ <- 核心引擎
+-- data/diff_result.py <- 验证结果数据模型
+-- runners/gixsql_runner.py <- GnuCOBOL 编译-执行运行器
+-- agents/llm.py <- LLM 客户端(可选)
```
---
## 2. 核心数据结构
### 2.1 GixsqlOrchestrator
```python
class GixsqlOrchestrator:
def __init__(self, config: Config, program_id: str,
cobol_src_dir: str | Path,
copybook_dirs: list[str | Path] | None = None,
work_dir: str | Path | None = None,
skip_jvm: bool = True):
```
**关键属性:**
| 属性 | 类型 | 说明 |
|------|------|------|
| config | Config | 项目配置 |
| program_id | str | COBOL 程序标识符 |
| cobol_src_dir | Path | COBOL 源码目录 |
| copybook_dirs | list[Path] | COPYBOOK 搜索路径 |
| work_dir | Path | 构建产物目录(ASCII 路径) |
| runtime_dir | Path | 运行时数据目录 |
| schema | ProgramSchema | YAML 程序定义 |
| runner | GixsqlCobolRunner | GnuCOBOL 运行器 |
| db_path | Path | 默认 SQLite DB 路径 |
| skip_jvm | bool | 是否跳过 Step 5/6 |
**管道状态:**
| 状态属性 | 类型 | 说明 |
|----------|------|------|
| src_path | Path | 预处理后源码路径 |
| pp_path | Path | gixpp 预处理输出路径 |
| exe_path | Path | 编译后可执行文件路径 |
| java_input_path | Path | Java 中介数据路径 |
| java_output_path | Path | Java 输出路径 |
| _current_db_path | Path | 当前场景的 DB 路径 |
| _multi_run_gcov_data | dict | 多轮合并后的 gcov 数据 |
| generated_records | list[dict] | 生成的测试数据记录 |
| generated_structure | dict | 解析后的程序结构 |
### 2.2 DbPipelineResult
```python
@dataclass
class DbPipelineResult:
program_id: str
step: int | float
success: bool
message: str = ""
data: dict = field(default_factory=dict)
```
---
## 3. 6 步流程设计
### 3.1 Step 1: 环境整备 (step1_setup_environment)
**职责:** gixpp 预处理 → cobc 编译
**执行流程:**
```
1. _copy_sources_to_workdir()
|-- 复制主源码 {program_id}.cbl → work_dir/src/
|-- 复制 COPYBOOK (*.cpy) → work_dir/src/
|-- 复制子程序 (SUB*.cbl) → work_dir/src/
(搜索: cobol_src_dir, sub/, production/sub/, cobol-tna-system/sub/)
2. runner.preprocess(src, preprocessed/, copybook_dirs)
|-- gixpp 预处理 + CONNECT TO 路径修补
| gixpp 错误转换: 'data/kin.db' → 'sqlite://localhost/kin'
| 修补为: 'sqlite:///{db_path}'
3. runner.compile(pp, exe, copybook_dirs, extra_srcs)
|-- cobc 编译 → work_dir/bin/{program_id}.exe
|-- 编译日志写入 runtime_dir/logs/compile/
4. 返回 DbPipelineResult(step=1, success, data={exe_path, log})
```
**关键逻辑:**
- 源码必须复制到 ASCII-only 路径(gixpp 不支持中文路径)
- CONNECT TO 字符串修补: gixpp 输出的 `sqlite://localhost/kin` 需替换为绝对路径
- 子程序从多个候选目录搜索,未找到仅 warning 不阻断
**输入:** cobol_src_dir, copybook_dirs, schema.subprograms
**输出:** src_path, pp_path, exe_path
### 3.2 Step 2: 输入数据生成 (step2_generate_inputs)
**职责:** COBOL 解析 → 测试数据生成 → DB 初始化 → 平面文件输出
**执行流程:**
```
1. COBOL 解析
|-- extract_structure(src_text) → 分支树 + 赋值表
|-- generate_all_data() → 测试数据记录(白盒+机能+策略)
|-- 后处理: R02APPL-ID 链接 R01APPL-ID
2. DB 初始化
|-- 确定 DB 路径(场景分离: {program_id}_{scenario_id}.db
|-- 清理旧 DB → _init_database(db_path)
| |-- _create_tables(): 按 YAML schema 创建表 + 主键
|-- _populate_database(): 注入种子行
| |-- 解析 COBOL → 分支树 → 路径枚举
| |-- build_db_input(): 生成 DB 输入行
| |-- 覆盖率驱动数据补充(日期、假期等)
| |-- 区间协调(INSURANCE-RATES / EMP-MASTER
| |-- INSERT OR IGNORE 写入 DB
|-- _inject_extra_seed_rows(): 大结果集注入
|-- _inject_sql_error_rows(): PK 冲突行注入
3. 记录修补
|-- records[0].R01EMP-ID = SPACE(触发空社员路径)
|-- 全零 EMP-ID → SPACE 清洗
|-- R01LINE 与 EMP-ID 一致性修补
|-- 注入重复 EMP-IDAGG UPDATE 路径)
|-- _deduplicate_r01_pk(): PK 去重
|-- _inject_aggregation_boundaries(): 聚合边界数据
4. 场景驱动修改
|-- collision 场景: INSERT 重复、OVT-MONTHLY 匹配、COMMIT 阈值
|-- abnormal 场景: orphan cancel ABEND
5. 平面文件输出
|-- write_all_files(): 全 FD 平面文件
|-- write_sysin_file(): SYSIN 配置
|-- _seed_matching_monthly_rows(): MONTHLY_ABSENCE 匹配行预填
6. JSON 输出(可选)
|-- 解析 DATA DIVISION → 字段字典
|-- 分支树 + MC/DC 路径枚举
|-- output_json(): 写入 json/{program_id}.json
7. 返回 DbPipelineResult(step=2, data={records, flat_files, db_path})
```
**关键逻辑:**
- 多场景时 DB 路径分离: `{program_id}_{scenario_id}.db`
- PK 冲突行必须与运行时 INSERT 实际值一致(基于输入记录而非合成值)
- 聚合边界注入: overflow(同月累加溢出)+ table-full>=110 个不同月)
- 日期值统一为 YYYYMMDD 格式
**输入:** src_path, pp_path, schema, scenario
**输出:** generated_records, generated_structure, db_path, 平面文件
### 3.3 Step 3: COBOL 执行 (step3_run_cobol)
**职责:** 调用编译后的 COBOL 程序并收集 gcov 覆盖率数据
**执行流程:**
```
1. 环境准备
|-- 创建 runtime/run_{id}/main/{input,output}/ 目录
|-- 复制生成的平面文件 → input/
|-- 复制 JSON → json/
2. 文件方向映射 (_scan_assign_to)
|-- 正则扫描 SELECT/ASSIGN-TO → {文件名: 方向}
|-- OPEN 语句解析 → INPUT/OUTPUT 方向确定
3. DB 路径准备
|-- 场景 DB → 复制到默认 DB 路径
|-- CWD/data/kin.dbCONNECT TO 路径)
|-- CWD/kingixsql regex 路径)
4. 执行
|-- 清理前次 .gcda 文件
|-- runner.run(exe, cwd, db_path, env_overrides, command_args)
|-- 日志写入 runtime_dir/logs/
5. gcov 数据收集
|-- .gcda 从 CWD + exe_dir 复制到 gcov/run_{id}/
|-- .gcno 同步(共享 .gcnoCOPY 不 MOVE
6. 返回 DbPipelineResult(step=3, data={returncode, log, ...})
```
**关键逻辑:**
- GIXSQL_DB_PATH 环境变量不生效,需通过 CWD/data/kin.db 传递
- GnuCOBOL 的 .gcda 写入编译时 CWD,多场景需 COPY 到各自 gcov 目录
- subprogram 的 .gcno 必须同步到每个 run 目录
**输入:** exe_path, schema, scenario
**输出:** 运行日志、gcov 数据、返回码
### 3.4 Step 4: 中间数据提取 (step4_extract_intermediate)
**职责:** 从 SQLite DB 导出 Java 程序所需的 JSON 中介数据
**执行流程:**
```
1. 打开 DB_current_db_path 或 db_path
2. 遍历 schema.db_tables,对每张表执行 SELECT * FROM [table]
3. 构建 meta = {program_id, tables: {table_name: [rows]}}
4. 写入 work_dir/intermediate/{program_id}_W01.json
5. 返回 DbPipelineResult(step=4, data={tables, w01_path})
```
**关键逻辑:**
- 使用 sql_name 或 name 查询表名
- 即使表不存在也不报错(空列表),允许部分执行
- 输出 JSON 包含所有表的全量行数据
**输入:** _current_db_path, schema.db_tables
**输出:** java_input_pathW01 JSON
### 3.5 Step 5: Java 执行 (step5_run_java)
**职责:** 调用 Java 转换程序处理 COBOL 输出数据
**执行流程:**
```
1. 创建 java_output 目录
2. 构建命令: java -jar {java_jar} -i {java_input_path} -o {java_out}
3. subprocess.run(cmd, capture_output=True, timeout=60)
4. 返回 DbPipelineResult(step=5, data={returncode, log})
```
**关键逻辑:**
- 超时限制 60 秒
- 若未指定 java_jar,仅执行 java -version 检测环境
- 依赖 Step 4 的输出作为输入
**输入:** java_input_path, java_jar
**输出:** java_output_path, 执行日志
### 3.6 Step 6: 结果验证 (step6_verify)
**职责:** 比较 Java 输出与 COBOL 期望值
**执行流程:**
```
1. 构建 VerificationRun 结果对象
2. 读取 DB 各表行数(调试信息)
3. 扫描 java_output_path 下的 .txt/.json 文件
4. 设置 exit_code 和 statusPASS/MISMATCH
5. 返回 VerificationRun
```
**关键逻辑:**
- fields_mismatched == 0 时判定为 PASS
- 输出 Java 输出文件列表作为调试信息
- 返回 VerificationRun 而非 DbPipelineResult
**输入:** java_output_path, _current_db_path, schema.db_tables
**输出:** VerificationRunstatus, exit_code, debug
---
## 4. 接口定义
### 4.1 主入口: run_all()
```python
def run_all(self, skip_steps: set[int] | None = None,
generate_coverage: bool = True) -> VerificationRun:
```
**参数:**
| 参数 | 类型 | 说明 |
|------|------|------|
| skip_steps | set[int] | 要跳过的步骤编号集合(如 {5, 6}) |
| generate_coverage | bool | 是否生成覆盖率报告(默认 True) |
**返回:** VerificationRun(最终验证结果)
**行为:**
- skip_jvm=True 时自动将 {5, 6} 加入 skip_steps
- 多场景执行: schema.runs 非空时循环执行 Step 2-3
- 每个场景失败即返回 BLOCKED(不继续后续步骤)
- Step 1 只执行一次(编译共享)
- 多场景执行后自动合并 gcov 数据
### 4.2 单步接口
| 方法 | 签名 | 返回 |
|------|------|------|
| step1_setup_environment | () -> DbPipelineResult | 编译结果 |
| step2_generate_inputs | (scenario: ScenarioDef?) -> DbPipelineResult | 数据生成结果 |
| step3_run_cobol | (scenario: ScenarioDef?) -> DbPipelineResult | 执行结果 |
| step4_extract_intermediate | () -> DbPipelineResult | 提取结果 |
| step5_run_java | (java_cmd, java_jar) -> DbPipelineResult | Java 执行结果 |
| step6_verify | () -> VerificationRun | 验证结果 |
| generate_coverage_report | (output_dir?) -> DbPipelineResult | 覆盖率报告 |
### 4.3 内部辅助接口
| 方法 | 职责 |
|------|------|
| _copy_sources_to_workdir | 源码 + COPYBOOK + 子程序复制到 ASCII 工作目录 |
| _scan_assign_to | 扫描 SELECT/ASSIGN-TO + OPEN 确定文件方向 |
| _init_database / _create_tables | 按 YAML schema 创建 SQLite 表结构 |
| _populate_database | 从测试记录生成 DB 种子行 |
| _inject_sql_error_rows | 注入 PK 冲突行触发 SQL 错误路径 |
| _inject_extra_seed_rows | 为 SELECT 型程序注入大结果集 |
| _inject_aggregation_boundaries | 注入聚合边界数据(溢出 + 表满) |
| _deduplicate_r01_pk | 确保 R01 记录 PK 唯一性 |
| _seed_matching_monthly_rows | 预填 MONTHLY_ABSENCE 匹配行 |
| _merge_multi_run_gcov | 多轮场景 gcov 数据合并 |
| _merge_schema_columns | YAML schema 列型合并到 declared_columns |
| _insert_pk_map | 构建 SQL 表 → PK 列名映射 |
| _coordinate_db_rule_matching | DB 属性区间对齐(AGE/DEPENDENTS/REGION |
| _coordinate_seed_numeric_types | DB 种子值数字化(PIC 9 对齐) |
| _make_synthetic_error_rows | 构建合成 PK 冲突行 |
---
## 5. 数据流
### 5.1 管道级数据流
```
cobol_src_dir/{program_id}.cbl
|
v
[Step 1: 环境整备]
|-- src_path (预处理源码)
|-- pp_path (gixpp 输出)
|-- exe_path (编译产物)
|
v
[Step 2: 输入数据生成]
|-- generated_records (测试数据)
|-- generated_structure (分支树 + 赋值表)
|-- db_path (SQLite DB with seeds)
|-- 平面文件 (input/)
|-- JSON (json/{program_id}.json)
|
v
[Step 3: COBOL 执行]
|-- 运行日志
|-- 输出文件 (output/)
|-- gcov 数据 (gcov/)
|
v
[Step 4: 中间数据提取]
|-- java_input_path (W01 JSON)
|
v
[Step 5: Java 执行]
|-- java_output_path
|
v
[Step 6: 结果验证]
|-- VerificationRun (PASS/MISMATCH)
```
### 5.2 每步输入输出明细
| 步骤 | 输入 | 输出 | 依赖 |
|------|------|------|------|
| Step 1 | cobol_src_dir, copybook_dirs, schema | src_path, pp_path, exe_path | 无 |
| Step 2 | src_path, pp_path, schema, scenario | records, structure, db_path, flat files | Step 1 |
| Step 3 | exe_path, records, db_path, scenario | logs, output files, gcov data | Step 1, 2 |
| Step 4 | db_path, schema.db_tables | java_input_path | Step 2, 3 |
| Step 5 | java_input_path, java_jar | java_output_path | Step 4 |
| Step 6 | java_output_path, db_path | VerificationRun | Step 4, 5 |
### 5.3 多场景数据流
```
schema.runs = [scenario_A, scenario_B, ...]
|
v
[Step 1] 编译一次(共享 exe_path
|
v
[Step 2-A] scenario_A → db_A, records_A, flat_A
[Step 3-A] 运行 A → gcov_A
|
v
[Step 2-B] scenario_B → db_B, records_B, flat_B
[Step 3-B] 运行 B → gcov_B
|
v
[_merge_multi_run_gcov] gcov_A + gcov_B → merged_gcov
|
v
[Step 4] 提取最后一个场景的 DB
[Step 5-6] Java 执行 + 验证
```
---
## 6. 错误处理
### 6.1 步骤级容错
每个 Step 方法内部用 try-except 包裹,返回 DbPipelineResult(success=False) 而非抛出异常:
```python
def step1_setup_environment(self) -> DbPipelineResult:
try:
# ... 编译逻辑 ...
return DbPipelineResult(self.program_id, 1, result.success, ...)
except Exception as e:
return DbPipelineResult(self.program_id, 1, False, str(e))
```
### 6.2 管道级中断
run_all() 中每个 Step 后检查 success,失败则立即返回 BLOCKED:
```python
r1 = self.step1_setup_environment()
if not r1.success:
return VerificationRun(status="BLOCKED", step_reached=1)
r2 = self.step2_generate_inputs(scenario)
if not r2.success:
return VerificationRun(status="BLOCKED", step_reached=2)
```
### 6.3 异常分类
| 异常场景 | 处理方式 | 影响 |
|----------|----------|------|
| gixpp 预处理失败 | Step 1 返回 success=False | 管道终止 |
| cobc 编译失败 | Step 1 返回 success=False | 管道终止 |
| COBOL 运行崩溃 | Step 3 返回 success=False | 管道终止 |
| DB 表不存在 | OperationalError 捕获,空列表 | 不阻断 |
| Java 超时 | subprocess.TimeoutExpired | Step 5 返回 False |
| gcov 文件缺失 | PermissionError 捕获,跳过 | 不阻断 |
| COPYBOOK 未找到 | logger.warning | 不阻断 |
| 子程序未找到 | logger.warning | 不阻断 |
| JSON 输出失败 | logger.warning | 不阻断,继续执行 |
### 6.4 数据一致性保障
- **PK 去重:** `_deduplicate_r01_pk` 确保所有 R01 记录的 (EMP_ID, DATE) 唯一
- **EMP-ID 清洗:** 全零 '00000000' → SPACE,避免 PK 冲突导致 ABEND
- **日期格式统一:** 所有日期值统一为 YYYYMMDD 8 位格式
- **DB 列型匹配:** INSERT 前按 PRAGMA table_info 转换值类型(INTEGER/DECIMAL
---
## 7. 性能设计
### 7.1 编译复用
Step 1 只执行一次,所有场景共享编译产物(exe_path)。
### 7.2 多场景顺序执行
Step 2-3 对每个场景顺序执行,避免 DB 并发写入冲突。每个场景有独立的:
- DB 文件: `{program_id}_{scenario_id}.db`
- 工作目录: `work_dir/run_{scenario_id}/`
- 运行目录: `runtime_dir/run_{scenario_id}/`
### 7.3 gcov 数据合并
多场景执行后调用 `_merge_multi_run_gcov()`,对每行取 max(count) 合并:
```python
merged[line] = max(merged.get(line, 0), cnt)
```
子程序 gcov 单独存储(`_sub_gcov_data`),避免行号冲突。
### 7.4 文件复制策略
- 构建产物放在 TEMP 目录(ASCII 路径),避免 gixpp 中文路径问题
- 运行时数据放在项目 runtime/ 目录
- DB 文件在多场景间通过 shutil.copy2 复制,而非共享
- .gcda/.gcno 使用 COPY 而非 MOVEGnuCOBOL 累积写入特性)
### 7.5 已执行步骤跳过
run_all() 支持 skip_steps 参数,允许跳过已执行的步骤:
```python
orch.run_all(skip_steps={1, 2, 3}) # 仅执行 Step 4-6
```
### 7.6 覆盖率报告可选
覆盖率报告生成由 generate_coverage 控制,默认开启但非管道核心路径:
```python
if '--coverage' in cv_flags and generate_coverage:
self.generate_coverage_report()
```
### 7.7 LLM 可选
Step 2 的 LLM 客户端仅在 config.llm_model 配置时初始化,未配置时回退到规则引擎:
```python
llm = None
if hasattr(self.config, 'llm_model') and self.config.llm_model:
llm = LLMClient(model=self.config.llm_model, timeout=self.config.llm_timeout)
recs = generate_all_data(..., llm_client=llm, ...)
```
---
## 8. 目录结构
```
runtime/{program_id}/
+-- main/
| +-- input/ <- 平面输入文件
| +-- output/ <- COBOL 输出文件
| +-- json/ <- JSON 输出
+-- logs/
| +-- compile/ <- 编译日志
| +-- {program_id}.log <- 运行日志
+-- gcov/
| +-- run_{scenario}/ <- 各场景 gcov 数据
+-- run_{scenario}/ <- 多场景隔离目录
+-- main/input/
+-- main/output/
work_dir/{program_id}/
+-- src/ <- ASCII 源码副本
+-- preprocessed/ <- gixpp 输出
+-- bin/ <- 编译产物 (.exe, .gcno)
+-- main/
| +-- input/ <- 生成的平面文件
| +-- json/ <- JSON 输出
+-- intermediate/ <- W01 JSONJava 中介数据)
+-- java_output/ <- Java 输出
+-- run_{scenario}/ <- 多场景隔离目录
```