deltachat/sql/
migrations.rs

1//! Migrations module.
2
3use std::collections::BTreeMap;
4use std::collections::BTreeSet;
5use std::time::Duration;
6
7use anyhow::{Context as _, Result, ensure};
8use deltachat_contact_tools::EmailAddress;
9use deltachat_contact_tools::addr_cmp;
10use pgp::composed::SignedPublicKey;
11use rusqlite::OptionalExtension;
12
13use crate::config::Config;
14use crate::configure::EnteredLoginParam;
15use crate::constants::ShowEmails;
16use crate::context::Context;
17use crate::imap;
18use crate::key::DcKey;
19use crate::log::{info, warn};
20use crate::login_param::ConfiguredLoginParam;
21use crate::message::MsgId;
22use crate::provider::get_provider_by_domain;
23use crate::sql::Sql;
24use crate::tools::{Time, inc_and_check, time_elapsed};
25
26const DBVERSION: i32 = 68;
27const VERSION_CFG: &str = "dbversion";
28const TABLES: &str = include_str!("./tables.sql");
29
30#[cfg(test)]
31tokio::task_local! {
32    static STOP_MIGRATIONS_AT: i32;
33}
34
35pub async fn run(context: &Context, sql: &Sql) -> Result<(bool, bool, bool)> {
36    let mut exists_before_update = false;
37    let mut dbversion_before_update = DBVERSION;
38
39    if !sql
40        .table_exists("config")
41        .await
42        .context("failed to check if config table exists")?
43    {
44        sql.transaction(move |transaction| {
45            transaction.execute_batch(TABLES)?;
46
47            // set raw config inside the transaction
48            transaction.execute(
49                "INSERT INTO config (keyname, value) VALUES (?, ?);",
50                (VERSION_CFG, format!("{dbversion_before_update}")),
51            )?;
52            Ok(())
53        })
54        .await
55        .context("Creating tables failed")?;
56
57        let mut lock = context.sql.config_cache.write().await;
58        lock.insert(
59            VERSION_CFG.to_string(),
60            Some(format!("{dbversion_before_update}")),
61        );
62        drop(lock);
63    } else {
64        exists_before_update = true;
65        dbversion_before_update = sql
66            .get_raw_config_int(VERSION_CFG)
67            .await?
68            .unwrap_or_default();
69    }
70
71    let dbversion = dbversion_before_update;
72    let mut update_icons = !exists_before_update;
73    let mut disable_server_delete = false;
74    let mut recode_avatar = false;
75
76    if dbversion < 1 {
77        sql.execute_migration(
78            r#"
79CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');
80CREATE INDEX leftgrps_index1 ON leftgrps (grpid);"#,
81            1,
82        )
83        .await?;
84    }
85    if dbversion < 2 {
86        sql.execute_migration(
87            "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
88            2,
89        )
90        .await?;
91    }
92    if dbversion < 7 {
93        sql.execute_migration(
94            "CREATE TABLE keypairs (\
95                 id INTEGER PRIMARY KEY, \
96                 addr TEXT DEFAULT '' COLLATE NOCASE, \
97                 is_default INTEGER DEFAULT 0, \
98                 private_key, \
99                 public_key, \
100                 created INTEGER DEFAULT 0);",
101            7,
102        )
103        .await?;
104    }
105    if dbversion < 10 {
106        sql.execute_migration(
107            "CREATE TABLE acpeerstates (\
108                 id INTEGER PRIMARY KEY, \
109                 addr TEXT DEFAULT '' COLLATE NOCASE, \
110                 last_seen INTEGER DEFAULT 0, \
111                 last_seen_autocrypt INTEGER DEFAULT 0, \
112                 public_key, \
113                 prefer_encrypted INTEGER DEFAULT 0); \
114              CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);",
115            10,
116        )
117        .await?;
118    }
119    if dbversion < 12 {
120        sql.execute_migration(
121            r#"
122CREATE TABLE msgs_mdns ( msg_id INTEGER,  contact_id INTEGER);
123CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);"#,
124            12,
125        )
126        .await?;
127    }
128    if dbversion < 17 {
129        sql.execute_migration(
130            r#"
131ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;
132CREATE INDEX chats_index2 ON chats (archived);
133ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;
134CREATE INDEX msgs_index5 ON msgs (starred);"#,
135            17,
136        )
137        .await?;
138    }
139    if dbversion < 18 {
140        sql.execute_migration(
141            r#"
142ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;
143ALTER TABLE acpeerstates ADD COLUMN gossip_key;"#,
144            18,
145        )
146        .await?;
147    }
148    if dbversion < 27 {
149        // chat.id=1 and chat.id=2 are the old deaddrops,
150        // the current ones are defined by chats.blocked=2
151        sql.execute_migration(
152            r#"
153DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;
154CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);
155ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
156ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;"#,
157            27,
158        )
159        .await?;
160    }
161    if dbversion < 34 {
162        sql.execute_migration(
163            r#"
164ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;
165ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
166ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';
167ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';
168CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
169CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);"#,
170            34,
171        )
172        .await?;
173    }
174    if dbversion < 39 {
175        sql.execute_migration(
176            r#"
177CREATE TABLE tokens ( 
178  id INTEGER PRIMARY KEY, 
179  namespc INTEGER DEFAULT 0, 
180  foreign_id INTEGER DEFAULT 0, 
181  token TEXT DEFAULT '', 
182  timestamp INTEGER DEFAULT 0
183);
184ALTER TABLE acpeerstates ADD COLUMN verified_key;
185ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';
186CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);"#,
187            39,
188        )
189        .await?;
190    }
191    if dbversion < 40 {
192        sql.execute_migration("ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;", 40)
193            .await?;
194    }
195    if dbversion < 44 {
196        sql.execute_migration("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", 44)
197            .await?;
198    }
199    if dbversion < 46 {
200        sql.execute_migration(
201            r#"
202ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;
203ALTER TABLE msgs ADD COLUMN mime_references TEXT;"#,
204            46,
205        )
206        .await?;
207    }
208    if dbversion < 47 {
209        sql.execute_migration("ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;", 47)
210            .await?;
211    }
212    if dbversion < 48 {
213        // NOTE: move_state is not used anymore
214        sql.execute_migration(
215            "ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
216            48,
217        )
218        .await?;
219    }
220    if dbversion < 49 {
221        sql.execute_migration(
222            "ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;",
223            49,
224        )
225        .await?;
226    }
227    if dbversion < 50 {
228        // installations <= 0.100.1 used DC_SHOW_EMAILS_ALL implicitly;
229        // keep this default and use DC_SHOW_EMAILS_NO
230        // only for new installations
231        if exists_before_update {
232            sql.set_raw_config_int("show_emails", ShowEmails::All as i32)
233                .await?;
234        }
235        sql.set_db_version(50).await?;
236    }
237    if dbversion < 53 {
238        // the messages containing _only_ locations
239        // are also added to the database as _hidden_.
240        sql.execute_migration(
241            r#"
242CREATE TABLE locations ( 
243  id INTEGER PRIMARY KEY AUTOINCREMENT, 
244  latitude REAL DEFAULT 0.0, 
245  longitude REAL DEFAULT 0.0, 
246  accuracy REAL DEFAULT 0.0, 
247  timestamp INTEGER DEFAULT 0, 
248  chat_id INTEGER DEFAULT 0, 
249  from_id INTEGER DEFAULT 0
250);"
251CREATE INDEX locations_index1 ON locations (from_id);
252CREATE INDEX locations_index2 ON locations (timestamp);
253ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;
254ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;
255ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;
256CREATE INDEX chats_index3 ON chats (locations_send_until);"#,
257            53,
258        )
259        .await?;
260    }
261    if dbversion < 54 {
262        sql.execute_migration(
263            r#"
264ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;
265CREATE INDEX msgs_index6 ON msgs (location_id);"#,
266            54,
267        )
268        .await?;
269    }
270    if dbversion < 55 {
271        sql.execute_migration(
272            "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
273            55,
274        )
275        .await?;
276    }
277    if dbversion < 59 {
278        // records in the devmsglabels are kept when the message is deleted.
279        // so, msg_id may or may not exist.
280        sql.execute_migration(
281            r#"
282CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);
283CREATE INDEX devmsglabels_index1 ON devmsglabels (label);"#, 59)
284            .await?;
285        if exists_before_update && sql.get_raw_config_int("bcc_self").await?.is_none() {
286            sql.set_raw_config_int("bcc_self", 1).await?;
287        }
288    }
289
290    if dbversion < 60 {
291        sql.execute_migration(
292            "ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;",
293            60,
294        )
295        .await?;
296    }
297    if dbversion < 61 {
298        sql.execute_migration(
299            "ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;",
300            61,
301        )
302        .await?;
303        update_icons = true;
304    }
305    if dbversion < 62 {
306        sql.execute_migration(
307            "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;",
308            62,
309        )
310        .await?;
311    }
312    if dbversion < 63 {
313        sql.execute_migration("UPDATE chats SET grpid='' WHERE type=100", 63)
314            .await?;
315    }
316    if dbversion < 64 {
317        sql.execute_migration("ALTER TABLE msgs ADD COLUMN error TEXT DEFAULT '';", 64)
318            .await?;
319    }
320    if dbversion < 65 {
321        sql.execute_migration(
322            r#"
323ALTER TABLE chats ADD COLUMN ephemeral_timer INTEGER;
324ALTER TABLE msgs ADD COLUMN ephemeral_timer INTEGER DEFAULT 0;
325ALTER TABLE msgs ADD COLUMN ephemeral_timestamp INTEGER DEFAULT 0;"#,
326            65,
327        )
328        .await?;
329    }
330    if dbversion < 66 {
331        update_icons = true;
332        sql.set_db_version(66).await?;
333    }
334    if dbversion < 67 {
335        for prefix in &["", "configured_"] {
336            if let Some(server_flags) = sql
337                .get_raw_config_int(&format!("{prefix}server_flags"))
338                .await?
339            {
340                let imap_socket_flags = server_flags & 0x700;
341                let key = &format!("{prefix}mail_security");
342                match imap_socket_flags {
343                    0x100 => sql.set_raw_config_int(key, 2).await?, // STARTTLS
344                    0x200 => sql.set_raw_config_int(key, 1).await?, // SSL/TLS
345                    0x400 => sql.set_raw_config_int(key, 3).await?, // Plain
346                    _ => sql.set_raw_config_int(key, 0).await?,
347                }
348                let smtp_socket_flags = server_flags & 0x70000;
349                let key = &format!("{prefix}send_security");
350                match smtp_socket_flags {
351                    0x10000 => sql.set_raw_config_int(key, 2).await?, // STARTTLS
352                    0x20000 => sql.set_raw_config_int(key, 1).await?, // SSL/TLS
353                    0x40000 => sql.set_raw_config_int(key, 3).await?, // Plain
354                    _ => sql.set_raw_config_int(key, 0).await?,
355                }
356            }
357        }
358        sql.set_db_version(67).await?;
359    }
360    if dbversion < 68 {
361        // the index is used to speed up get_fresh_msg_cnt() (see comment there for more details) and marknoticed_chat()
362        sql.execute_migration(
363            "CREATE INDEX IF NOT EXISTS msgs_index7 ON msgs (state, hidden, chat_id);",
364            68,
365        )
366        .await?;
367    }
368    if dbversion < 69 {
369        sql.execute_migration(
370            r#"
371ALTER TABLE chats ADD COLUMN protected INTEGER DEFAULT 0;
372-- 120=group, 130=old verified group
373UPDATE chats SET protected=1, type=120 WHERE type=130;"#,
374            69,
375        )
376        .await?;
377    }
378
379    if dbversion < 71 {
380        if let Ok(addr) = context.get_primary_self_addr().await {
381            if let Ok(domain) = EmailAddress::new(&addr).map(|email| email.domain) {
382                context
383                    .set_config_internal(
384                        Config::ConfiguredProvider,
385                        get_provider_by_domain(&domain).map(|provider| provider.id),
386                    )
387                    .await?;
388            } else {
389                warn!(context, "Can't parse configured address: {:?}", addr);
390            }
391        }
392
393        sql.set_db_version(71).await?;
394    }
395    if dbversion < 72 && !sql.col_exists("msgs", "mime_modified").await? {
396        sql.execute_migration(
397            r#"
398    ALTER TABLE msgs ADD COLUMN mime_modified INTEGER DEFAULT 0;"#,
399            72,
400        )
401        .await?;
402    }
403    if dbversion < 73 {
404        use Config::*;
405        sql.execute(
406            r#"
407CREATE TABLE imap_sync (folder TEXT PRIMARY KEY, uidvalidity INTEGER DEFAULT 0, uid_next INTEGER DEFAULT 0);"#,
408()
409        )
410            .await?;
411        for c in &[
412            ConfiguredInboxFolder,
413            ConfiguredSentboxFolder,
414            ConfiguredMvboxFolder,
415        ] {
416            if let Some(folder) = context.get_config(*c).await? {
417                let (uid_validity, last_seen_uid) =
418                    imap::get_config_last_seen_uid(context, &folder).await?;
419                if last_seen_uid > 0 {
420                    imap::set_uid_next(context, &folder, last_seen_uid + 1).await?;
421                    imap::set_uidvalidity(context, &folder, uid_validity).await?;
422                }
423            }
424        }
425        if exists_before_update {
426            disable_server_delete = true;
427
428            // Don't disable server delete if it was on by default (Nauta):
429            if let Some(provider) = context.get_configured_provider().await? {
430                if let Some(defaults) = &provider.config_defaults {
431                    if defaults.iter().any(|d| d.key == Config::DeleteServerAfter) {
432                        disable_server_delete = false;
433                    }
434                }
435            }
436        }
437        sql.set_db_version(73).await?;
438    }
439    if dbversion < 74 {
440        sql.execute_migration("UPDATE contacts SET name='' WHERE name=authname", 74)
441            .await?;
442    }
443    if dbversion < 75 {
444        sql.execute_migration(
445            "ALTER TABLE contacts ADD COLUMN status TEXT DEFAULT '';",
446            75,
447        )
448        .await?;
449    }
450    if dbversion < 76 {
451        sql.execute_migration("ALTER TABLE msgs ADD COLUMN subject TEXT DEFAULT '';", 76)
452            .await?;
453    }
454    if dbversion < 77 {
455        recode_avatar = true;
456        sql.set_db_version(77).await?;
457    }
458    if dbversion < 78 {
459        // move requests to "Archived Chats",
460        // this way, the app looks familiar after the contact request upgrade.
461        sql.execute_migration("UPDATE chats SET archived=1 WHERE blocked=2;", 78)
462            .await?;
463    }
464    if dbversion < 79 {
465        sql.execute_migration(
466            r#"
467        ALTER TABLE msgs ADD COLUMN download_state INTEGER DEFAULT 0;
468        "#,
469            79,
470        )
471        .await?;
472    }
473    if dbversion < 80 {
474        sql.execute_migration(
475            r#"CREATE TABLE multi_device_sync (
476id INTEGER PRIMARY KEY AUTOINCREMENT,
477item TEXT DEFAULT '');"#,
478            80,
479        )
480        .await?;
481    }
482    if dbversion < 81 {
483        sql.execute_migration("ALTER TABLE msgs ADD COLUMN hop_info TEXT;", 81)
484            .await?;
485    }
486    if dbversion < 82 {
487        sql.execute_migration(
488            r#"CREATE TABLE imap (
489id INTEGER PRIMARY KEY AUTOINCREMENT,
490rfc724_mid TEXT DEFAULT '', -- Message-ID header
491folder TEXT DEFAULT '', -- IMAP folder
492target TEXT DEFAULT '', -- Destination folder, empty to delete.
493uid INTEGER DEFAULT 0, -- UID
494uidvalidity INTEGER DEFAULT 0,
495UNIQUE (folder, uid, uidvalidity)
496);
497CREATE INDEX imap_folder ON imap(folder);
498CREATE INDEX imap_messageid ON imap(rfc724_mid);
499
500INSERT INTO imap
501(rfc724_mid, folder, target, uid, uidvalidity)
502SELECT
503rfc724_mid,
504server_folder AS folder,
505server_folder AS target,
506server_uid AS uid,
507(SELECT uidvalidity FROM imap_sync WHERE folder=server_folder) AS uidvalidity
508FROM msgs
509WHERE server_uid>0
510ON CONFLICT (folder, uid, uidvalidity)
511DO UPDATE SET rfc724_mid=excluded.rfc724_mid,
512              target=excluded.target;
513"#,
514            82,
515        )
516        .await?;
517    }
518    if dbversion < 83 {
519        sql.execute_migration(
520            "ALTER TABLE imap_sync
521             ADD COLUMN modseq -- Highest modification sequence
522             INTEGER DEFAULT 0",
523            83,
524        )
525        .await?;
526    }
527    if dbversion < 84 {
528        sql.execute_migration(
529            r#"CREATE TABLE msgs_status_updates (
530id INTEGER PRIMARY KEY AUTOINCREMENT,
531msg_id INTEGER,
532update_item TEXT DEFAULT '',
533update_item_read INTEGER DEFAULT 0 -- XXX unused
534);
535CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);"#,
536            84,
537        )
538        .await?;
539    }
540    if dbversion < 85 {
541        sql.execute_migration(
542            r#"CREATE TABLE smtp (
543id INTEGER PRIMARY KEY,
544rfc724_mid TEXT NOT NULL,          -- Message-ID
545mime TEXT NOT NULL,                -- SMTP payload
546msg_id INTEGER NOT NULL,           -- ID of the message in `msgs` table
547recipients TEXT NOT NULL,          -- List of recipients separated by space
548retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
549);
550CREATE INDEX smtp_messageid ON imap(rfc724_mid);
551"#,
552            85,
553        )
554        .await?;
555    }
556    if dbversion < 86 {
557        sql.execute_migration(
558            r#"CREATE TABLE bobstate (
559                   id INTEGER PRIMARY KEY AUTOINCREMENT,
560                   invite TEXT NOT NULL,
561                   next_step INTEGER NOT NULL,
562                   chat_id INTEGER NOT NULL
563            );"#,
564            86,
565        )
566        .await?;
567    }
568    if dbversion < 87 {
569        // the index is used to speed up delete_expired_messages()
570        sql.execute_migration(
571            "CREATE INDEX IF NOT EXISTS msgs_index8 ON msgs (ephemeral_timestamp);",
572            87,
573        )
574        .await?;
575    }
576    if dbversion < 88 {
577        sql.execute_migration("DROP TABLE IF EXISTS backup_blobs;", 88)
578            .await?;
579    }
580    if dbversion < 89 {
581        sql.execute_migration(
582            r#"CREATE TABLE imap_markseen (
583              id INTEGER,
584              FOREIGN KEY(id) REFERENCES imap(id) ON DELETE CASCADE
585            );"#,
586            89,
587        )
588        .await?;
589    }
590    if dbversion < 90 {
591        sql.execute_migration(
592            r#"CREATE TABLE smtp_mdns (
593              msg_id INTEGER NOT NULL, -- id of the message in msgs table which requested MDN (DEPRECATED 2024-06-21)
594              from_id INTEGER NOT NULL, -- id of the contact that sent the message, MDN destination
595              rfc724_mid TEXT NOT NULL, -- Message-ID header
596              retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send MDN
597            );"#,
598            90,
599        )
600        .await?;
601    }
602    if dbversion < 91 {
603        sql.execute_migration(
604            r#"CREATE TABLE smtp_status_updates (
605              msg_id INTEGER NOT NULL UNIQUE, -- msg_id of the webxdc instance with pending updates
606              first_serial INTEGER NOT NULL, -- id in msgs_status_updates
607              last_serial INTEGER NOT NULL, -- id in msgs_status_updates
608              descr TEXT NOT NULL -- text to send along with the updates
609            );"#,
610            91,
611        )
612        .await?;
613    }
614    if dbversion < 92 {
615        sql.execute_migration(
616            r#"CREATE TABLE reactions (
617              msg_id INTEGER NOT NULL, -- id of the message reacted to
618              contact_id INTEGER NOT NULL, -- id of the contact reacting to the message
619              reaction TEXT DEFAULT '' NOT NULL, -- a sequence of emojis separated by spaces
620              PRIMARY KEY(msg_id, contact_id),
621              FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE -- delete reactions when message is deleted
622              FOREIGN KEY(contact_id) REFERENCES contacts(id) ON DELETE CASCADE -- delete reactions when contact is deleted
623            )"#,
624            92
625        ).await?;
626    }
627    if dbversion < 93 {
628        // `sending_domains` is now unused, but was not removed for backwards compatibility.
629        sql.execute_migration(
630            "CREATE TABLE sending_domains(domain TEXT PRIMARY KEY, dkim_works INTEGER DEFAULT 0);",
631            93,
632        )
633        .await?;
634    }
635    if dbversion < 94 {
636        sql.execute_migration(
637            // Create new `acpeerstates` table, same as before but with unique constraint.
638            //
639            // This allows to use `UPSERT` to update existing or insert a new peerstate
640            // depending on whether one exists already.
641            "CREATE TABLE new_acpeerstates (
642             id INTEGER PRIMARY KEY,
643             addr TEXT DEFAULT '' COLLATE NOCASE,
644             last_seen INTEGER DEFAULT 0,
645             last_seen_autocrypt INTEGER DEFAULT 0,
646             public_key,
647             prefer_encrypted INTEGER DEFAULT 0,
648             gossip_timestamp INTEGER DEFAULT 0,
649             gossip_key,
650             public_key_fingerprint TEXT DEFAULT '',
651             gossip_key_fingerprint TEXT DEFAULT '',
652             verified_key,
653             verified_key_fingerprint TEXT DEFAULT '',
654             UNIQUE (addr) -- Only one peerstate per address
655             );
656            INSERT OR IGNORE INTO new_acpeerstates SELECT
657                id, addr, last_seen, last_seen_autocrypt, public_key, prefer_encrypted,
658                gossip_timestamp, gossip_key, public_key_fingerprint,
659                gossip_key_fingerprint, verified_key, verified_key_fingerprint
660            FROM acpeerstates;
661            DROP TABLE acpeerstates;
662            ALTER TABLE new_acpeerstates RENAME TO acpeerstates;
663            CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);
664            CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
665            CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);
666            CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);
667            ",
668            94,
669        )
670        .await?;
671    }
672    if dbversion < 95 {
673        sql.execute_migration(
674            "CREATE TABLE new_chats_contacts (chat_id INTEGER, contact_id INTEGER, UNIQUE(chat_id, contact_id));\
675            INSERT OR IGNORE INTO new_chats_contacts SELECT chat_id, contact_id FROM chats_contacts;\
676            DROP TABLE chats_contacts;\
677            ALTER TABLE new_chats_contacts RENAME TO chats_contacts;\
678            CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);\
679            CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
680            95
681        ).await?;
682    }
683    if dbversion < 96 {
684        sql.execute_migration(
685            "ALTER TABLE acpeerstates ADD COLUMN verifier TEXT DEFAULT '';",
686            96,
687        )
688        .await?;
689    }
690    if dbversion < 97 {
691        sql.execute_migration(
692            "CREATE TABLE dns_cache (
693               hostname TEXT NOT NULL,
694               address TEXT NOT NULL, -- IPv4 or IPv6 address
695               timestamp INTEGER NOT NULL,
696               UNIQUE (hostname, address)
697             )",
698            97,
699        )
700        .await?;
701    }
702    if dbversion < 98 {
703        if exists_before_update && sql.get_raw_config_int("show_emails").await?.is_none() {
704            sql.set_raw_config_int("show_emails", ShowEmails::Off as i32)
705                .await?;
706        }
707        sql.set_db_version(98).await?;
708    }
709    if dbversion < 99 {
710        // sql.execute_migration(
711        //     "ALTER TABLE msgs DROP COLUMN server_folder;
712        //      ALTER TABLE msgs DROP COLUMN server_uid;
713        //      ALTER TABLE msgs DROP COLUMN move_state;
714        //      ALTER TABLE chats DROP COLUMN draft_timestamp;
715        //      ALTER TABLE chats DROP COLUMN draft_txt",
716        //     99,
717        // )
718        // .await?;
719
720        // Reverted above, as it requires to load the whole DB in memory.
721        sql.set_db_version(99).await?;
722    }
723    if dbversion < 100 {
724        sql.execute_migration(
725            "ALTER TABLE msgs ADD COLUMN mime_compressed INTEGER NOT NULL DEFAULT 0",
726            100,
727        )
728        .await?;
729    }
730    if dbversion < 101 {
731        // Recreate `smtp` table with autoincrement.
732        // rfc724_mid index is not recreated, because it is not used.
733        sql.execute_migration(
734            "DROP TABLE smtp;
735             CREATE TABLE smtp (
736             id INTEGER PRIMARY KEY AUTOINCREMENT,
737             rfc724_mid TEXT NOT NULL,          -- Message-ID
738             mime TEXT NOT NULL,                -- SMTP payload
739             msg_id INTEGER NOT NULL,           -- ID of the message in `msgs` table
740             recipients TEXT NOT NULL,          -- List of recipients separated by space
741             retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
742            );
743            ",
744            101,
745        )
746        .await?;
747    }
748
749    if dbversion < 102 {
750        sql.execute_migration(
751            "CREATE TABLE download (
752            msg_id INTEGER NOT NULL -- id of the message stub in msgs table
753            )",
754            102,
755        )
756        .await?;
757    }
758
759    // Add is_bot column to contacts table with default false.
760    if dbversion < 103 {
761        sql.execute_migration(
762            "ALTER TABLE contacts ADD COLUMN is_bot INTEGER NOT NULL DEFAULT 0",
763            103,
764        )
765        .await?;
766    }
767
768    if dbversion < 104 {
769        sql.execute_migration(
770            "ALTER TABLE acpeerstates
771             ADD COLUMN secondary_verified_key;
772             ALTER TABLE acpeerstates
773             ADD COLUMN secondary_verified_key_fingerprint TEXT DEFAULT '';
774             ALTER TABLE acpeerstates
775             ADD COLUMN secondary_verifier TEXT DEFAULT ''",
776            104,
777        )
778        .await?;
779    }
780
781    if dbversion < 105 {
782        // Create UNIQUE uid column and drop unused update_item_read column.
783        sql.execute_migration(
784            r#"CREATE TABLE new_msgs_status_updates (
785id INTEGER PRIMARY KEY AUTOINCREMENT,
786msg_id INTEGER,
787update_item TEXT DEFAULT '',
788uid TEXT UNIQUE
789);
790INSERT OR IGNORE INTO new_msgs_status_updates SELECT
791  id, msg_id, update_item, NULL
792FROM msgs_status_updates;
793DROP TABLE msgs_status_updates;
794ALTER TABLE new_msgs_status_updates RENAME TO msgs_status_updates;
795CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
796CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
797"#,
798            105,
799        )
800        .await?;
801    }
802
803    if dbversion < 106 {
804        // Recreate `config` table with UNIQUE constraint on `keyname`.
805        sql.execute_migration(
806            "CREATE TABLE new_config (
807               id INTEGER PRIMARY KEY,
808               keyname TEXT UNIQUE,
809               value TEXT NOT NULL
810             );
811             INSERT OR IGNORE INTO new_config SELECT
812               id, keyname, value
813             FROM config;
814             DROP TABLE config;
815             ALTER TABLE new_config RENAME TO config;
816             CREATE INDEX config_index1 ON config (keyname);",
817            106,
818        )
819        .await?;
820    }
821
822    if dbversion < 107 {
823        sql.execute_migration(
824            "CREATE TABLE new_keypairs (
825               id INTEGER PRIMARY KEY AUTOINCREMENT,
826               private_key UNIQUE NOT NULL,
827               public_key UNIQUE NOT NULL
828             );
829             INSERT OR IGNORE INTO new_keypairs SELECT id, private_key, public_key FROM keypairs;
830
831             INSERT OR IGNORE
832             INTO config (keyname, value)
833             VALUES
834             ('key_id', (SELECT id FROM new_keypairs
835                         WHERE private_key=
836                           (SELECT private_key FROM keypairs
837                            WHERE addr=(SELECT value FROM config WHERE keyname='configured_addr')
838                            AND is_default=1)));
839
840             -- We do not drop the old `keypairs` table for now,
841             -- but move it to `old_keypairs`. We can remove it later
842             -- in next migrations. This may be needed for recovery
843             -- in case something is wrong with the migration.
844             ALTER TABLE keypairs RENAME TO old_keypairs;
845             ALTER TABLE new_keypairs RENAME TO keypairs;
846             ",
847            107,
848        )
849        .await?;
850    }
851
852    if dbversion < 108 {
853        let version = 108;
854        let chunk_size = context.get_max_smtp_rcpt_to().await?;
855        sql.transaction(move |trans| {
856            Sql::set_db_version_trans(trans, version)?;
857            let id_max =
858                trans.query_row("SELECT IFNULL((SELECT MAX(id) FROM smtp), 0)", (), |row| {
859                    let id_max: i64 = row.get(0)?;
860                    Ok(id_max)
861                })?;
862            while let Some((id, rfc724_mid, mime, msg_id, recipients, retries)) = trans
863                .query_row(
864                    "SELECT id, rfc724_mid, mime, msg_id, recipients, retries FROM smtp \
865                    WHERE id<=? LIMIT 1",
866                    (id_max,),
867                    |row| {
868                        let id: i64 = row.get(0)?;
869                        let rfc724_mid: String = row.get(1)?;
870                        let mime: String = row.get(2)?;
871                        let msg_id: MsgId = row.get(3)?;
872                        let recipients: String = row.get(4)?;
873                        let retries: i64 = row.get(5)?;
874                        Ok((id, rfc724_mid, mime, msg_id, recipients, retries))
875                    },
876                )
877                .optional()?
878            {
879                trans.execute("DELETE FROM smtp WHERE id=?", (id,))?;
880                let recipients = recipients.split(' ').collect::<Vec<_>>();
881                for recipients in recipients.chunks(chunk_size) {
882                    let recipients = recipients.join(" ");
883                    trans.execute(
884                        "INSERT INTO smtp (rfc724_mid, mime, msg_id, recipients, retries) \
885                        VALUES (?, ?, ?, ?, ?)",
886                        (&rfc724_mid, &mime, msg_id, recipients, retries),
887                    )?;
888                }
889            }
890            Ok(())
891        })
892        .await
893        .with_context(|| format!("migration failed for version {version}"))?;
894
895        sql.set_db_version_in_cache(version).await?;
896    }
897
898    if dbversion < 109 {
899        sql.execute_migration(
900            r#"ALTER TABLE acpeerstates
901               ADD COLUMN backward_verified_key_id -- What we think the contact has as our verified key
902               INTEGER;
903               UPDATE acpeerstates
904               SET backward_verified_key_id=(SELECT value FROM config WHERE keyname='key_id')
905               WHERE verified_key IS NOT NULL
906               "#,
907            109,
908        )
909        .await?;
910    }
911
912    if dbversion < 110 {
913        sql.execute_migration(
914            "ALTER TABLE keypairs ADD COLUMN addr TEXT DEFAULT '' COLLATE NOCASE;
915            ALTER TABLE keypairs ADD COLUMN is_default INTEGER DEFAULT 0;
916            ALTER TABLE keypairs ADD COLUMN created INTEGER DEFAULT 0;
917            UPDATE keypairs SET addr=(SELECT value FROM config WHERE keyname='configured_addr'), is_default=1;",
918            110,
919        )
920        .await?;
921    }
922
923    if dbversion < 111 {
924        sql.execute_migration(
925            "CREATE TABLE iroh_gossip_peers (msg_id TEXT not NULL, topic TEXT NOT NULL, public_key TEXT NOT NULL)",
926            111,
927        )
928        .await?;
929    }
930
931    if dbversion < 112 {
932        sql.execute_migration(
933            "DROP TABLE iroh_gossip_peers; CREATE TABLE iroh_gossip_peers (msg_id INTEGER not NULL, topic BLOB NOT NULL, public_key BLOB NOT NULL, relay_server TEXT, UNIQUE (public_key, topic)) STRICT",
934            112,
935        )
936        .await?;
937    }
938
939    if dbversion < 113 {
940        sql.execute_migration(
941            "DROP TABLE iroh_gossip_peers; CREATE TABLE iroh_gossip_peers (msg_id INTEGER not NULL, topic BLOB NOT NULL, public_key BLOB NOT NULL, relay_server TEXT, UNIQUE (topic, public_key), PRIMARY KEY(topic, public_key)) STRICT",
942            113,
943        )
944        .await?;
945    }
946
947    if dbversion < 114 {
948        sql.execute_migration("CREATE INDEX reactions_index1 ON reactions (msg_id)", 114)
949            .await?;
950    }
951
952    if dbversion < 115 {
953        sql.execute_migration("ALTER TABLE msgs ADD COLUMN txt_normalized TEXT", 115)
954            .await?;
955    }
956    let mut migration_version: i32 = 115;
957
958    inc_and_check(&mut migration_version, 116)?;
959    if dbversion < migration_version {
960        // Whether the message part doesn't need to be stored on the server. If all parts are marked
961        // deleted, a server-side deletion is issued.
962        sql.execute_migration(
963            "ALTER TABLE msgs ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0",
964            migration_version,
965        )
966        .await?;
967    }
968
969    inc_and_check(&mut migration_version, 117)?;
970    if dbversion < migration_version {
971        sql.execute_migration(
972            "CREATE TABLE connection_history (
973                host TEXT NOT NULL, -- server hostname
974                port INTEGER NOT NULL, -- server port
975                alpn TEXT NOT NULL, -- ALPN such as smtp or imap
976                addr TEXT NOT NULL, -- IP address
977                timestamp INTEGER NOT NULL, -- timestamp of the most recent successful connection
978                UNIQUE (host, port, alpn, addr)
979            ) STRICT",
980            migration_version,
981        )
982        .await?;
983    }
984
985    inc_and_check(&mut migration_version, 118)?;
986    if dbversion < migration_version {
987        sql.execute_migration(
988            "CREATE TABLE tokens_new (
989                id INTEGER PRIMARY KEY,
990                namespc INTEGER DEFAULT 0,
991                foreign_key TEXT DEFAULT '',
992                token TEXT DEFAULT '',
993                timestamp INTEGER DEFAULT 0
994            ) STRICT;
995            INSERT INTO tokens_new
996                SELECT t.id, t.namespc, IFNULL(c.grpid, ''), t.token, t.timestamp
997                FROM tokens t LEFT JOIN chats c ON t.foreign_id=c.id;
998            DROP TABLE tokens;
999            ALTER TABLE tokens_new RENAME TO tokens;",
1000            migration_version,
1001        )
1002        .await?;
1003    }
1004
1005    inc_and_check(&mut migration_version, 119)?;
1006    if dbversion < migration_version {
1007        sql.execute_migration(
1008            "CREATE TABLE imap_send (
1009                id INTEGER PRIMARY KEY AUTOINCREMENT,
1010                mime TEXT NOT NULL, -- Message content
1011                msg_id INTEGER NOT NULL, -- ID of the message in the `msgs` table
1012                attempts INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
1013            )",
1014            migration_version,
1015        )
1016        .await?;
1017    }
1018
1019    inc_and_check(&mut migration_version, 120)?;
1020    if dbversion < migration_version {
1021        // Core 1.143.0 changed the default for `delete_server_after`
1022        // to delete immediately (`1`) for chatmail accounts that don't have multidevice
1023        // and updating to `0` when backup is exported.
1024        //
1025        // Since we don't know if existing configurations
1026        // are multidevice, we set `delete_server_after` for them
1027        // to the old default of `0`, so only new configurations are
1028        // affected by the default change.
1029        //
1030        // `INSERT OR IGNORE` works
1031        // because `keyname` was made UNIQUE in migration 106.
1032        sql.execute_migration(
1033            "INSERT OR IGNORE INTO config (keyname, value)
1034             SELECT 'delete_server_after', '0'
1035             FROM config WHERE keyname='configured'
1036            ",
1037            migration_version,
1038        )
1039        .await?;
1040    }
1041
1042    inc_and_check(&mut migration_version, 121)?;
1043    if dbversion < migration_version {
1044        sql.execute_migration(
1045            "CREATE INDEX chats_index4 ON chats (name)",
1046            migration_version,
1047        )
1048        .await?;
1049    }
1050
1051    inc_and_check(&mut migration_version, 122)?;
1052    if dbversion < migration_version {
1053        sql.execute_migration(
1054            "ALTER TABLE tokens ADD COLUMN foreign_id INTEGER NOT NULL DEFAULT 0",
1055            migration_version,
1056        )
1057        .await?;
1058    }
1059
1060    inc_and_check(&mut migration_version, 123)?;
1061    if dbversion < migration_version {
1062        // Add FOREIGN KEY(msg_id).
1063        sql.execute_migration(
1064            "CREATE TABLE new_msgs_status_updates (
1065                id INTEGER PRIMARY KEY AUTOINCREMENT,
1066                msg_id INTEGER,
1067                update_item TEXT DEFAULT '',
1068                uid TEXT UNIQUE,
1069                FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE
1070            );
1071            INSERT OR IGNORE INTO new_msgs_status_updates SELECT
1072                id, msg_id, update_item, uid
1073            FROM msgs_status_updates;
1074            DROP TABLE msgs_status_updates;
1075            ALTER TABLE new_msgs_status_updates RENAME TO msgs_status_updates;
1076            CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
1077            CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
1078            ",
1079            migration_version,
1080        )
1081        .await?;
1082    }
1083
1084    inc_and_check(&mut migration_version, 124)?;
1085    if dbversion < migration_version {
1086        // Mark Saved Messages chat as protected if it already exists.
1087        sql.execute_migration(
1088            "UPDATE chats
1089             SET protected=1 -- ProtectionStatus::Protected
1090             WHERE type==100 -- Chattype::Single
1091             AND EXISTS (
1092                 SELECT 1 FROM chats_contacts cc
1093                 WHERE cc.chat_id==chats.id
1094                 AND cc.contact_id=1
1095             )
1096             ",
1097            migration_version,
1098        )
1099        .await?;
1100    }
1101
1102    inc_and_check(&mut migration_version, 125)?;
1103    if dbversion < migration_version {
1104        sql.execute_migration(
1105            "CREATE TABLE http_cache (
1106                url TEXT PRIMARY KEY,
1107                expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1108                blobname TEXT NOT NULL,
1109                mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
1110                encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
1111            ) STRICT",
1112            migration_version,
1113        )
1114        .await?;
1115    }
1116
1117    inc_and_check(&mut migration_version, 126)?;
1118    if dbversion < migration_version {
1119        // Recreate http_cache table with new `stale` column.
1120        sql.execute_migration(
1121            "DROP TABLE http_cache;
1122             CREATE TABLE http_cache (
1123                url TEXT PRIMARY KEY,
1124                expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1125                stale INTEGER NOT NULL, -- When the cache entry is considered stale, timestamp in seconds.
1126                blobname TEXT NOT NULL,
1127                mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
1128                encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
1129            ) STRICT",
1130            migration_version,
1131        )
1132        .await?;
1133    }
1134
1135    inc_and_check(&mut migration_version, 127)?;
1136    if dbversion < migration_version {
1137        // This is buggy: `delete_server_after` > 1 isn't handled. Migration #129 fixes this.
1138        sql.execute_migration(
1139            "INSERT OR IGNORE INTO config (keyname, value)
1140             SELECT 'bcc_self', '1'
1141             FROM config WHERE keyname='delete_server_after' AND value='0'
1142            ",
1143            migration_version,
1144        )
1145        .await?;
1146    }
1147
1148    inc_and_check(&mut migration_version, 128)?;
1149    if dbversion < migration_version {
1150        // Add the timestamps of addition and removal.
1151        //
1152        // If `add_timestamp >= remove_timestamp`,
1153        // then the member is currently a member of the chat.
1154        // Otherwise the member is a past member.
1155        sql.execute_migration(
1156            "ALTER TABLE chats_contacts
1157             ADD COLUMN add_timestamp NOT NULL DEFAULT 0;
1158             ALTER TABLE chats_contacts
1159             ADD COLUMN remove_timestamp NOT NULL DEFAULT 0;
1160            ",
1161            migration_version,
1162        )
1163        .await?;
1164    }
1165
1166    inc_and_check(&mut migration_version, 129)?;
1167    if dbversion < migration_version {
1168        // Existing chatmail configurations having `delete_server_after` != "delete at once" should
1169        // get `bcc_self` enabled, they may be multidevice configurations:
1170        // - Before migration #127, `delete_server_after` was set to 0 upon a backup export, but
1171        //   then `bcc_self` is enabled instead (whose default is changed to 0 for chatmail).
1172        // - The user might set `delete_server_after` to a value other than 0 or 1 when that was
1173        //   possible in UIs.
1174        // We don't check `is_chatmail` for simplicity.
1175        sql.execute_migration(
1176            "INSERT OR IGNORE INTO config (keyname, value)
1177             SELECT 'bcc_self', '1'
1178             FROM config WHERE keyname='delete_server_after' AND value!='1'
1179            ",
1180            migration_version,
1181        )
1182        .await?;
1183    }
1184
1185    inc_and_check(&mut migration_version, 130)?;
1186    if dbversion < migration_version {
1187        sql.execute_migration(
1188            "
1189CREATE TABLE gossip_timestamp (
1190  chat_id INTEGER NOT NULL, 
1191  fingerprint TEXT NOT NULL, -- Upper-case fingerprint of the key.
1192  timestamp INTEGER NOT NULL,
1193  UNIQUE (chat_id, fingerprint)
1194) STRICT;
1195CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint);
1196",
1197            migration_version,
1198        )
1199        .await?;
1200    }
1201
1202    inc_and_check(&mut migration_version, 131)?;
1203    if dbversion < migration_version {
1204        let entered_param = EnteredLoginParam::load(context).await?;
1205        let configured_param = ConfiguredLoginParam::load_legacy(context).await?;
1206
1207        sql.execute_migration_transaction(
1208            |transaction| {
1209                transaction.execute(
1210                    "CREATE TABLE transports (
1211                        id INTEGER PRIMARY KEY AUTOINCREMENT,
1212                        addr TEXT NOT NULL,
1213                        entered_param TEXT NOT NULL,
1214                        configured_param TEXT NOT NULL,
1215                        UNIQUE(addr)
1216                        )",
1217                    (),
1218                )?;
1219                if let Some(configured_param) = configured_param {
1220                    transaction.execute(
1221                        "INSERT INTO transports (addr, entered_param, configured_param)
1222                         VALUES (?, ?, ?)",
1223                        (
1224                            configured_param.addr.clone(),
1225                            serde_json::to_string(&entered_param)?,
1226                            configured_param.into_json()?,
1227                        ),
1228                    )?;
1229                }
1230
1231                Ok(())
1232            },
1233            migration_version,
1234        )
1235        .await?;
1236    }
1237
1238    inc_and_check(&mut migration_version, 132)?;
1239    if dbversion < migration_version {
1240        let start = Time::now();
1241        sql.execute_migration_transaction(|t| migrate_key_contacts(context, t), migration_version)
1242            .await?;
1243        info!(
1244            context,
1245            "key-contacts migration took {:?} in total.",
1246            time_elapsed(&start),
1247        );
1248        // Schedule `msgs_to_key_contacts()`.
1249        context
1250            .set_config_internal(Config::LastHousekeeping, None)
1251            .await?;
1252    }
1253
1254    inc_and_check(&mut migration_version, 133)?;
1255    if dbversion < migration_version {
1256        // Make `ProtectionBroken` chats `Unprotected`. Chats can't break anymore.
1257        sql.execute_migration(
1258            "UPDATE chats SET protected=0 WHERE protected!=1",
1259            migration_version,
1260        )
1261        .await?;
1262    }
1263
1264    let new_version = sql
1265        .get_raw_config_int(VERSION_CFG)
1266        .await?
1267        .unwrap_or_default();
1268    if new_version != dbversion || !exists_before_update {
1269        let created_db = if exists_before_update {
1270            ""
1271        } else {
1272            "Created new database. "
1273        };
1274        info!(context, "{}Migration done from v{}.", created_db, dbversion);
1275    }
1276    info!(context, "Database version: v{new_version}.");
1277
1278    Ok((update_icons, disable_server_delete, recode_avatar))
1279}
1280
1281fn migrate_key_contacts(
1282    context: &Context,
1283    transaction: &mut rusqlite::Transaction<'_>,
1284) -> std::result::Result<(), anyhow::Error> {
1285    info!(context, "Starting key-contact transition.");
1286
1287    // =============================== Step 1: ===============================
1288    //                              Alter tables
1289    transaction.execute_batch(
1290        "ALTER TABLE contacts ADD COLUMN fingerprint TEXT NOT NULL DEFAULT '';
1291
1292        -- Verifier is an ID of the verifier contact.
1293        -- 0 if the contact is not verified.
1294        ALTER TABLE contacts ADD COLUMN verifier INTEGER NOT NULL DEFAULT 0;
1295
1296        CREATE INDEX contacts_fingerprint_index ON contacts (fingerprint);
1297
1298        CREATE TABLE public_keys (
1299        id INTEGER PRIMARY KEY AUTOINCREMENT,
1300        fingerprint TEXT NOT NULL UNIQUE, -- Upper-case fingerprint of the key.
1301        public_key BLOB NOT NULL -- Binary key, not ASCII-armored
1302        ) STRICT;
1303        CREATE INDEX public_key_index ON public_keys (fingerprint);
1304
1305        INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1306        SELECT public_key_fingerprint, public_key FROM acpeerstates
1307         WHERE public_key_fingerprint IS NOT NULL AND public_key IS NOT NULL;
1308
1309        INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1310        SELECT gossip_key_fingerprint, gossip_key FROM acpeerstates
1311         WHERE gossip_key_fingerprint IS NOT NULL AND gossip_key IS NOT NULL;
1312
1313        INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1314        SELECT verified_key_fingerprint, verified_key FROM acpeerstates
1315         WHERE verified_key_fingerprint IS NOT NULL AND verified_key IS NOT NULL;
1316
1317        INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1318        SELECT secondary_verified_key_fingerprint, secondary_verified_key FROM acpeerstates
1319         WHERE secondary_verified_key_fingerprint IS NOT NULL AND secondary_verified_key IS NOT NULL;",
1320    )
1321    .context("Creating key-contact tables")?;
1322
1323    let Some(self_addr): Option<String> = transaction
1324        .query_row(
1325            "SELECT value FROM config WHERE keyname='configured_addr'",
1326            (),
1327            |row| row.get(0),
1328        )
1329        .optional()
1330        .context("Step 0")?
1331    else {
1332        info!(
1333            context,
1334            "Not yet configured, no need to migrate key-contacts"
1335        );
1336        return Ok(());
1337    };
1338
1339    // =============================== Step 2: ===============================
1340    // Create up to 3 new contacts for every contact that has a peerstate:
1341    // one from the Autocrypt key fingerprint, one from the verified key fingerprint,
1342    // one from the secondary verified key fingerprint.
1343    // In the process, build maps from old contact id to new contact id:
1344    // one that maps to Autocrypt key-contact, one that maps to verified key-contact.
1345    let mut autocrypt_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1346    let mut autocrypt_key_contacts_with_reset_peerstate: BTreeMap<u32, u32> = BTreeMap::new();
1347    let mut verified_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1348    {
1349        // This maps from the verified contact to the original contact id of the verifier.
1350        // It can't map to the verified key contact id, because at the time of constructing
1351        // this map, not all key-contacts are in the database.
1352        let mut verifications: BTreeMap<u32, u32> = BTreeMap::new();
1353
1354        let mut load_contacts_stmt = transaction
1355            .prepare(
1356                "SELECT c.id, c.name, c.addr, c.origin, c.blocked, c.last_seen,
1357                c.authname, c.param, c.status, c.is_bot, c.selfavatar_sent,
1358                IFNULL(p.public_key, p.gossip_key),
1359                p.verified_key, IFNULL(p.verifier, ''),
1360                p.secondary_verified_key, p.secondary_verifier, p.prefer_encrypted
1361                FROM contacts c
1362                INNER JOIN acpeerstates p ON c.addr=p.addr
1363                WHERE c.id > 9
1364                ORDER BY p.last_seen DESC",
1365            )
1366            .context("Step 2")?;
1367
1368        let all_address_contacts: rusqlite::Result<Vec<_>> = load_contacts_stmt
1369            .query_map((), |row| {
1370                let id: i64 = row.get(0)?;
1371                let name: String = row.get(1)?;
1372                let addr: String = row.get(2)?;
1373                let origin: i64 = row.get(3)?;
1374                let blocked: Option<bool> = row.get(4)?;
1375                let last_seen: i64 = row.get(5)?;
1376                let authname: String = row.get(6)?;
1377                let param: String = row.get(7)?;
1378                let status: Option<String> = row.get(8)?;
1379                let is_bot: bool = row.get(9)?;
1380                let selfavatar_sent: i64 = row.get(10)?;
1381                let autocrypt_key = row
1382                    .get(11)
1383                    .ok()
1384                    .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1385                let verified_key = row
1386                    .get(12)
1387                    .ok()
1388                    .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1389                let verifier: String = row.get(13)?;
1390                let secondary_verified_key = row
1391                    .get(12)
1392                    .ok()
1393                    .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1394                let secondary_verifier: String = row.get(15)?;
1395                let prefer_encrypt: u8 = row.get(16)?;
1396                Ok((
1397                    id,
1398                    name,
1399                    addr,
1400                    origin,
1401                    blocked,
1402                    last_seen,
1403                    authname,
1404                    param,
1405                    status,
1406                    is_bot,
1407                    selfavatar_sent,
1408                    autocrypt_key,
1409                    verified_key,
1410                    verifier,
1411                    secondary_verified_key,
1412                    secondary_verifier,
1413                    prefer_encrypt,
1414                ))
1415            })
1416            .context("Step 3")?
1417            .collect();
1418
1419        let mut insert_contact_stmt = transaction
1420            .prepare(
1421                "INSERT INTO contacts (name, addr, origin, blocked, last_seen,
1422                authname, param, status, is_bot, selfavatar_sent, fingerprint)
1423                VALUES(?,?,?,?,?,?,?,?,?,?,?)",
1424            )
1425            .context("Step 4")?;
1426        let mut fingerprint_to_id_stmt = transaction
1427            .prepare("SELECT id FROM contacts WHERE fingerprint=? AND id>9")
1428            .context("Step 5")?;
1429        let mut original_contact_id_from_addr_stmt = transaction
1430            .prepare("SELECT id FROM contacts WHERE addr=? AND fingerprint='' AND id>9")
1431            .context("Step 6")?;
1432
1433        for row in all_address_contacts? {
1434            let (
1435                original_id,
1436                name,
1437                addr,
1438                origin,
1439                blocked,
1440                last_seen,
1441                authname,
1442                param,
1443                status,
1444                is_bot,
1445                selfavatar_sent,
1446                autocrypt_key,
1447                verified_key,
1448                verifier,
1449                secondary_verified_key,
1450                secondary_verifier,
1451                prefer_encrypt,
1452            ) = row;
1453            let mut insert_contact = |key: SignedPublicKey| -> Result<u32> {
1454                let fingerprint = key.dc_fingerprint().hex();
1455                let existing_contact_id: Option<u32> = fingerprint_to_id_stmt
1456                    .query_row((&fingerprint,), |row| row.get(0))
1457                    .optional()
1458                    .context("Step 7")?;
1459                if let Some(existing_contact_id) = existing_contact_id {
1460                    return Ok(existing_contact_id);
1461                }
1462                insert_contact_stmt
1463                    .execute((
1464                        &name,
1465                        &addr,
1466                        origin,
1467                        blocked,
1468                        last_seen,
1469                        &authname,
1470                        &param,
1471                        &status,
1472                        is_bot,
1473                        selfavatar_sent,
1474                        fingerprint.clone(),
1475                    ))
1476                    .context("Step 8")?;
1477                let id = transaction
1478                    .last_insert_rowid()
1479                    .try_into()
1480                    .context("Step 9")?;
1481                info!(
1482                    context,
1483                    "Inserted new contact id={id} name='{name}' addr='{addr}' fingerprint={fingerprint}"
1484                );
1485                Ok(id)
1486            };
1487            let mut original_contact_id_from_addr = |addr: &str, default: u32| -> Result<u32> {
1488                if addr_cmp(addr, &self_addr) {
1489                    Ok(1) // ContactId::SELF
1490                } else if addr.is_empty() {
1491                    Ok(default)
1492                } else {
1493                    Ok(original_contact_id_from_addr_stmt
1494                        .query_row((addr,), |row| row.get(0))
1495                        .optional()
1496                        .with_context(|| format!("Original contact '{addr}' not found"))?
1497                        .unwrap_or(default))
1498                }
1499            };
1500
1501            let Some(autocrypt_key) = autocrypt_key else {
1502                continue;
1503            };
1504            let new_id = insert_contact(autocrypt_key).context("Step 10")?;
1505
1506            // prefer_encrypt == 20 would mean EncryptPreference::Reset,
1507            // i.e. we shouldn't encrypt if possible.
1508            if prefer_encrypt != 20 {
1509                autocrypt_key_contacts.insert(original_id.try_into().context("Step 11")?, new_id);
1510            } else {
1511                autocrypt_key_contacts_with_reset_peerstate
1512                    .insert(original_id.try_into().context("Step 12")?, new_id);
1513            }
1514
1515            let Some(verified_key) = verified_key else {
1516                continue;
1517            };
1518            let new_id = insert_contact(verified_key).context("Step 13")?;
1519            verified_key_contacts.insert(original_id.try_into().context("Step 14")?, new_id);
1520
1521            let verifier_id = if addr_cmp(&verifier, &addr) {
1522                // Earlier versions of Delta Chat signalled a direct verification
1523                // by putting the contact's own address into the verifier column
1524                1 // 1=ContactId::SELF
1525            } else {
1526                // If the original verifier is unknown, we represent this in the database
1527                // by putting `new_id` into the place of the verifier,
1528                // i.e. we say that this contact verified itself.
1529                original_contact_id_from_addr(&verifier, new_id).context("Step 15")?
1530            };
1531            verifications.insert(new_id, verifier_id);
1532
1533            let Some(secondary_verified_key) = secondary_verified_key else {
1534                continue;
1535            };
1536            let new_id = insert_contact(secondary_verified_key).context("Step 16")?;
1537            let verifier_id: u32 = if addr_cmp(&secondary_verifier, &addr) {
1538                1 // 1=ContactId::SELF
1539            } else {
1540                original_contact_id_from_addr(&secondary_verifier, new_id).context("Step 17")?
1541            };
1542            // Only use secondary verification if there is no primary verification:
1543            verifications.entry(new_id).or_insert(verifier_id);
1544        }
1545        info!(
1546            context,
1547            "Created key-contacts identified by autocrypt key: {autocrypt_key_contacts:?}"
1548        );
1549        info!(
1550            context,
1551            "Created key-contacts  with 'reset' peerstate identified by autocrypt key: {autocrypt_key_contacts_with_reset_peerstate:?}"
1552        );
1553        info!(
1554            context,
1555            "Created key-contacts identified by verified key: {verified_key_contacts:?}"
1556        );
1557
1558        for (&new_contact, &verifier_original_contact) in &verifications {
1559            let verifier = if verifier_original_contact == 1 {
1560                1 // Verified by ContactId::SELF
1561            } else if verifier_original_contact == new_contact {
1562                new_contact // unkwnown verifier
1563            } else {
1564                // `verifications` contains the original contact id.
1565                // We need to get the new, verified-pgp-identified contact id.
1566                match verified_key_contacts.get(&verifier_original_contact) {
1567                    Some(v) => *v,
1568                    None => {
1569                        warn!(
1570                            context,
1571                            "Couldn't find key-contact for {verifier_original_contact} who verified {new_contact}"
1572                        );
1573                        continue;
1574                    }
1575                }
1576            };
1577            transaction
1578                .execute(
1579                    "UPDATE contacts SET verifier=? WHERE id=?",
1580                    (verifier, new_contact),
1581                )
1582                .context("Step 18")?;
1583        }
1584        info!(context, "Migrated verifications: {verifications:?}");
1585    }
1586
1587    // ======================= Step 3: =======================
1588    // For each chat, modify the memberlist to retain the correct contacts
1589    // In the process, track the set of contacts which remained no any chat at all
1590    // in a `BTreeSet<u32>`, which initially contains all contact ids
1591    let mut orphaned_contacts: BTreeSet<u32> = transaction
1592        .prepare("SELECT id FROM contacts WHERE id>9")
1593        .context("Step 19")?
1594        .query_map((), |row| row.get::<usize, u32>(0))
1595        .context("Step 20")?
1596        .collect::<Result<BTreeSet<u32>, rusqlite::Error>>()
1597        .context("Step 21")?;
1598
1599    {
1600        let mut stmt = transaction
1601            .prepare(
1602                "SELECT c.id, c.type, c.grpid, c.protected
1603            FROM chats c
1604            WHERE id>9",
1605            )
1606            .context("Step 22")?;
1607        let all_chats = stmt
1608            .query_map((), |row| {
1609                let id: u32 = row.get(0)?;
1610                let typ: u32 = row.get(1)?;
1611                let grpid: String = row.get(2)?;
1612                let protected: u32 = row.get(3)?;
1613                Ok((id, typ, grpid, protected))
1614            })
1615            .context("Step 23")?;
1616        let mut load_chat_contacts_stmt = transaction.prepare(
1617            "SELECT contact_id, add_timestamp>=remove_timestamp FROM chats_contacts
1618             WHERE chat_id=? AND contact_id>9",
1619        )?;
1620        let is_chatmail: Option<String> = transaction
1621            .query_row(
1622                "SELECT value FROM config WHERE keyname='is_chatmail'",
1623                (),
1624                |row| row.get(0),
1625            )
1626            .optional()
1627            .context("Step 23.1")?;
1628        let is_chatmail = is_chatmail
1629            .and_then(|s| s.parse::<i32>().ok())
1630            .unwrap_or_default()
1631            != 0;
1632        let map_to_key_contact = |old_member: &u32| {
1633            autocrypt_key_contacts
1634                .get(old_member)
1635                .or_else(|| {
1636                    // For chatmail servers,
1637                    // we send encrypted even if the peerstate is reset,
1638                    // because an unencrypted message likely won't arrive.
1639                    // This is the same behavior as before key-contacts migration.
1640                    if is_chatmail {
1641                        autocrypt_key_contacts_with_reset_peerstate.get(old_member)
1642                    } else {
1643                        None
1644                    }
1645                })
1646                .copied()
1647        };
1648
1649        let mut update_member_stmt = transaction
1650            .prepare("UPDATE chats_contacts SET contact_id=? WHERE contact_id=? AND chat_id=?")?;
1651        let mut addr_cmp_stmt = transaction
1652            .prepare("SELECT c.addr=d.addr FROM contacts c, contacts d WHERE c.id=? AND d.id=?")?;
1653        for chat in all_chats {
1654            let (chat_id, typ, grpid, protected) = chat.context("Step 24")?;
1655            // In groups, this also contains past members, i.e. `(_, false)` entries.
1656            let old_members: Vec<(u32, bool)> = load_chat_contacts_stmt
1657                .query_map((chat_id,), |row| {
1658                    let id: u32 = row.get(0)?;
1659                    let present: bool = row.get(1)?;
1660                    Ok((id, present))
1661                })
1662                .context("Step 25")?
1663                .collect::<Result<Vec<_>, _>>()
1664                .context("Step 26")?;
1665
1666            let mut keep_address_contacts = |reason: &str| -> Result<()> {
1667                info!(
1668                    context,
1669                    "Chat {chat_id} will be an unencrypted chat with contacts identified by email address: {reason}."
1670                );
1671                for (m, _) in &old_members {
1672                    orphaned_contacts.remove(m);
1673                }
1674
1675                // Unprotect this chat if it was protected.
1676                //
1677                // Otherwise we get protected chat with address-contact(s).
1678                transaction
1679                    .execute("UPDATE chats SET protected=0 WHERE id=?", (chat_id,))
1680                    .context("Step 26.0")?;
1681
1682                Ok(())
1683            };
1684            let old_and_new_members: Vec<(u32, bool, Option<u32>)> = match typ {
1685                // 1:1 chats retain:
1686                // - address-contact if peerstate is in the "reset" state,
1687                //   or if there is no key-contact that has the right email address.
1688                // - key-contact identified by the Autocrypt key if Autocrypt key does not match the verified key.
1689                // - key-contact identified by the verified key if peerstate Autocrypt key matches the Verified key.
1690                //   Since the autocrypt and verified key-contact are identital in this case, we can add the Autocrypt key-contact,
1691                //   and the effect will be the same.
1692                100 => {
1693                    let Some((old_member, _)) = old_members.first() else {
1694                        info!(
1695                            context,
1696                            "1:1 chat {chat_id} doesn't contain contact, probably it's self or device chat."
1697                        );
1698                        continue;
1699                    };
1700
1701                    let Some(new_contact) = map_to_key_contact(old_member) else {
1702                        keep_address_contacts("No peerstate, or peerstate in 'reset' state")?;
1703                        continue;
1704                    };
1705                    if !addr_cmp_stmt
1706                        .query_row((old_member, new_contact), |row| row.get::<_, bool>(0))?
1707                    {
1708                        keep_address_contacts("key contact has different email")?;
1709                        continue;
1710                    }
1711                    vec![(*old_member, true, Some(new_contact))]
1712                }
1713
1714                // Group
1715                120 => {
1716                    if grpid.is_empty() {
1717                        // Ad-hoc group that has empty Chat-Group-ID
1718                        // because it was created in response to receiving a non-chat email.
1719                        keep_address_contacts("Empty chat-Group-ID")?;
1720                        continue;
1721                    } else if protected == 1 {
1722                        old_members
1723                            .iter()
1724                            .map(|&(id, present)| {
1725                                (id, present, verified_key_contacts.get(&id).copied())
1726                            })
1727                            .collect()
1728                    } else {
1729                        old_members
1730                            .iter()
1731                            .map(|&(id, present)| (id, present, map_to_key_contact(&id)))
1732                            .collect::<Vec<(u32, bool, Option<u32>)>>()
1733                    }
1734                }
1735
1736                // Mailinglist
1737                140 => {
1738                    keep_address_contacts("Mailinglist")?;
1739                    continue;
1740                }
1741
1742                // Broadcast channel
1743                160 => old_members
1744                    .iter()
1745                    .map(|(original, _)| {
1746                        (
1747                            *original,
1748                            true,
1749                            autocrypt_key_contacts
1750                                .get(original)
1751                                // There will be no unencrypted broadcast lists anymore,
1752                                // so, if a peerstate is reset,
1753                                // the best we can do is encrypting to this key regardless.
1754                                .or_else(|| {
1755                                    autocrypt_key_contacts_with_reset_peerstate.get(original)
1756                                })
1757                                .copied(),
1758                        )
1759                    })
1760                    .collect::<Vec<(u32, bool, Option<u32>)>>(),
1761                _ => {
1762                    warn!(context, "Invalid chat type {typ}");
1763                    continue;
1764                }
1765            };
1766
1767            // If a group contains a contact without a key or with 'reset' peerstate,
1768            // downgrade to unencrypted Ad-Hoc group.
1769            if typ == 120
1770                && old_and_new_members
1771                    .iter()
1772                    .any(|&(_old, present, new)| present && new.is_none())
1773            {
1774                transaction
1775                    .execute("UPDATE chats SET grpid='' WHERE id=?", (chat_id,))
1776                    .context("Step 26.1")?;
1777                keep_address_contacts("Group contains contact without peerstate")?;
1778                continue;
1779            }
1780
1781            let human_readable_transitions = old_and_new_members
1782                .iter()
1783                .map(|(old, _, new)| format!("{old}->{}", new.unwrap_or_default()))
1784                .collect::<Vec<String>>()
1785                .join(" ");
1786            info!(
1787                context,
1788                "Migrating chat {chat_id} to key-contacts: {human_readable_transitions}"
1789            );
1790
1791            for (old_member, _, new_member) in old_and_new_members {
1792                if let Some(new_member) = new_member {
1793                    orphaned_contacts.remove(&new_member);
1794                    let res = update_member_stmt.execute((new_member, old_member, chat_id));
1795                    if res.is_err() {
1796                        // The same chat partner exists multiple times in the chat,
1797                        // with mutliple profiles which have different email addresses
1798                        // but the same key.
1799                        // We can only keep one of them.
1800                        // So, if one of them is not in the chat anymore, delete it,
1801                        // otherwise delete the one that was added least recently.
1802                        let member_to_delete: u32 = transaction
1803                            .query_row(
1804                                "SELECT contact_id
1805                               FROM chats_contacts
1806                              WHERE chat_id=? AND contact_id IN (?,?)
1807                           ORDER BY add_timestamp>=remove_timestamp, add_timestamp LIMIT 1",
1808                                (chat_id, new_member, old_member),
1809                                |row| row.get(0),
1810                            )
1811                            .context("Step 27")?;
1812                        info!(
1813                            context,
1814                            "Chat partner is in the chat {chat_id} multiple times. \
1815                            Deleting {member_to_delete}, then trying to update \
1816                            {old_member}->{new_member} again"
1817                        );
1818                        transaction
1819                            .execute(
1820                                "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?",
1821                                (chat_id, member_to_delete),
1822                            )
1823                            .context("Step 28")?;
1824                        // If we removed `old_member`, then this will be a no-op,
1825                        // which is exactly what we want in this case:
1826                        update_member_stmt.execute((new_member, old_member, chat_id))?;
1827                    }
1828                } else {
1829                    info!(
1830                        context,
1831                        "Old member {old_member} in chat {chat_id} can't be upgraded to key-contact, removing them"
1832                    );
1833                    transaction
1834                        .execute(
1835                            "DELETE FROM chats_contacts WHERE contact_id=? AND chat_id=?",
1836                            (old_member, chat_id),
1837                        )
1838                        .context("Step 29")?;
1839                }
1840            }
1841        }
1842    }
1843
1844    // ======================= Step 4: =======================
1845    {
1846        info!(
1847            context,
1848            "Marking contacts which remained in no chat at all as hidden: {orphaned_contacts:?}"
1849        );
1850        let mut mark_as_hidden_stmt = transaction
1851            .prepare("UPDATE contacts SET origin=? WHERE id=?")
1852            .context("Step 30")?;
1853        for contact in orphaned_contacts {
1854            mark_as_hidden_stmt
1855                .execute((0x8, contact))
1856                .context("Step 31")?;
1857        }
1858    }
1859
1860    // ======================= Step 5: =======================
1861    // Prepare for rewriting `from_id`, `to_id` in messages
1862    {
1863        let mut contacts_map = autocrypt_key_contacts_with_reset_peerstate;
1864        for (old, new) in autocrypt_key_contacts {
1865            contacts_map.insert(old, new);
1866        }
1867        transaction
1868            .execute(
1869                "CREATE TABLE key_contacts_map (
1870                    old_id INTEGER PRIMARY KEY NOT NULL,
1871                    new_id INTEGER NOT NULL
1872                ) STRICT",
1873                (),
1874            )
1875            .context("Step 32")?;
1876        {
1877            let mut stmt = transaction
1878                .prepare("INSERT INTO key_contacts_map (old_id, new_id) VALUES (?, ?)")
1879                .context("Step 33")?;
1880            for ids in contacts_map {
1881                stmt.execute(ids).context("Step 34")?;
1882            }
1883        }
1884        transaction
1885            .execute(
1886                "INSERT INTO config (keyname, value) VALUES (
1887                    'first_key_contacts_msg_id',
1888                    IFNULL((SELECT MAX(id)+1 FROM msgs), 0)
1889                )",
1890                (),
1891            )
1892            .context("Step 35")?;
1893    }
1894
1895    Ok(())
1896}
1897
1898/// Rewrite `from_id`, `to_id` in >= 1000 messages starting from the newest ones, to key-contacts.
1899pub(crate) async fn msgs_to_key_contacts(context: &Context) -> Result<()> {
1900    let sql = &context.sql;
1901    if sql
1902        .get_raw_config_int64("first_key_contacts_msg_id")
1903        .await?
1904        <= Some(0)
1905    {
1906        return Ok(());
1907    }
1908    let trans_fn = |t: &mut rusqlite::Transaction| {
1909        let mut first_key_contacts_msg_id: u64 = t
1910            .query_one(
1911                "SELECT CAST(value AS INTEGER) FROM config WHERE keyname='first_key_contacts_msg_id'",
1912                (),
1913                |row| row.get(0),
1914            )
1915            .context("Get first_key_contacts_msg_id")?;
1916        let mut stmt = t
1917            .prepare(
1918                "UPDATE msgs SET
1919                    from_id=IFNULL(
1920                        (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.from_id),
1921                        from_id
1922                    ),
1923                    to_id=IFNULL(
1924                        (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.to_id),
1925                        to_id
1926                    )
1927                WHERE id>=? AND id<?
1928                AND chat_id>9
1929                AND (param GLOB '*\nc=1*' OR param GLOB 'c=1*')",
1930            )
1931            .context("Prepare stmt")?;
1932        let msgs_to_migrate = 1000;
1933        let mut msgs_migrated: u64 = 0;
1934        while first_key_contacts_msg_id > 0 && msgs_migrated < msgs_to_migrate {
1935            let start_msg_id = first_key_contacts_msg_id.saturating_sub(msgs_to_migrate);
1936            let cnt: u64 = stmt
1937                .execute((start_msg_id, first_key_contacts_msg_id))
1938                .context("UPDATE msgs")?
1939                .try_into()?;
1940            msgs_migrated += cnt;
1941            first_key_contacts_msg_id = start_msg_id;
1942        }
1943        t.execute(
1944            "UPDATE config SET value=? WHERE keyname='first_key_contacts_msg_id'",
1945            (first_key_contacts_msg_id,),
1946        )
1947        .context("Update first_key_contacts_msg_id")?;
1948        Ok((msgs_migrated, first_key_contacts_msg_id))
1949    };
1950    let start = Time::now();
1951    let mut msgs_migrated = 0;
1952    loop {
1953        let (n, first_key_contacts_msg_id) = sql.transaction(trans_fn).await?;
1954        msgs_migrated += n;
1955        if first_key_contacts_msg_id == 0 || time_elapsed(&start) >= Duration::from_millis(500) {
1956            break;
1957        }
1958    }
1959    sql.uncache_raw_config("first_key_contacts_msg_id").await;
1960    info!(
1961        context,
1962        "Rewriting {msgs_migrated} msgs to key-contacts took {:?}.",
1963        time_elapsed(&start),
1964    );
1965    Ok(())
1966}
1967
1968impl Sql {
1969    async fn set_db_version(&self, version: i32) -> Result<()> {
1970        self.set_raw_config_int(VERSION_CFG, version).await?;
1971        Ok(())
1972    }
1973
1974    // Sets db `version` in the `transaction`.
1975    fn set_db_version_trans(transaction: &mut rusqlite::Transaction, version: i32) -> Result<()> {
1976        transaction.execute(
1977            "UPDATE config SET value=? WHERE keyname=?;",
1978            (format!("{version}"), VERSION_CFG),
1979        )?;
1980        Ok(())
1981    }
1982
1983    async fn set_db_version_in_cache(&self, version: i32) -> Result<()> {
1984        let mut lock = self.config_cache.write().await;
1985        lock.insert(VERSION_CFG.to_string(), Some(format!("{version}")));
1986        Ok(())
1987    }
1988
1989    async fn execute_migration(&self, query: &str, version: i32) -> Result<()> {
1990        self.execute_migration_transaction(
1991            |transaction| {
1992                transaction.execute_batch(query)?;
1993                Ok(())
1994            },
1995            version,
1996        )
1997        .await
1998    }
1999
2000    async fn execute_migration_transaction(
2001        &self,
2002        migration: impl Send + FnOnce(&mut rusqlite::Transaction) -> Result<()>,
2003        version: i32,
2004    ) -> Result<()> {
2005        #[cfg(test)]
2006        if STOP_MIGRATIONS_AT.try_with(|stop_migrations_at| version > *stop_migrations_at)
2007            == Ok(true)
2008        {
2009            println!("Not running migration {version}, because STOP_MIGRATIONS_AT is set");
2010            return Ok(());
2011        }
2012
2013        self.transaction(move |transaction| {
2014            let curr_version: String = transaction.query_row(
2015                "SELECT IFNULL(value, ?) FROM config WHERE keyname=?;",
2016                ("0", VERSION_CFG),
2017                |row| row.get(0),
2018            )?;
2019            let curr_version: i32 = curr_version.parse()?;
2020            ensure!(curr_version < version, "Db version must be increased");
2021            Self::set_db_version_trans(transaction, version)?;
2022            migration(transaction)?;
2023
2024            Ok(())
2025        })
2026        .await
2027        .with_context(|| format!("execute_migration failed for version {version}"))?;
2028
2029        self.config_cache.write().await.clear();
2030
2031        Ok(())
2032    }
2033}
2034
2035#[cfg(test)]
2036mod migrations_tests;