Skip to content

fix(driver-memory,driver-mongodb): one storage form per temporal field type (#4047) - #4060

Merged
os-zhuang merged 1 commit into
mainfrom
claude/dashboard-date-range-data-loss-b7qt5o
Jul 30, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/dashboard-date-range-data-loss-b7qt5o

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #4047。ADR-0053 D-B(#3912,SQL 侧「每方言单一存储形态」)的非 SQL 对应物;续 #4041 / #4048(边界语义)。

验收门(先红后绿)

两个驱动各一份行结果探针,在未动实现前先跑红:

修复前 修复后
mongodb {$gte:'2026-04-29', $lte:'2026-07-28'} ['s_evening','s_morning'](Date 行全不可见) 4 行全中
mongodb Date 比较值 ['d_midnight','d_yesterday'](字符串行全不可见) 4 行全中
memory 同上 8 条断言红 9/9 绿

mongodb 探针跑在真实 MongoDB 上(mongodb-memory-server,本环境可用),不是形状断言。

根因

MongoDB 按 BSON 类型桶比较,mingo 对 JS 类型照搬同一规则:字符串比较值与 Date 值互不匹配,所有操作符都如此($gte 也不例外)。SQLite 的 affinity 至少还留下一半可达;这里窗口是「谁的类型对上就答谁」。

而两个 datetime 列确实是混的:驱动自己的 created_at/updated_at 默认值写一种形态(mongo 是 new Date(),memory 是 ISO 文本),REST/JSON 写入、相对日期 token、initialData 夹具供另一种。mongo 上 dashboard 默认窗口(字符串边界 × BSON Date 的 created_at)返回空集——比 #3777 的「丢一天」更糟。

每驱动一个 canon,按该存储原生有什么来选

驱动 datetime 理由
driver-mongodb BSON Date 方言原生瞬时类型:可索引、可范围查询、无时区歧义。这个驱动的 timestamptz
driver-memory canonical UTC ISO 文本 无原生瞬时类型;ISO-8601 UTC 在 mingo 所做的纯字符串比较下按时间序排列,且是 wire form,JSON 持久化往返不变

Field.date 两边都保持时区无关文本——转成瞬时等于凭空造一个午夜并把它重新绑到某个时区。

写路径与过滤路径共用同一规则:mongodb 的 create/update/updateMany/bulkCreate/bulkUpdate 转换,translateFilter 接一个 temporal-kind resolver(find/deleteMany/聚合 $match 全覆盖);memory 两条 lowering 与三个写路径同理。

三个承重细节

  1. 未声明的对象原样不动。 两者都是 schemaless 存储,字段类型来自 syncSchema(驱动唯一被交付对象定义的地方)。从值猜类型会去强转平台从未声明拥有的数据——有专门测试钉住。
  2. memory 会追认已在表里的行:schema 到达时收敛存量行,这才捕获得到 initialData 夹具与持久化恢复(两者都早于任何 schema 声明)。是 backfillCanonicalDatetimes 的内存版,同样幂等。mongo 不做自动 backfill——重写真实 collection 是迁移不是启动步骤,PR 描述里明说了这条边界。
  3. 与 dashboard 的日期区间上界打在 datetime 列上丢失当天数据 —— 默认配置即命中 #3777/driver-memory / driver-mongodb:裸日期 $lte 上界在 datetime 值上同样丢当天数据(#3777 的非 SQL 驱动对齐) #4042 的顺序是承重的:日历日上界改写是日历操作,先在裸日期字符串上跑,只有产出的边界才转存储形态。反过来会把 Date 交给 nextUtcCalendarDay(它会正确地拒绝放宽),整天窗口就悄悄缩回一个瞬时。

覆盖

每驱动一套:混合形态窗口、Date 比较值、收敛后的边界语义($gte/$lt/完整 ISO 不放宽)、$between + 数组拼写、date 列不被放宽、未声明对象透传;memory 另有 initialData 追认与 update() 形态两条。

一处测试自纠:update() 最初我拿 created_at 做被测字段,红了——该驱动有意让 created_at 不可变(每次 update 都重新保留原值)。改用可变 datetime 字段,并在注释里记下这个语义。

测试

driver-memory 194 ✅(含新 9)/ driver-mongodb 107 ✅(含新 7);两包 turbo build(带 dts)✅;改动文件 eslint 干净。既有断言零改动。

构建期修了一处自己引入的类型收窄:toStorageForms 显式返回 Record<string, any> 让 update() 的 | null 不再被 any 吞掉,破坏了 IDataDriver 兼容——改为保型泛型。

关联

  • ADR-0053 新增 D-E 附录(D-E1 类型桶、D-E2 每驱动 canon、D-E3 存量数据与顺序、D-E4 对 D-A3 的影响)。
  • 仍开放:@objectstack/formula.matchesFilterCondition(RLS 写侧 check,第五个求值面)因层级约束(不依赖 core)未对齐 D-D/D-E 两条规则,归属待定(私有拷贝 vs 工具下沉 spec/types),已记入 ADR D-E4。

🤖 Generated with Claude Code

https://claude.ai/code/session_01EkL3RGJrzjLEGURsLxfS2f


Generated by Claude Code

…d type (#4047)

The non-SQL counterpart of ADR-0053 D-B (#3912). Both drivers let the
writer decide a datetime value's runtime type, and both compare across
types by type bracket rather than by value — so a string comparand never
matched a Date value, in either direction, for EVERY operator including
$gte. SQLite's affinity rules at least left one half reachable; here a
window silently answers with whichever half matches the comparand type.

Both columns really were mixed: each driver's own created_at/updated_at
default writes one form (new Date() on mongo, ISO text in memory) while
REST/JSON writes, relative-date tokens and initialData fixtures supply
the other. On MongoDB the dashboard's default window — string bounds
against a BSON-Date created_at — returned NOTHING, worse than the
final-day loss #3777 fixed.

One canon per driver, chosen by what the store natively has, applied on
write and to every filter comparand:

- driver-mongodb: BSON Date (the dialect's native instant, its
  timestamptz) — create/update/updateMany/bulkCreate/bulkUpdate convert,
  and translateFilter takes a temporal-kind resolver so find(),
  deleteMany and the aggregation $match all coerce comparands.
- driver-memory: canonical UTC ISO text (sorts chronologically under the
  string comparison mingo performs; survives JSON persistence) — create/
  update/updateMany convert, and both filter lowerings coerce.

Both learn their temporal fields from syncSchema; an undeclared object
is left exactly as written — schemaless stores, so guessing a type from
a value would coerce data the platform never claimed. driver-memory also
converges rows already in the table when the schema arrives, catching
initialData and persistence restores (the in-memory analogue of
backfillCanonicalDatetimes, idempotent like it).

Field.date stays timezone-naive text on both — an instant would invent a
midnight and re-couple it to a zone. Ordering where this meets #3777/
#4042 is load-bearing: the calendar-day rewrite runs on the bare-day
STRING first, and only the resulting bound is converted to storage form.

Tests: row-result probes on both, red before the fix — mixed-form
windows, Date-object comparands, bound semantics on top of the converged
storage, $between + array spelling, date-column non-widening, undeclared
passthrough; mongodb runs against a real MongoDB (mongodb-memory-server).
ADR-0053 gains the D-E addendum.

Closes #4047

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkL3RGJrzjLEGURsLxfS2f
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 30, 2026 7:31am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/xl labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/driver-memory, @objectstack/driver-mongodb.

10 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb)
  • content/docs/deployment/vercel.mdx (via @objectstack/driver-memory)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/driver-memory)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/driver-memory)
  • content/docs/permissions/authentication.mdx (via @objectstack/driver-memory)
  • content/docs/plugins/index.mdx (via @objectstack/driver-memory)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb)
  • content/docs/releases/implementation-status.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

driver-memory / driver-mongodb:Field.datetime 无单一存储形态,跨类型比较恒 false —— #3912 的非 SQL 版

2 participants