Repository navigation
Description
一句话
让 FieldReferenceSchema({ $field: '...' })在 SQL 下推路径上真正执行 —— 编译为列对列比较(where "amount" > "budget"),补齐 #5041 裁决 C 明确挂起的那一半。
背景:本单是 #5041 的下半场
#5041 的维护者裁决(2026-08-04)选了 C —— 最低限度:SqlDriver 遇到 { $field } 值形态时按 ADR-0112 响亮拒绝(INVALID_FILTER + httpStatus 400),消除裸 TypeError 与 $in/$between 列表里的静默零行;保留 spec 声明;真正的 SQL 实现另立本单。
也就是说,现状是已知且诚实的半态,不是遗漏:
| 执行路径 | $field 行为 |
|---|---|
内存求值 matchesFilter(packages/formula/src/matches-filter.ts) |
✅ 按记录解析引用,支持点号路径 |
SQL 下推 driver-sql / driver-sqlite-wasm |
❌ INVALID_FILTER 响亮拒绝(#5041 已落地) |
生产者是真实的:compileCelToFilter(packages/formula/src/cel-to-filter.ts)在把含字段间比较的 CEL 权限 / RLS 规则转译为 filter 时会产出 { $field: path }。所以今天这类规则只有内存路径能跑,一旦下推到 SQL 就是一个 400,而不是结果。
本单要做的事
在 packages/plugins/driver-sql 的比较发射点(applyFilterCondition / applyAstComparison)把 { $field: 'col' } 编译成列引用而非绑定值 —— SQL 层面 where ?? > ?? 是平凡的,难点全在语义边界(见下)。落地时把 #5041 装的那道拒绝闸门收窄到「仍然编译不了的形态」,而不是整个删掉。
验收面:
{ amount: { $gt: { $field: 'budget' } } }在 SqlDriver 上返回正确行集,与内存求值器对同一条 filter 的结果一致(建议做成跨路径 conformance 测试,和filter-logic-conformance/temporal-conformance同形);- 拒绝臂只保留给下面两问决定不支持的形态,且仍是
INVALID_FILTER信封; - 现有
sql-driver-cross-field-reference.test.ts([spec]$field跨字段比较:spec 声明 + cel-to-filter 产出,但无任何 SQL 执行层实现 —— enforce-or-remove 裁决位 #5041 装的)随之改写,不是删除。
两个待决语义点(本单一并议,勿默认)
1. 点号关联路径的语义
内存求值器的 getPath 对 { $field: 'account.owner_id' } 是沿对象逐层取属性。SQL 侧同一个字符串有至少三种读法:
- A. 只允许同表列,点号一律拒绝 —— 最小、最安全,但和内存路径语义不等价(同一条 filter 两条路径答案不同,正是 [spec]
$field跨字段比较:spec 声明 + cel-to-filter 产出,但无任何 SQL 执行层实现 —— enforce-or-remove 裁决位 #5041 想消除的那类分裂); - B. 点号 = 关联字段解引用,编译为 JOIN 或相关子查询 —— 与内存语义对齐,但把 filter 编译器变成了 join 规划器,代价与影响面都大;
- C. 点号 = 已 JOIN 别名的限定列引用,只在调用方已经建立该别名时有效 —— 折中,但需要一个别名契约。
选哪个直接决定 FieldReferenceSchema 的 $field 是「列名」还是「路径」,属于契约面,不应由实现顺手定。
2. 被引用列名的校验边界
$field 的值最终要进入 SQL 标识符位置。cloud#1051 讨论过:放行不校验等于拆护栏。需要明确:
- 是否只允许对象已声明的字段(
initObjects已知列),未知列一律INVALID_FILTER? - 系统列(
created_at/space/ 租户列)是否可被引用?尤其租户隔离列若可被引用,是否构成越权比较面? - 外部表 / 联邦表(ADR-0015,列集未知)上如何取舍 —— 拒绝,还是放行由数据库报错?
倾向:声明即枚举,只接受已声明字段,未知列在编译期响亮拒绝 —— 与本仓「declared = enforced」的一贯姿态一致,也让 AI 产出的元数据在写的时候就错不了,而不是在数据库里错。但这是需要维护者拍板的契约决定。
Refs
- [spec]
$field跨字段比较:spec 声明 + cel-to-filter 产出,但无任何 SQL 执行层实现 —— enforce-or-remove 裁决位 #5041(裁决 C + 完整探针证据) - objectstack-ai/cloud#1051(列名校验边界讨论)
- objectstack-ai/cloud#1058(Turso transport 静默零行,独立跟踪)
- ADR-0112 / data: unsupported-filter-operator refusal ships without error.code and leaks the [sql-driver] prefix #4436(
INVALID_FILTER信封与错误码词表)
Activity
fix(driver-sql): `$field` 跨字段比较按 ADR-0112 响亮拒绝,不再抛裸 TypeError (object…
os-zhuang commentedon Aug 6, 2026
分诊(spec 车道欠账代扫,维护者 2026-08-05 指令):挂
needs-user-decision。理由:正文自述两个待决语义点均属契约面拍板——①$field点号路径在 SQL 侧的语义(A 同表列 / B JOIN 解引用 / C 别名限定)决定FieldReferenceSchema是「列名」还是「路径」;② 被引用列名的校验边界(含租户列可否被引用的越权比较面)。填单人倾向「声明即枚举」已记录。target:v18在身,不急,随 v18 规划批量过即可;裁决后转pm:queue归 spec 车道(实现半边落 driver-sql,届时按跨域拆分处理)。不构成认领。会话:session_01N3uGFF8teXbpgtbEJ1aYXuGenerated by Claude Code
os-zhuang commentedon Aug 6, 2026
维护者裁决(2026-08-06,经本会话转达):语义点 1 取 A——v1 只允许同表列,点号路径一律
INVALID_FILTER响亮拒绝(B 的 JOIN 规划成本不成比例,C 缺别名契约);语义点 2 取声明即枚举——只接受对象已声明字段,未知列编译期拒收,租户隔离列明确禁止被引用(越权比较面),外部/联邦表(列集未知)一律拒绝。执行窗口 = 协议 18 列车(target:v18已在身),转pm:on-hold:重启条件 = v18 列车开启时由 spec 座位回捡(届时先按 #5041 的探针复核前提)。会话:session_01N3uGFF8teXbpgtbEJ1aYXuGenerated by Claude Code
os-zhuang commentedon Aug 11, 2026
Maintainer ruling (2026-08-11, spec-lane PM session), verbatim and untranslated: 「改 v17,现在派」 — re-targeted
target:v18→target:v17and dispatched now, superseding the 08-06 hold (whose restart condition was the v18 train opening; the maintainer explicitly waived it after a difficulty assessment on this seat's session). The 08-06 semantic rulings stand unchanged and bound the implementation:INVALID_FILTER), no JOIN planning, no alias contract.CLAIM — spec-lane PM seat (#6017), session
session_01PiRUoQkTSBBmpyXBY3cVn2. Branch:claude/issue-5222-field-ref-pushdown. Dispatching a cloud dev session (model: Fable — cross-path semantic equivalence with SQL three-valued NULL logic to pin, multi-position gate narrowing, and two drivers; the edge discovery is the work).Direction bounds recorded at dispatch:
$field跨字段比较:spec 声明 + cel-to-filter 产出,但无任何 SQL 执行层实现 —— enforce-or-remove 裁决位 #5041 choke point (assertCompilableComparand, sql-driver.ts ~:936) and the comparison emit sites:{ $field: 'col' }compiles to a column reference (knex??) for the six scalar comparison ops; the refusal gate is narrowed, not removed — dot paths, unknown/tenant columns, list positions ($in/$betweenmembers), string ops, and federated tables stay in theINVALID_FILTERarm (string-op support only if trivially compilable AND conformance-proven; record the call either way).:2725) and per-table tenant-isolation column (:2546).sql-driver-cross-field-reference.test.tsrewritten (not deleted) per the card; cross-path conformance suite (memorymatchesFiltervs SQL push-down, same filter same rows) shaped likefilter-logic-conformance, NULL-carrying rows pinned explicitly — SQL three-valued logic vs the memory evaluator is the one place equivalence can genuinely diverge; where they cannot be made to agree, the shape goes to the refusal arm rather than shipping a divergence.driver-sqlanddriver-sqlite-wasm(verify the base-class choke point reaches both).$field跨字段比较:spec 声明 + cel-to-filter 产出,但无任何 SQL 执行层实现 —— enforce-or-remove 裁决位 #5041's probes on current main.likeis folded to$containsat the wire — wildcards bind as literals and driver-sql'slike/ilikearm is unreachable #7536 (like-fold, same window) also touches spec filter semantics and driver-sql tests — later-lander merges main, never rebases.Generated by Claude Code
7 remaining items
fix(spec): lower equality triples with a $field comparand to {$eq: re…
fix(analytics): refuse a `{ $field }` comparand on both SQL-lowering …
fix(spec): remove FieldReferenceSchema from the $between endpoints an…
fix(service-analytics): fence `$icontains` comparands on the analytic…
feat(service-analytics): serve a `$field` RLS rule by declining nativ…
docs(objectql): name the include-relation boundary of a cross-field c…
docs(objectql): name the include-relation boundary of a cross-field c…
fix(spec): refuse a { $field } reference as a $between endpoint at th…
fix(objectql)!: `having` takes the rest of `where`'s filter doors — t…
fix(formula)!: refuse comparands that are not one value at the CEL lo…
feat(spec,lint)!: refuse an RLS or sharing-rule comparison between tw…
fix(security)!: the RLS write check refuses a field-to-field comparis…
fix(service-analytics): runtime strings state each decision in words …
test(spec): the third data/ file group's test titles state each cited…
test(spec): the fourth data/ file group's test titles state each cite…