Skip to content

fix(oracle): read a generated-key object's text before string checks - #3710

Merged
bpamiri merged 2 commits into
developfrom
fix/3708-oracle-rowid-generated-key
Sep 27, 2026
Merged

bpamiri merged 2 commits into
developfrom
fix/3708-oracle-rowid-generated-key

Conversation

@bpamiri

@bpamiri bpamiri commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #3708 (4.1.1 release blocker). On BoxLang + Oracle the JDBC generated key comes back as an
oracle.sql.ROWID object, and OracleModel.$lastIdLookup called Len()/ListFirst() on it:
Cannot determine length of object of type oracle.sql.ROWID (OracleModel.cfc:194, via fixture
populate.cfm:507). Fixture population aborted, so the whole BoxLang + Oracle leg ran 0 tests. The string
assumption dates from #3010.

Fix: a new $generatedKeyText(value) normalizes the driver key before any string work:

  • simple value → returned unchanged, so Lucee/Adobe string keys follow the exact old path;
  • object → stringValue() (oracle.sql.ROWID), else toString() (the java.sql.RowId contract);
  • no readable text form → "", which means "no usable key" and falls back to the existing CURRVAL lookup.

The numeric and extended-ROWID (^[A-Za-z0-9/+]{18}$) gates then run on that text unchanged before
anything reaches SQL. oracle.sql is an ordinary exported package in the ojdbc jar, so the member call is not the
JDK-internal case from invariant 14.

Tests (RED first)

OracleUnitSpec → non-simple driver keys (#3708). These run DB-free, through $identitySelect and the existing
OracleProbe:

case before after
object with stringValue() → numeric key Error Can't cast Component … to String pass
object with only toString() via result.rowid (the ACF surface) Error pass
ROWID-text object → exact-row CHARTOROWID('…') lookup Error pass
opaque object → CURRVAL fallback, no CHARTOROWID pass pass
real oracle.sql.ROWID (only where the driver is on the classpath) skipped locally runs on the Oracle legs

Lucee 7 + SQLite local: wheels.tests.specs.database 167/0/0 (before: 164 + 3 errors); wheels.tests.specs.model
1050/0/0.

Oracle legs

Oracle is soft-fail in CI, so the leg's colour proves nothing; the totals are the gate. I couldn't run
tools/test-matrix.sh locally: the shared wheels compose project is in use by another session, and memory is tight.
So the evidence is the compat-matrix dispatch on this branch, with per-leg totals read from the job logs:
https://github.com/wheels-dev/wheels/actions/runs/36341832355. I'll add the boxlang/lucee7/adobe2023 + oracle
totals here when it finishes.

Not changed

  • The MSSQL and CockroachDB adapters also call Len/ListFirst on result.generatedKey. Their BoxLang legs
    pass today (their drivers return strings), so they're left alone in this release fix. $generatedKeyText could
    move to Base later if another driver starts returning key objects.

🤖 Generated with Claude Code

On BoxLang + Oracle the JDBC generated key comes back as an oracle.sql.ROWID
object, and OracleModel.$lastIdLookup called Len()/ListFirst() on it, throwing
"Cannot determine length of object of type oracle.sql.ROWID". Fixture
population aborted, so the whole BoxLang + Oracle leg ran 0 tests.

Add $generatedKeyText(): simple values pass through unchanged; a key object is
read via stringValue() (oracle.sql.ROWID) or toString() (the java.sql.RowId
contract); anything without a text form is "" and falls back to CURRVAL. The
numeric and extended-ROWID gates run on the text exactly as before, so the
string paths on Lucee and Adobe are unchanged.

Specs feed stringValue(), toString()-only and opaque key objects through
$identitySelect (DB-free via OracleProbe), and a real oracle.sql.ROWID where
the driver is on the classpath. The first three errored before the fix.

Closes #3708

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
@bpamiri

bpamiri commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review: independent reviewer (rev1.r1), commit 8b2a9f814. Verdict: approve. No MUST-FIX. Merge gate: the boxlang + oracle leg of dispatch 36341832355 (on this exact head) must return real TestBox results, not the application-exception envelope from #3708. It was still running when I posted; I'll follow up with the result.

$generatedKeyText

  • Simple values pass through unchanged, so Lucee and Adobe, which hand over the key as a string, never reach a member call.
  • An object is read with stringValue(), then toString(), and anything unreadable or non-simple becomes "".
  • Both catch blocks write to a var state = {text = ""} struct, not local., which follows the BoxLang catch invariant (CLAUDE.md New master #11).
  • It's public and $-prefixed (invariant New master - readme #7).

Placement in $lastIdLookup. It's applied before ListFirst/Len, which is where BoxLang threw Cannot determine length of object of type oracle.sql.ROWID. For string keys the behavior is unchanged: an empty generatedKey still falls through to rowid, as before.

Injection safety. The text an object produces is still fully gated before it reaches any SQL:

  • IsNumeric means it's returned as the id;
  • otherwise it must match the anchored ^[A-Za-z0-9/+]{18}$ (18 base64-alphabet characters, no quote possible) to be interpolated into CHARTOROWID('…');
  • anything else, including an arbitrary toString() such as a component's or an object's default text, falls back to CURRVAL.

Invariant #14 (Adobe 2025 JPMS), checked explicitly. There's no member call on a JDK-internal type.

  • The stringValue()/toString() calls run only on the non-simple path, which only BoxLang reaches.
  • The object there is oracle.sql.ROWID, a class from the Oracle driver jar loaded in the unnamed module, not com.sun.* or jdk.internal.*. So Adobe 2025's bulk setAccessible on the concrete class doesn't throw InaccessibleObjectException.
  • The one spec that builds a real oracle.sql.ROWID (via CreateObject("java", …).init(CharsetDecode(...))) does run on every engine, Adobe 2025 included. But it's the same driver class, and it skips cleanly when the driver isn't on the classpath.

Specs. They're DB-free (stubs plus OracleProbe capturing SQL) and cover stringValue(), toString(), the exact-row CHARTOROWID path, the unreadable-to-CURRVAL fallback, and a real driver ROWID. They assert the captured SQL, not just the return value.

@bpamiri

bpamiri commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review of 8b2a9f8: source review clean; Oracle runtime evidence pending.

Independently ran bash tools/test-local.sh wheels.tests.specs.database on an isolated Lucee 7 + SQLite server: 167 passed, 0 failures/errors. Reviewed the normalization before ListFirst/Len, unchanged numeric/extended-ROWID validation, and the DB-free OracleProbe assertions for exact-row lookup and CURRVAL fallback.

CLAUDE.md invariant #14: these calls receive the driver-supplied Oracle key, not an object from a JDK-internal factory. oracle.sql.ROWID is an Oracle-driver type; no JDK-internal implementation receiver is introduced by this change. Unsupported object member calls are caught. Catch state uses unprefixed state.text, preserving BoxLang writes rather than the discarded nested local scope.

Final clearance awaits the actual Oracle totals from dispatch 36341832355, whose head matches this PR, including BoxLang and the Adobe 2025 leg if it reaches the suite. At review time those results are not available; a soft-fail job colour would not substitute for executed-test totals.

@bpamiri

bpamiri commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up from rev1.r1: the merge gate is met. From dispatch 36341832355, reading the result JSON rather than the job colour (via dev1-design):

The 5 BoxLang errors have separate root causes that this leg couldn't reach until now: TimestampRoundTripSpec (oracle.sql.TIMESTAMP not normalized), and bulkOperationsSpec insertAll ×4 (connection closed inside a transaction). This PR doesn't introduce them, and they should be tracked as their own issues. My approval stands for 8b2a9f814. The Adobe Oracle legs were still running when I posted.

@bpamiri

bpamiri commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Final review of 8b2a9f8: APPROVE for #3708's scope. No blocking findings in this patch.

Downloaded and inspected the actual JSON artifacts from dispatch 36341832355 at this exact head:

  • Lucee 7 + Oracle: 5791 pass / 0 fail / 0 error / 24 skip.
  • Adobe 2023 + Oracle: 5791 pass / 0 fail / 0 error / 24 skip.
  • BoxLang + Oracle: 5786 pass / 0 fail / 5 error / 24 skip. All five new generated-key specs passed, including the real oracle.sql.ROWID case (not skipped).

The five BoxLang errors are the timestamp diagnostic/normalization and four bulk insert transaction cases now tracked in #3714/#3715, outside this patch's generated-key conversion. This approval does not certify the full release matrix green. Adobe 2025 is still pending and has its separate #3709 blocker; it is not being silently counted as a pass.

Earlier independent local verification: database suite 167/0/0. Invariant #14 and BoxLang catch-state handling reviewed in the preceding comment.

@bpamiri
bpamiri merged commit b313a44 into develop Sep 27, 2026
19 checks passed
@bpamiri
bpamiri deleted the fix/3708-oracle-rowid-generated-key branch September 27, 2026 19:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BoxLang + Oracle: ROWID generated-key object aborts fixture population in $lastIdLookup

1 participant