Skip to content

db-adapters: same-batch MSSQL identity fallback, Oracle CURRVAL, and $identitySelect six-way dedup #2906

Description

@bpamiri

Split out of the identity-retrieval-race fix (finding "Top 10 #10" / DA3 / DA8 / DA9 of the internal multi-agent framework review, 2026-06-09). That PR makes both adapters prefer the driver-supplied generated key, which fixes the race/trigger hazard on Lucee and Adobe CF. Three pieces of deliberately deferred scope remain:

1. SQL Server: same-batch retrieval for the no-generated-key fallback

vendor/wheels/databaseAdapters/MicrosoftSQLServer/MicrosoftSQLServerModel.cfc — when the engine surfaces no result.generatedKey (currently BoxLang), the adapter falls back to a standalone SELECT @@IDENTITY, which is session-scoped and can return a trigger-generated identity from another table. A standalone SELECT SCOPE_IDENTITY() is not a fix: it executes in its own batch (a batch is a scope per MS docs) and returns NULL over JDBC. Proper fix options:

  • run the retrieval in the insert's own batch (e.g. INSERT ...; SELECT SCOPE_IDENTITY() AS lastId as one statement), or
  • use OUTPUT INSERTED.<pk>, or
  • drop the fallback entirely once BoxLang surfaces JDBC generated keys in its query result struct.

2. Oracle: replace the MAX(ROWID) last-resort with CURRVAL

vendor/wheels/databaseAdapters/Oracle/OracleModel.cfc — the same no-generated-key fallback still resolves the new PK via WHERE ROWID = (SELECT MAX(ROWID) FROM <table>). ROWID encodes physical storage location, not insertion order, so this races under concurrent inserts and can assign another session's key. Replace with CURRVAL on the backing identity sequence (session-scoped, race-free), or remove once BoxLang surfaces generated keys.

3. DA3: dedupe the six divergent $identitySelect implementations

$identitySelect is copy-pasted across six adapters with divergent engine workarounds:

  • vendor/wheels/databaseAdapters/Base.cfc
  • MicrosoftSQLServer/MicrosoftSQLServerModel.cfc
  • PostgreSQL/PostgreSQLModel.cfc
  • Oracle/OracleModel.cfc
  • SQLite/SQLiteModel.cfc
  • CockroachDB/CockroachDBModel.cfc

The BoxLang ReplaceList column-parse workaround and the missing-closing-paren guard each exist in only a subset (the race-fix PR copied the ReplaceList workaround into MSSQL/Oracle, widening the duplication). Extract a Base $parseInsertColumnList template method plus a small per-engine $lastIdLookup hook so future engine fixes land once.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions