1use 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 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 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 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 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 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 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?, 0x200 => sql.set_raw_config_int(key, 1).await?, 0x400 => sql.set_raw_config_int(key, 3).await?, _ => 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?, 0x20000 => sql.set_raw_config_int(key, 1).await?, 0x40000 => sql.set_raw_config_int(key, 3).await?, _ => sql.set_raw_config_int(key, 0).await?,
350 }
351 }
352 }
353 sql.set_db_version(67).await?;
354 }
355 if dbversion < 68 {
356 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 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 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 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 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 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.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 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 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 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 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 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 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 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 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 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 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 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 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 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 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)?; 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 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 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 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 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 ¶m,
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) } 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 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 1 } else {
1704 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 } else {
1718 original_contact_id_from_addr(&secondary_verifier, new_id).context("Step 17")?
1719 };
1720 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 } else if verifier_original_contact == new_contact {
1740 new_contact } else {
1742 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 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 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 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 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 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 120 => {
1894 if grpid.is_empty() {
1895 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 140 => {
1916 keep_address_contacts("Mailinglist")?;
1917 continue;
1918 }
1919
1920 160 => old_members
1922 .iter()
1923 .map(|(original, _)| {
1924 (
1925 *original,
1926 true,
1927 autocrypt_key_contacts
1928 .get(original)
1929 .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 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 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 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 {
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 {
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
2076pub(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 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;