Skip to content

[finding] applyMigrationEntries reports every SQLite entry as applied after a table rebuild, including ops the rebuild is documented to ignore #11722

Description

@huangyiirene

Observation while measuring the reconciler for #11535 / #11720. Latent, not reachable today — filed so it is not discovered by the op that makes it reachable.

The shape

SqlDriver.applyMigrationEntries splits by dialect. The non-SQLite arm asks per entry and believes the answer:

for(constdofents){constok=awaitthis.applyDriftOpInPlace(d.op);(ok ? applied : skipped).push(d);}

The SQLite arm does not ask at all:

if(this.isSqlite){awaitthis.rebuildSqliteTablePatched(table,ents);applied.push(...ents);// every entry, unconditionally}

But rebuildSqliteTablePatched handles four op types (relax_not_null, tighten_not_null, drop_column, drop_column_default) and silently ignores anything else — its own docstring says so for the varchar ops: "varchar widen/narrow are no-ops on SQLite (dynamic typing) and ignored." An ignored op is still pushed into applied.

So on SQLite, "applied" means "a rebuild ran", not "this op happened". Every consumer reads it as the latter: reconcileAndWarnDrift logs auto-reconciled <op> on <table>.<col>, and the artifact boot gate prints ↪ migrated <op>. A finding reported as migrated but still present comes back on the next boot, reports as drift again, and is "migrated" again — a loop with no failing signal anywhere in it.

Why it is not reachable today

The only column ops the differ emits on SQLite are the four the rebuild handles. The varchar ops are excluded by enforcesVarcharLength (SQLite is not in it), and #11720's new manual_column_type_change is excluded by multiValueColumnTypeIsLoadBearing for an unrelated, measured reason (SQLite does not corrupt the value). So the gap is currently unreachable by construction — from two independent directions, neither of which is aware it is holding this closed.

Why it is worth a note anyway

The next column op that is not SQLite-rebuildable opens it, and the failure mode is a false green rather than an error: nothing throws, nothing is skipped, and the log says the work was done. The asymmetry is also invisible at the call site — the two arms of the same function disagree about what applied means, and only one of them can be wrong.

A minimal fix is for rebuildSqliteTablePatched to return the set of entries it actually honoured (it already partitions them into four Sets to do its work) and for the caller to push the remainder into skipped. That is sql-driver.ts, outside #11720's declared surface.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions