"""SELECT BETWEEN WHERE 约束的 DB 预置行生成测试(Phase 2)。 根因:KYU05DED 的 `WHERE :WRK-TAXABLE-INCOME BETWEEN TAX-FROM AND TAX-TO` 在修复解析后成为 between 约束,但 build_db_input 不处理 between 类型, TAX_FROM/TAX_TO 仍落入默认序号值 → 运行时 SELECT 查不到行 → SQLCODE<>0。 修复:build_db_input 对 between 约束,用记录中的 subject 值播种 lo/hi 列, 使运行时 `:hv BETWEEN TAX-FROM AND TAX-TO` 能命中。 """ 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_between(): return [{ "type": "exec_sql_select", "table": "TAX-TABLE", "select_list": "TAX-RATE, DEDUCTION", "into_vars": ["DBV-TAX-RATE", "DBV-TAX-DEDUCTION"], "where": ":WRK-TAXABLE-INCOME BETWEEN TAX-FROM AND TAX-TO", "where_constraints": [ {"type": "between", "neg": False, "op": "BETWEEN", "subject": {"kind": "host_var", "name": "WRK-TAXABLE-INCOME"}, "lo": {"kind": "column", "name": "TAX-FROM"}, "hi": {"kind": "column", "name": "TAX-TO"}}, ], }] def _declared_tax_table(): return { "TAX-TABLE": [ {"name": "TAX_FROM", "db_type": "DECIMAL"}, {"name": "TAX_TO", "db_type": "DECIMAL"}, {"name": "TAX_RATE", "db_type": "DECIMAL"}, {"name": "DEDUCTION", "db_type": "DECIMAL"}, ], } def test_select_between_hostvar_subject_seeds_lo_hi_from_record(): """`:hv BETWEEN TAX-FROM AND TAX-TO`:TAX_FROM/TAX_TO 播成记录中 WRK-TAXABLE-INCOME 的值,保证运行时 BETWEEN 命中。""" records = [{"WRK-TAXABLE-INCOME": "000000000"}] db_input = build_db_input( [([('SQLCODE', '=', '0', True)], {})], [], {}, _sql_meta_select_between(), _declared_tax_table(), records=records) rows = db_input.get('TAX-TABLE', []) assert len(rows) == 1 assert int(rows[0]['TAX_FROM']) == 0 assert int(rows[0]['TAX_TO']) == 0 def test_select_between_hostvar_subject_nonzero(): """subject 非零时,lo/hi 取记录值。""" records = [{"WRK-TAXABLE-INCOME": "000102400"}] db_input = build_db_input( [([('SQLCODE', '=', '0', True)], {})], [], {}, _sql_meta_select_between(), _declared_tax_table(), records=records) rows = db_input.get('TAX-TABLE', []) assert len(rows) == 1 assert int(rows[0]['TAX_FROM']) == 102400 assert int(rows[0]['TAX_TO']) == 102400 def test_select_between_hostvar_subject_moved_from_input(): """subject 主机变量经 MOVE 链到输入键时,优先用输入键值。""" sql_meta = [{ "type": "exec_sql_select", "table": "TAX-TABLE", "select_list": "TAX-RATE, DEDUCTION", "into_vars": ["DBV-TAX-RATE", "DBV-TAX-DEDUCTION"], "where": ":WRK-TAXABLE-INCOME BETWEEN TAX-FROM AND TAX-TO", "where_constraints": [ {"type": "between", "neg": False, "op": "BETWEEN", "subject": {"kind": "host_var", "name": "WRK-TAXABLE-INCOME"}, "lo": {"kind": "column", "name": "TAX-FROM"}, "hi": {"kind": "column", "name": "TAX-TO"}}, ], }] records = [{"R01GROSS-PAYMENT": "000102400", "WRK-TAXABLE-INCOME": "000000000"}] assignments = { 'WRK-TAXABLE-INCOME': [{'type': 'move', 'source_vars': ['R01GROSS-PAYMENT']}], } db_input = build_db_input( [([('SQLCODE', '=', '0', True)], {})], [], assignments, sql_meta, _declared_tax_table(), records=records) rows = db_input.get('TAX-TABLE', []) assert len(rows) == 1 assert int(rows[0]['TAX_FROM']) == 102400 def test_select_between_column_subject_seeds_subject_col(): """`SALARY BETWEEN :LO AND :HI`:subject 是列时,把该列播成 lo 值 (满足 lo <= col <= hi)。""" sql_meta = [{ "type": "exec_sql_select", "table": "EMP", "select_list": "EMP_NAME", "into_vars": ["DBV-EMPLOYEE-NAME"], "where": "SALARY BETWEEN :LO AND :HI", "where_constraints": [ {"type": "between", "neg": False, "op": "BETWEEN", "subject": {"kind": "column", "name": "SALARY"}, "lo": {"kind": "host_var", "name": "LO"}, "hi": {"kind": "host_var", "name": "HI"}}, ], }] declared = { "EMP": [ {"name": "SALARY", "db_type": "DECIMAL"}, {"name": "EMP_NAME", "db_type": "CHAR", "size": 40}, ], } records = [{"LO": "000005000", "HI": "000009000"}] db_input = build_db_input( [([('SQLCODE', '=', '0', True)], {})], [], {}, sql_meta, declared, records=records) rows = db_input.get('EMP', []) assert len(rows) == 1 sal = int(rows[0]['SALARY']) assert 5000 <= sal <= 9000 def test_select_between_sql_error_path_skips_row(): """SQL 失败路径(SQLCODE<>0)仍不生成预置行。""" records = [{"WRK-TAXABLE-INCOME": "000102400"}] db_input = build_db_input( [([('SQLCODE', '<>', '0', True)], {})], [], {}, _sql_meta_select_between(), _declared_tax_table(), records=records) rows = db_input.get('TAX-TABLE', []) assert not rows def _fdict_with_ws_constants(): """KYU05DED 简化的字段定义:CNS-TAX-BASIC-DEDUCTION 常量 + MOVE/COMPUTE 链。""" return [ {'name': 'R01GROSS-PAYMENT', 'pic_info': {'type': 'numeric', 'digits': 9, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'INPUT', 'value': None}, {'name': 'WRK-GROSS-PAYMENT', 'pic_info': {'type': 'numeric', 'digits': 9, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'WORKING-STORAGE', 'value': None}, {'name': 'WRK-TAXABLE-INCOME', 'pic_info': {'type': 'numeric', 'digits': 9, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'WORKING-STORAGE', 'value': None}, {'name': 'CNS-TAX-BASIC-DEDUCTION', 'pic_info': {'type': 'numeric', 'digits': 6, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'WORKING-STORAGE', 'value': '50000'}, ] def test_select_between_derives_runtime_value_from_input_key(): """记录中 WRK-TAXABLE-INCOME 为合成值时,按运行时推导值播种带。 输入 gross=000100000 → 运行时 taxable = gross - 50000 = 50000, 预置行 TAX_FROM/TAX_TO 应为 50000,而非记录的合成值。""" assignments = { 'WRK-GROSS-PAYMENT': [{'type': 'move', 'source_vars': ['R01GROSS-PAYMENT']}], 'WRK-TAXABLE-INCOME': [ {'type': 'move_literal', 'literal': 'ZERO'}, {'type': 'compute', 'source_vars': ['WRK-GROSS-PAYMENT', 'CNS-TAX-BASIC-DEDUCTION'], 'op': '-'}, ], } records = [{"R01GROSS-PAYMENT": "000100000", "WRK-TAXABLE-INCOME": "000003701"}] db_input = build_db_input( [([('SQLCODE', '=', '0', True)], {})], _fdict_with_ws_constants(), assignments, _sql_meta_select_between(), _declared_tax_table(), records=records) rows = db_input.get('TAX-TABLE', []) assert len(rows) == 1 assert int(rows[0]['TAX_FROM']) == 50000 assert int(rows[0]['TAX_TO']) == 50000 def test_select_between_derives_zero_for_low_gross(): """gross < 基本控除(50000)时,运行时 taxable = 0 → 带为 [0,0]。""" assignments = { 'WRK-GROSS-PAYMENT': [{'type': 'move', 'source_vars': ['R01GROSS-PAYMENT']}], 'WRK-TAXABLE-INCOME': [ {'type': 'move_literal', 'literal': 'ZERO'}, {'type': 'compute', 'source_vars': ['WRK-GROSS-PAYMENT', 'CNS-TAX-BASIC-DEDUCTION'], 'op': '-'}, ], } records = [{"R01GROSS-PAYMENT": "000000501", "WRK-TAXABLE-INCOME": "000003701"}] db_input = build_db_input( [([('SQLCODE', '=', '0', True)], {})], _fdict_with_ws_constants(), assignments, _sql_meta_select_between(), _declared_tax_table(), records=records) rows = db_input.get('TAX-TABLE', []) assert len(rows) == 1 assert int(rows[0]['TAX_FROM']) == 0 assert int(rows[0]['TAX_TO']) == 0