TL;DR
#4134 / #4164 / #4181 / #4121 把 filter 轴收口成了「要么生效,要么抛错」。同一台机器在另外三条轴上还在漏气:sort、select、expand 指向不存在的字段/关系时,一律静默不生效并返回 200。
实测(真实 ObjectQL 引擎 + 真 registry;5 行,插入顺序 C A E B D)
── SORT ──────────────────────────────────────────────
sort=title -> 200 ABCDE ✅ 真实字段,排序生效
sort=-title -> 200 EDCBA ✅
sort=no_such_field -> 200 CAEBD ❌ 与「完全不排序」逐字节相同
sort={oops (乱码) -> 200 CAEBD ❌
orderBy:[{field:不存在}] -> 200 CAEBD ❌
── SELECT ────────────────────────────────────────────
select=title -> 200 keys=id,title ✅
select=no_such_field -> 200 keys=<全部字段> ❌ 要一列,给了全部
select=title,no_such -> 200 keys=id,title ⚠️ 未知的那半被静默丢弃
── EXPAND ────────────────────────────────────────────
expand=project_id -> 200 展开为 {id,name} ✅
expand=no_such_rel -> 200 无该键,无报错 ❌
对照:filter 轴上任何一种坏输入现在都是 400(INVALID_FILTER / INVALID_FIELD / INVALID_REQUEST)。
三条轴的危害不一样,不该一刀切
sort —— 最该修。 结果集不变,所以不像 filter 那样致命;但 sort + top 正是调用方取「最新 N 条」的写法,排序被丢弃后拿到的是任意 N 条,且完全无法察觉。这与 #4181 里 export 路由 orderby 的那半是同一个 bug(那半已修:400 INVALID_REQUEST),list 路由这半没修 —— 又是一次「两条路由对同一份输入给出相反答案」。
select —— 需要先定性。 当前行为是 engine.find 的两段逻辑叠加:先丢弃未知列(注释里写明是刻意的 OData / SELECT * 容忍),再用「空投影就退回 *」防止 SQL 报错。结果是 ?select=<拼错> 要一列拿到全部 —— 一个用途是「返回更少」的参数,失败方向却是「返回更多」。是否该拒绝有两种读法:
我倾向至少把全未知投影退回 * 这一支改掉(那是纯粹的过度返回,与 FLS/数据最小化方向相反),部分未知那支可以另议。
expand —— 最轻。 不改结果集也不改字段,只是关系没展开;但同样静默。
已经钉住了
packages/objectql/src/query-expression-conformance.test.ts(随本轮工作提交)把这三条轴以 KNOWN GAP pin 测试的形式写进了 CI:每条轴都有一个「真实字段确实生效」的对照组(否则 pin 会 vacuously 通过)和一个钉住当前行为的断言。修好其中任何一条,对应的 pin 会失败 —— 那正是提示,把该用例从 KNOWN GAP 挪到上面 bad — rejected 的形状里,而不是放宽断言。
该文件同时用突变测试验证过有效性:把 #4134 / #4164 / #4181 / #4121 各自还原一次,分别挂 2 / 1 / 5 / 3 条。
关联
#4134、#4164、#4181、#4121(filter 轴四案,均已合并)、#3948(原则出处:An unapplied filter must not look like a satisfied one —— 这里是它在 sort/projection/expand 上的推论)。
TL;DR
#4134 / #4164 / #4181 / #4121 把 filter 轴收口成了「要么生效,要么抛错」。同一台机器在另外三条轴上还在漏气:
sort、select、expand指向不存在的字段/关系时,一律静默不生效并返回200。实测(真实 ObjectQL 引擎 + 真 registry;5 行,插入顺序
C A E B D)对照:filter 轴上任何一种坏输入现在都是
400(INVALID_FILTER/INVALID_FIELD/INVALID_REQUEST)。三条轴的危害不一样,不该一刀切
sort—— 最该修。 结果集不变,所以不像 filter 那样致命;但sort+top正是调用方取「最新 N 条」的写法,排序被丢弃后拿到的是任意 N 条,且完全无法察觉。这与 #4181 里 export 路由orderby的那半是同一个 bug(那半已修:400 INVALID_REQUEST),list 路由这半没修 —— 又是一次「两条路由对同一份输入给出相反答案」。select—— 需要先定性。 当前行为是engine.find的两段逻辑叠加:先丢弃未知列(注释里写明是刻意的 OData /SELECT *容忍),再用「空投影就退回*」防止 SQL 报错。结果是?select=<拼错>要一列拿到全部 —— 一个用途是「返回更少」的参数,失败方向却是「返回更多」。是否该拒绝有两种读法:我倾向至少把全未知投影退回
*这一支改掉(那是纯粹的过度返回,与 FLS/数据最小化方向相反),部分未知那支可以另议。expand—— 最轻。 不改结果集也不改字段,只是关系没展开;但同样静默。已经钉住了
packages/objectql/src/query-expression-conformance.test.ts(随本轮工作提交)把这三条轴以KNOWN GAPpin 测试的形式写进了 CI:每条轴都有一个「真实字段确实生效」的对照组(否则 pin 会 vacuously 通过)和一个钉住当前行为的断言。修好其中任何一条,对应的 pin 会失败 —— 那正是提示,把该用例从KNOWN GAP挪到上面bad — rejected的形状里,而不是放宽断言。该文件同时用突变测试验证过有效性:把 #4134 / #4164 / #4181 / #4121 各自还原一次,分别挂 2 / 1 / 5 / 3 条。
关联
#4134、#4164、#4181、#4121(filter 轴四案,均已合并)、#3948(原则出处:An unapplied filter must not look like a satisfied one —— 这里是它在 sort/projection/expand 上的推论)。