_migrate_to_v3: deterministic dedupe, explicit column list, and LMC foreign-key sweep #128
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
_migrate_to_v3: deterministic dedupe, explicit column list, and LMC foreign-key sweepLabels: database · correctness
Severity: medium (silent data loss / dangling LMC links on v2 → v3 upgrade)
Split out of #112 (items #7 and #11). Both concern a single statement in
_migrate_to_v3(src/database_sqlite.c) and are fixed by one change, so theyare tracked together here.
Problem
The v2 → v3 migration copies and deduplicates rows with:
Three defects:
Non-deterministic dedupe (was #7). SQLite's bare-column-with-
GROUP BYextension picks an implementation-defined row perarchive_idgroup. In practice it keeps the first row scanned, not the latest — verified: for a duplicate group{id=2 (old), id=3 (MAM rebroadcast)}the migration keepsid=2and dropsid=3. The choice is arbitrary and can change across SQLite versions.Dangling LMC foreign keys (was #7). When a dropped duplicate is referenced by an XEP-0308 correction chain, the surviving rows'
replaces_db_id/replaced_by_db_idare left pointing at ids that no longer exist. The migration produces exactly the "Broken LMC reference" condition that_sqlite_verify_integrityalready flags as an integrity error. The migration runs once per upgrade and is irreversible without a backup.Positional
SELECT *(was #11). The copy relies on the v2 and v3 column lists being identical in order. Any out-of-band schema change between v2 and v3 (e.g. a side branch adding a column toChatLogs) would silently mis-bind columns at migration time.Real-world incidence of (1)/(2) is low — MAM rebroadcast of the same
archive_idis rare on well-behaved servers — but the blast radius is the user's whole chat history, applied silently on first launch after upgrade.Acceptance criteria
MAX(id)perarchive_idgroup ("latest insert wins") instead of relying onGROUP BY's arbitrary pick.replaces_db_id/replaced_by_db_idand NULL out references to rows that no longer exist, so the migrated table carries no dangling local FKs (andverify_integrityreports clean).SELECT *with an explicit column list matching theChatLogs_v3_migrationschema, in every branch of the copy.ChatLogsschema noting that column changes must be mirrored in the migration's explicit column list.archive_idduplicates plus an LMC chain spanning the duplicates, runs the migration, and asserts: deterministic survivor, no dangling FKs, valid FKs preserved.Out of scope
duplicate_check_queryand the newarchive_id UNIQUE).Status / notes
A fix and a local-only regression test are already prepared (branch
fix/issue-112-followups, to be submitted as its own PR):src/database_sqlite.c_migrate_to_v3: explicit column list,id IN (SELECT MAX(id) ... GROUP BY archive_id), and twoUPDATE ... SET <fk> = NULL WHERE <fk> NOT IN (SELECT id ...)sweep statements before theDROP/RENAME.tests/migration/test_migrate_v3_dedup.sh—sqlite3-CLI test, deliberately not wired into Makefile.am/CI (one-shot migration). Passes on the fix; fails 4 assertions on the original SQL (wrong survivor + dangling FKs), confirming it guards the regression.