Skip to content

drivers(sql): 共享 canonical 修复表达式把「裸数字文本」当儒略日读,backfill 会把这个误读写进盘里(观察项) #6009

Description

@os-zhuang

Restart-when: any PR touches sqliteCanonicalDatetimeSql or backfillCanonicalDatetimes in packages/drivers/driver-sql/src/sql-driver.ts (price the backfill-SET-side guard, NOT the cloud#1005-rejected shared-read heuristic), or unresolvedEpochTextRows counts non-zero on real data, or a bare-numeric-TEXT Field.datetime site is reported

观察项,不是今天有人会撞上的缺陷 —— 在做 #5770(driver-turso remote backfill,PR #6006)时实测到,顺手记录。不建议在没有真实业务拉动前动手

实测

SqlDriver.sqliteCanonicalDatetimeSql 的 else 分支是 coalesce(strftime('%Y-%m-%dT%H:%M:%fZ', col), col)。SQLite 的 strftime 接受裸数字并按儒略日解释,于是一个 Field.datetime 列里的裸数字文本会被读成公元前的日期(better-sqlite3 13.0.2 实测):

'12'                       -> -4713-12-06T12:00:00.000Z
'0'                        -> -4713-11-24T12:00:00.000Z
'1999'                     -> -4707-05-15T12:00:00.000Z
'2026'                     -> -4707-06-11T12:00:00.000Z
'86400'                    -> -4476-06-15T12:00:00.000Z
'2440587.5'                -> 1970-01-01T00:00:00.000Z   (儒略日纪元,确实是这个语义)
'2026-08-06'               -> 2026-08-06T00:00:00.000Z   (正常)
'2026-08-06T00:00:00.000Z' -> 2026-08-06T00:00:00.000Z   (正常,不动点)

coalesce 的兜底在这里帮不上忙:strftime 不返回 NULL,它返回了一个自信的错误答案,所以「解析不了的值原样保留」这条设计意图在这一支上不成立。

为什么今天基本撞不上

需要「Field.datetime 列里存着裸数字 TEXT」。两种 affinity 的行为不同:

  • local / knex datetime 列是 NUMERIC 亲和:'12' / '2026' 入库即被转成 INTEGER(实测 typeofinteger),于是走的是表达式的 integer/real 分支(按 epoch 毫秒),不是儒略日分支。所以 local 基本绕开了。
  • remote / RemoteTransport.mapFieldTypeToSQL 把时间列声明为 TEXT:值保持 TEXT,儒略日分支就会命中。这是唯一比较容易到达的一侧。

而要有这种值,本身就得是脏数据(合法的 canonical 拼写一定带 - / : / T / Z)。

值得记一笔的地方:读是临时的,backfill 是永久的

误读本身自 #3912 起就存在,但那时它只是每次读临时算出来,盘上的原始字节还在。backfillCanonicalDatetimes 的 SET 表达式就是同一个表达式,所以它会把这个儒略日误读写回磁盘,原值不可恢复。PR #6006 让 remote 也有了 backfill,于是这条路径在 remote 侧也从「临时误读」变成了「落盘」。

需要说清楚的是:#6004/#6006 没有改变任何查询答案 —— backfill 写下去的正是读路径本来就返回的值(这也是那个模块的核心不变量)。变的只是「原始字节是否还留着」。#6006 的 epoch 恢复限带在 1e12 ≤ v 小于 4102444800000,正是为了不去碰这类小数字,把它们留给共享表达式并计入 unresolvedEpochTextRows

如果将来要处理

倾向不要在共享表达式里加启发式 —— 维护者 2026-08-03 在 cloud#1005 已就近似的提案裁过否决(公共契约 + 每次读都跑 + 会误读合法的数字字符串列)。真要做,更像是在 backfill 的 SET 侧加一道「不把 strftime 的儒略日结果写下去」的守卫(纯迁移期、一次性、可按声明类型收窄),读路径保持原样。

判断留给 PM/维护者分诊。

参考:packages/drivers/driver-sql/src/sql-driver.tssqliteCanonicalDatetimeSql / backfillCanonicalDatetimes;ADR-0053 storage-form 轴(#4191)。


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions