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