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