826 lines
30 KiB
Markdown
826 lines
30 KiB
Markdown
# 数据流设计文档
|
||
|
||
> 版本: v1.0 | 日期: 2026-08-22
|
||
> 本文档描述 COBOL 迁移验证平台 V3 的完整数据流,覆盖非 DB(flat file)管道和 DB(SQL/SQLite)管道。
|
||
|
||
---
|
||
|
||
## 1. 数据流概述
|
||
|
||
### 1.1 设计目标
|
||
|
||
| 目标 | 说明 | 实现方式 |
|
||
|------|------|----------|
|
||
| **可追溯性** | 每条测试数据可追溯到源码决策点 | 路径约束 (field, op, value, want_true) 记录决策分支选择 |
|
||
| **一致性** | 非 DB 与 DB 管道共享同一套解析/生成引擎 | cobol_testgen/ 统一提供 extract_structure + generate_data |
|
||
| **容错性** | 单步失败不中断整体管道 | 每步返回 DbPipelineResult(success, data),失败立即终止后续 |
|
||
| **可观测性** | 全流程可监控、可调试 | 覆盖率报告、gcov 数据、JSON 中间产物、运行日志 |
|
||
|
||
### 1.2 管道对比
|
||
|
||
| 维度 | 非 DB 管道 | DB 管道 |
|
||
|------|-----------|---------|
|
||
| 编排器 | orchestrator.py | orchestrator_db.py |
|
||
| 运行器 | runners/cobol_runner.py | runners/gixsql_runner.py |
|
||
| 输入格式 | 固定长度平面文件 | 平面文件 + SQLite DB 种子 |
|
||
| 输出格式 | 平面文件 | 平面文件 + SQLite DB |
|
||
| 验证方式 | comparator/ 逐字段比对 | Step 6 验证 COBOL/Java 输出一致性 |
|
||
| 步骤数 | 4 步(生成-运行-比对-报告) | 6 步(环境-生成-运行-提取-Java-验证) |
|
||
---
|
||
|
||
## 2. 非 DB 管道数据流
|
||
|
||
### 2.1 总体流程
|
||
|
||
COBOL 源码 (.cbl)
|
||
|
|
||
v [输入阶段]
|
||
read.py: preprocess() + parse_data_division()
|
||
|
|
||
v [生成阶段]
|
||
core.py: build_branch_tree() -> branch_tree + assignments
|
||
|
|
||
v
|
||
design.py: enum_paths() -> path_infos
|
||
|
|
||
v
|
||
design.py: make_base_record() -> base_record
|
||
|
|
||
v
|
||
design.py: apply_constraint() -> records (测试数据)
|
||
|
|
||
v [运行阶段]
|
||
output.py: output_json() -> 测试数据 JSON
|
||
output.py: output_input_files() -> 固定长度平面文件
|
||
|
|
||
v
|
||
runners/cobol_runner.py: compile() + run() -> 输出文件
|
||
|
|
||
v [比对阶段]
|
||
comparator/aligner.py: 按主键对齐 COBOL/Java 记录
|
||
comparator/field_compare.py: 逐字段比较
|
||
comparator/normalizer.py: EBCDIC/COMP-3 标准化
|
||
|
|
||
v
|
||
report/generator.py: 生成 HTML 报告
|
||
|
||
### 2.2 输入阶段
|
||
|
||
**COBOL 源码 -> read.py 预处理 -> DATA DIVISION 解析 -> fields 结构信息**
|
||
|
||
COBOL 源码 (fixed/free format)
|
||
|
|
||
v read.preprocess()
|
||
+-- resolve_copybooks(): 展开 COPY 语句
|
||
+-- _is_fixed_format(): 自动检测 fixed/free 格式
|
||
+-- 展开至每行 <=72 字符 (fixed) 或保持原样 (free)
|
||
+-- 去除 EXEC CICS/SQL 块、逗号、ALL 关键字
|
||
|
|
||
v 预处理后源码 (preprocessed)
|
||
|
|
||
v read.extract_data_division()
|
||
+-- 提取 DATA DIVISION 文本块
|
||
|
|
||
v read.parse_data_division()
|
||
+-- Lark grammar.lark 解析 (Earley parser, dynamic lexer)
|
||
+-- 逐行解析 FieldDef: level, name, PIC, USAGE, VALUE, REDEFINES, OCCURS
|
||
+-- PIC 子句解析 -> PicInfo (type, digits, decimal, length, signed)
|
||
+-- 返回 list[FieldDef]
|
||
|
|
||
v expand_occurs()
|
||
+-- OCCURS 展开为下标副本: WS-CELL(1), WS-CELL(2), ...
|
||
|
||
**输出数据结构:**
|
||
|
||
# fields: list[dict] -- 每个字段一个 dict
|
||
{
|
||
'name': 'R01EMP-ID', # 字段名 (大写)
|
||
'level': 1, # 层号 (01, 05, 77, 88)
|
||
'pic': 'X(8)', # PIC 子句原始文本
|
||
'pic_info': { # PIC 解析结果
|
||
'type': 'alphanumeric', # numeric | alphanumeric | alphabetic
|
||
'digits': 0, # 整数位数 (numeric)
|
||
'decimal': 0, # 小数位数 (numeric)
|
||
'length': 8, # 总长度 (alphanumeric)
|
||
'signed': False,
|
||
},
|
||
'section': 'FILE', # DATA DIVISION 节
|
||
'occurs': 0, # OCCURS 次数
|
||
'redefines': None, # REDEFINES 目标
|
||
'usage': None, # COMP | COMP-3 | BINARY | DISPLAY
|
||
'is_88': False, # 是否 88 级条件
|
||
'parent': None, # 88 级父字段名
|
||
'value': None, # VALUE 子句
|
||
'values': None, # 88 级多值列表
|
||
'is_filler': False,
|
||
}
|
||
### 2.3 生成阶段
|
||
|
||
**fields -> core.py build_branch_tree -> design.py enum_paths -> make_base_record -> apply_constraint -> 测试数据**
|
||
|
||
#### 2.3.1 分支树构建
|
||
|
||
PROCEDURE DIVISION 文本
|
||
|
|
||
v core.build_branch_tree_fallback()
|
||
+-- pipeline_bridge: 3秒超时桥接
|
||
| +-- 优先: procedure_parser.py (新解析器, 行级状态机)
|
||
| +-- 回退: core._BrParser (旧解析器, 正则驱动)
|
||
|
|
||
+-- 段落扫描: scan_paragraphs() -> {name: (start, end)}
|
||
+-- 逐段解析 IF / EVALUATE / PERFORM / READ / WRITE / MOVE / COMPUTE
|
||
+-- 返回 (branch_tree: BrSeq, assignments: dict)
|
||
|
||
**分支树节点类型:**
|
||
|
||
| 节点 | 类 | 属性 | 说明 |
|
||
|------|----|------|------|
|
||
| 序列 | BrSeq | children: list | 顺序执行的语句序列 |
|
||
| 条件 | BrIf | condition, cond_tree, true_seq, false_seq | IF-ELSE 分支 |
|
||
| 评估 | BrEval | subject, subjects, when_list, other_seq | EVALUATE 多分支 |
|
||
| 循环 | BrPerform | perf_type, condition, body_seq | PERFORM UNTIL/VARYING |
|
||
| 查找 | BrSearch | table_name, is_all, when_list | SEARCH/SEARCH ALL |
|
||
| 赋值 | Assign | target, source_info | MOVE/COMPUTE/ADD/SUBTRACT/MULTIPLY/DIVIDE |
|
||
| 调用 | CallNode | program_name, using_params | CALL 子程序 |
|
||
| 跳转 | GoTo | target, body_seq | GO TO |
|
||
| 退出 | ExitNode | exit_type | EXIT PARAGRAPH/PERFORM/PROGRAM |
|
||
|
||
#### 2.3.2 路径枚举
|
||
|
||
branch_tree + assignments
|
||
|
|
||
v design_mcdc.enum_paths() / design.enum_paths()
|
||
+-- 遍历分支树,收集每个决策点 (BrIf/BrEval/BrPerform)
|
||
+-- 对每个决策点生成 True/False 或 WHEN 分支路径
|
||
+-- MC/DC 约束集: cond.mcdc_sets() -> 每个叶条件的 T/F 独立约束
|
||
+-- 路径约束合并: prior_false 累积(EVALUATE WHEN 入口条件)
|
||
+-- PERFORM VARYING: 最后一次迭代约束 (last-iteration)
|
||
+-- SEARCH: 表索引约束 + AT END 分支
|
||
+-- 返回 path_infos: list[(constraints, path_assignments, term_type)]
|
||
|
||
**路径约束格式:**
|
||
|
||
# path_infos 的每个元素
|
||
(
|
||
# constraints: Path = list[Constraint]
|
||
[
|
||
('WRK-MONTH', '>', '0', True), # (field, op, value, want_true)
|
||
('WRK-MONTH', '<', '13', True),
|
||
('R01EMP-ID', '<>', '00000000', True),
|
||
],
|
||
# path_assignments: dict -- 赋值表
|
||
{
|
||
'WRK-PREV-EMP-ID': [{'type': 'move', 'source_vars': ['R01EMP-ID']}],
|
||
'DBV-EMP-ID': [{'type': 'move_literal', 'literal': 'A0000001'}],
|
||
},
|
||
# term_type: str
|
||
'normal' # 或 'abend'
|
||
)
|
||
|
||
#### 2.3.3 测试数据记录生成
|
||
|
||
path_infos + data_fields
|
||
|
|
||
v design.generate_records()
|
||
|
|
||
+-- 遍历每条路径 (seq=1,2,3...):
|
||
| |
|
||
| +-- make_base_record(seq, data_fields)
|
||
| | +-- 按 VALUE 子句设置初始值
|
||
| | +-- 按 PIC 类型生成默认值:
|
||
| | | +-- numeric: _make_numeric_value(idx, seq, total_digits)
|
||
| | | +-- alphanumeric: _make_alpha_value(idx, seq, length)
|
||
| | | +-- date: seq_date(record_num) -> YYYYMMDD
|
||
| | +-- 返回 base_record: dict {field_name: value}
|
||
| |
|
||
| +-- Pass A: propagate_assignments(rec, path_assign, data_fields)
|
||
| | +-- 模拟赋值传播: MOVE/COMPUTE/READ INTO 等
|
||
| |
|
||
| +-- Pass B: apply_constraint(rec, field, op, val, want, ...)
|
||
| | +-- trace_to_root(): 沿 MOVE 链追溯到根字段
|
||
| | +-- invert_through_chain(): 反向求解约束值
|
||
| | +-- satisfying_value(): 满足约束的值计算
|
||
| | | +-- numeric: 边界值 +/-1
|
||
| | | +-- alphanumeric: 字典序边界
|
||
| | | +-- date: YYYYMMDD 格式边界
|
||
| | +-- 递归写入 base_record[field] = 满足值
|
||
| |
|
||
| +-- Pass B.5: forward propagate (变量间 MOVE 一致性)
|
||
| +-- Pass B.75: COMPUTE 重算 (约束修改源字段后)
|
||
| +-- get_term_type(path_cons) -> (filtered_cons, term_type)
|
||
|
|
||
+-- 返回 (records, kept_path_cons, term_types)
|
||
|
||
**测试数据记录格式:**
|
||
|
||
# records: list[dict] -- 每条记录一个 dict
|
||
[
|
||
{
|
||
'R01EMP-ID': 'A0000001',
|
||
'R01DATE': '20250101',
|
||
'R01LINE': '0001',
|
||
'WRK-PREV-EMP-ID': '',
|
||
'WRK-MONTH': '06',
|
||
'DBV-EMP-ID': 'A0000001',
|
||
'_assigned_fields': {'R01EMP-ID', 'R01DATE'}, # 内部标记
|
||
'_w02_path': True, # PREV 连锁标记
|
||
},
|
||
# ... 更多记录
|
||
]
|
||
### 2.4 运行阶段
|
||
|
||
**测试数据 -> output.py -> cobol_runner compile/run -> 输出文件**
|
||
|
||
records + fd_fields + open_dir
|
||
|
|
||
v output.output_json()
|
||
+-- 按 FD 分组: field_to_fd 映射字段到 FD
|
||
+-- 按 open_dir 确定方向: INPUT/OUTPUT/I-O
|
||
+-- 按 roles 分类: input/inout -> input 块, output/inout -> expected_output 块
|
||
+-- 不属于任何 FD 的字段 -> working_storage 块
|
||
+-- 输出 JSON: {program, records: [{input, expected_output, working_storage, termination}]}
|
||
|
|
||
v output.output_input_files()
|
||
+-- 仅处理 INPUT/I-O 方向的 FD
|
||
+-- 按 FD 分组,每 FD 输出一个 JSON: {stem}_{fd_name}.json
|
||
+-- abend 记录单独输出: {stem}_abend_{fd_name}.json
|
||
+-- 二进制模式: 按 field offsets 打包为固定长度二进制文件
|
||
|
|
||
v flatfile.write_all_files()
|
||
+-- analyze_fd_layout(): 解析 FD 记录布局 (字段名/PIC/offset/length)
|
||
+-- 按 FD 布局序列化每条记录为固定长度字节
|
||
+-- write_flat_file(): 输出到 outdir/{assign_name}
|
||
|
|
||
v runners/cobol_runner.compile()
|
||
+-- cobc -std=ibm -free -x src.cbl -o exe
|
||
|
|
||
v runners/cobol_runner.run()
|
||
+-- 设置 CWD,复制输入文件到 input/
|
||
+-- subprocess.run(exe) 执行
|
||
+-- 捕获 stdout/stderr,收集输出文件
|
||
+-- 返回 RunResult(success, records, log)
|
||
|
||
### 2.5 比对阶段
|
||
|
||
**输出文件 -> aligner.py -> field_compare.py -> normalizer.py -> report/generator.py -> HTML 报告**
|
||
|
||
COBOL 输出文件 + Java 输出文件
|
||
|
|
||
v comparator/aligner.py: align_records()
|
||
+-- 按主键字段 (如 CUST-ID) 分组
|
||
+-- 取两侧键的并集,按字符串排序
|
||
+-- 逐键配对: MATCHED / MISSING_IN_SPARK / EXTRA_IN_SPARK
|
||
+-- 返回 list[(cobol_record, java_record, status)]
|
||
|
|
||
v comparator/normalizer.py: normalize_encoding()
|
||
+-- EBCDIC -> ASCII 解码 (EBCDIC_037 映射表)
|
||
+-- COMP-3 压缩十进制解码 (nibble -> decimal)
|
||
+-- 日期格式标准化 (YYYYMMDD -> YYYY-MM-DD)
|
||
|
|
||
v comparator/field_compare.py: compare_field()
|
||
+-- 按字段类型选择比较策略:
|
||
| +-- numeric: Decimal 精度比较 + 容差 (tolerance=0.01)
|
||
| +-- date: YYYYMMDD 格式归一化后比较
|
||
| +-- string: strip 后直接比较
|
||
+-- 返回 FieldResult(field_name, status, cobol_value, java_value)
|
||
+-- status: PASS / MISMATCH / TOLERATED / NOT_SET
|
||
|
|
||
v comparator/rounding_detect.py: detect_rounding()
|
||
+-- 判断数值差异是否由 COBOL ROUNDED 子句引起
|
||
+-- 计算舍入误差范围
|
||
|
|
||
v report/generator.py: ReportGenerator
|
||
+-- generate_json(): 输出 JSON 报告 (VerificationRun 数据)
|
||
+-- generate_html(): 输出 HTML 报告
|
||
| +-- 覆盖率卡片: 段落覆盖率、分支覆盖率、决策点覆盖率
|
||
| +-- HINA 卡片: 判定类型、确信度
|
||
| +-- 质量评分卡片: quality_score
|
||
| +-- 重试历史卡片: heal_retry, simple_retry
|
||
| +-- 字段比对表格: PASS/MISMATCH 高亮
|
||
+-- 返回报告文件路径
|
||
---
|
||
|
||
## 3. DB 管道数据流
|
||
|
||
### 3.1 总体 6 步流程
|
||
|
||
COBOL 源码 (含 EXEC SQL)
|
||
|
|
||
v [Step 1: 环境整备]
|
||
step1_setup_environment()
|
||
+-- 复制源码到 ASCII 工作目录
|
||
+-- gixpp 预处理 + CONNECT TO 路径修补
|
||
+-- cobc 编译 -> exe_path
|
||
|
|
||
v [Step 2: 输入数据生成]
|
||
step2_generate_inputs(scenario)
|
||
+-- extract_structure() -> 分支树 + 赋值表
|
||
+-- generate_all_data() -> 测试数据记录
|
||
+-- _init_database() + _populate_database() -> SQLite DB 种子
|
||
+-- flatfile.write_all_files() -> 平面文件
|
||
|
|
||
v [Step 3: COBOL 执行]
|
||
step3_run_cobol(scenario)
|
||
+-- runner.run() -> 输出文件 + gcov 数据
|
||
|
|
||
v [Step 4: 中间数据提取]
|
||
step4_extract_intermediate()
|
||
+-- SELECT * FROM each table -> W01 JSON
|
||
|
|
||
v [Step 5: Java 执行] (可选)
|
||
step5_run_java()
|
||
+-- java -jar migration.jar -i W01.json -o output/
|
||
|
|
||
v [Step 6: 验证]
|
||
step6_verify()
|
||
+-- 比较 Java 输出与 COBOL 期望值
|
||
+-- 返回 VerificationRun (PASS/MISMATCH)
|
||
|
||
### 3.2 Step 1: 环境整备 (step1_setup_environment)
|
||
|
||
**职责:** gixpp 预处理 + cobc 编译
|
||
|
||
cobol_src_dir/{program_id}.cbl
|
||
|
|
||
v _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/)
|
||
|
|
||
v runner.preprocess(src, preprocessed/, copybook_dirs)
|
||
+-- gixpp 预处理 + CONNECT TO 路径修补
|
||
| gixpp 错误转换: 'data/kin.db' -> 'sqlite://localhost/kin'
|
||
| 修补为: 'sqlite:///{db_path}'
|
||
|
|
||
v runner.compile(pp, exe, copybook_dirs, extra_srcs)
|
||
+-- cobc 编译 -> work_dir/bin/{program_id}.exe
|
||
+-- 编译日志写入 runtime_dir/logs/compile/
|
||
|
|
||
v 返回 DbPipelineResult(step=1, success, data={exe_path, log})
|
||
|
||
**输入:** cobol_src_dir, copybook_dirs, schema.subprograms
|
||
**输出:** src_path, pp_path, exe_path
|
||
|
||
### 3.3 Step 2: 输入数据生成 (step2_generate_inputs)
|
||
|
||
**职责:** COBOL 解析 -> 测试数据生成 -> DB 初始化 -> 平面文件输出
|
||
|
||
src_text + schema + scenario
|
||
|
|
||
v COBOL 解析
|
||
+-- extract_structure(src_text) -> 分支树 + 赋值表
|
||
+-- generate_all_data() -> 测试数据记录 (白盒+机能+策略)
|
||
+-- 后处理: R02APPL-ID 链接 R01APPL-ID
|
||
|
|
||
v DB 初始化
|
||
+-- 确定 DB 路径(场景分离: {program_id}_{scenario_id}.db)
|
||
+-- 清理旧 DB -> _init_database(db_path)
|
||
| +-- _create_tables(): 按 YAML schema 创建表 + 主键
|
||
+-- _populate_database(): 注入种子行
|
||
| +-- 解析 COBOL -> 分支树 -> 路径枚举
|
||
| +-- build_db_input(): 生成 DB 输入行
|
||
| | +-- collect_sql_meta(): 提取 SQL 元数据 (SELECT/INSERT/UPDATE/DELETE)
|
||
| | +-- _hostvar_root(): WHERE 宿主变量 MOVE 链解析
|
||
| | +-- _resolve_where_hostvar(): 宿主变量追溯到输入记录根字段
|
||
| | +-- 用输入键建种子(与运行时一致)
|
||
| +-- 覆盖率驱动数据补充(日期、假期等)
|
||
| +-- 区间协调(INSURANCE-RATES / EMP-MASTER)
|
||
| +-- INSERT OR IGNORE 写入 DB
|
||
+-- _inject_extra_seed_rows(): 大结果集注入
|
||
+-- _inject_sql_error_rows(): PK 冲突行注入
|
||
|
|
||
v 记录修补
|
||
+-- records[0].R01EMP-ID = SPACE(触发空社员路径)
|
||
+-- 全零 EMP-ID -> SPACE 清洗
|
||
+-- 注入重复 EMP-ID(AGG UPDATE 路径)
|
||
+-- _deduplicate_r01_pk(): PK 去重
|
||
+-- _inject_aggregation_boundaries(): 聚合边界数据
|
||
|
|
||
v 平面文件输出
|
||
+-- write_all_files(): 全 FD 平面文件
|
||
+-- write_sysin_file(): SYSIN 配置
|
||
|
|
||
v 返回 DbPipelineResult(step=2, data={records, flat_files, db_path})
|
||
|
||
**输入:** src_path, pp_path, schema, scenario
|
||
**输出:** generated_records, generated_structure, db_path, 平面文件
|
||
|
||
### 3.4 Step 3: COBOL 执行 (step3_run_cobol)
|
||
|
||
**职责:** 调用编译后的 COBOL 程序并收集 gcov 覆盖率数据
|
||
|
||
exe_path + schema + scenario
|
||
|
|
||
v 环境准备
|
||
+-- 创建 runtime/run_{id}/main/{input,output}/ 目录
|
||
+-- 复制生成的平面文件 -> input/
|
||
+-- 复制 JSON -> json/
|
||
|
|
||
v 文件方向映射 (_scan_assign_to)
|
||
+-- 正则扫描 SELECT/ASSIGN-TO -> {文件名: 方向}
|
||
+-- OPEN 语句解析 -> INPUT/OUTPUT 方向确定
|
||
|
|
||
v DB 路径准备
|
||
+-- 场景 DB -> 复制到默认 DB 路径
|
||
+-- CWD/data/kin.db(CONNECT TO 路径)
|
||
|
|
||
v 执行
|
||
+-- 清理前次 .gcda 文件
|
||
+-- runner.run(exe, cwd, db_path, env_overrides, command_args)
|
||
+-- 日志写入 runtime_dir/logs/
|
||
|
|
||
v gcov 数据收集
|
||
+-- .gcda 从 CWD + exe_dir 复制到 gcov/run_{id}/
|
||
+-- .gcno 同步(共享 .gcno,COPY 不 MOVE)
|
||
|
|
||
v 返回 DbPipelineResult(step=3, data={returncode, log, ...})
|
||
|
||
### 3.5 Step 4: 中间数据提取 (step4_extract_intermediate)
|
||
|
||
**职责:** 从 SQLite DB 导出 Java 程序所需的 JSON 中介数据
|
||
|
||
_current_db_path + schema.db_tables
|
||
|
|
||
v
|
||
+-- 打开 DB
|
||
+-- 遍历 schema.db_tables,对每张表执行 SELECT * FROM [table]
|
||
+-- 构建 meta = {program_id, tables: {table_name: [rows]}}
|
||
+-- 写入 work_dir/intermediate/{program_id}_W01.json
|
||
+-- 返回 DbPipelineResult(step=4, data={tables, w01_path})
|
||
|
||
### 3.6 Step 5: Java 执行 (step5_run_java)
|
||
|
||
**职责:** 调用 Java 转换程序处理 COBOL 输出数据
|
||
|
||
java_input_path + java_jar
|
||
|
|
||
v
|
||
+-- 创建 java_output 目录
|
||
+-- 构建命令: java -jar {java_jar} -i {java_input_path} -o {java_out}
|
||
+-- subprocess.run(cmd, capture_output=True, timeout=60)
|
||
+-- 返回 DbPipelineResult(step=5, data={returncode, log})
|
||
|
||
### 3.7 Step 6: 结果验证 (step6_verify)
|
||
|
||
**职责:** 比较 Java 输出与 COBOL 期望值
|
||
|
||
java_output_path + _current_db_path
|
||
|
|
||
v
|
||
+-- 构建 VerificationRun 结果对象
|
||
+-- 读取 DB 各表行数(调试信息)
|
||
+-- 扫描 java_output_path 下的 .txt/.json 文件
|
||
+-- 设置 exit_code 和 status(PASS/MISMATCH)
|
||
+-- 返回 VerificationRun
|
||
---
|
||
|
||
## 4. 核心数据结构
|
||
|
||
### 4.1 fields 列表格式
|
||
|
||
fields 是整个数据流的核心数据结构,贯穿输入-生成-运行全流程。
|
||
|
||
# 类型: list[dict]
|
||
# 来源: read.parse_data_division() + expand_occurs()
|
||
# 用途: 分支树构建、路径枚举、约束应用、JSON 输出
|
||
|
||
[
|
||
{
|
||
'name': str, # 字段名 (大写, 如 'R01EMP-ID')
|
||
'level': int, # 层号 (01, 05, 77, 88)
|
||
'pic': str | None, # PIC 子句原始文本
|
||
'pic_info': { # PIC 解析结果
|
||
'type': str, # 'numeric' | 'alphanumeric' | 'alphabetic' | 'unknown'
|
||
'digits': int, # 整数位数 (numeric)
|
||
'decimal': int, # 小数位数 (numeric)
|
||
'length': int, # 总长度 (alphanumeric)
|
||
'signed': bool, # 是否有符号
|
||
},
|
||
'section': str, # 'FILE' | 'WORKING-STORAGE' | 'LINKAGE'
|
||
'occurs': int, # OCCURS 次数 (0=无)
|
||
'occurs_depending': str | None, # OCCURS DEPENDING ON 目标
|
||
'redefines': str | None, # REDEFINES 目标字段名
|
||
'usage': str | None, # 'COMP' | 'COMP-3' | 'BINARY' | 'PACKED-DECIMAL' | 'DISPLAY'
|
||
'is_88': bool, # 是否 88 级条件
|
||
'parent': str | None, # 88 级父字段名
|
||
'value': str | None, # VALUE 子句值
|
||
'values': list[str] | None, # 88 级多值列表
|
||
'is_filler': bool, # 是否 FILLER
|
||
},
|
||
# ... 更多字段
|
||
]
|
||
|
||
### 4.2 Constraint 约束格式
|
||
|
||
Constraint 是路径枚举和约束应用的基本单元。
|
||
|
||
# 类型: tuple (4 元组)
|
||
# 定义: models.py 中 Constraint = tuple
|
||
# 格式: (field, operator, value, want_true)
|
||
|
||
Constraint = (
|
||
str, # field: 字段名 (如 'WRK-MONTH', 'R01EMP-ID')
|
||
str, # operator: 比较运算符 ('=' | '<>' | '>' | '<' | '>=' | '<=' | 'not_in')
|
||
str, # value: 比较值 (字符串形式, 如 '12', '00000000', 'SPACE')
|
||
bool, # want_true: True=满足条件, False=不满足条件
|
||
)
|
||
|
||
# 示例:
|
||
('WRK-MONTH', '>', '0', True) # WRK-MONTH > 0 为真
|
||
('R01EMP-ID', '<>', '00000000', True) # R01EMP-ID 不等于 00000000 为真
|
||
('SQLCODE', '=', '100', False) # SQLCODE = 100 为假 (即 SQLCODE != 100)
|
||
|
||
### 4.3 Path 路径格式
|
||
|
||
Path 是一条完整执行路径的所有约束集合。
|
||
|
||
# 类型: list[Constraint]
|
||
# 定义: models.py 中 Path = list[Constraint]
|
||
# 含义: 所有约束同时满足时,程序沿该路径执行
|
||
|
||
Path = [
|
||
('WRK-MONTH', '>', '0', True),
|
||
('WRK-MONTH', '<', '13', True),
|
||
('R01EMP-ID', '<>', '00000000', True),
|
||
('__DP', '=', 'T', True), # 决策点标记 (设计内部使用)
|
||
]
|
||
|
||
# path_infos 格式 (生成记录的输入):
|
||
path_infos = [
|
||
# (constraints, path_assignments, term_type)
|
||
(
|
||
[Constraint, ...], # 路径约束列表
|
||
{str: list[dict]}, # 赋值表 (目标 -> 赋值操作列表)
|
||
'normal' | 'abend', # 终止类型
|
||
),
|
||
# ... 更多路径
|
||
]
|
||
|
||
# 赋值表 (path_assignments) 格式:
|
||
{
|
||
'WRK-PREV-EMP-ID': [
|
||
{'type': 'move', 'source_vars': ['R01EMP-ID']}
|
||
],
|
||
'DBV-EMP-ID': [
|
||
{'type': 'move_literal', 'literal': 'A0000001'}
|
||
],
|
||
'WS-COUNT': [
|
||
{'type': 'compute', 'op': 'add', 'left': 'WS-COUNT', 'right': '1'}
|
||
],
|
||
}
|
||
|
||
### 4.4 测试数据 JSON 格式
|
||
|
||
测试数据 JSON 是非 DB 管道和 DB 管道共享的输出格式。
|
||
|
||
{
|
||
'program': str, # 程序名 (如 'KIN04CHK')
|
||
'records': [
|
||
{
|
||
'input': { # 按 FD 分组的输入字段
|
||
'FD_NAME': {
|
||
'FIELD1': str,
|
||
'FIELD2': str,
|
||
...
|
||
}
|
||
},
|
||
'expected_output': { # 按 FD 分组的期望输出字段
|
||
'FD_NAME': {
|
||
'FIELD1': str,
|
||
...
|
||
}
|
||
},
|
||
'working_storage': { # 不属于任何 FD 的工作存储字段
|
||
'WRK-MONTH': str,
|
||
'WS-COUNT': str,
|
||
...
|
||
},
|
||
'termination': str, # 'normal' | 'abend'
|
||
},
|
||
# ... 更多记录
|
||
],
|
||
'db_input': { # DB 管道专用: DB 种子数据 (可选)
|
||
'table_name': [
|
||
{'col1': val1, 'col2': val2, ...},
|
||
...
|
||
]
|
||
}
|
||
}
|
||
---
|
||
|
||
## 5. 数据流图
|
||
|
||
### 5.1 非 DB 管道完整流程 (Mermaid)
|
||
|
||
`mermaid
|
||
flowchart TD
|
||
A[COBOL 源码 .cbl] --> B[read.py preprocess]
|
||
B --> C[read.py parse_data_division]
|
||
C --> D[expand_occurs]
|
||
D --> E[fields: list dict]
|
||
|
||
E --> F[core.py build_branch_tree]
|
||
F --> G[branch_tree + assignments]
|
||
|
||
G --> H[design.py enum_paths]
|
||
H --> I[path_infos]
|
||
|
||
I --> J[design.py generate_records]
|
||
E --> J
|
||
J --> K[records: list dict]
|
||
|
||
K --> L[output.py output_json]
|
||
L --> M[测试数据 JSON]
|
||
|
||
K --> N[output.py output_input_files]
|
||
K --> O[flatfile.write_all_files]
|
||
N --> P[固定长度平面文件]
|
||
O --> P
|
||
|
||
P --> Q[cobol_runner compile]
|
||
Q --> R[cobol_runner run]
|
||
R --> S[COBOL 输出文件]
|
||
|
||
S --> T[comparator/aligner align_records]
|
||
U[Java 输出文件] --> T
|
||
T --> V[记录对 pairs]
|
||
|
||
V --> W[comparator/normalizer]
|
||
W --> X[comparator/field_compare]
|
||
X --> Y[FieldResult list]
|
||
|
||
Y --> Z[report/generator generate_html]
|
||
Z --> AA[HTML 验证报告]
|
||
`
|
||
|
||
### 5.2 DB 管道完整流程 (Mermaid)
|
||
|
||
`mermaid
|
||
flowchart TD
|
||
A[COBOL 源码 含 EXEC SQL] --> B[Step 1: 环境整备]
|
||
B --> B1[gixpp 预处理]
|
||
B1 --> B2[cobc 编译]
|
||
B2 --> B3[exe_path]
|
||
|
||
A --> C[Step 2: 输入数据生成]
|
||
B3 --> C
|
||
C --> C1[extract_structure]
|
||
C1 --> C2[generate_all_data]
|
||
C2 --> C3[records]
|
||
|
||
C3 --> C4[flatfile.write_all_files]
|
||
C4 --> C5[平面文件 input/]
|
||
|
||
C3 --> C6[build_db_input]
|
||
C6 --> C7[INSERT INTO SQLite DB]
|
||
C7 --> C8[db_path]
|
||
|
||
C5 --> D[Step 3: COBOL 执行]
|
||
C8 --> D
|
||
D --> D1[runner.run]
|
||
D1 --> D2[COBOL 输出文件]
|
||
D1 --> D3[gcov 数据]
|
||
|
||
D2 --> E[Step 4: 中间数据提取]
|
||
C8 --> E
|
||
E --> E1[SELECT * FROM tables]
|
||
E1 --> E2[W01 JSON]
|
||
|
||
E2 --> F[Step 5: Java 执行 可选]
|
||
F --> F1[java -jar migration.jar]
|
||
F1 --> F2[Java 输出文件]
|
||
|
||
F2 --> G[Step 6: 验证]
|
||
D2 --> G
|
||
G --> G1[VerificationRun]
|
||
G1 --> G2[PASS / MISMATCH]
|
||
`
|
||
|
||
### 5.3 数据流关键节点汇总
|
||
|
||
`mermaid
|
||
flowchart LR
|
||
subgraph 输入层
|
||
A1[COBOL 源码]
|
||
A2[COPYBOOK]
|
||
A3[YAML schema]
|
||
end
|
||
|
||
subgraph 解析层
|
||
B1[preprocess]
|
||
B2[parse_data_division]
|
||
B3[build_branch_tree]
|
||
end
|
||
|
||
subgraph 生成层
|
||
C1[enum_paths]
|
||
C2[generate_records]
|
||
C3[build_db_input]
|
||
end
|
||
|
||
subgraph 输出层
|
||
D1[output_json]
|
||
D2[write_all_files]
|
||
D3[write_sysin_file]
|
||
end
|
||
|
||
subgraph 执行层
|
||
E1[cobol_runner]
|
||
E2[gixsql_runner]
|
||
E3[java -jar]
|
||
end
|
||
|
||
subgraph 验证层
|
||
F1[aligner]
|
||
F2[field_compare]
|
||
F3[coverage]
|
||
F4[report]
|
||
end
|
||
|
||
A1 --> B1 --> B2 --> B3
|
||
A2 --> B1
|
||
A3 --> C3
|
||
B3 --> C1 --> C2
|
||
C2 --> D1
|
||
C2 --> D2
|
||
C3 --> D2
|
||
C3 --> D3
|
||
D2 --> E1
|
||
D2 --> E2
|
||
D3 --> E2
|
||
E1 --> F1
|
||
E2 --> F1
|
||
E3 --> F1
|
||
F1 --> F2 --> F4
|
||
B3 --> F3 --> F4
|
||
`
|
||
|
||
---
|
||
|
||
## 6. 跨模块数据流转
|
||
|
||
### 6.1 模块间数据传递关系
|
||
|
||
| 源模块 | 目标模块 | 传递数据 | 数据格式 |
|
||
|--------|----------|----------|----------|
|
||
| read.py | core.py | preprocessed, data_fields | str, list[dict] |
|
||
| read.py | design.py | data_fields, open_dir, file_sec | list[dict], dict, dict |
|
||
| core.py | design.py | branch_tree, assignments | BrSeq, dict |
|
||
| cond.py | design.py | path constraints | list[Constraint] |
|
||
| design.py | output.py | records, kept_path_cons, term_types | list[dict], list, list[str] |
|
||
| design.py | to_sql.py | records, data_fields, assignments | list[dict], list[dict], dict |
|
||
| output.py | runner.py | records (JSON/binary) | dict, bytes |
|
||
| runner.py | comparator/ | output files | file paths |
|
||
| to_sql.py | orchestrator_db.py | db_input (DB seed rows) | dict[str, list[dict]] |
|
||
| flatfile.py | orchestrator_db.py | flat files (binary) | file paths |
|
||
| orchestrator_db.py | gixsql_runner.py | exe_path, db_path, env | Path, Path, dict |
|
||
| gixsql_runner.py | orchestrator_db.py | RunResult | dataclass |
|
||
| comparator/aligner.py | comparator/field_compare.py | aligned pairs | list[tuple] |
|
||
| comparator/field_compare.py | report/generator.py | FieldResults | list[FieldResult] |
|
||
| coverage.py | report/generator.py | DecisionPoints, coverage rates | list[DecisionPoint], float |
|
||
| data_merger.py | __init__.py | merged records | list[dict] |
|
||
| hina/strategy.py | data_merger.py | strategy records | list[dict] |
|
||
|
||
### 6.2 核心数据流路径
|
||
|
||
**路径 1: 非 DB 管道 (flat file)**
|
||
|
||
read.py (fields) -> core.py (branch_tree) -> design.py (records)
|
||
-> output.py (JSON + flat files) -> cobol_runner (output files)
|
||
-> comparator/ (verification results) -> report/ (HTML)
|
||
|
||
**路径 2: DB 管道 (SQL/SQLite)**
|
||
|
||
read.py (fields) -> core.py (branch_tree) -> design.py (records)
|
||
-> to_sql.py (DB seed rows) -> flatfile.py (flat files)
|
||
-> gixsql_runner (output files + DB state)
|
||
-> step4 (W01 JSON) -> step5 (Java output) -> step6 (verification)
|
||
|
||
**路径 3: 覆盖率收集**
|
||
|
||
core.py (branch_tree) -> coverage.py (decision_points)
|
||
-> mark_coverage (covered branches) -> gcov.py (dynamic coverage)
|
||
-> coverage.py (merged coverage) -> report/ (HTML report)
|
||
|
||
### 6.3 关键中间产物
|
||
|
||
| 产物 | 生成位置 | 消费位置 | 格式 |
|
||
|------|----------|----------|------|
|
||
| preprocessed | read.py | core.py, read.py | str (预处理后源码) |
|
||
| data_fields | read.py | core.py, design.py, to_sql.py | list[dict] |
|
||
| branch_tree | core.py | design.py, coverage.py | BrSeq |
|
||
| assignments | core.py | design.py, to_sql.py | dict |
|
||
| path_infos | design.py | design.py (generate_records) | list[tuple] |
|
||
| records | design.py | output.py, to_sql.py, flatfile.py | list[dict] |
|
||
| fd_fields | read.py | output.py | dict[str, set[str]] |
|
||
| open_dir | read.py | output.py, flatfile.py | dict[str, str] |
|
||
| file_sec | read.py | output.py, __init__.py | dict[str, list[str]] |
|
||
| db_input | to_sql.py | orchestrator_db.py | dict[str, list[dict]] |
|
||
| flat files | flatfile.py | runner.py | file paths (binary) |
|
||
| JSON | output.py | runner.py, coverage.py | file path |
|
||
| gcov data | gcov.py | coverage.py | dict[int, int] |
|
||
| DecisionPoints | coverage.py | report/generator.py | list[DecisionPoint] |
|
||
| VerificationRun | comparator/ | report/generator.py | dataclass |
|
||
| FieldResult | field_compare.py | report/generator.py | dataclass |
|