fix(#4560): defaultValue 运行时令牌不再下发为列 DEFAULT - #4598
Merged
Merged
Conversation
The SQL DDL passed any non-object `defaultValue` through to
`col.defaultTo(dv)`, so `Field.user({ defaultValue: 'current_user' })`
was created as `DEFAULT 'current_user'` and the DATABASE stamped the
literal token into every insert the engine had deliberately left unset
(system/anonymous writes) — a non-id in a `lookup('sys_user')` column,
found by #4551's dangling-reference audit.
Declare the token family once in `@objectstack/spec/data`
(`DEFAULT_VALUE_TOKENS` + predicates) so the engine's insert-time
resolution and the driver's DDL read one set: `'NOW()'` keeps its
driver-native default, every other token emits none, literals and
Expression envelopes are unchanged. Column-default emission is now a
single `applyDeclaredColumnDefault` shared by createColumn and the
SQLite table rebuild.
Existing databases are corrected through the managed schema-drift path:
a `default_mismatch` finding with a `safe` `drop_column_default` op
(ALTER … DROP DEFAULT on pg/mysql, table rebuild on SQLite, which now
re-materializes sibling defaults from metadata). Rows already holding
the bogus value are NOT rewritten — #4551's report-never-rewrite rule.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 112 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 2, 2026 09:38
os-zhuang
enabled auto-merge
August 2, 2026 09:38
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 #4560
根因:两侧各认识一半的令牌
Field.user({ defaultValue: 'current_user' })由引擎在插入时解析;没有登录用户时(system/anonymous 写入:种子重放、包安装、启动期供给)applyFieldDefaults刻意不落值,而不是盖一个假 owner。SQL 的 DDL 从来不知道这个令牌。
createColumn把任意非对象defaultValue原样交给col.defaultTo(dv),于是列被建成DEFAULT 'current_user',数据库覆盖了引擎的决定:每一次省略该字段的插入,都往lookup('sys_user')列里写进字面串current_user—— 一个不是任何用户 id 的值。?expand解析到空;用在 owner/审批人字段上就是一条静默的错误归属。这条只能被 #4551 的悬空引用巡检事后发现:#4441 的引用完整性校验只看调用方给出的值,而这里调用方和引擎都什么都没给。
紧邻的上一个分支里
'NOW()'早就被特判成驱动原生默认值,理由完全相同 ——current_user是同一个约定家族的成员,只是在 DDL 能看见的地方从来没有登记过。修法:令牌词汇表只声明一次
新增
packages/spec/src/data/default-value-tokens.ts:DEFAULT_VALUE_TOKENS加四个判定式(isRuntimeDefaultToken/isNowDefaultToken/isCurrentUserDefaultToken/isAppResolvedDefaultToken)。引擎的插入时解析与驱动的 DDL 读同一份集合 —— 这才是缺陷本身。把令牌写在 spec 里也意味着明天新增一个令牌会自动被排除在字面列 DEFAULT 之外,而不是像这次一样把自己的拼写漏进数据库。依赖方向是干净的:
spec是叶子包,objectql与driver-sql都已经依赖它,没有引入任何新的边。列默认值的发出收敛到一个方法(
applyDeclaredColumnDefault,建列与 SQLite 重建共用):defaultValue'NOW()'{ dialect, source }既有表:走既有的 schema-drift 迁移路
已建的列带着错误的 DEFAULT,所以修正走 #2186 的托管漂移检测,而不是另起一条专用迁移:
default_mismatch发现 +drop_column_default操作,归类safe(语句不会失败,也不触碰任何行);autoMigrate: 'safe'的启动会自动 reconcile(showcase 下次启动即修正),其余环境按既定礼节由os migrate apply施加,并带可执行的告警文案;ALTER COLUMN … DROP DEFAULT;SQLite 不能就地改默认值,走既有的整表重建 —— 重建现在从 metadata 重新物化每一列的默认值,所以同表的defaultValue: 'NOW()'兄弟列不会被重建顺手抹掉(这是重建路径原本就有的隐患,本 PR 必须先堵住它才敢用)。判定只认令牌自身的拼写(
physicalDefaultIsToken会剥掉方言的引号与::type转型),一个 DBA 手工设的真实字面默认值不会被误伤。已写入的错误行不改写。 这是 #4551 的既定哲学 —— 只报告,不改写 —— 那两行继续对悬空引用巡检可见,由运维依据报告决定处置。
测试
packages/plugins/driver-sql/src/sql-driver-runtime-token-default.test.ts(11 例):current_user列建出来没有任何数据库默认值、无用户上下文的插入落 NULL 而不是字面串、'NOW()'行为不变、漂移被报为safe/drop_column_default、autoMigrate=safe的启动修正后续插入、既有错误行保持不变、重建不误伤兄弟默认值、真实字面默认值不被误判。其中一例遍历 spec 的DEFAULT_VALUE_TOKENS本身,所以 DDL 若退回硬编码单个名字会在下一个令牌加入时立刻失败。applyDeclaredColumnDefault里的令牌排除分支,上述 3 条立刻复现字面 DEFAULT(3 failed | 8 passed),恢复后 11 例全绿。packages/objectql/src/engine-default-value-tokens.test.ts(4 例):引擎侧契约 —— 有用户时盖 id、system 写入保持 unset、显式 null 视作未提供、近似拼写仍是字面量(不是别名)。packages/spec/src/data/default-value-tokens.test.ts(5 例):家族完整、NOW()大小写/空白容忍、current_user精确匹配、两类令牌的归属划分。全量:
driver-sql634 passed / 38 skipped、objectql1600 passed、spec7273 passed、driver-sqlite-wasm214 passed、service-datasource197 passed、CLIschema-migrate.integration2 passed;三个包typecheck全绿;packages/spec的check:api-surface(已gen:api-surface重生成并提交)/check:generated/check:authorable-surface/check:dual-source-exports/check:exported-any/check:spec-changes全过。顺带发现(未在本 PR 修)
盘点令牌家族时实测到同一条裂缝的镜像:
'NOW()'只有 SQL 驱动的formatInput兜底认识,引擎不认识 —— 在 memory/mongodb 数据源上,Field.datetime({ defaultValue: 'NOW()' })会让引擎填入字面串'NOW()',随后被引擎自己的写入校验以ValidationError: must be a valid datetime (ISO-8601)拒绝。已另立 #4597(未认领),其中的修法涉及「省略 datetime 字段时值由谁产生」的分工,属契约级选择,不在本 PR 自决。🤖 Generated with Claude Code
Generated by Claude Code