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

18 KiB
Raw Blame History

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

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

@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()

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) 而非抛出异常:

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:

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) 合并:

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 参数,允许跳过已执行的步骤:

orch.run_all(skip_steps={1, 2, 3})  # 仅执行 Step 4-6

7.6 覆盖率报告可选

覆盖率报告生成由 generate_coverage 控制,默认开启但非管道核心路径:

if '--coverage' in cv_flags and generate_coverage:
    self.generate_coverage_report()

7.7 LLM 可选

Step 2 的 LLM 客户端仅在 config.llm_model 配置时初始化,未配置时回退到规则引擎:

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}/     <- 多场景隔离目录