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    inc_and_check(&mut migration_version, 142)?;
1443    if dbversion < migration_version {
1444        sql.execute_migration(
1445            "ALTER TABLE transports
1446             ADD COLUMN add_timestamp INTEGER NOT NULL DEFAULT 0;
1447             CREATE TABLE removed_transports (
1448                 addr TEXT NOT NULL,
1449                 remove_timestamp INTEGER NOT NULL,
1450                 UNIQUE(addr)
1451             ) STRICT;",
1452            migration_version,
1453        )
1454        .await?;
1455    }
1456
1457    let new_version = sql
1458        .get_raw_config_int(VERSION_CFG)
1459        .await?
1460        .unwrap_or_default();
1461    if new_version != dbversion || !exists_before_update {
1462        let created_db = if exists_before_update {
1463            ""
1464        } else {
1465            "Created new database. "
1466        };
1467        info!(context, "{}Migration done from v{}.", created_db, dbversion);
1468    }
1469    info!(context, "Database version: v{new_version}.");
1470
1471    Ok(recode_avatar)
1472}
1473
1474fn migrate_key_contacts(
1475    context: &Context,
1476    transaction: &mut rusqlite::Transaction<'_>,
1477) -> std::result::Result<(), anyhow::Error> {
1478    info!(context, "Starting key-contact transition.");
1479
1480    // =============================== Step 1: ===============================
1481    //                              Alter tables
1482    transaction.execute_batch(
1483        "ALTER TABLE contacts ADD COLUMN fingerprint TEXT NOT NULL DEFAULT '';
1484
1485        -- Verifier is an ID of the verifier contact.
1486        -- 0 if the contact is not verified.
1487        ALTER TABLE contacts ADD COLUMN verifier INTEGER NOT NULL DEFAULT 0;
1488
1489        CREATE INDEX contacts_fingerprint_index ON contacts (fingerprint);
1490
1491        CREATE TABLE public_keys (
1492        id INTEGER PRIMARY KEY AUTOINCREMENT,
1493        fingerprint TEXT NOT NULL UNIQUE, -- Upper-case fingerprint of the key.
1494        public_key BLOB NOT NULL -- Binary key, not ASCII-armored
1495        ) STRICT;
1496        CREATE INDEX public_key_index ON public_keys (fingerprint);
1497
1498        INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1499        SELECT public_key_fingerprint, public_key FROM acpeerstates
1500         WHERE public_key_fingerprint IS NOT NULL AND public_key IS NOT NULL;
1501
1502        INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1503        SELECT gossip_key_fingerprint, gossip_key FROM acpeerstates
1504         WHERE gossip_key_fingerprint IS NOT NULL AND gossip_key IS NOT NULL;
1505
1506        INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1507        SELECT verified_key_fingerprint, verified_key FROM acpeerstates
1508         WHERE verified_key_fingerprint IS NOT NULL AND verified_key IS NOT NULL;
1509
1510        INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1511        SELECT secondary_verified_key_fingerprint, secondary_verified_key FROM acpeerstates
1512         WHERE secondary_verified_key_fingerprint IS NOT NULL AND secondary_verified_key IS NOT NULL;",
1513    )
1514    .context("Creating key-contact tables")?;
1515
1516    let Some(self_addr): Option<String> = transaction
1517        .query_row(
1518            "SELECT value FROM config WHERE keyname='configured_addr'",
1519            (),
1520            |row| row.get(0),
1521        )
1522        .optional()
1523        .context("Step 0")?
1524    else {
1525        info!(
1526            context,
1527            "Not yet configured, no need to migrate key-contacts"
1528        );
1529        return Ok(());
1530    };
1531
1532    // =============================== Step 2: ===============================
1533    // Create up to 3 new contacts for every contact that has a peerstate:
1534    // one from the Autocrypt key fingerprint, one from the verified key fingerprint,
1535    // one from the secondary verified key fingerprint.
1536    // In the process, build maps from old contact id to new contact id:
1537    // one that maps to Autocrypt key-contact, one that maps to verified key-contact.
1538    let mut autocrypt_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1539    let mut autocrypt_key_contacts_with_reset_peerstate: BTreeMap<u32, u32> = BTreeMap::new();
1540    let mut verified_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1541    {
1542        // This maps from the verified contact to the original contact id of the verifier.
1543        // It can't map to the verified key contact id, because at the time of constructing
1544        // this map, not all key-contacts are in the database.
1545        let mut verifications: BTreeMap<u32, u32> = BTreeMap::new();
1546
1547        let mut load_contacts_stmt = transaction
1548            .prepare(
1549                "SELECT c.id, c.name, c.addr, c.origin, c.blocked, c.last_seen,
1550                c.authname, c.param, c.status, c.is_bot, c.selfavatar_sent,
1551                IFNULL(p.public_key, p.gossip_key),
1552                p.verified_key, IFNULL(p.verifier, ''),
1553                p.secondary_verified_key, p.secondary_verifier, p.prefer_encrypted
1554                FROM contacts c
1555                INNER JOIN acpeerstates p ON c.addr=p.addr
1556                WHERE c.id > 9
1557                ORDER BY p.last_seen DESC",
1558            )
1559            .context("Step 2")?;
1560
1561        let all_address_contacts: rusqlite::Result<Vec<_>> = load_contacts_stmt
1562            .query_map((), |row| {
1563                let id: i64 = row.get(0)?;
1564                let name: String = row.get(1)?;
1565                let addr: String = row.get(2)?;
1566                let origin: i64 = row.get(3)?;
1567                let blocked: Option<bool> = row.get(4)?;
1568                let last_seen: i64 = row.get(5)?;
1569                let authname: String = row.get(6)?;
1570                let param: String = row.get(7)?;
1571                let status: Option<String> = row.get(8)?;
1572                let is_bot: bool = row.get(9)?;
1573                let selfavatar_sent: i64 = row.get(10)?;
1574                let autocrypt_key = row
1575                    .get(11)
1576                    .ok()
1577                    .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1578                let verified_key = row
1579                    .get(12)
1580                    .ok()
1581                    .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1582                let verifier: String = row.get(13)?;
1583                let secondary_verified_key = row
1584                    .get(12)
1585                    .ok()
1586                    .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1587                let secondary_verifier: String = row.get(15)?;
1588                let prefer_encrypt: u8 = row.get(16)?;
1589                Ok((
1590                    id,
1591                    name,
1592                    addr,
1593                    origin,
1594                    blocked,
1595                    last_seen,
1596                    authname,
1597                    param,
1598                    status,
1599                    is_bot,
1600                    selfavatar_sent,
1601                    autocrypt_key,
1602                    verified_key,
1603                    verifier,
1604                    secondary_verified_key,
1605                    secondary_verifier,
1606                    prefer_encrypt,
1607                ))
1608            })
1609            .context("Step 3")?
1610            .collect();
1611
1612        let mut insert_contact_stmt = transaction
1613            .prepare(
1614                "INSERT INTO contacts (name, addr, origin, blocked, last_seen,
1615                authname, param, status, is_bot, selfavatar_sent, fingerprint)
1616                VALUES(?,?,?,?,?,?,?,?,?,?,?)",
1617            )
1618            .context("Step 4")?;
1619        let mut fingerprint_to_id_stmt = transaction
1620            .prepare("SELECT id FROM contacts WHERE fingerprint=? AND id>9")
1621            .context("Step 5")?;
1622        let mut original_contact_id_from_addr_stmt = transaction
1623            .prepare("SELECT id FROM contacts WHERE addr=? AND fingerprint='' AND id>9")
1624            .context("Step 6")?;
1625
1626        for row in all_address_contacts? {
1627            let (
1628                original_id,
1629                name,
1630                addr,
1631                origin,
1632                blocked,
1633                last_seen,
1634                authname,
1635                param,
1636                status,
1637                is_bot,
1638                selfavatar_sent,
1639                autocrypt_key,
1640                verified_key,
1641                verifier,
1642                secondary_verified_key,
1643                secondary_verifier,
1644                prefer_encrypt,
1645            ) = row;
1646            let mut insert_contact = |key: SignedPublicKey| -> Result<u32> {
1647                let fingerprint = key.dc_fingerprint().hex();
1648                let existing_contact_id: Option<u32> = fingerprint_to_id_stmt
1649                    .query_row((&fingerprint,), |row| row.get(0))
1650                    .optional()
1651                    .context("Step 7")?;
1652                if let Some(existing_contact_id) = existing_contact_id {
1653                    return Ok(existing_contact_id);
1654                }
1655                insert_contact_stmt
1656                    .execute((
1657                        &name,
1658                        &addr,
1659                        origin,
1660                        blocked,
1661                        last_seen,
1662                        &authname,
1663                        &param,
1664                        &status,
1665                        is_bot,
1666                        selfavatar_sent,
1667                        fingerprint.clone(),
1668                    ))
1669                    .context("Step 8")?;
1670                let id = transaction
1671                    .last_insert_rowid()
1672                    .try_into()
1673                    .context("Step 9")?;
1674                info!(
1675                    context,
1676                    "Inserted new contact id={id} name='{name}' addr='{addr}' fingerprint={fingerprint}"
1677                );
1678                Ok(id)
1679            };
1680            let mut original_contact_id_from_addr = |addr: &str, default: u32| -> Result<u32> {
1681                if addr_cmp(addr, &self_addr) {
1682                    Ok(1) // ContactId::SELF
1683                } else if addr.is_empty() {
1684                    Ok(default)
1685                } else {
1686                    Ok(original_contact_id_from_addr_stmt
1687                        .query_row((addr,), |row| row.get(0))
1688                        .optional()
1689                        .with_context(|| format!("Original contact '{addr}' not found"))?
1690                        .unwrap_or(default))
1691                }
1692            };
1693
1694            let Some(autocrypt_key) = autocrypt_key else {
1695                continue;
1696            };
1697            let new_id = insert_contact(autocrypt_key).context("Step 10")?;
1698
1699            // prefer_encrypt == 20 would mean EncryptPreference::Reset,
1700            // i.e. we shouldn't encrypt if possible.
1701            if prefer_encrypt != 20 {
1702                autocrypt_key_contacts.insert(original_id.try_into().context("Step 11")?, new_id);
1703            } else {
1704                autocrypt_key_contacts_with_reset_peerstate
1705                    .insert(original_id.try_into().context("Step 12")?, new_id);
1706            }
1707
1708            let Some(verified_key) = verified_key else {
1709                continue;
1710            };
1711            let new_id = insert_contact(verified_key).context("Step 13")?;
1712            verified_key_contacts.insert(original_id.try_into().context("Step 14")?, new_id);
1713
1714            let verifier_id = if addr_cmp(&verifier, &addr) {
1715                // Earlier versions of Delta Chat signalled a direct verification
1716                // by putting the contact's own address into the verifier column
1717                1 // 1=ContactId::SELF
1718            } else {
1719                // If the original verifier is unknown, we represent this in the database
1720                // by putting `new_id` into the place of the verifier,
1721                // i.e. we say that this contact verified itself.
1722                original_contact_id_from_addr(&verifier, new_id).context("Step 15")?
1723            };
1724            verifications.insert(new_id, verifier_id);
1725
1726            let Some(secondary_verified_key) = secondary_verified_key else {
1727                continue;
1728            };
1729            let new_id = insert_contact(secondary_verified_key).context("Step 16")?;
1730            let verifier_id: u32 = if addr_cmp(&secondary_verifier, &addr) {
1731                1 // 1=ContactId::SELF
1732            } else {
1733                original_contact_id_from_addr(&secondary_verifier, new_id).context("Step 17")?
1734            };
1735            // Only use secondary verification if there is no primary verification:
1736            verifications.entry(new_id).or_insert(verifier_id);
1737        }
1738        info!(
1739            context,
1740            "Created key-contacts identified by autocrypt key: {autocrypt_key_contacts:?}"
1741        );
1742        info!(
1743            context,
1744            "Created key-contacts  with 'reset' peerstate identified by autocrypt key: {autocrypt_key_contacts_with_reset_peerstate:?}"
1745        );
1746        info!(
1747            context,
1748            "Created key-contacts identified by verified key: {verified_key_contacts:?}"
1749        );
1750
1751        for (&new_contact, &verifier_original_contact) in &verifications {
1752            let verifier = if verifier_original_contact == 1 {
1753                1 // Verified by ContactId::SELF
1754            } else if verifier_original_contact == new_contact {
1755                new_contact // unkwnown verifier
1756            } else {
1757                // `verifications` contains the original contact id.
1758                // We need to get the new, verified-pgp-identified contact id.
1759                match verified_key_contacts.get(&verifier_original_contact) {
1760                    Some(v) => *v,
1761                    None => {
1762                        warn!(
1763                            context,
1764                            "Couldn't find key-contact for {verifier_original_contact} who verified {new_contact}"
1765                        );
1766                        continue;
1767                    }
1768                }
1769            };
1770            transaction
1771                .execute(
1772                    "UPDATE contacts SET verifier=? WHERE id=?",
1773                    (verifier, new_contact),
1774                )
1775                .context("Step 18")?;
1776        }
1777        info!(context, "Migrated verifications: {verifications:?}");
1778    }
1779
1780    // ======================= Step 3: =======================
1781    // For each chat, modify the memberlist to retain the correct contacts
1782    // In the process, track the set of contacts which remained no any chat at all
1783    // in a `BTreeSet<u32>`, which initially contains all contact ids
1784    let mut orphaned_contacts: BTreeSet<u32> = transaction
1785        .prepare("SELECT id FROM contacts WHERE id>9")
1786        .context("Step 19")?
1787        .query_map((), |row| row.get::<usize, u32>(0))
1788        .context("Step 20")?
1789        .collect::<Result<BTreeSet<u32>, rusqlite::Error>>()
1790        .context("Step 21")?;
1791
1792    {
1793        let mut stmt = transaction
1794            .prepare(
1795                "SELECT c.id, c.type, c.grpid, c.protected
1796            FROM chats c
1797            WHERE id>9",
1798            )
1799            .context("Step 22")?;
1800        let all_chats = stmt
1801            .query_map((), |row| {
1802                let id: u32 = row.get(0)?;
1803                let typ: u32 = row.get(1)?;
1804                let grpid: String = row.get(2)?;
1805                let protected: u32 = row.get(3)?;
1806                Ok((id, typ, grpid, protected))
1807            })
1808            .context("Step 23")?;
1809        let mut load_chat_contacts_stmt = transaction.prepare(
1810            "SELECT contact_id, add_timestamp>=remove_timestamp FROM chats_contacts
1811             WHERE chat_id=? AND contact_id>9",
1812        )?;
1813        let is_chatmail: Option<String> = transaction
1814            .query_row(
1815                "SELECT value FROM config WHERE keyname='is_chatmail'",
1816                (),
1817                |row| row.get(0),
1818            )
1819            .optional()
1820            .context("Step 23.1")?;
1821        let is_chatmail = is_chatmail
1822            .and_then(|s| s.parse::<i32>().ok())
1823            .unwrap_or_default()
1824            != 0;
1825        let map_to_key_contact = |old_member: &u32| {
1826            autocrypt_key_contacts
1827                .get(old_member)
1828                .or_else(|| {
1829                    // For chatmail servers,
1830                    // we send encrypted even if the peerstate is reset,
1831                    // because an unencrypted message likely won't arrive.
1832                    // This is the same behavior as before key-contacts migration.
1833                    if is_chatmail {
1834                        autocrypt_key_contacts_with_reset_peerstate.get(old_member)
1835                    } else {
1836                        None
1837                    }
1838                })
1839                .copied()
1840        };
1841
1842        let mut update_member_stmt = transaction
1843            .prepare("UPDATE chats_contacts SET contact_id=? WHERE contact_id=? AND chat_id=?")?;
1844        let mut addr_cmp_stmt = transaction
1845            .prepare("SELECT c.addr=d.addr FROM contacts c, contacts d WHERE c.id=? AND d.id=?")?;
1846        for chat in all_chats {
1847            let (chat_id, typ, grpid, protected) = chat.context("Step 24")?;
1848            // In groups, this also contains past members, i.e. `(_, false)` entries.
1849            let old_members: Vec<(u32, bool)> = load_chat_contacts_stmt
1850                .query_map((chat_id,), |row| {
1851                    let id: u32 = row.get(0)?;
1852                    let present: bool = row.get(1)?;
1853                    Ok((id, present))
1854                })
1855                .context("Step 25")?
1856                .collect::<Result<Vec<_>, _>>()
1857                .context("Step 26")?;
1858
1859            let mut keep_address_contacts = |reason: &str| -> Result<()> {
1860                info!(
1861                    context,
1862                    "Chat {chat_id} will be an unencrypted chat with contacts identified by email address: {reason}."
1863                );
1864                for (m, _) in &old_members {
1865                    orphaned_contacts.remove(m);
1866                }
1867
1868                // Unprotect this chat if it was protected.
1869                //
1870                // Otherwise we get protected chat with address-contact(s).
1871                transaction
1872                    .execute("UPDATE chats SET protected=0 WHERE id=?", (chat_id,))
1873                    .context("Step 26.0")?;
1874
1875                Ok(())
1876            };
1877            let old_and_new_members: Vec<(u32, bool, Option<u32>)> = match typ {
1878                // 1:1 chats retain:
1879                // - address-contact if peerstate is in the "reset" state,
1880                //   or if there is no key-contact that has the right email address.
1881                // - key-contact identified by the Autocrypt key if Autocrypt key does not match the verified key.
1882                // - key-contact identified by the verified key if peerstate Autocrypt key matches the Verified key.
1883                //   Since the autocrypt and verified key-contact are identital in this case, we can add the Autocrypt key-contact,
1884                //   and the effect will be the same.
1885                100 => {
1886                    let Some((old_member, _)) = old_members.first() else {
1887                        info!(
1888                            context,
1889                            "1:1 chat {chat_id} doesn't contain contact, probably it's self or device chat."
1890                        );
1891                        continue;
1892                    };
1893
1894                    let Some(new_contact) = map_to_key_contact(old_member) else {
1895                        keep_address_contacts("No peerstate, or peerstate in 'reset' state")?;
1896                        continue;
1897                    };
1898                    if !addr_cmp_stmt
1899                        .query_row((old_member, new_contact), |row| row.get::<_, bool>(0))?
1900                    {
1901                        keep_address_contacts("key contact has different email")?;
1902                        continue;
1903                    }
1904                    vec![(*old_member, true, Some(new_contact))]
1905                }
1906
1907                // Group
1908                120 => {
1909                    if grpid.is_empty() {
1910                        // Ad-hoc group that has empty Chat-Group-ID
1911                        // because it was created in response to receiving a non-chat email.
1912                        keep_address_contacts("Empty chat-Group-ID")?;
1913                        continue;
1914                    } else if protected == 1 {
1915                        old_members
1916                            .iter()
1917                            .map(|&(id, present)| {
1918                                (id, present, verified_key_contacts.get(&id).copied())
1919                            })
1920                            .collect()
1921                    } else {
1922                        old_members
1923                            .iter()
1924                            .map(|&(id, present)| (id, present, map_to_key_contact(&id)))
1925                            .collect::<Vec<(u32, bool, Option<u32>)>>()
1926                    }
1927                }
1928
1929                // Mailinglist
1930                140 => {
1931                    keep_address_contacts("Mailinglist")?;
1932                    continue;
1933                }
1934
1935                // Broadcast channel
1936                160 => old_members
1937                    .iter()
1938                    .map(|(original, _)| {
1939                        (
1940                            *original,
1941                            true,
1942                            autocrypt_key_contacts
1943                                .get(original)
1944                                // There will be no unencrypted broadcast lists anymore,
1945                                // so, if a peerstate is reset,
1946                                // the best we can do is encrypting to this key regardless.
1947                                .or_else(|| {
1948                                    autocrypt_key_contacts_with_reset_peerstate.get(original)
1949                                })
1950                                .copied(),
1951                        )
1952                    })
1953                    .collect::<Vec<(u32, bool, Option<u32>)>>(),
1954                _ => {
1955                    warn!(context, "Invalid chat type {typ}");
1956                    continue;
1957                }
1958            };
1959
1960            // If a group contains a contact without a key or with 'reset' peerstate,
1961            // downgrade to unencrypted Ad-Hoc group.
1962            if typ == 120
1963                && old_and_new_members
1964                    .iter()
1965                    .any(|&(_old, present, new)| present && new.is_none())
1966            {
1967                transaction
1968                    .execute("UPDATE chats SET grpid='' WHERE id=?", (chat_id,))
1969                    .context("Step 26.1")?;
1970                keep_address_contacts("Group contains contact without peerstate")?;
1971                continue;
1972            }
1973
1974            let human_readable_transitions = old_and_new_members
1975                .iter()
1976                .map(|(old, _, new)| format!("{old}->{}", new.unwrap_or_default()))
1977                .collect::<Vec<String>>()
1978                .join(" ");
1979            info!(
1980                context,
1981                "Migrating chat {chat_id} to key-contacts: {human_readable_transitions}"
1982            );
1983
1984            for (old_member, _, new_member) in old_and_new_members {
1985                if let Some(new_member) = new_member {
1986                    orphaned_contacts.remove(&new_member);
1987                    let res = update_member_stmt.execute((new_member, old_member, chat_id));
1988                    if res.is_err() {
1989                        // The same chat partner exists multiple times in the chat,
1990                        // with mutliple profiles which have different email addresses
1991                        // but the same key.
1992                        // We can only keep one of them.
1993                        // So, if one of them is not in the chat anymore, delete it,
1994                        // otherwise delete the one that was added least recently.
1995                        let member_to_delete: u32 = transaction
1996                            .query_row(
1997                                "SELECT contact_id
1998                               FROM chats_contacts
1999                              WHERE chat_id=? AND contact_id IN (?,?)
2000                           ORDER BY add_timestamp>=remove_timestamp, add_timestamp LIMIT 1",
2001                                (chat_id, new_member, old_member),
2002                                |row| row.get(0),
2003                            )
2004                            .context("Step 27")?;
2005                        info!(
2006                            context,
2007                            "Chat partner is in the chat {chat_id} multiple times. \
2008                            Deleting {member_to_delete}, then trying to update \
2009                            {old_member}->{new_member} again"
2010                        );
2011                        transaction
2012                            .execute(
2013                                "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?",
2014                                (chat_id, member_to_delete),
2015                            )
2016                            .context("Step 28")?;
2017                        // If we removed `old_member`, then this will be a no-op,
2018                        // which is exactly what we want in this case:
2019                        update_member_stmt.execute((new_member, old_member, chat_id))?;
2020                    }
2021                } else {
2022                    info!(
2023                        context,
2024                        "Old member {old_member} in chat {chat_id} can't be upgraded to key-contact, removing them"
2025                    );
2026                    transaction
2027                        .execute(
2028                            "DELETE FROM chats_contacts WHERE contact_id=? AND chat_id=?",
2029                            (old_member, chat_id),
2030                        )
2031                        .context("Step 29")?;
2032                }
2033            }
2034        }
2035    }
2036
2037    // ======================= Step 4: =======================
2038    {
2039        info!(
2040            context,
2041            "Marking contacts which remained in no chat at all as hidden: {orphaned_contacts:?}"
2042        );
2043        let mut mark_as_hidden_stmt = transaction
2044            .prepare("UPDATE contacts SET origin=? WHERE id=?")
2045            .context("Step 30")?;
2046        for contact in orphaned_contacts {
2047            mark_as_hidden_stmt
2048                .execute((0x8, contact))
2049                .context("Step 31")?;
2050        }
2051    }
2052
2053    // ======================= Step 5: =======================
2054    // Prepare for rewriting `from_id`, `to_id` in messages
2055    {
2056        let mut contacts_map = autocrypt_key_contacts_with_reset_peerstate;
2057        for (old, new) in autocrypt_key_contacts {
2058            contacts_map.insert(old, new);
2059        }
2060        transaction
2061            .execute(
2062                "CREATE TABLE key_contacts_map (
2063                    old_id INTEGER PRIMARY KEY NOT NULL,
2064                    new_id INTEGER NOT NULL
2065                ) STRICT",
2066                (),
2067            )
2068            .context("Step 32")?;
2069        {
2070            let mut stmt = transaction
2071                .prepare("INSERT INTO key_contacts_map (old_id, new_id) VALUES (?, ?)")
2072                .context("Step 33")?;
2073            for ids in contacts_map {
2074                stmt.execute(ids).context("Step 34")?;
2075            }
2076        }
2077        transaction
2078            .execute(
2079                "INSERT INTO config (keyname, value) VALUES (
2080                    'first_key_contacts_msg_id',
2081                    IFNULL((SELECT MAX(id)+1 FROM msgs), 0)
2082                )",
2083                (),
2084            )
2085            .context("Step 35")?;
2086    }
2087
2088    Ok(())
2089}
2090
2091/// Rewrite `from_id`, `to_id` in >= 1000 messages starting from the newest ones, to key-contacts.
2092pub(crate) async fn msgs_to_key_contacts(context: &Context) -> Result<()> {
2093    let sql = &context.sql;
2094    if sql
2095        .get_raw_config_int64("first_key_contacts_msg_id")
2096        .await?
2097        <= Some(0)
2098    {
2099        return Ok(());
2100    }
2101    let trans_fn = |t: &mut rusqlite::Transaction| {
2102        let mut first_key_contacts_msg_id: u64 = t
2103            .query_one(
2104                "SELECT CAST(value AS INTEGER) FROM config WHERE keyname='first_key_contacts_msg_id'",
2105                (),
2106                |row| row.get(0),
2107            )
2108            .context("Get first_key_contacts_msg_id")?;
2109        let mut stmt = t
2110            .prepare(
2111                "UPDATE msgs SET
2112                    from_id=IFNULL(
2113                        (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.from_id),
2114                        from_id
2115                    ),
2116                    to_id=IFNULL(
2117                        (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.to_id),
2118                        to_id
2119                    )
2120                WHERE id>=? AND id<?
2121                AND chat_id>9
2122                AND (param GLOB '*\nc=1*' OR param GLOB 'c=1*')",
2123            )
2124            .context("Prepare stmt")?;
2125        let msgs_to_migrate = 1000;
2126        let mut msgs_migrated: u64 = 0;
2127        while first_key_contacts_msg_id > 0 && msgs_migrated < msgs_to_migrate {
2128            let start_msg_id = first_key_contacts_msg_id.saturating_sub(msgs_to_migrate);
2129            let cnt: u64 = stmt
2130                .execute((start_msg_id, first_key_contacts_msg_id))
2131                .context("UPDATE msgs")?
2132                .try_into()?;
2133            msgs_migrated += cnt;
2134            first_key_contacts_msg_id = start_msg_id;
2135        }
2136        t.execute(
2137            "UPDATE config SET value=? WHERE keyname='first_key_contacts_msg_id'",
2138            (first_key_contacts_msg_id,),
2139        )
2140        .context("Update first_key_contacts_msg_id")?;
2141        Ok((msgs_migrated, first_key_contacts_msg_id))
2142    };
2143    let start = Time::now();
2144    let mut msgs_migrated = 0;
2145    loop {
2146        let (n, first_key_contacts_msg_id) = sql.transaction(trans_fn).await?;
2147        msgs_migrated += n;
2148        if first_key_contacts_msg_id == 0 || time_elapsed(&start) >= Duration::from_millis(500) {
2149            break;
2150        }
2151    }
2152    sql.uncache_raw_config("first_key_contacts_msg_id").await;
2153    info!(
2154        context,
2155        "Rewriting {msgs_migrated} msgs to key-contacts took {:?}.",
2156        time_elapsed(&start),
2157    );
2158    Ok(())
2159}
2160
2161impl Sql {
2162    async fn set_db_version(&self, version: i32) -> Result<()> {
2163        self.set_raw_config_int(VERSION_CFG, version).await?;
2164        Ok(())
2165    }
2166
2167    // Sets db `version` in the `transaction`.
2168    fn set_db_version_trans(transaction: &mut rusqlite::Transaction, version: i32) -> Result<()> {
2169        transaction.execute(
2170            "UPDATE config SET value=? WHERE keyname=?;",
2171            (format!("{version}"), VERSION_CFG),
2172        )?;
2173        Ok(())
2174    }
2175
2176    async fn set_db_version_in_cache(&self, version: i32) -> Result<()> {
2177        let mut lock = self.config_cache.write().await;
2178        lock.insert(VERSION_CFG.to_string(), Some(format!("{version}")));
2179        Ok(())
2180    }
2181
2182    async fn execute_migration(&self, query: &str, version: i32) -> Result<()> {
2183        self.execute_migration_transaction(
2184            |transaction| {
2185                transaction.execute_batch(query)?;
2186                Ok(())
2187            },
2188            version,
2189        )
2190        .await
2191    }
2192
2193    async fn execute_migration_transaction(
2194        &self,
2195        migration: impl Send + FnOnce(&mut rusqlite::Transaction) -> Result<()>,
2196        version: i32,
2197    ) -> Result<()> {
2198        #[cfg(test)]
2199        if STOP_MIGRATIONS_AT.try_with(|stop_migrations_at| version > *stop_migrations_at)
2200            == Ok(true)
2201        {
2202            println!("Not running migration {version}, because STOP_MIGRATIONS_AT is set");
2203            return Ok(());
2204        }
2205
2206        self.transaction(move |transaction| {
2207            let curr_version: String = transaction.query_row(
2208                "SELECT IFNULL(value, ?) FROM config WHERE keyname=?;",
2209                ("0", VERSION_CFG),
2210                |row| row.get(0),
2211            )?;
2212            let curr_version: i32 = curr_version.parse()?;
2213            ensure!(curr_version < version, "Db version must be increased");
2214            Self::set_db_version_trans(transaction, version)?;
2215            migration(transaction)?;
2216
2217            Ok(())
2218        })
2219        .await
2220        .with_context(|| format!("execute_migration failed for version {version}"))?;
2221
2222        self.config_cache.write().await.clear();
2223
2224        Ok(())
2225    }
2226}
2227
2228#[cfg(test)]
2229mod migrations_tests;