Skip to content

Commit a64705f

Browse files
bpamiriclaude
andauthored
fix(model): resolve cockroachdb bulk-ops and locking test failures (#2106) (#2206)
* fix(model): resolve cockroachdb bulk-ops and locking test failures (#2106) Two CockroachDB-only core-test regressions, 10 failures total. Bug 1 (6 failures in bulkOperationsSpec): CockroachDBModel.$querySetup unconditionally appended `RETURNING <primaryKey>` to any `INSERT INTO` statement. bulk.cfc's insertAll/upsertAll invoke $querySetup without passing $primaryKey (default ""), so emitted SQL ended in a bare `RETURNING `, which CockroachDB parses as a syntax error near EOF. Guard the append: skip when $primaryKey is empty/whitespace, and skip when any SQL chunk contains `ON CONFLICT` (upsert statements). The single-row INSERT + $identitySelect path on PostgreSQL is unaffected because it lives on PostgreSQLModel, not CockroachDBModel, and in the CockroachDB single-row path $primaryKey is populated. Bug 2 (4 failures in lockingSpec): the spec was missing the _isCockroachDB skip shim that 8 sibling specs already declare. CockroachDBModel correctly throws Wheels.AdvisoryLockNotSupported (parity with Oracle/H2). Added the shim and `if (_isCockroachDB) return;` to each withAdvisoryLock test. The forUpdate describes are left intact since CockroachDB supports SELECT ... FOR UPDATE. SQLite suite: 3250 passed locally. CI compat-matrix against real CockroachDB will validate the actual fix. Closes #2106 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(docs): correct claude.md ci soft-fail claim SOFT_FAIL_DBS in .github/workflows/compat-matrix.yml (lines 389, 519) is empty (""); CockroachDB is currently hard-gated in CI, not soft-fail as previously documented. Update the reference paragraph to reflect reality and explain how to mark a database as soft-fail in the future. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2ac13a8 commit a64705f

3 files changed

Lines changed: 23 additions & 2 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ curl -s "http://localhost:62023/wheels/core/tests?db=mysql&format=json" | \
609609
- **`private` mixin functions not integrated**: `$integrateComponents()` only copies `public` methods into model/controller objects. ALL helper functions in mixin CFCs (`vendor/wheels/model/*.cfc`, view helpers, etc.) MUST use `public` access. Use `$` prefix for internal scope instead of `private` keyword. BoxLang handles this differently, so `private` may pass BoxLang tests but fail Lucee/Adobe.
610610

611611
### CI soft-fail databases
612-
CockroachDB is marked as soft-fail in `.github/workflows/tests.yml` — failures are logged as warnings but don't block the build. The `SOFT_FAIL_DBS` variable controls this. Remove a database from the list once its tests are fixed.
612+
`SOFT_FAIL_DBS` in `.github/workflows/compat-matrix.yml` (lines 389, 519) is currently empty (`""`) — **all databases, including CockroachDB, are hard-gated in CI**. To mark a database as soft-fail (failures logged as warnings but not blocking the build), add it to `SOFT_FAIL_DBS` in both locations. Remove a database from the list once its tests are fixed.
613613

614614
### Cleanup
615615
```bash

vendor/wheels/databaseAdapters/CockroachDB/CockroachDBModel.cfc

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ component extends="wheels.databaseAdapters.PostgreSQL.PostgreSQLModel" output=fa
6565
* Override query setup to append RETURNING clause to INSERTs.
6666
* CockroachDB does not support pg_get_serial_sequence()/currval(),
6767
* so the RETURNING clause is the correct way to retrieve generated keys.
68+
*
69+
* Skip the RETURNING append on bulk / upsert paths: bulk.cfc's insertAll
70+
* and upsertAll call $querySetup without $primaryKey (default empty), and
71+
* upsert statements already include ON CONFLICT clauses. In both cases,
72+
* appending `RETURNING ` produces syntactically invalid SQL.
6873
*/
6974
public struct function $querySetup(
7075
required array sql,
@@ -74,7 +79,18 @@ component extends="wheels.databaseAdapters.PostgreSQL.PostgreSQLModel" output=fa
7479
string $primaryKey = ""
7580
) {
7681
if (Left(arguments.sql[1], 11) == "INSERT INTO") {
77-
ArrayAppend(arguments.sql, "RETURNING #arguments.$primaryKey#");
82+
local.shouldAppendReturning = Len(Trim(arguments.$primaryKey)) > 0;
83+
if (local.shouldAppendReturning) {
84+
for (local.chunk in arguments.sql) {
85+
if (IsSimpleValue(local.chunk) && ReFindNoCase("ON[[:space:]]+CONFLICT", local.chunk)) {
86+
local.shouldAppendReturning = false;
87+
break;
88+
}
89+
}
90+
}
91+
if (local.shouldAppendReturning) {
92+
ArrayAppend(arguments.sql, "RETURNING #arguments.$primaryKey#");
93+
}
7894
}
7995
$convertMaxRowsToLimit(args = arguments);
8096
$removeColumnAliasesInOrderClause(args = arguments);

vendor/wheels/tests/specs/model/lockingSpec.cfc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ component extends="wheels.WheelsTest" {
33
function run() {
44

55
g = application.wo
6+
var _isCockroachDB = CreateObject("component", "wheels.migrator.Migration").init().adapter.adapterName() == "CockroachDB";
67

78
describe("Tests that withAdvisoryLock", () => {
89

910
it("executes callback and returns result", () => {
11+
if (_isCockroachDB) return;
1012
local.result = g.model("author").withAdvisoryLock(name="test_lock_1", callback=function() {
1113
return 42;
1214
});
1315
expect(local.result).toBe(42);
1416
})
1517

1618
it("releases lock even when callback throws an exception", () => {
19+
if (_isCockroachDB) return;
1720
local.exceptionThrown = false;
1821
try {
1922
g.model("author").withAdvisoryLock(name="test_lock_2", callback=function() {
@@ -32,13 +35,15 @@ component extends="wheels.WheelsTest" {
3235
})
3336

3437
it("accepts a custom timeout argument", () => {
38+
if (_isCockroachDB) return;
3539
local.result = g.model("author").withAdvisoryLock(name="test_lock_timeout", timeout=5, callback=function() {
3640
return "locked";
3741
});
3842
expect(local.result).toBe("locked");
3943
})
4044

4145
it("safely handles lock names with single quotes", () => {
46+
if (_isCockroachDB) return;
4247
// Regression guard: verify lock names are parameterized, not interpolated.
4348
// On SQLite this is a no-op, but the call must not throw a SQL syntax error
4449
// regardless of adapter. With proper parameterization, "O'Brien" is fine.

0 commit comments

Comments
 (0)