Repository navigation
Fix #3985: keep whole DBCS characters when a text field is too wide - #4038
Merged
Merged
Conversation
Under a mixed EBCDIC page (IBM930 / 939 / 933 / 935 / 937) each double-byte run is wrapped in Shift-Out (0x0E) / Shift-In (0x0F). CobolRecords.putText cut the encoded bytes at the field width, so an over-wide value left a Shift-Out with no Shift-In and half a double-byte character: `AB日本` into 6 bytes wrote C1C2 0E 4562 45 and read back as `AB日?`, and that record went back to the mainframe. - CobolRecords.fit(value, width, text): cut to whole characters (never half a DBCS character or a surrogate pair; the encoder closes the Shift-Out run) and space-pad to exactly `width` bytes. putText stores through it. A single-byte page writes exactly the bytes it did before. - CobolRecords.width(value, text): a value's width in record bytes, Shift-Out / Shift-In included -- what String.length() is not under a multi-byte page. - A porting rule when data.code_page is multi-byte (cp930-937, shift_jis, cp932, ...): fixed-width logic through width / fit, never length() / substring(); a reference modification of such a field flagged in a TODO. - The tickets' target config carries the data section (the code page). Decoding (CobolRecords.text) is unchanged: it is lossless, and padding the String to the byte width would not make character positions byte positions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #3985.
What was actually broken
In a mixed EBCDIC code page (IBM930 / 939 / 933 / 935 / 937), each run of double-byte characters is wrapped in Shift-Out (
0x0E) and Shift-In (0x0F). Probed with Java 21 and IBM939:AB日本C(9 bytes:C1C2 0E 4562 4566 0F C3) decodes to an 8-character String, and encoding it again gives exactly 12 bytes. The round trip is lossless. The only effect is thatlength()andsubstring()stop matching byte positions.CobolRecords.putTextcut the encoded bytes at the field width.AB日本put into 6 bytes wroteC1C2 0E 4562 45: a Shift-Out with no Shift-In, and half a double-byte character. It read back asAB日�, and that broken record went back to the mainframe.Changes
CobolRecords.fit(value, width, text)cuts to whole characters and pads with spaces to exactlywidthbytes. It never splits a double-byte character or a surrogate pair, and the JDK encoder closes the Shift-Out run.putTextnow stores through it.CobolRecords.width(value, text)returns a value's width in record bytes, Shift-Out and Shift-In included.multibyte_rules), added only whendata.code_pagecan encode a CJK character (cp930–937, shift_jis, cp932, …): fixed-width logic goes throughwidthandfit, neverlength(),substring()orString.formatwidths. A reference modification (X(3:5)) of such a field has to be flagged in a TODO, because it can split a double-byte run. Single-byte estates keep today's rules.datasection, so the porter sees the code page. That also covers thecodePageargument the Key order: EBCDIC vs the target database's collation -- generated repositories browse and sort differently, MySQL collides case-variant keys #3822sortKeyrule asks for.dead_key_audit.py: allowlistcode_page. Likedecimal_pointandkey_collation, it's a dataclass field thattarget_as_dictwrites withvars().Not done, on purpose
length()match, but character positions still aren't byte positions, and encoding the padded String gives more bytes than the field holds.CobolStringwrapper. It would change every generated entity's field types for a problem the two helpers already solve.refmodhelper. A mid-run byte slice has no Shift-Out, so there's no faithful String to return; the ticket flags it instead.Verification
test_dbcs_widths.py: a Java test (IBM939 / IBM037 / UTF-8, 18 byte-level cases) covers the lossless round trip, balanced Shift-Out/Shift-In when an over-wide value goes into 4-, 6-, 7- and 8-byte fields,widthandfit, IBM037 output unchanged from today, and surrogate pairs. Unit tests check which code pages get the new rule. The test skips withoutjavac; I ran it locally with JDK 21's compiler module.tests/cobol_mainframe+tests/cics_crucible: 1170 passed, 22 skipped.ruff formatclean.CobolRecords.🤖 Generated with Claude Code