fix(database): make v2->v3 chatlog migration deterministic and FK-safe
All checks were successful
CI Code / Check spelling (pull_request) Successful in 16s
CI Code / Check coding style (pull_request) Successful in 31s
CI Code / Code Coverage (pull_request) Successful in 3m12s
CI Code / Linux (debian) (pull_request) Successful in 4m40s
CI Code / Linux (arch) (pull_request) Successful in 6m54s
CI Code / Linux (ubuntu) (pull_request) Successful in 7m3s
All checks were successful
CI Code / Check spelling (pull_request) Successful in 16s
CI Code / Check coding style (pull_request) Successful in 31s
CI Code / Code Coverage (pull_request) Successful in 3m12s
CI Code / Linux (debian) (pull_request) Successful in 4m40s
CI Code / Linux (arch) (pull_request) Successful in 6m54s
CI Code / Linux (ubuntu) (pull_request) Successful in 7m3s
_migrate_to_v3 deduplicated duplicate archive_id rows with a bare-column
GROUP BY, which lets SQLite keep an arbitrary row per group (in practice the
older row, dropping the latest), and copied rows with SELECT *, which relies
on positional column matching between the v2 and v3 schemas.
- Keep MAX(id) per archive_id group ("latest insert wins") instead of
GROUP BY's implementation-defined pick.
- Sweep replaces_db_id / replaced_by_db_id after dedupe, NULLing references
to dropped rows so the migrated table carries no dangling XEP-0308 (LMC)
links (the "Broken LMC reference" condition verify_integrity flags).
- List columns explicitly instead of SELECT * so the copy cannot mis-bind,
and note at the schema that column changes must be mirrored here.
Refs #128
This commit is contained in:
@@ -178,6 +178,9 @@ _sqlite_init(ProfAccount* account)
|
||||
// replaced_by_db_id Inverse FK: id of the most recent correction.
|
||||
// Maintained by the AFTER INSERT trigger below so
|
||||
// readers can follow the chain forward in O(1).
|
||||
//
|
||||
// NOTE: any change to these columns (add/remove/reorder) must be mirrored
|
||||
// in `_migrate_to_v3`, which copies rows via an explicit column list.
|
||||
const char* query = "CREATE TABLE IF NOT EXISTS `ChatLogs` ("
|
||||
"`id` INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"`from_jid` TEXT NOT NULL, "
|
||||
@@ -865,11 +868,37 @@ _migrate_to_v3(void)
|
||||
"`replaces_db_id` INTEGER, "
|
||||
"`replaced_by_db_id` INTEGER)",
|
||||
|
||||
// Copy rows into the deduped table. Columns are listed explicitly
|
||||
// (instead of `SELECT *`) so the migration cannot silently mis-bind
|
||||
// should the v2/v3 column order ever diverge — keep this list in sync
|
||||
// with the `ChatLogs_v3_migration` schema above and the live `ChatLogs`
|
||||
// schema. For duplicate `archive_id` groups (MAM rebroadcasts) keep the
|
||||
// highest `id` deterministically ("latest insert wins") rather than
|
||||
// GROUP BY's implementation-defined arbitrary pick.
|
||||
"INSERT INTO `ChatLogs_v3_migration` "
|
||||
"SELECT * FROM `ChatLogs` "
|
||||
"WHERE `archive_id` IS NULL "
|
||||
"(`id`, `from_jid`, `to_jid`, `from_resource`, `to_resource`, `message`, "
|
||||
"`timestamp`, `type`, `stanza_id`, `archive_id`, `encryption`, "
|
||||
"`marked_read`, `replace_id`, `replaces_db_id`, `replaced_by_db_id`) "
|
||||
"SELECT `id`, `from_jid`, `to_jid`, `from_resource`, `to_resource`, `message`, "
|
||||
"`timestamp`, `type`, `stanza_id`, `archive_id`, `encryption`, "
|
||||
"`marked_read`, `replace_id`, `replaces_db_id`, `replaced_by_db_id` "
|
||||
"FROM `ChatLogs` WHERE `archive_id` IS NULL "
|
||||
"UNION ALL "
|
||||
"SELECT * FROM (SELECT * FROM `ChatLogs` WHERE `archive_id` IS NOT NULL GROUP BY `archive_id`)",
|
||||
"SELECT `id`, `from_jid`, `to_jid`, `from_resource`, `to_resource`, `message`, "
|
||||
"`timestamp`, `type`, `stanza_id`, `archive_id`, `encryption`, "
|
||||
"`marked_read`, `replace_id`, `replaces_db_id`, `replaced_by_db_id` "
|
||||
"FROM `ChatLogs` WHERE `archive_id` IS NOT NULL "
|
||||
"AND `id` IN (SELECT MAX(`id`) FROM `ChatLogs` WHERE `archive_id` IS NOT NULL GROUP BY `archive_id`)",
|
||||
|
||||
// Dropping duplicate rows above can leave the surviving rows' correction
|
||||
// back-links pointing at ids that no longer exist. NULL them out so the
|
||||
// XEP-0308 (LMC) chain carries no dangling local foreign keys.
|
||||
"UPDATE `ChatLogs_v3_migration` SET `replaces_db_id` = NULL "
|
||||
"WHERE `replaces_db_id` IS NOT NULL "
|
||||
"AND `replaces_db_id` NOT IN (SELECT `id` FROM `ChatLogs_v3_migration`)",
|
||||
"UPDATE `ChatLogs_v3_migration` SET `replaced_by_db_id` = NULL "
|
||||
"WHERE `replaced_by_db_id` IS NOT NULL "
|
||||
"AND `replaced_by_db_id` NOT IN (SELECT `id` FROM `ChatLogs_v3_migration`)",
|
||||
|
||||
"DROP TABLE `ChatLogs` ",
|
||||
"ALTER TABLE `ChatLogs_v3_migration` RENAME TO `ChatLogs` ",
|
||||
|
||||
Reference in New Issue
Block a user