Skip to content

[finding] On SQLite, applyMigrationEntries runs a FULL table rebuild even when zero entries in the batch are honourable #12132

Description

@os-warren

Filed by the domain:engine PM seat on behalf of the #11722 dev, which measured it but could not run its dedup read (its REST channel returns the session-gate 403 — see #12123) and correctly handed the filing over rather than creating a possible duplicate blind. Dedup run here with a firing positive control.

⛔ Latent, exactly like #11722 itself, and deliberately left alone by PR #12127.

The observation

On SQLite, SqlDriver.applyMigrationEntries delegates the whole batch to rebuildSqliteTablePatched, which performs a full table rebuild — create tmp table, copy every row, drop the original, rename, re-materialize defaults and the entire declared index set. After #12127 it correctly reports which entries it honoured.

But it still performs that rebuild even when the batch contains zero entries it can honour. A batch consisting only of, say, widen_varchar entries (no-ops on SQLite by dynamic typing) causes a complete copy-and-swap of the table and then reports every entry as skipped — which is now honest, and still did all that work for nothing.

Why PR #12127 did not fix it, and that was correct

Removing the rebuild would change what the reconciler does, not what it reports — and that is the clause ② line #12127 was fenced at. The rebuild is not merely a carrier for the four honourable ops:

So a batch that honours nothing is not obviously a no-op: on a table whose physical defaults or indexes have drifted from metadata, the rebuild is the thing that repairs them. Suppressing it would silently drop that repair. Whether that repair should be coupled to a drift batch at all is the real question, and it is a design decision rather than an edit.

PR #12127 parks this explicitly in rebuildSqliteTablePatched's docblock so nobody "optimizes" it without a decision.

Why it is worth a card

The cost is a full table copy — on a large table, an arbitrarily expensive one — triggered by a batch that will accomplish nothing the caller asked for. Its reachability is the same as #11722's: the differ does not emit non-rebuildable column ops on SQLite today (enforcesVarcharLength excludes SQLite; multiValueColumnTypeIsLoadBearing excludes manual_column_type_change), so the next column op that is not SQLite-rebuildable opens it — the same door, from the same two directions, and now with #11722's reporting half already closed so the operator sees skipped and no explanation for the pause.

Not claimed

Dedup

Run from the PM seat, where the search channel is live: rebuildSqliteTablePatched returns exactly one issue (#11722 itself) — a firing positive control, so the "no covering card" reading is real rather than a broken query.

Refs

#11722 (the reporting half) · PR #12127 · #11321 / #4560 / #3696 (what the rebuild re-materializes) · #12123 (why the dev could not file this itself)

Activity

  1. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    H17 trigger-file notice (spec seat, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T20:44Z): this hold's declared Restart-touch file packages/drivers/driver-sql/src/sql-driver.ts is in the file surface of #14104's dispatch (branch claude/issue-14104-field-reference-add-days; FieldReferenceSchema gains an addDays offset, compiled in the PR #7582 cross-field arm). The surface is the filter compiler only — schema-drift.ts, applyMigrationEntries and the differ are not in it, so the Restart-when condition is not expected to be met. Notification only; the card stays pm:on-hold.


    Generated by Claude Code

  2. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    关 not_planned —— 潜在问题,目前触发不到,且已写进代码注释

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T09:39Z。维护者 2026-09-23 在分诊会话里逐张复核 pm:on-hold 卡,对第六组的回复原文:「第六组其余 9 张 同意」。本卡在那一组里的建议是关闭。

    呈给维护者的事实

    • SQLite 上,applyMigrationEntries 在一批条目「一条都不能执行」时,仍然会通过 rebuildSqliteTablePatched 把整张表重建一遍(建临时表、复制所有行、换表)。这是因为这次重建还顺带重新落实默认值和索引,所以不能简单跳过。
    • 目前触发不到:差异计算器今天不会在 SQLite 上产出这种批次(正文已经说明)。卡面自己也说,第一步应该是先测一下它到底会不会发生。
    • 这个坑已经写在代码里:本地 main 上 packages/drivers/driver-sql/src/sql-driver.ts 中 rebuildSqliteTablePatched 的文档注释写着「The rebuild still runs for the WHOLE table even when it honours nothing」,不会有人在不知情的情况下去「优化」它。

    重开条件

    差异计算器开始在 SQLite 上产出一种不能靠重建完成的列操作(那样一批「全都执行不了」的条目就会真的出现);或者测到一次整表重建带来了实际的耗时问题。

    关闭理由:not_planned,同时摘掉 pm:on-hold。


    Generated by Claude Code

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions