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