范围外发现,来自 #6262 / PR #6433 的实施过程(PD #10 )。#6262 与其分诊都把范围钉在 multi 分支 (「Scope as queued = route A only」、「非 multi 路径不动」),所以本条未在该 PR 内修改 ,只记录。
事实
#6262 收口的是 multi 分支的 SET 载荷。同一个缺陷在 by-id 分支 上还留着一份,触发条件是 data.id 非标量、而 options.where.id 是标量真值 —— 也就是 ENGINE_UPDATE_DISPATCH_CASES 自己列着的那一行:
{ what: 'operator object in data.id, scalar where.id — the WHERE id wins, the operator is not one',
data: { id: { $in: ['a', 'b'] }, title: 'x' },
options: { where: { id: 'rec_1' } },
expect: 'by-id', expectId: 'rec_1' }
派发是对的 (#5748 裁 A / PR #5919 :算子对象不是 id,where.id 胜出,绑定 rec_1),但载荷同样没被清理。实测(PR #6433 新增测试里对现状的钉死断言,绿):
driver.update('task', 'rec_1', { id: { $in: ['a','b'] }, title: 'x' })
^^^^^^^^^^^^^^^^^^^^^^ 进 SET 子句
驱动侧确认这确实落到 SET 而非被忽略 —— packages/drivers/driver-sql/src/sql-driver.ts:3254 的 update():
const builder = this . getBuilder ( object , options ) . where ( 'id' , id ) ;
const formatted = this . applyWriteColumnMap ( object , this . formatInput ( object , data ) ) ;
await builder . update ( formatted ) ;
formatted 由整个 data 得出,id 不在任何跳过名单里。于是 SQL 形如 UPDATE task SET id = '{"$in":["a","b"]}', title = 'x' WHERE id = 'rec_1' —— rec_1 的主键被改写成一个序列化的算子对象。
同一族、不同分支,不是重复 :
行定位
载荷里的 id
状态
#6262
where 谓词(AST)
算子对象等 ⇒ 已剥离(PR #6433 )
已修
本条
driver.update 的独立 id 参数
算子对象等 ⇒ 仍原样进 SET
未修
PR #6433 的注释与测试对 by-id 路径的说法是「主键走独立参数,载荷里的 id 是冗余而非破坏」—— 那句话对标量 data.id 成立(SET id = 'rec_1' WHERE id = 'rec_1',同值空写),对非标量 不成立,这就是本条。该 PR 已按现状把 by-id 载荷钉死,所以本条一旦修,那两个 pin 会响亮翻红,不会被悄悄改掉。
另一个更常见的入口
同样的判定阶梯下,data: { id: null, … } + 标量 where.id 也走 by-id,SET 里就带上 id = NULL:客户端 GET 一条记录、改两个字段、整体 PUT 回来,而序列化把 id 写成 null 的形状,就够了。落到 SQL 是 NOT NULL 约束报错(好的情况),或在宽松存储上留下一条主键为空的行。未实测这条端到端 (REST 层是否先行剥 id 没有查),只记录形状,严重度请分诊裁。
方向(不预设结论)
A(与 data.id 是算子对象 + multi: true 时,{"$in":[...]} 作为普通列进入 updateMany 的 SET 载荷,写向主键列 #6262 同构) :by-id 分支也剥 —— 但只剥「派发判定不是 id」的那一份;标量 data.id 保持现状(它等于绑定的主键,同值空写,且这是长期行为,动它是另一个决定)。
B :响亮拒绝非标量 data.id(无论哪条分支)—— 与 data.id 是算子对象 + multi: true 时,{"$in":[...]} 作为普通列进入 updateMany 的 SET 载荷,写向主键列 #6262 的 B 案是同一个裁决,要改 ENGINE_UPDATE_DISPATCH_CASES 里那条 expect: 'by-id',属对 ObjectQL.update 的 data.id 不做标量测试 —— 载荷里的算子对象被当成主键绑定,且盖过显式 options.multi: true #5748 裁 A 的部分回退,需要新裁决。
C :驱动侧各自跳过 id —— 不建议,五个后端各给一个答案,正是 { field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240 / sharing: DELETE /sharing/rules/:idOrName answers 500 for both address forms — rules cannot be deleted over REST #4434 那一族。
关联:#6262 / PR #6433 (multi 分支那一半)、#5748 / PR #5919 (data.id 的标量判定)、#5922 (id 之外的标量面)、#4550 / #4434 (为什么共享谓词而不是第二个答案)。
范围外发现,来自 #6262 / PR #6433 的实施过程(PD #10)。#6262 与其分诊都把范围钉在 multi 分支(「Scope as queued = route A only」、「非 multi 路径不动」),所以本条未在该 PR 内修改,只记录。
事实
#6262 收口的是 multi 分支的 SET 载荷。同一个缺陷在 by-id 分支上还留着一份,触发条件是
data.id非标量、而options.where.id是标量真值 —— 也就是ENGINE_UPDATE_DISPATCH_CASES自己列着的那一行:派发是对的(#5748 裁 A / PR #5919:算子对象不是 id,
where.id胜出,绑定rec_1),但载荷同样没被清理。实测(PR #6433 新增测试里对现状的钉死断言,绿):驱动侧确认这确实落到 SET 而非被忽略 ——
packages/drivers/driver-sql/src/sql-driver.ts:3254的update():formatted由整个data得出,id不在任何跳过名单里。于是 SQL 形如UPDATE task SET id = '{"$in":["a","b"]}', title = 'x' WHERE id = 'rec_1'—— rec_1 的主键被改写成一个序列化的算子对象。与 #6262 的关系
同一族、不同分支,不是重复:
idwhere谓词(AST)driver.update的独立 id 参数PR #6433 的注释与测试对 by-id 路径的说法是「主键走独立参数,载荷里的
id是冗余而非破坏」—— 那句话对标量data.id成立(SET id = 'rec_1' WHERE id = 'rec_1',同值空写),对非标量不成立,这就是本条。该 PR 已按现状把 by-id 载荷钉死,所以本条一旦修,那两个 pin 会响亮翻红,不会被悄悄改掉。另一个更常见的入口
同样的判定阶梯下,
data: { id: null, … }+ 标量where.id也走 by-id,SET 里就带上id = NULL:客户端 GET 一条记录、改两个字段、整体 PUT 回来,而序列化把id写成null的形状,就够了。落到 SQL 是 NOT NULL 约束报错(好的情况),或在宽松存储上留下一条主键为空的行。未实测这条端到端(REST 层是否先行剥id没有查),只记录形状,严重度请分诊裁。方向(不预设结论)
data.id是算子对象 +multi: true时,{"$in":[...]}作为普通列进入updateMany的 SET 载荷,写向主键列 #6262 同构):by-id 分支也剥 —— 但只剥「派发判定不是 id」的那一份;标量data.id保持现状(它等于绑定的主键,同值空写,且这是长期行为,动它是另一个决定)。data.id(无论哪条分支)—— 与data.id是算子对象 +multi: true时,{"$in":[...]}作为普通列进入updateMany的 SET 载荷,写向主键列 #6262 的 B 案是同一个裁决,要改ENGINE_UPDATE_DISPATCH_CASES里那条expect: 'by-id',属对 ObjectQL.update 的data.id不做标量测试 —— 载荷里的算子对象被当成主键绑定,且盖过显式options.multi: true#5748 裁 A 的部分回退,需要新裁决。id—— 不建议,五个后端各给一个答案,正是{ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240 / sharing: DELETE /sharing/rules/:idOrName answers 500 for both address forms — rules cannot be deleted over REST #4434 那一族。关联:#6262 / PR #6433(multi 分支那一半)、#5748 / PR #5919(
data.id的标量判定)、#5922(id之外的标量面)、#4550 / #4434(为什么共享谓词而不是第二个答案)。