Skip to content

EXEC SQL COMMIT / ROLLBACK / SAVEPOINT: a real Db2 unit of work in batch det ports (Part of #4269) - #4729

Merged
squid-protocol merged 2 commits into
mainfrom
feat/4269-sql-rollback
Oct 9, 2026
Merged

squid-protocol merged 2 commits into
mainfrom
feat/4269-sql-rollback

Conversation

@squid-protocol

@squid-protocol squid-protocol commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Part of #4269.

Slice chosen by demand

Census (cics_census.py blockers --baseline --all-programs, origin/main d3b7440, 179 programs over burned + non-burned corpora): of all #4269 forms, only ROLLBACK reaches a translatable program -- WHENEVER, dynamic SQL, CALL, CONNECT, rowset FETCH: 0 programs; SCROLL: 1 (GenApp, not a gap). The only EXEC SQL gaps were ROLLBACK: the generated repositories commit each statement (3 programs, 2 one gap-class from whole) and no generated Db2 repository method (the same 3; 2 one-away) -- nexusbank NEND-DAY, NINT-CALC (batch) and NBLK-ACCT (CICS), all non-burned. The second turned out to be a det translator bug: the generated Javadoc's program name NEND-DAY did not match \w+. Once those were fixed, NINT-CALC's last gap was ROLLBACK TO SAVEPOINT, so savepoints are in this slice too.

Census before -> after (programs translated whole): 95 -> 96 / 179 (non-burned 9 -> 10). NINT-CALC is now whole (89 -> 97/97 statements). NEND-DAY goes 86 -> 100/101: its one gap left is a host variable the translator does not find, not SQL. NBLK-ACCT goes 118 -> 128/166, and its ROLLBACKs are now refused by name (CICS). EXEC SQL gap classes over all programs: 2 classes in 3 programs -> 1 (CICS ROLLBACK, 1 program).

What changed

  • det translator (det/sql.py)
    • COMMIT [WORK] -> DetSql.commit, ROLLBACK [WORK] -> DetSql.rollback. SAVEPOINT ... ON ROLLBACK RETAIN CURSORS, ROLLBACK [WORK] TO SAVEPOINT [x] and RELEASE [TO] SAVEPOINT x -> DetSql.savepoint (run as written).
    • A cursor declared WITH HOLD opens with hold. A hyphenated program name now finds its generated methods.
    • Refused by name: a CICS program's ROLLBACK (z/OS answers -926; SYNCPOINT ROLLBACK is the task's), a savepoint statement in CICS or with a host variable, and COMMIT / ROLLBACK with other options. A CICS COMMIT keeps its old behaviour.
  • det runtime (DetSql)
    • UnitOfWork, given by the runner, holds a batch step's Db2 transaction. COMMIT closes the cursors not declared WITH HOLD. A held cursor keeps no current row, so a positioned statement before the next FETCH gets -508.
    • ROLLBACK closes every cursor. With no unit of work given, ROLLBACK and savepoint statements are refused at run time and never answered.
  • oracle (ggsql.c, equivalence_sql.py, ggabend.c)
    • COMMIT closes the cursors that are not held and ROLLBACK closes all of them, as z/OS does. CLI keeps cursors open by default, so ggsql.c closes and frees the handles itself; the precompiler marks a held OPEN with H.
    • CEE3ABD backs out the unit of work before exit, as z/OS does for a batch program that abends.
    • Two CLI quirks were measured and neutralised. CLI runs ROLLBACK WORK TO SAVEPOINT as a full ROLLBACK (0 where Db2 answers -880), so the precompiler drops the noise word WORK. CLI's row count of -1 no longer reaches SQLERRD(3).
  • harness
    • A Db2 batch step runs as one transaction and gives the det runtime its connection, by reflection.
    • After a matching abend the Db2 tables are compared too: the abend backed out the uncommitted work, so they hold what the step committed.
    • The Equivalence harness: DB2 SQL support #4507 outcome fold no longer counts the stub's -501 on a cursor that COMMIT / ROLLBACK closed.
    • New equivalence.py run --corpus-dir, never with --record, for a synthetic estate.

IBM references (register Q3 / Q4, docs/language_status/oracle_assumptions.md)

Db2 for z/OS SQL Reference: COMMIT (closes cursors not WITH HOLD), ROLLBACK (backs out, closes all cursors), SAVEPOINT / ROLLBACK TO SAVEPOINT / RELEASE SAVEPOINT (-880, -881). Codes: -501, -508, -803, -925 / -926 (2D521, COMMIT / ROLLBACK invalid in CICS). Application Programming and SQL Guide: unit of work in TSO / CAF (commit at a normal end, backout at an abnormal one). Q3 goes from DIFFERS (batch) to MATCHED.

Oracle / z/OS differences, declared:

  • A CICS program's EXEC SQL COMMIT is still a reset on the Java side and a real commit on the oracle, where z/OS answers -925. No burned CICS program issues one (CBSA's COMMIT is in BANKDATA, a batch program). This is the next slice.
  • A libcob run-time failure that is not CEE3ABD still commits at exit. It never compares equal anyway: the Java side reports an UNCODED abend.

Proof

  • Hand-traced, both sides, real Db2: tests/equivalence/db2/uow (synthetic UOWDEMO estate) + test_sql_unit_of_work.py::test_uowdemo_det_port_proves_on_db2_against_the_hand_trace, gated on EQUIVALENCE_E2E=1 and passing locally in normal and abend modes.
    • Covers a commit, two changes backed out, -803 then ROLLBACK, -501 after a COMMIT on a cursor that is not held, a held cursor kept by COMMIT, ROLLBACK closing it, a savepoint (insert backed out; -880 after RELEASE), and uncommitted work kept at a normal end and backed out by an abend.
    • SYSOUT (24 lines), the table and 18/18 statement outcomes match on both sides and match the hand trace.
    • No burned program issues ROLLBACK, and the census repos are blind, hence the synthetic estate.
  • Negative control: with the Java ROLLBACK switched off, the e2e test fails.
  • Unit tests (no Db2): the translator's output and refusals, DetSql with a fake unit of work run on javac/java, the precompiler's hold mark, and the harness's step wrapper.
  • det_port.py check vs origin/main: 29 ports changed. Every Db2 port carries DetSql.java, and LGUPDB01 also gains the WITH HOLD flag. Re-proven with proof_sweep.py --det-only --cases <29> --faults all: 29/29 proven, "sweep: as expected".
  • Gates: pr_gates.py --fast 5/5 pass; pr_gates.py --ratchets 7/7 pass, none skipped.
  • Evidence: no committed port, case, corpus pin or declared difference changed. The harness and oracle changes make records stale only for the scheduled refresh. The evidence report is not committed.

Next slices (#4269)

  1. EXEC SQL COMMIT / ROLLBACK in CICS: -925 / -926 on both sides. ggsql.c needs to know it runs under CICS (ggcics.c, CICS files).
  2. NEND-DAY's last gap: a host variable the translator does not find (EXEC ... HV-SO-IM: no such item, line 408). Probably not SQL; it makes the program whole.
  3. Savepoints in a CICS task, through the task's unit of work.
  4. The forms with no demand in the census (0 programs): WHENEVER, EXECUTE IMMEDIATE / PREPARE / EXECUTE / DESCRIBE, CALL, CONNECT, rowset FETCH, SCROLL (1 GenApp declaration), DSNTIAR (Q5).

🤖 Generated with Claude Code

https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D

…orts (Part of #4269)

A batch step is one Db2 unit of work, as Db2 for z/OS runs a DSN / CAF program: committed at its
normal end, backed out by an abend. EXEC SQL COMMIT / ROLLBACK [WORK] end it in between
(DetSql.commit / rollback over the runner's DetSql.unitOfWork); COMMIT closes cursors not declared
WITH HOLD, ROLLBACK every cursor -- on both sides (ggsql.c closes the CLI handles itself; the
precompiler marks a held cursor's OPEN). ggabend.c backs the unit out before an abend exits; the
Java harness runs a Db2 batch step in one transaction and gives DetSql its connection; Db2 tables
are compared after a matching abend. ROLLBACK TO SAVEPOINT and a CICS program's ROLLBACK (-926 on
z/OS) are refused by name. The det translator also finds a hyphenated program's generated Db2
methods (NEND-DAY): the census's other SQL gap in the same programs.

Proven end to end on Db2 with a synthetic, hand-traced estate (tests/equivalence/db2/uow;
EQUIVALENCE_E2E=1): no burned program issues ROLLBACK. Register Q3 MATCHED for batch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D
@squid-protocol squid-protocol added enhancement New feature, sensor, or structural signature legacy-modernization COBOL refractor, dead-code extraction, and JCL forging labels Oct 8, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

…it of work (Part of #4269)

The census's last gap in NINT-CALC. Each runs as written on the unit of work's connection on both
sides (DetSql.savepoint; the precompiler makes it an EXEC), so Db2 answers it (-880 for a savepoint
that does not exist or was released). Refused with a host variable, or in a CICS program.

Oracle quirks measured and neutralised: IBM's CLI runs `ROLLBACK WORK TO SAVEPOINT x` as a ROLLBACK
of the whole unit (answering 0 where Db2 answers -880), so the precompiler drops the noise word
WORK; CLI's row count -1 for a statement with none no longer reaches SQLERRD(3). UOWDEMO gains a
hand-traced savepoint section; register Q3 updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D
@squid-protocol squid-protocol changed the title EXEC SQL COMMIT / ROLLBACK: a real Db2 unit of work in batch det ports (Part of #4269) EXEC SQL COMMIT / ROLLBACK / SAVEPOINT: a real Db2 unit of work in batch det ports (Part of #4269) Oct 9, 2026
@squid-protocol
squid-protocol marked this pull request as ready for review October 9, 2026 00:48
@squid-protocol
squid-protocol merged commit 63c1a6a into main Oct 9, 2026
37 checks passed
@squid-protocol
squid-protocol deleted the feat/4269-sql-rollback branch October 9, 2026 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature, sensor, or structural signature legacy-modernization COBOL refractor, dead-code extraction, and JCL forging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant