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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions