Skip to content

applyFormulaPlannowSnapshot 形参零调用者 —— 同一次 insert 里 defaultValue 的 now() 与 formula 的 now() 观察到两个不同瞬时 #5699

Description

@os-zhuang

Blocked-by: #5504(同文件在飞:PR #5698 的写路径共享水合点即本单的第三个调用点;其落地后 sweep 回队)。

观察类发现,在 #5504(PR #5698)选水合求值语义时撞到,当下没有用户会踩到,故不带 pm:queue,请 PM 分诊定级。

事实

packages/objectql/src/engine.tsapplyFormulaPlan 声明了第四个可选形参 nowSnapshot?: Date:

function applyFormulaPlan(plan, records, execCtx?, nowSnapshot?): void {
  const now = nowSnapshot ?? new Date();
  ...
}

全仓 applyFormulaPlan( 的调用点在 #5504 之前是两个(find / findOne),之后是三个(加上写路径的共享水合点),没有任何一个传 nowSnapshot。也就是说这个形参自诞生起就走的是 ?? new Date() 分支,是一段休眠代码。

为什么它当初存在

该函数自己的 docstring 写明了意图:

  • 「a now pinned ONCE per operation(每行、每个 formula 字段在一次 find 里观察同一个瞬时 —— 确定性,不做逐次求值的 new Date() 漂移)」
  • 「The eval context mirrors applyFieldDefaults so formula and default expressions see the same shape」

第一条目前由「每次调用 applyFormulaPlan 只取一次 new Date()」自然满足,所以形参不传也不影响读路径的行内确定性。第二条则只做到了一半:applyFieldDefaultsapplyFormulaPlan 各自取自己的 now

#5504 之后新增的可观察面

engine.insert 的中间件体开头已经有一个 nowSnap = new Date(),交给 applyFieldDefaults 用于解析 defaultValue 里的 NOW() / current_user 令牌。#5504 让同一次 insert 在写完之后又做了一次 formula 水合,而它按「与 find/findOne 完全一致」的原则没有nowSnap。于是同一次 insert 内:

  • defaultValue 里的 now() = 写前那一刻;
  • formula 里的 now() = 写后那一刻;

两者相差一次 driver 往返(通常毫秒级)。跨越秒/日边界时,一个「created_at 默认值」与一个「基于 now() 的 formula」理论上可以落在不同的日历日上。

需要说清楚的是:这不是 #5504 引入的新语义,读路径本来就是读时求值、与写时的 default 瞬时无关;#5504 只是让「同一次操作里两个瞬时」第一次变得可观察。PR #5698 刻意选了「与 find/findOne 逐字一致」而不是「顺手传 nowSnap」,因为分派单明确要求水合语义与读路径一致,且悄悄让写路径比读路径多一条确定性保证会造成两侧不对称。

可能的处置(留给分诊)

  1. 接上:insert 水合点传 nowSnap,让 default 与 formula 在一次写里共享瞬时。代价是写路径比读路径多一条保证,两侧语义不再逐字相同 —— 需要判定这是想要的。
  2. 退休形参:确认「一次调用一个 new Date()」就是全部需要的确定性,删掉零调用者的 nowSnapshot,并把 docstring 里 mirrors applyFieldDefaults 的措辞收窄到实际成立的范围(ADR-0049 enforce-or-remove 的精神)。
  3. 维持现状:承认毫秒级偏差不构成业务问题,但把这一段写进 docstring,免得下一个读者以为形参是活的。

倾向 2 或 3 —— 1 会制造读写不对称,而这个偏差目前没有任何被测量到的业务拉力。真正的价值在于:现在这个形参看起来是活的,任何按它推理的人都会推出错误结论。

落点

  • packages/objectql/src/engine.ts —— applyFormulaPlan 定义、其 docstring,以及 insert 中间件体里的 nowSnap

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions