Skip to content

update() 的前置行门是全局的(hooks.get('afterUpdate').length > 0),任一对象注册 afterUpdate 就让所有对象的单 id update 多付一次读 #5284

Description

@os-zhuang

在实现 #5272(单记录 delete 绑定 hookContext.previous)时发现,PD #10 记录,未在该 PR 内修改(越界即停)。

事实

packages/objectql/src/engine.ts,update() 单 id 分支的 demand-driven 前置行门:

if (needsPriorRecord(updateSchema as any) || (this.hooks.get('afterUpdate')?.length ?? 0) > 0) {
    const priorAst: QueryAST = { object, where: { id: hookContext.input.id }, limit: 1 };
    priorRecord = await driver.findOne(object, priorAst, hookContext.input.options as any);
}

this.hooks.get('afterUpdate') 返回的是全部 afterUpdate 注册项,不区分对象。而引擎自己另有一个按对象回答同一问题的谓词 —— hasHooksFor(event, object)(同文件,private hasHooksFor),#5038 的批量 update / 批量 delete 路径用的正是它:

if (this.hasHooksFor('afterDelete', object)) { ... }

于是同一件事在同一个文件里有两种精度:批量路径按对象问,单 id update 按全局问。

影响

只要任意一个对象注册了 afterUpdate hook,所有对象的每一次单 id update() 都会多做一次 driver.findOne。这在真实部署里不是边角情形:plugin-audit 之类的插件会在多对象上注册 afterUpdate,一旦启用,平台范围内每次单记录 update 都吃到这次读。

不是正确性缺陷 —— 门只会过度取,不会漏取,previous 的语义一律正确;纯粹是每次写多一次数据库往返。严重度我判断不准(读放大是否已在真实负载上可见,我没有测量数据),按 #4949 的纪律平铺记录,交 PM 分诊定级。

修法(若采纳)

把该门换成按对象:

if (needsPriorRecord(updateSchema as any) || this.hasHooksFor('afterUpdate', object)) {

⚠️ 收窄这条门有一个已被代码注释预告的陷阱,必须一并处理。同一处 [#4784] 的注释写着:

Deliberately: adding a "does the condition reference previous?" analysis on top would be dead code today. If this gate is ever NARROWED (e.g. scoped per object), hook conditions reading previous must be counted into the new demand test — pinned by hook-condition-previous-scope.test.ts.

也就是说,今天一个 beforeUpdate hook 的 condition 里写 previous.*,是靠「本对象或别的对象存在 afterUpdate hook」顺带捞到的前置行才求得出值的。改成按对象之后,一个只有 beforeUpdate hook、且其 conditionprevious 的对象会失去这次读,该 hook 立刻按 #4775 fail-loud 打回 —— 也就是把 #5272 刚在 delete 侧修掉的那类故障,在 update 侧造出来。所以按对象化的门必须把「本对象存在 任一 update 侧 hook(before/after 皆算)」计入需求测试,而不只是 afterUpdate

#5272 的 delete 侧新门就是按这个形状写的(hasHooksFor('beforeDelete', object) || hasHooksFor('afterDelete', object) || 有 roll-up summary),可直接对照。

关联

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