fix(objectql): resolve the NOW() defaultValue token in the engine (#4597) - #4993
Merged
Merged
Conversation
…4597) `applyFieldDefaults` special-cased exactly two `defaultValue` shapes — the Expression envelope and the `current_user` token — and passed everything else through verbatim, so `'NOW()'` was written into the record as a literal string. `SqlDriver.formatInput`'s safety net swapped it for a real timestamp before the wire; memory and mongodb have no such net. The mirror of #4560: there `current_user` was known to the engine and not to the DDL, so the DDL stored the token text. Here `NOW()` was known to the SQL driver and not to the engine. It surfaced two ways — a validated field was rejected by the engine's own write validator against a value the engine itself had filled in, and a `readonly`/`system` field (which validateRecord skips, i.e. the ~100 platform `created_at`/`updated_at` declarations) stored the four characters `NOW()` silently. The engine now resolves the token from the same per-insert `now` snapshot it already passes to Expression defaults, so every field defaulted in one insert — and every row of one batch — carries the identical instant. The spelling it matches is the spec's (`isNowDefaultToken`), the same predicate a driver's DDL consults; the engine does not re-derive its own. Resolution follows the field's declared type, which is what `SqlDriver.nowColumnDefault` already emits per type (ADR-0053), so no datasource disagrees about the stored form: `date` -> `YYYY-MM-DD`, `time` -> `HH:MM:SS[.fff]`, everything else -> `YYYY-MM-DDTHH:MM:SS.sssZ`. Both driver-side mechanisms stay unchanged as defence in depth: the `formatInput` safety net (now unreachable from this path) and the native column DEFAULT, which still serves engine-bypassing writes — the same division of labour `current_user` has. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 13 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4597
按 issue 上的 PM 裁决取 方案 A —— 引擎解析:
applyFieldDefaults里像current_user一样特判NOW(),所有驱动拿到同一个答案。packages/spec/**与packages/plugins/driver-sql/**零改动。问题
applyFieldDefaults只认两种defaultValue:Expression 信封与current_user令牌,其余out[f.name] = dv原样落值 —— 包括'NOW()',落下去是一个字面串。SQL 驱动formatInput的兜底在上线前把它换成真实时间戳,memory / mongodb 没有这层网。这是 #4560 的镜像:那边
current_user引擎认识、DDL 不认识,于是 DDL 把令牌文本写成了列默认值;这边NOW()SQL 驱动认识、引擎不认识。实测:裂缝有两张脸,issue 只记了响亮的那张
复现时发现受影响的不止「被拒绝的插入」:
validateRecorddatetimeValidationError: … must be a valid datetime (ISO-8601)—— 引擎自己填的值,被引擎自己的校验判非法,报错还指不到根因readonly/systemif (def.system || def.readonly) continue)NOW()四个字符被静默存进列里第二行正是平台对象里那 ~100 处声明的形状(
created_at/updated_at一律readonly: true),也就是说非 SQL 数据源上它们一直在静默写坏数据 —— 与 #4560 同一个失败模式。两张脸同一处修好,测试各钉一条。顺带更正 issue 里的一句描述:「SQL 驱动有兜底」只对
readonly/system字段成立。普通字段在 SQL 上同样会被validateRecord拦下(校验发生在驱动之前,formatInput根本没机会跑),所以这条裂缝并非只影响非 SQL 数据源。修法
applyFieldDefaults自己解析该令牌:nowSnapshot(insert()里const nowSnap = new Date(),批量整批共用),所以一次插入里的每个默认字段、一批里的每一行,拿到的是同一个瞬间。isNowDefaultToken(大小写不敏感、容空白),不在引擎里另写一份 —— 否则两侧对「哪些声明算令牌」会有分歧。令牌家族盘点(裁决第 3 点)
DEFAULT_VALUE_TOKENS目前恰好两个成员,没有TODAY()之类:current_userNOW()家族已全覆盖,无部分支持残留。未发明新令牌。
按声明类型落值,不是一律 ISO 瞬间
给
Field.date塞一个完整瞬间只会把旧的跨驱动漂移换成一个新的(SQL 的formatInput仍会toDateOnly收成日历日,memory/mongodb 不会)。所以按字段声明类型解析,形状与SqlDriver.nowColumnDefault早已按类型下发的完全一致(ADR-0053),不是新造契约:dateYYYY-MM-DD(UTC 日历日)toDateOnly/ date 列默认值timeHH:MM:SS[.fff](UTC 墙钟,.000裁掉)canonicalTimeOfDaydatetime及任何非时间类型YYYY-MM-DDTHH:MM:SS.sssZcanonicalUtcDatetime生产元数据里
NOW()全部落在Field.datetime上;date/time+NOW()是 driver-sql 测试在用的受支持形状(#4022 / #3994)。驱动侧原样保留(裁决第 4 点)
driver-sql只读、零改动。formatInput的兜底留着当纵深防御(引擎路径上已到不了),nowColumnDefault继续服务绕过引擎的写入 —— 与current_user的分工一致。测试
沿用
engine-default-value-tokens.test.ts的既有惯例(#4560 建立的那份),current_user四条原样保留作回归钉。新增 9 条,覆盖裁决第 5 点全部四项:defaultValue: 'NOW()'的 datetime 插入成功,存下合法 ISO-8601-Znull仍视为「未提供」并解析([objectql] 字段 defaultValue 语义:显式 null 不回填、解析晚于 hook、表单不预填 current_user #2706)readonly字段存瞬间而不是NOW()四个字符(静默那面;该对象上只有这一个令牌字段,以免被兄弟字段的报错顶掉)now()这类宽松拼写能解析);近似拼写NOW仍是字面量date/time/datetime三种形状,且三者切自同一瞬间反向验证(防空转):把
engine.ts的改动 stash 掉重跑,9 条新增里 7 条失败,报的正是 issue 里那条ValidationError: Seen At must be a valid datetime (ISO-8601);另外 2 条是两侧都该成立的不变量(不覆盖调用方值、近似拼写不解析),因此两种情况下都通过。命令与结果:
driver-sql / driver-memory / runtime 一并跑,是因为改动后引擎会在驱动看到记录之前就填好这些字段 —— 需要确认 SQL 侧最终落库形状不变、平台对象经引擎插入的集成路径不回归。全部通过。
兼容性
授权侧无变化:
defaultValue: 'NOW()'还是原来那句声明,调用方给的值仍然永不被覆盖。变的是它在 memory / mongodb 上终于和在 SQL 上是同一个意思。修复前在非 SQL 数据源上写入的记录可能仍存着字面串NOW(),本 PR 不做回填重写。Changeset:
.changeset/now-default-token-engine-resolved.md(@objectstack/objectqlpatch)。🤖 Generated with Claude Code
https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX
Generated by Claude Code