feat(driver-turso): remote 模式补 canonical 时间列 backfill 通道(分批、可恢复、完成标记) (#5770) - #6006
Merged
Merged
Conversation
…umable, marked only when proved (#5770) `SqlDriver.backfillCanonicalDatetimes` / `backfillCanonicalTimes` are Knex paths, and TursoDriver's remote mode routes all DDL and CRUD over `@libsql/client`, so they never ran there. `canonicalDatetimeFields` / `canonicalTimeFields` stayed empty in remote mode, `needsLegacyDatetimeRepair` was permanently true, and every `Field.datetime` / `Field.time` filter compiled to the unindexable repair CASE with no exit (cloud#1005 consequence A). Adds `remote-canonical-backfill.ts` and `TursoDriver.backfillRemoteCanonicalTemporal()`, run automatically after remote `initObjects` / `syncSchema` — the position local mode calls its backfill from: - batched via `rowid IN (SELECT ... LIMIT ?)` (portable; `UPDATE ... LIMIT` needs a compile-time option libSQL does not promise); - resumable with no checkpoint state — the WHERE guard IS the checkpoint; - marks canonical only when a post-pass probe proves nothing is left for either phase to change, writing to the same maps `needsLegacyDatetimeRepair` reads; - recovers TEXT-affinity numeric epochs (consequence B) by feeding `cast(col as real)` back into the driver's OWN expression, so no second epoch conversion rule is introduced; - interprets only 1e12 <= v < 4102444800000, which puts every plausible epoch-SECONDS value out of band; anything outside is left on disk and counted. Correctness posture is unchanged (ADR-0053 D-B3 / cloud#1003): every failure mode degrades to "column unmarked, reads keep the repair, answers correct", and nothing may depend on the backfill having run. The two remote legacy conformance sweeps now clear the canonical marker explicitly, as `driver-sql`'s `LegacyStorageDriver.forgetCanonical` does, and assert the repair still applies — "un-backfilled" used to hold by accident because remote had no backfill at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 6 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 6, 2026 14:37
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 #5770
承接 cloud#1005,按维护者 2026-08-03 的方案 1 裁定落地。方案 2(DDL 亲和性对齐)等 staging 存量探针另议、不在本单;方案 3(在
@objectstack/driver-sql公共表达式里加启发式)已被否决,本 PR 未触碰该表达式一个字节。前提复核(基于 origin/main
d82b85fee,含 #5921 拒收闸)派单前先在 origin/main 上实测,两个后果都成立,但后果 B 的实际形态比原单描述更尖锐,如实记录在下面。
后果 A —— 成立。 remote 模式下:
registerExternalObject会填datetimeFields/timeFields,但从不填 canonical 标记 —— 因为填标记的只有 Knex 路径的backfillCanonicalDatetimes,remote 永不经过。于是修复表达式恒定生效:正确,但不可索引,且没有出口。后果 B —— 成立,但形态与原单不同。 原单记为「这一群 legacy 行在 remote 任何 filter 都匹配不到」。实测:
'1753660800000.0'(TEXT 亲和下的 pre-#942 epoch)确实永远转不过来 —— 它是共享修复表达式的不动点(typeof分支进不去,strftime解析不了,coalesce原样返回)—— 但它并非匹配不到任何 filter,而是按 TEXT 比较进了错误的窗口,双向都错:字典序上
'1753660800000.0'排在所有'2…'之前,所以是假阴 + 假阳同时存在。成因与修法与原单一致,只是描述需要更准 —— 这一条也写进了新套件的文件头。落地
新增
packages/drivers/driver-turso/src/remote-canonical-backfill.ts与TursoDriver.backfillRemoteCanonicalTemporal(),在 remote 的initObjects/syncSchema之后自动运行(与 local 在initObjects里调用 backfill 的位置一一对应),同时公开供运维用更大预算把大表跑到底。UPDATE至多改batchSize行,借rowid IN (SELECT … LIMIT ?)子查询限量。没有用UPDATE … LIMIT,那需要 libSQL 不保证的编译选项;buildCreateTableSQL从不发WITHOUT ROWID,所以 rowid 一定在。canonicalDatetimeFields/canonicalTimeFields—— 与 local 完全相同的消费点(needsLegacyDatetimeRepair),两种 transport 因此靠同一条规则拿回可索引形态,而不是两条。cast(col as real)喂回驱动自己的表达式 —— typeof 变成 'real',于是走它原本就有的 integer/real 分支。因此本仓不存在第二套 epoch 转换规则,「canonical 是什么」仍然只有一个定义。'1753660800'(2025-07-28)静默改写成 1970-01-21(已实测)。界外的行原样留在盘上并计入unresolvedEpochTextRows,不猜。设计中被自己的测试纠正的一处
完成标记最初只看「非不动点行数」。写用例时发现这是错的:未转换的 TEXT epoch 恰恰是共享修复的不动点,所以一张全是 legacy epoch 行的表测出来残留为 0,会被判定为「已完成」→ 标记 canonical → 而已标记的列下次会被跳过 → 这批行永远转不过来。现在的门是两个计数(残留 + 在界内待转),用例
a budget-stopped run withholds the mark把这个陷阱钉死并写明了原因。正确性姿态不变(ADR-0053 D-B3 / cloud#1003)
backfill 是性能出口,不是正确性前提。任何失败(远端不可达、标识符非法、预算耗尽)都只导致该列不被标记、读侧继续带修复、答案照旧正确,且不会让 boot 失败 —— 连非法标识符都是 report 而非 throw。用例里有「标记与不标记答案完全一致」的直接断言。
验证
基线 691 条,新增 20 条。
反向验证(方向事先预判,结果与预判一致):摘掉
initObjects/syncSchema两处自动调用后,断言新能力的 6 条转红;两条 legacy conformance sweep 保持绿(它们现在显式清标记,与接线正交);直接调模块的分批/续跑/D-B3/标识符用例也保持绿(不经过接线)。恢复后 711/711 全绿。fixture 处置
turso-remote-temporal-conformance.test.ts的两条 legacy sweep 原本带一句注释:「remote 模式没有 backfill 会把列标成 canonical,所以不需要像 local 那样清标记」。本 PR 让这句话失效,所以两条 sweep 现在:LegacyStorageDriver.forgetCanonical同一做法与同一理由);needsLegacyDatetimeRepair为 true)。这不是削弱用例,而是让 fixture 真正建立它名字所宣称的状态 —— 此前「未 backfill」是因为压根没有 backfill 而碰巧成立;现在它是 fixture 必须自己声明的状态,并且是一个真实可达的状态(backfill 撞上批次预算或跑不起来的库),正是 D-B3 要求继续答对的那一个。
已按消费半径扫描过闸外:仓内 canonical 标记的消费方只有 driver-sql / driver-sqlite-wasm / driver-turso 三处,前两者未触碰;仓内也没有 driver-turso 之外的 remote 模式时间列 fixture。
⛔ 未触碰 #5921 的条件层拒收闸与
buildWhereSQL;⛔ 未改content/docs/releases/。Generated by Claude Code