Files
cobol-java-v3/tests/cobol_testgen/test_to_sql_select_hostvar.py
T

280 lines
10 KiB
Python
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.
"""SELECT 的 WHERE 宿主变量经 MOVE 链解析到输入记录键的测试。
根因:`MOVE R02EMP-ID TO DBV-EMP-ID` 在程序里先执行,运行时 SQL 查询键是
输入记录键(R02EMP-ID)。但 build_db_input 直接读记录中的 DBV-EMP-ID
(生成器的独立合成值,如 J 前缀),与输入键不一致,导致 DB 预置行查不到、
正常路径(SQLCODE=0)永远无法覆盖。修复:WHERE 宿主变量若有 MOVE 链,
应优先使用输入记录键。
"""
import sys, os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
from cobol_testgen.to_sql import build_db_input
def _sql_meta_select(table="EMP-MASTER"):
return [{
"type": "exec_sql_select",
"table": table,
"select_list": "EMP_NAME",
"into_vars": ["DBV-EMPLOYEE-NAME"],
"where": "EMP_ID = :DBV-EMP-ID",
"where_constraints": [
{"col": "EMP-ID", "type": "host_var", "host_var": "DBV-EMP-ID", "op": "="}
],
}]
def _declared_columns():
return {
"EMP-MASTER": [
{"name": "EMP-ID", "db_type": "CHAR", "size": 8},
{"name": "EMP_NAME", "db_type": "CHAR", "size": 40},
],
}
def test_select_where_hostvar_moved_from_input_uses_input_key():
"""WHERE 宿主变量 DBV-EMP-ID 由 MOVE R02EMP-ID 赋值时,
DB 预置行主键必须用输入记录键(R02EMP-ID),而非 DBV-EMP-ID 的合成值。"""
branch_paths = [
([('SQLCODE', '=', '0', True)], {}), # SQL 成功路径
]
records = [{'R02EMP-ID': 'A0000001', 'DBV-EMP-ID': 'J0000001'}]
assignments = {
'DBV-EMP-ID': [{'type': 'move', 'source_vars': ['R02EMP-ID']}],
}
db_input = build_db_input(
branch_paths, [], assignments, _sql_meta_select(),
_declared_columns(),
records=records,
)
rows = db_input.get('EMP-MASTER', [])
assert len(rows) == 1
assert rows[0]['EMP-ID'].strip() == 'A0000001'
def test_select_where_hostvar_no_move_keeps_record_value():
"""WHERE 宿主变量没有 MOVE 链时,维持原行为(用记录中宿主变量的值)。"""
branch_paths = [
([('SQLCODE', '=', '0', True)], {}),
]
records = [{'R02EMP-ID': 'A0000001', 'DBV-EMP-ID': 'J0000001'}]
db_input = build_db_input(
branch_paths, [], {}, _sql_meta_select(),
_declared_columns(),
records=records,
)
rows = db_input.get('EMP-MASTER', [])
assert len(rows) == 1
assert rows[0]['EMP-ID'].strip() == 'J0000001'
def test_select_where_hostvar_chain_through_two_moves():
"""MOVE 链可跨多跳解析(A <- B <- C),最终用输入记录键。"""
branch_paths = [
([('SQLCODE', '=', '0', True)], {}),
]
records = [{'R02EMP-ID': 'A0000001', 'WK-EMP-ID': 'M0000001', 'DBV-EMP-ID': 'J0000001'}]
assignments = {
'WK-EMP-ID': [{'type': 'move', 'source_vars': ['R02EMP-ID']}],
'DBV-EMP-ID': [{'type': 'move', 'source_vars': ['WK-EMP-ID']}],
}
db_input = build_db_input(
branch_paths, [], assignments, _sql_meta_select(),
_declared_columns(),
records=records,
)
rows = db_input.get('EMP-MASTER', [])
assert len(rows) == 1
assert rows[0]['EMP-ID'].strip() == 'A0000001'
def test_select_where_hostvar_skip_sql_error_path():
"""SQL 失败路径(SQLCODE<>0)仍不生成预置行。"""
branch_paths = [
([('SQLCODE', '<>', '0', True)], {}),
]
records = [{'R02EMP-ID': 'A0000001', 'DBV-EMP-ID': 'J0000001'}]
assignments = {
'DBV-EMP-ID': [{'type': 'move', 'source_vars': ['R02EMP-ID']}],
}
db_input = build_db_input(
branch_paths, [], assignments, _sql_meta_select(),
_declared_columns(),
records=records,
)
rows = db_input.get('EMP-MASTER', [])
assert not rows
def test_select_uses_declared_columns_with_underscore_key():
"""declared_columns 以下划线键(EMP_MASTER)声明时,SELECT 行生成
也要命中,使用声明的 CHAR(8) 列宽,避免回退到 10 字符推断导致
存储值带尾随空格、与运行时 8 字符键失配。"""
branch_paths = [
([('SQLCODE', '=', '0', True)], {}),
]
records = [{'R02EMP-ID': 'A0000001', 'DBV-EMP-ID': 'A0000001'}]
assignments = {
'DBV-EMP-ID': [{'type': 'move', 'source_vars': ['R02EMP-ID']}],
}
declared = {
"EMP_MASTER": [
{"name": "EMP_ID", "db_type": "CHAR", "size": 8},
{"name": "EMP_NAME", "db_type": "CHAR", "size": 40},
],
}
db_input = build_db_input(
branch_paths, [], assignments, _sql_meta_select(),
declared,
records=records,
)
rows = db_input.get('EMP-MASTER', [])
assert len(rows) == 1
assert rows[0]['EMP_ID'] == 'A0000001'
assert len(rows[0]['EMP_ID']) == 8
def test_update_where_hostvar_moved_from_input_uses_input_key():
"""UPDATE/DELETE 的 WHERE 宿主变量同样经 MOVE 链解析到输入记录键。"""
sql_meta = [{
"type": "exec_sql_update",
"table": "EMP-MASTER",
"columns": ["STATUS"],
"host_vars": ["DBV-STATUS"],
"where": "EMP_ID = :DBV-EMP-ID",
"where_constraints": [
{"col": "EMP-ID", "type": "host_var", "host_var": "DBV-EMP-ID", "op": "="}
],
}]
branch_paths = [
([('SQLCODE', '=', '0', True)], {}),
]
records = [{'R02EMP-ID': 'A0000001', 'DBV-EMP-ID': 'J0000001', 'DBV-STATUS': 'M'}]
assignments = {
'DBV-EMP-ID': [{'type': 'move', 'source_vars': ['R02EMP-ID']}],
}
db_input = build_db_input(
branch_paths, [], assignments, sql_meta,
_declared_columns(),
records=records,
)
rows = db_input.get('EMP-MASTER', [])
assert len(rows) == 1
assert rows[0]['EMP-ID'].strip() == 'A0000001'
def _sql_meta_two_selects():
"""两个 SELECTEMP-MASTER 在前(运行时先查),OVT-MONTHLY 在后。"""
return [
{
"type": "exec_sql_select", "table": "EMP-MASTER",
"select_list": "EMP_NAME", "into_vars": ["DBV-EMPLOYEE-NAME"],
"where": "EMP_ID = :DBV-EMP-ID",
"where_constraints": [
{"col": "EMP-ID", "type": "host_var", "host_var": "DBV-EMP-ID", "op": "="}
],
"pos": 100,
},
{
"type": "exec_sql_select", "table": "OVT-MONTHLY",
"select_list": "SUM(OVT_HOURS)", "into_vars": ["DBV-OVT-HOURS"],
"where": "EMP_ID = :DBV-EMP-ID AND YEAR_MONTH = :WRK-YEAR-MONTH",
"where_constraints": [
{"col": "EMP-ID", "type": "host_var", "host_var": "DBV-EMP-ID", "op": "="},
{"col": "YEAR-MONTH", "type": "host_var", "host_var": "WRK-YEAR-MONTH", "op": "="},
],
"pos": 200,
},
]
def _declared_two_tables():
return {
"EMP-MASTER": [
{"name": "EMP-ID", "db_type": "CHAR", "size": 8},
{"name": "EMP_NAME", "db_type": "CHAR", "size": 40},
],
"OVT-MONTHLY": [
{"name": "EMP-ID", "db_type": "CHAR", "size": 8},
{"name": "YEAR-MONTH", "db_type": "CHAR", "size": 6},
],
}
def test_multiple_selects_drop_last_table_for_last_ok_path():
"""两个 SELECT 时,为最后一条 SQL 成功路径丢弃最后一张表(OVT)的行,
使该记录到达下游查询时无匹配行 → 覆盖下游 SELECT 的 no-data 分支
IF SQLCODE = 0 的 ELSE 分支)。"""
branch_paths = [
([('SQLCODE', '=', '0', True)], {}), # path0 sql ok
([('SQLCODE', '=', '0', True)], {}), # path1 sql ok (last)
]
records = [
{'R02EMP-ID': 'A0000001', 'DBV-EMP-ID': 'A0000001',
'WRK-YEAR-MONTH': 'B00001', 'DBV-OVT-HOURS': '00101'},
{'R02EMP-ID': 'A0000002', 'DBV-EMP-ID': 'A0000002',
'WRK-YEAR-MONTH': 'B00002', 'DBV-OVT-HOURS': '00102'},
]
assignments = {
'DBV-EMP-ID': [{'type': 'move', 'source_vars': ['R02EMP-ID']}],
}
db_input = build_db_input(
branch_paths, [], assignments, _sql_meta_two_selects(),
_declared_two_tables(),
records=records,
)
emp = db_input.get('EMP-MASTER', [])
ovt = db_input.get('OVT-MONTHLY', [])
assert len(emp) == 2, emp # 两条路径都种 EMP(前置查询必须命中)
assert len(ovt) == 1, ovt # 最后一条路径的 OVT 行被丢弃
assert emp[0]['EMP-ID'].strip() == 'A0000001'
assert ovt[0]['EMP-ID'].strip() == 'A0000001'
def test_single_select_keeps_all_rows():
"""只有一个 SELECT 时不触发丢弃(保持原行为)。"""
branch_paths = [
([('SQLCODE', '=', '0', True)], {}),
([('SQLCODE', '=', '0', True)], {}),
]
records = [
{'R02EMP-ID': 'A0000001', 'DBV-EMP-ID': 'A0000001'},
{'R02EMP-ID': 'A0000002', 'DBV-EMP-ID': 'A0000002'},
]
assignments = {
'DBV-EMP-ID': [{'type': 'move', 'source_vars': ['R02EMP-ID']}],
}
db_input = build_db_input(
branch_paths, [], assignments, _sql_meta_select(),
_declared_columns(),
records=records,
)
rows = db_input.get('EMP-MASTER', [])
assert len(rows) == 2, rows
def test_drop_requires_two_sql_ok_paths():
"""只有一条 SQL 成功路径时不丢弃最后一张表(避免该表行被清空)。"""
branch_paths = [
([('SQLCODE', '=', '0', True)], {}), # 唯一的 sql ok 路径
([('SQLCODE', '<>', '0', True)], {}), # 失败路径
]
records = [
{'R02EMP-ID': 'A0000001', 'DBV-EMP-ID': 'A0000001',
'WRK-YEAR-MONTH': 'B00001', 'DBV-OVT-HOURS': '00101'},
{'R02EMP-ID': 'A0000002', 'DBV-EMP-ID': 'A0000002',
'WRK-YEAR-MONTH': 'B00002', 'DBV-OVT-HOURS': '00102'},
]
assignments = {
'DBV-EMP-ID': [{'type': 'move', 'source_vars': ['R02EMP-ID']}],
}
db_input = build_db_input(
branch_paths, [], assignments, _sql_meta_two_selects(),
_declared_two_tables(),
records=records,
)
assert len(db_input.get('EMP-MASTER', [])) == 1
assert len(db_input.get('OVT-MONTHLY', [])) == 1, db_input.get('OVT-MONTHLY')