Skip to content

delete() 的按对象前置行门在任何 kernel 托管引擎上恒真 —— ObjectQLPlugin 自带的 sys_fetch_previous_delete 以 object: '*' 注册在 beforeDelete #5929

Description

@baozhoutao

发现于 #5860 的实施核对(PD #10 范围外发现,未在任何 PR 内修改)。

事实(origin/main)

packages/objectql/src/engine.ts:6112,delete() 的前置行(pre-image)需求门:

const wantsPreImage =
  this.hasHooksFor('beforeDelete', object) ||
  this.hasHooksFor('afterDelete', object) ||
  this.getSummaryDescriptors(object).length > 0;

packages/objectql/src/plugin.ts:871,ObjectQLPlugin 自己注册的内建 hook:

{ name: 'sys_fetch_previous_delete', object: '*', events: ['beforeDelete'], priority: 5, ... }

hasHooksFor'*' 正确地当作命中每个对象,于是只要 ObjectQLPlugin 在(即任何 kernel 托管的引擎),wantsPreImage 的第一项恒真、后两项永不被求值 —— 这个门在真实部署里从来没有判过假。

实测(计数驱动,只注册这一条内建形状,完全不装 plugin-audit):对一个既无 delete hook、也无 roll-up 汇总的对象做单 id delete(),driver.findOne 增量 = 1。

为什么这不是 #5272 的重复

#5272 把 delete 侧的前置读提前到派发 beforeDelete 之前并绑定 previous,让 before/after 两个阶段共用一次读 —— 那次改动是对的,而且正因如此,内建 sys_fetch_previous_deleteif (!hookCtx.previous) 现在恒为假,它自己不再多读一次。

剩下的是另一个问题:那条 object: '*' 的注册仍然留在注册表里,而需求门是按注册面算的,于是门本身失去了判假的能力。换句话说,内建 hook 的存在理由(「引擎不填 previous」)在 delete 侧已经不成立,但它的注册还在,并且它现在唯一可观察的效果,就是让按对象门恒真。

#5860 / #5928 的关系

可选方向(交分诊定,本单不预设)

  1. 既然 单记录 delete 从不绑定 hookContext.previous —— 契约声明「for update/delete」,引擎只在 update 分支赋值;#5038 之后批量 delete 反而比单记录 delete 更完整 #5272 之后 delete 侧的 previous 已由引擎自己绑定,sys_fetch_previous_delete 是否可以直接退休?需要先确认引擎的绑定覆盖了哪些分支(单 id 有;谓词 delete 走另一条路径)。
  2. 或者让内建 hook 不参与需求门的计算 —— 例如按 packageId: 'sys:audit'hasHooksFor 中排除,或把它改成一个不经注册表的引擎内联步骤。⚠️ 前者会让「门」和「派发」的语义分叉,[17.x] 批量写按行语义实现:hook 按行触发 + record-change trigger 按行绑定 previous/record(#4800/#4862 拍板 A) #5038 的注释专门警告过这个方向,若走这条要显式论证。

Refs:#5860#5928#5272#5284#5038#5846

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions