Commit eccb8b2
fix(engine): probe a multiple:true reference field with a spelling its storage answers (#9437)
* fix(engine): probe a multiple:true reference field with a spelling its storage answers (#9362)
`cascadeDeleteRelations` built a bare-equality dependents filter for every
`lookup` / `master_detail` field aimed at the object being deleted, including
the ones declaring `multiple: true`. Such a field stores an array, which every
SQL backend here puts in a JSON TEXT column, so bare equality compares the whole
serialization against one id; `driver-sql` refuses that spelling with
`INVALID_FILTER` / 400 (#7398). Result: any object pointed at by any registered
`multiple: true` lookup could not be deleted at all — schema-driven, so an empty
referring table did not help. On the stock showcase that is `showcase_account`.
The driver's refusal and #8895's discriminate-or-propagate `catch` are both
correct and both untouched. The fix is at the probe's construction site: a
multi-value field is asked with `$contains`, the membership spelling the refusal
prescribes and the one every driver here answers. `$contains` is a substring
test, so the pushdown answers a superset and the rows are narrowed exactly
afterwards — element-wise, the same reading the dangling-reference audit applies
to a stored reference. An id needing JSON escaping is asked for in both stored
spellings so the guard cannot fail open on it. No filter or predicate surface is
widened, and the single-valued probe is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
* test(engine): gate the #9362 driver double on the FILTER, not on the rows
`driver-sql` raises the #7398 JSON-column refusal while COMPILING the
predicate, so an empty table refuses exactly as a full one does — which is what
makes the card's fault schema-driven. The double evaluated it per row, so with
no rows to scan it refused nothing: measured on the reverse-verification lap,
the card's own reproduction (a delete refused with an EMPTY referring table)
passed with the fix reverted. A double looser than the driver it stands in for
turns a green suite into no suite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
* test(runtime): keep the #9362 driver counts off the query-options erasure surface
`SqlDriver.count`'s query argument is optional and typed, so the four
`{} as any` / `{ where: … } as any` casts bought nothing and pushed
check:query-options-erasure's test-surface ceiling 240 -> 244. Raising that
number is a reviewed edit, not a remedy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
* fix(engine): hold back set_null on a multi-value reference instead of nulling the array
Maintainer-ruled option B, shipping with the probe repair in this PR and
explicitly a temporary holding position rather than a semantic.
Repairing the probe is what makes the `set_null` limb run for a multi-value
relationship for the first time in this codebase, and that limb writes `null`
over the WHOLE array, dropping every other member. Measured on the real stack: a
row holding ["acc_a","acc_b"] re-reads as null once acc_a is deleted. The right
semantics is "remove just the deleted member", but the residual shape when the
array empties ([] or null) is observable and unpinned; that question is tracked
in objectstack#9438. Refusing decides nothing and reverts in one `if`; writing
decides it by accident.
Shaped as the required-FK escalation directly above it rather than as a new
mechanism, and covering the explicitly authored `set_null` for the same reason
that one does: `fdef.deleteBehavior || 'set_null'` collapses the absent
declaration and the explicit spelling into a single value, so telling them apart
would be new machinery — and would leave the explicit spelling running the very
write this holds back.
No new wire code: `operation-message.ts` already rules this envelope one
DELETE_RESTRICTED with more than one sentence. The reason is developer-facing and
rides `developerMessage`, naming the hold as temporary and citing the tracking
issue literally so its removal is one grep.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
---------
Co-authored-by: os-steve <sam@objectstack.ai>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>1 parent e374b4d commit eccb8b2
4 files changed
Lines changed: 1057 additions & 5 deletions
File tree
- .changeset
- packages
- objectql/src
- runtime/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
0 commit comments