# 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 中介 JSON(Step 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-ID(AGG 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.db(CONNECT TO 路径) |-- CWD/kin(gixsql 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 同步(共享 .gcno,COPY 不 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_path(W01 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 和 status(PASS/MISMATCH) 5. 返回 VerificationRun ``` **关键逻辑:** - fields_mismatched == 0 时判定为 PASS - 输出 Java 输出文件列表作为调试信息 - 返回 VerificationRun 而非 DbPipelineResult **输入:** java_output_path, _current_db_path, schema.db_tables **输出:** VerificationRun(status, 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 而非 MOVE(GnuCOBOL 累积写入特性) ### 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 JSON(Java 中介数据) +-- java_output/ <- Java 输出 +-- run_{scenario}/ <- 多场景隔离目录 ```