fix(driver-sql): $field 跨字段比较按 ADR-0112 响亮拒绝,不再抛裸 TypeError (#5041) - #5223
Merged
Conversation
…FILTER (#5041) `FieldReferenceSchema` (`{ $field: '...' }`) is declared in `packages/spec` and really is produced — `compileCelToFilter` emits it for a field-to-field comparison in a CEL permission/RLS rule — but the only implementation is the in-memory evaluator. Pushed down to SQL, the reference object was handed to Knex as a BIND VALUE, so sqlite answered with a bare `TypeError` carrying no `code` and no `status`: outside the ADR-0112 envelope every sibling filter refusal in this driver already speaks (#4436), and an opaque server error on the wire. Inside an `$in` / `$nin` / `$between` list it was worse than a crash: the member compiled and the query returned ZERO ROWS. A silent wrong answer on a permission-scoped read is the failure #3948 / #4209 exist to prevent. Both now refuse with the full envelope (`INVALID_FILTER`, HTTP 400, no `[sql-driver]` prefix), naming the field, the operator and the referenced field, and stating that cross-field comparison is currently supported only on the in-memory evaluation path (`matchesFilter`). The gate runs at all three comparison emitters, so the Filter Protocol and array-triple spellings of one condition get one answer. The same choke point closes the general arm the issue reported as missing: a KNOWN operator whose comparand is a shape no dialect can bind (a plain object or an array in a scalar comparison position) was measured to be the same bare `TypeError`, and now returns `INVALID_FILTER` too. Scoped to scalar comparison operators, so the legitimate array binds of `$in` / `$nin` / `$between` are untouched. `FieldReferenceSchema` keeps its declaration — it has a real producer and a real implementation, so it is not a dead key. Its JSDoc now records the execution support surface (memory evaluates, SQL refuses loudly) and links #5222, which tracks compiling it to a column-to-column comparison along with the two open semantic questions (dot-path relation references, referenced-column validation boundary). Refs #5041, #5222, objectstack-ai/cloud#1051, objectstack-ai/cloud#1058 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
- 列表位置的失效描述改为实测所见(查询编译并返回零行),不再断言未捕获的 SQL 文本; - 「Filed separately」改为如实说明:LIKE 家族与 $in 非 $field 对象成员的静默零行是 另一类缺陷、方向 fail-closed,本 PR 有意不扩,测量记录在 #5041 的 PR 讨论中。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
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.
Fixes #5041
按维护者 2026-08-04 的裁决 C(最低限度,响亮拒绝) 实施:消除静默半态,保留声明,真正的 SQL 列对列实现另立能力单。
问题
FieldReferenceSchema({ $field: '...' })在 spec 里声明了,并且真的有生产者——compileCelToFilter在转译含字段间比较的 CEL 权限 / RLS 规则时会产出它。但全仓唯一实现是内存求值器。下推到 SQL 时,这个引用对象被当作绑定值交给 Knex:该错误既无
code也无status,落在本驱动其它 filter 拒绝一致遵守的 ADR-0112 信封(#4436)之外,到客户端表现为不透明的服务端错误。实测发现两件 issue 未记录的事
落地前先做了探针测量,结果改变了改动范围:
数组三元组写法有同样的缺陷。
[['amount', 'gt', { $field: 'budget' }]]走的是另一个发射点(applyAstComparison),同样抛裸TypeError。一个 filter 条件不该因为写法不同得到两种答案,所以闸门装在全部三个比较发射点上。列表位置比崩溃更糟:静默零行。
$in/$nin/$between里的$field成员连报错都没有,查询正常返回、结果为空。权限域读取上的静默错误答案,正是 A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948 / fix(data): a filter the server cannot apply is rejected, not silently ignored (#4181) #4209 要根除的那一类,因此一并纳入拒绝(报错点名成员下标)。改动
1. 响亮拒绝(裁决 C 第 1 条)
{ $field }出现在任何比较操作数位置(含列表成员)时,以完整信封拒绝:error.code = INVALID_FILTER、HTTP 400、无[sql-driver]前缀,消息点名字段、运算符、被引用字段,并说明跨字段比较当前仅内存求值路径(matchesFilter)支持。识别
$field的谓词刻意与@objectstack/formula的resolveValue保持逐字一致——两条执行路径必须对「什么是字段引用」有同一个认识,只在「拿它怎么办」上分岔(内存求值 / SQL 拒绝)。驱动若认得比求值器更窄的形状,剩下的又会被当字面值绑定,正是本单要关掉的那个洞。2. 通用臂(裁决要求先测量再决定)
issue 指出「已知运算符 + 值形态编译不了」这条路径完全没有拒绝臂。实测:标量比较位上的普通对象、数组、
{}全部是同一个裸TypeError。已纳入,同一闸门、同一信封。作用域刻意收窄到标量比较运算符(
$eq/$ne/$gt/$gte/$lt/$lte及其中缀拼写),因此$in/$nin/$between的合法数组绑定完全不受影响——它们走各自的whereIn/whereBetween臂,只有成员被检查(且只查$field),元数不管,原有的$between描述性报错保持原样。未纳入(避免超出实测范围的行为变更,见下方遗留发现):
LIKE家族的对象操作数。3. 保留声明(裁决第 2 条)
FieldReferenceSchema不动,仅补 JSDoc 记录执行支持面(内存求值 ✅ / SQL 下推 ❌ 响亮拒绝)并链接 #5222。纯注释改动,形态与姿态均未变,check:generated8 项产物全部无漂移,未触碰任何台账文件。4. v18 能力单(裁决第 3 条)
#5222 —— SqlDriver 编译
$field为列对列比较,两个待决语义点(点号关联路径语义、被引用列名校验边界)一并在那单议。遗留发现(未在本 PR 修,供后续判断)
$in成员为非$field的普通对象、以及$startsWith等LIKE家族的对象操作数,目前仍静默返回零行(LIKE家族会把操作数String()成[object Object])。这是另一类缺陷——filter 被无意义地应用了,而不是无法应用——且方向是 fail-closed(收窄结果,非放宽),不构成 filter 绕过。把闸门扩到这里会改变当前不抛错路径的行为,超出 #5041 的实测范围,故留待单独判断。验证
pnpm --filter @objectstack/driver-sql testpnpm --filter @objectstack/driver-sqlite-wasm test(继承本 filter 编译器)pnpm --filter @objectstack/spec testpnpm --filter @objectstack/formula test(负对照)pnpm --filter @objectstack/driver-sql --filter @objectstack/spec typecheckpnpm --filter @objectstack/spec check:generated新增
sql-driver-cross-field-reference.test.ts(30 例):断言完整信封(code+status+ 消息内容),而非只断言抛了什么类型;覆盖 issue 原始形态、各运算符拼写、$and/$or/$not嵌套、数组三元组、列表成员、通用臂;并有一组「合法操作数不受影响」的守护(标量等值、真实$in/$nin/$between、Date、null谓词、数组三元组)。负对照:内存求值器对同一条 filter 仍正确匹配 ——
packages/formula/src/matches-filter.test.ts的$field reference (field-to-field)单独跑通过,本 PR 未触碰求值器与cel-to-filter生产者。Refs
$field编译为列对列比较(cross-field comparison push-down) #5222 列对列实现)INVALID_FILTER信封)https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Generated by Claude Code