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(
1017 "CREATE TABLE imap_send (
1018 id INTEGER PRIMARY KEY AUTOINCREMENT,
1019 mime TEXT NOT NULL, -- Message content
1020 msg_id INTEGER NOT NULL, -- ID of the message in the `msgs` table
1021 attempts INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
1022 )",
1023 migration_version,
1024 )
1025 .await?;
1026 }
1027
1028 inc_and_check(&mut migration_version, 120)?;
1029 if dbversion < migration_version {
1030 sql.execute_migration(
1042 "INSERT OR IGNORE INTO config (keyname, value)
1043 SELECT 'delete_server_after', '0'
1044 FROM config WHERE keyname='configured'
1045 ",
1046 migration_version,
1047 )
1048 .await?;
1049 }
1050
1051 inc_and_check(&mut migration_version, 121)?;
1052 if dbversion < migration_version {
1053 sql.execute_migration(
1054 "CREATE INDEX chats_index4 ON chats (name)",
1055 migration_version,
1056 )
1057 .await?;
1058 }
1059
1060 inc_and_check(&mut migration_version, 122)?;
1061 if dbversion < migration_version {
1062 sql.execute_migration(
1063 "ALTER TABLE tokens ADD COLUMN foreign_id INTEGER NOT NULL DEFAULT 0",
1064 migration_version,
1065 )
1066 .await?;
1067 }
1068
1069 inc_and_check(&mut migration_version, 123)?;
1070 if dbversion < migration_version {
1071 sql.execute_migration(
1073 "CREATE TABLE new_msgs_status_updates (
1074 id INTEGER PRIMARY KEY AUTOINCREMENT,
1075 msg_id INTEGER,
1076 update_item TEXT DEFAULT '',
1077 uid TEXT UNIQUE,
1078 FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE
1079 );
1080 INSERT OR IGNORE INTO new_msgs_status_updates SELECT
1081 id, msg_id, update_item, uid
1082 FROM msgs_status_updates;
1083 DROP TABLE msgs_status_updates;
1084 ALTER TABLE new_msgs_status_updates RENAME TO msgs_status_updates;
1085 CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
1086 CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
1087 ",
1088 migration_version,
1089 )
1090 .await?;
1091 }
1092
1093 inc_and_check(&mut migration_version, 124)?;
1094 if dbversion < migration_version {
1095 sql.execute_migration(
1097 "UPDATE chats
1098 SET protected=1 -- ProtectionStatus::Protected
1099 WHERE type==100 -- Chattype::Single
1100 AND EXISTS (
1101 SELECT 1 FROM chats_contacts cc
1102 WHERE cc.chat_id==chats.id
1103 AND cc.contact_id=1
1104 )
1105 ",
1106 migration_version,
1107 )
1108 .await?;
1109 }
1110
1111 inc_and_check(&mut migration_version, 125)?;
1112 if dbversion < migration_version {
1113 sql.execute_migration(
1114 "CREATE TABLE http_cache (
1115 url TEXT PRIMARY KEY,
1116 expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1117 blobname TEXT NOT NULL,
1118 mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
1119 encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
1120 ) STRICT",
1121 migration_version,
1122 )
1123 .await?;
1124 }
1125
1126 inc_and_check(&mut migration_version, 126)?;
1127 if dbversion < migration_version {
1128 sql.execute_migration(
1130 "DROP TABLE http_cache;
1131 CREATE TABLE http_cache (
1132 url TEXT PRIMARY KEY,
1133 expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1134 stale INTEGER NOT NULL, -- When the cache entry is considered stale, timestamp in seconds.
1135 blobname TEXT NOT NULL,
1136 mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
1137 encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
1138 ) STRICT",
1139 migration_version,
1140 )
1141 .await?;
1142 }
1143
1144 inc_and_check(&mut migration_version, 127)?;
1145 if dbversion < migration_version {
1146 sql.execute_migration(
1148 "INSERT OR IGNORE INTO config (keyname, value)
1149 SELECT 'bcc_self', '1'
1150 FROM config WHERE keyname='delete_server_after' AND value='0'
1151 ",
1152 migration_version,
1153 )
1154 .await?;
1155 }
1156
1157 inc_and_check(&mut migration_version, 128)?;
1158 if dbversion < migration_version {
1159 sql.execute_migration(
1165 "ALTER TABLE chats_contacts
1166 ADD COLUMN add_timestamp NOT NULL DEFAULT 0;
1167 ALTER TABLE chats_contacts
1168 ADD COLUMN remove_timestamp NOT NULL DEFAULT 0;
1169 ",
1170 migration_version,
1171 )
1172 .await?;
1173 }
1174
1175 inc_and_check(&mut migration_version, 129)?;
1176 if dbversion < migration_version {
1177 sql.execute_migration(
1185 "INSERT OR IGNORE INTO config (keyname, value)
1186 SELECT 'bcc_self', '1'
1187 FROM config WHERE keyname='delete_server_after' AND value!='1'
1188 ",
1189 migration_version,
1190 )
1191 .await?;
1192 }
1193
1194 inc_and_check(&mut migration_version, 130)?;
1195 if dbversion < migration_version {
1196 sql.execute_migration(
1197 "
1198CREATE TABLE gossip_timestamp (
1199 chat_id INTEGER NOT NULL,
1200 fingerprint TEXT NOT NULL, -- Upper-case fingerprint of the key.
1201 timestamp INTEGER NOT NULL,
1202 UNIQUE (chat_id, fingerprint)
1203) STRICT;
1204CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint);
1205",
1206 migration_version,
1207 )
1208 .await?;
1209 }
1210
1211 inc_and_check(&mut migration_version, 131)?;
1212 if dbversion < migration_version {
1213 let entered_param = EnteredLoginParam::load(context).await?;
1214 let configured_param = ConfiguredLoginParam::load_legacy(context).await?;
1215
1216 sql.execute_migration_transaction(
1217 |transaction| {
1218 transaction.execute(
1219 "CREATE TABLE transports (
1220 id INTEGER PRIMARY KEY AUTOINCREMENT,
1221 addr TEXT NOT NULL,
1222 entered_param TEXT NOT NULL,
1223 configured_param TEXT NOT NULL,
1224 UNIQUE(addr)
1225 )",
1226 (),
1227 )?;
1228 if let Some(configured_param) = configured_param {
1229 transaction.execute(
1230 "INSERT INTO transports (addr, entered_param, configured_param)
1231 VALUES (?, ?, ?)",
1232 (
1233 configured_param.addr.clone(),
1234 serde_json::to_string(&entered_param)?,
1235 configured_param.into_json()?,
1236 ),
1237 )?;
1238 }
1239
1240 Ok(())
1241 },
1242 migration_version,
1243 )
1244 .await?;
1245 }
1246
1247 inc_and_check(&mut migration_version, 132)?;
1248 if dbversion < migration_version {
1249 let start = Time::now();
1250 sql.execute_migration_transaction(|t| migrate_key_contacts(context, t), migration_version)
1251 .await?;
1252 info!(
1253 context,
1254 "key-contacts migration took {:?} in total.",
1255 time_elapsed(&start),
1256 );
1257 context
1259 .set_config_internal(Config::LastHousekeeping, None)
1260 .await?;
1261 }
1262
1263 inc_and_check(&mut migration_version, 133)?;
1264 if dbversion < migration_version {
1265 sql.execute_migration(
1267 "UPDATE chats SET protected=0 WHERE protected!=1",
1268 migration_version,
1269 )
1270 .await?;
1271 }
1272
1273 inc_and_check(&mut migration_version, 134)?; inc_and_check(&mut migration_version, 135)?;
1276 if dbversion < migration_version {
1277 sql.execute_migration(
1278 "CREATE TABLE stats_securejoin_sources(
1279 source INTEGER PRIMARY KEY,
1280 count INTEGER NOT NULL DEFAULT 0
1281 ) STRICT;
1282 CREATE TABLE stats_securejoin_uipaths(
1283 uipath INTEGER PRIMARY KEY,
1284 count INTEGER NOT NULL DEFAULT 0
1285 ) STRICT;
1286 CREATE TABLE stats_securejoin_invites(
1287 already_existed INTEGER NOT NULL,
1288 already_verified INTEGER NOT NULL,
1289 type TEXT NOT NULL
1290 ) STRICT;
1291 CREATE TABLE stats_msgs(
1292 chattype INTEGER PRIMARY KEY,
1293 verified INTEGER NOT NULL DEFAULT 0,
1294 unverified_encrypted INTEGER NOT NULL DEFAULT 0,
1295 unencrypted INTEGER NOT NULL DEFAULT 0,
1296 only_to_self INTEGER NOT NULL DEFAULT 0,
1297 last_counted_msg_id INTEGER NOT NULL DEFAULT 0
1298 ) STRICT;",
1299 migration_version,
1300 )
1301 .await?;
1302 }
1303
1304 inc_and_check(&mut migration_version, 136)?;
1305 if dbversion < migration_version {
1306 sql.execute_migration(
1307 "CREATE TABLE stats_sending_enabled_events(timestamp INTEGER NOT NULL) STRICT;
1308 CREATE TABLE stats_sending_disabled_events(timestamp INTEGER NOT NULL) STRICT;",
1309 migration_version,
1310 )
1311 .await?;
1312 }
1313
1314 inc_and_check(&mut migration_version, 137)?;
1315 if dbversion < migration_version {
1316 sql.execute_migration(
1317 "DELETE FROM config WHERE keyname IN (
1318 'configured',
1319 'configured_imap_certificate_checks',
1320 'configured_imap_servers',
1321 'configured_mail_port',
1322 'configured_mail_pw',
1323 'configured_mail_security',
1324 'configured_mail_server',
1325 'configured_mail_user',
1326 'configured_send_port',
1327 'configured_send_pw',
1328 'configured_send_security',
1329 'configured_send_server',
1330 'configured_send_user',
1331 'configured_server_flags',
1332 'configured_smtp_certificate_checks',
1333 'configured_smtp_servers'
1334 )",
1335 migration_version,
1336 )
1337 .await?;
1338 }
1339
1340 inc_and_check(&mut migration_version, 138)?;
1341 if dbversion < migration_version {
1342 sql.execute_migration(
1343 "CREATE TABLE broadcast_secrets(
1344 chat_id INTEGER PRIMARY KEY NOT NULL,
1345 secret TEXT NOT NULL
1346 ) STRICT",
1347 migration_version,
1348 )
1349 .await?;
1350 }
1351
1352 inc_and_check(&mut migration_version, 139)?;
1353 if dbversion < migration_version {
1354 sql.execute_migration_transaction(
1355 |transaction| {
1356 if exists_before_update {
1357 let is_chatmail = transaction
1358 .query_row(
1359 "SELECT value FROM config WHERE keyname='is_chatmail'",
1360 (),
1361 |row| {
1362 let value: String = row.get(0)?;
1363 Ok(value)
1364 },
1365 )
1366 .optional()?
1367 .as_deref()
1368 == Some("1");
1369
1370 if !is_chatmail {
1377 transaction.execute(
1378 "INSERT OR IGNORE
1379 INTO config (keyname, value)
1380 VALUES (?, ?)",
1381 ("bcc_self", "1"),
1382 )?;
1383 }
1384 }
1385 Ok(())
1386 },
1387 migration_version,
1388 )
1389 .await?;
1390 }
1391
1392 inc_and_check(&mut migration_version, 140)?;
1393 if dbversion < migration_version {
1394 sql.execute_migration(
1395 "
1396CREATE TABLE new_imap (
1397id INTEGER PRIMARY KEY AUTOINCREMENT,
1398transport_id INTEGER NOT NULL, -- ID of the transport in the `transports` table.
1399rfc724_mid TEXT NOT NULL, -- Message-ID header
1400folder TEXT NOT NULL, -- IMAP folder
1401target TEXT NOT NULL, -- Destination folder. Empty string means that the message shall be deleted.
1402uid INTEGER NOT NULL, -- UID
1403uidvalidity INTEGER NOT NULL,
1404UNIQUE (transport_id, folder, uid, uidvalidity)
1405) STRICT;
1406
1407INSERT OR IGNORE INTO new_imap SELECT
1408 id, 1, rfc724_mid, folder, target, uid, uidvalidity
1409FROM imap;
1410DROP TABLE imap;
1411ALTER TABLE new_imap RENAME TO imap;
1412CREATE INDEX imap_folder ON imap(transport_id, folder);
1413CREATE INDEX imap_rfc724_mid ON imap(transport_id, rfc724_mid);
1414
1415CREATE TABLE new_imap_sync (
1416 transport_id INTEGER NOT NULL, -- ID of the transport in the `transports` table.
1417 folder TEXT NOT NULL,
1418 uidvalidity INTEGER NOT NULL DEFAULT 0,
1419 uid_next INTEGER NOT NULL DEFAULT 0,
1420 modseq INTEGER NOT NULL DEFAULT 0,
1421 UNIQUE (transport_id, folder)
1422) STRICT;
1423INSERT OR IGNORE INTO new_imap_sync SELECT
1424 1, folder, uidvalidity, uid_next, modseq
1425FROM imap_sync;
1426DROP TABLE imap_sync;
1427ALTER TABLE new_imap_sync RENAME TO imap_sync;
1428CREATE INDEX imap_sync_index ON imap_sync(transport_id, folder);
1429",
1430 migration_version,
1431 )
1432 .await?;
1433 }
1434
1435 inc_and_check(&mut migration_version, 141)?;
1436 if dbversion < migration_version {
1437 sql.execute_migration(
1438 "CREATE INDEX imap_only_rfc724_mid ON imap(rfc724_mid)",
1439 migration_version,
1440 )
1441 .await?;
1442 }
1443
1444 inc_and_check(&mut migration_version, 142)?;
1445 if dbversion < migration_version {
1446 sql.execute_migration(
1447 "ALTER TABLE transports
1448 ADD COLUMN add_timestamp INTEGER NOT NULL DEFAULT 0;
1449 CREATE TABLE removed_transports (
1450 addr TEXT NOT NULL,
1451 remove_timestamp INTEGER NOT NULL,
1452 UNIQUE(addr)
1453 ) STRICT;",
1454 migration_version,
1455 )
1456 .await?;
1457 }
1458
1459 inc_and_check(&mut migration_version, 143)?;
1460 if dbversion < migration_version {
1461 sql.execute_migration(
1462 "
1463ALTER TABLE chats ADD COLUMN name_normalized TEXT;
1464ALTER TABLE contacts ADD COLUMN name_normalized TEXT;
1465 ",
1466 migration_version,
1467 )
1468 .await?;
1469 }
1470
1471 inc_and_check(&mut migration_version, 144)?;
1472 if dbversion < migration_version {
1473 sql.execute_migration_transaction(
1474 |transaction| {
1475 let is_chatmail = transaction
1476 .query_row(
1477 "SELECT value FROM config WHERE keyname='is_chatmail'",
1478 (),
1479 |row| {
1480 let value: String = row.get(0)?;
1481 Ok(value)
1482 },
1483 )
1484 .optional()?
1485 .as_deref()
1486 == Some("1");
1487
1488 if is_chatmail {
1489 transaction.execute_batch(
1490 "DELETE FROM config WHERE keyname='only_fetch_mvbox';
1491 DELETE FROM config WHERE keyname='show_emails';
1492 UPDATE config SET value='0' WHERE keyname='mvbox_move'",
1493 )?;
1494 }
1495 Ok(())
1496 },
1497 migration_version,
1498 )
1499 .await?;
1500 }
1501
1502 let new_version = sql
1503 .get_raw_config_int(VERSION_CFG)
1504 .await?
1505 .unwrap_or_default();
1506 if new_version != dbversion || !exists_before_update {
1507 let created_db = if exists_before_update {
1508 ""
1509 } else {
1510 "Created new database. "
1511 };
1512 info!(context, "{}Migration done from v{}.", created_db, dbversion);
1513 }
1514 info!(context, "Database version: v{new_version}.");
1515
1516 Ok(recode_avatar)
1517}
1518
1519fn migrate_key_contacts(
1520 context: &Context,
1521 transaction: &mut rusqlite::Transaction<'_>,
1522) -> std::result::Result<(), anyhow::Error> {
1523 info!(context, "Starting key-contact transition.");
1524
1525 transaction.execute_batch(
1528 "ALTER TABLE contacts ADD COLUMN fingerprint TEXT NOT NULL DEFAULT '';
1529
1530 -- Verifier is an ID of the verifier contact.
1531 -- 0 if the contact is not verified.
1532 ALTER TABLE contacts ADD COLUMN verifier INTEGER NOT NULL DEFAULT 0;
1533
1534 CREATE INDEX contacts_fingerprint_index ON contacts (fingerprint);
1535
1536 CREATE TABLE public_keys (
1537 id INTEGER PRIMARY KEY AUTOINCREMENT,
1538 fingerprint TEXT NOT NULL UNIQUE, -- Upper-case fingerprint of the key.
1539 public_key BLOB NOT NULL -- Binary key, not ASCII-armored
1540 ) STRICT;
1541 CREATE INDEX public_key_index ON public_keys (fingerprint);
1542
1543 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1544 SELECT public_key_fingerprint, public_key FROM acpeerstates
1545 WHERE public_key_fingerprint IS NOT NULL AND public_key IS NOT NULL;
1546
1547 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1548 SELECT gossip_key_fingerprint, gossip_key FROM acpeerstates
1549 WHERE gossip_key_fingerprint IS NOT NULL AND gossip_key IS NOT NULL;
1550
1551 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1552 SELECT verified_key_fingerprint, verified_key FROM acpeerstates
1553 WHERE verified_key_fingerprint IS NOT NULL AND verified_key IS NOT NULL;
1554
1555 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1556 SELECT secondary_verified_key_fingerprint, secondary_verified_key FROM acpeerstates
1557 WHERE secondary_verified_key_fingerprint IS NOT NULL AND secondary_verified_key IS NOT NULL;",
1558 )
1559 .context("Creating key-contact tables")?;
1560
1561 let Some(self_addr): Option<String> = transaction
1562 .query_row(
1563 "SELECT value FROM config WHERE keyname='configured_addr'",
1564 (),
1565 |row| row.get(0),
1566 )
1567 .optional()
1568 .context("Step 0")?
1569 else {
1570 info!(
1571 context,
1572 "Not yet configured, no need to migrate key-contacts"
1573 );
1574 return Ok(());
1575 };
1576
1577 let mut autocrypt_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1584 let mut autocrypt_key_contacts_with_reset_peerstate: BTreeMap<u32, u32> = BTreeMap::new();
1585 let mut verified_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1586 {
1587 let mut verifications: BTreeMap<u32, u32> = BTreeMap::new();
1591
1592 let mut load_contacts_stmt = transaction
1593 .prepare(
1594 "SELECT c.id, c.name, c.addr, c.origin, c.blocked, c.last_seen,
1595 c.authname, c.param, c.status, c.is_bot, c.selfavatar_sent,
1596 IFNULL(p.public_key, p.gossip_key),
1597 p.verified_key, IFNULL(p.verifier, ''),
1598 p.secondary_verified_key, p.secondary_verifier, p.prefer_encrypted
1599 FROM contacts c
1600 INNER JOIN acpeerstates p ON c.addr=p.addr
1601 WHERE c.id > 9
1602 ORDER BY p.last_seen DESC",
1603 )
1604 .context("Step 2")?;
1605
1606 let all_address_contacts: rusqlite::Result<Vec<_>> = load_contacts_stmt
1607 .query_map((), |row| {
1608 let id: i64 = row.get(0)?;
1609 let name: String = row.get(1)?;
1610 let addr: String = row.get(2)?;
1611 let origin: i64 = row.get(3)?;
1612 let blocked: Option<bool> = row.get(4)?;
1613 let last_seen: i64 = row.get(5)?;
1614 let authname: String = row.get(6)?;
1615 let param: String = row.get(7)?;
1616 let status: Option<String> = row.get(8)?;
1617 let is_bot: bool = row.get(9)?;
1618 let selfavatar_sent: i64 = row.get(10)?;
1619 let autocrypt_key = row
1620 .get(11)
1621 .ok()
1622 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1623 let verified_key = row
1624 .get(12)
1625 .ok()
1626 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1627 let verifier: String = row.get(13)?;
1628 let secondary_verified_key = row
1629 .get(12)
1630 .ok()
1631 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1632 let secondary_verifier: String = row.get(15)?;
1633 let prefer_encrypt: u8 = row.get(16)?;
1634 Ok((
1635 id,
1636 name,
1637 addr,
1638 origin,
1639 blocked,
1640 last_seen,
1641 authname,
1642 param,
1643 status,
1644 is_bot,
1645 selfavatar_sent,
1646 autocrypt_key,
1647 verified_key,
1648 verifier,
1649 secondary_verified_key,
1650 secondary_verifier,
1651 prefer_encrypt,
1652 ))
1653 })
1654 .context("Step 3")?
1655 .collect();
1656
1657 let mut insert_contact_stmt = transaction
1658 .prepare(
1659 "INSERT INTO contacts (name, addr, origin, blocked, last_seen,
1660 authname, param, status, is_bot, selfavatar_sent, fingerprint)
1661 VALUES(?,?,?,?,?,?,?,?,?,?,?)",
1662 )
1663 .context("Step 4")?;
1664 let mut fingerprint_to_id_stmt = transaction
1665 .prepare("SELECT id FROM contacts WHERE fingerprint=? AND id>9")
1666 .context("Step 5")?;
1667 let mut original_contact_id_from_addr_stmt = transaction
1668 .prepare("SELECT id FROM contacts WHERE addr=? AND fingerprint='' AND id>9")
1669 .context("Step 6")?;
1670
1671 for row in all_address_contacts? {
1672 let (
1673 original_id,
1674 name,
1675 addr,
1676 origin,
1677 blocked,
1678 last_seen,
1679 authname,
1680 param,
1681 status,
1682 is_bot,
1683 selfavatar_sent,
1684 autocrypt_key,
1685 verified_key,
1686 verifier,
1687 secondary_verified_key,
1688 secondary_verifier,
1689 prefer_encrypt,
1690 ) = row;
1691 let mut insert_contact = |key: SignedPublicKey| -> Result<u32> {
1692 let fingerprint = key.dc_fingerprint().hex();
1693 let existing_contact_id: Option<u32> = fingerprint_to_id_stmt
1694 .query_row((&fingerprint,), |row| row.get(0))
1695 .optional()
1696 .context("Step 7")?;
1697 if let Some(existing_contact_id) = existing_contact_id {
1698 return Ok(existing_contact_id);
1699 }
1700 insert_contact_stmt
1701 .execute((
1702 &name,
1703 &addr,
1704 origin,
1705 blocked,
1706 last_seen,
1707 &authname,
1708 ¶m,
1709 &status,
1710 is_bot,
1711 selfavatar_sent,
1712 fingerprint.clone(),
1713 ))
1714 .context("Step 8")?;
1715 let id = transaction
1716 .last_insert_rowid()
1717 .try_into()
1718 .context("Step 9")?;
1719 info!(
1720 context,
1721 "Inserted new contact id={id} name='{name}' addr='{addr}' fingerprint={fingerprint}"
1722 );
1723 Ok(id)
1724 };
1725 let mut original_contact_id_from_addr = |addr: &str, default: u32| -> Result<u32> {
1726 if addr_cmp(addr, &self_addr) {
1727 Ok(1) } else if addr.is_empty() {
1729 Ok(default)
1730 } else {
1731 Ok(original_contact_id_from_addr_stmt
1732 .query_row((addr,), |row| row.get(0))
1733 .optional()
1734 .with_context(|| format!("Original contact '{addr}' not found"))?
1735 .unwrap_or(default))
1736 }
1737 };
1738
1739 let Some(autocrypt_key) = autocrypt_key else {
1740 continue;
1741 };
1742 let new_id = insert_contact(autocrypt_key).context("Step 10")?;
1743
1744 if prefer_encrypt != 20 {
1747 autocrypt_key_contacts.insert(original_id.try_into().context("Step 11")?, new_id);
1748 } else {
1749 autocrypt_key_contacts_with_reset_peerstate
1750 .insert(original_id.try_into().context("Step 12")?, new_id);
1751 }
1752
1753 let Some(verified_key) = verified_key else {
1754 continue;
1755 };
1756 let new_id = insert_contact(verified_key).context("Step 13")?;
1757 verified_key_contacts.insert(original_id.try_into().context("Step 14")?, new_id);
1758
1759 let verifier_id = if addr_cmp(&verifier, &addr) {
1760 1 } else {
1764 original_contact_id_from_addr(&verifier, new_id).context("Step 15")?
1768 };
1769 verifications.insert(new_id, verifier_id);
1770
1771 let Some(secondary_verified_key) = secondary_verified_key else {
1772 continue;
1773 };
1774 let new_id = insert_contact(secondary_verified_key).context("Step 16")?;
1775 let verifier_id: u32 = if addr_cmp(&secondary_verifier, &addr) {
1776 1 } else {
1778 original_contact_id_from_addr(&secondary_verifier, new_id).context("Step 17")?
1779 };
1780 verifications.entry(new_id).or_insert(verifier_id);
1782 }
1783 info!(
1784 context,
1785 "Created key-contacts identified by autocrypt key: {autocrypt_key_contacts:?}"
1786 );
1787 info!(
1788 context,
1789 "Created key-contacts with 'reset' peerstate identified by autocrypt key: {autocrypt_key_contacts_with_reset_peerstate:?}"
1790 );
1791 info!(
1792 context,
1793 "Created key-contacts identified by verified key: {verified_key_contacts:?}"
1794 );
1795
1796 for (&new_contact, &verifier_original_contact) in &verifications {
1797 let verifier = if verifier_original_contact == 1 {
1798 1 } else if verifier_original_contact == new_contact {
1800 new_contact } else {
1802 match verified_key_contacts.get(&verifier_original_contact) {
1805 Some(v) => *v,
1806 None => {
1807 warn!(
1808 context,
1809 "Couldn't find key-contact for {verifier_original_contact} who verified {new_contact}"
1810 );
1811 continue;
1812 }
1813 }
1814 };
1815 transaction
1816 .execute(
1817 "UPDATE contacts SET verifier=? WHERE id=?",
1818 (verifier, new_contact),
1819 )
1820 .context("Step 18")?;
1821 }
1822 info!(context, "Migrated verifications: {verifications:?}");
1823 }
1824
1825 let mut orphaned_contacts: BTreeSet<u32> = transaction
1830 .prepare("SELECT id FROM contacts WHERE id>9")
1831 .context("Step 19")?
1832 .query_map((), |row| row.get::<usize, u32>(0))
1833 .context("Step 20")?
1834 .collect::<Result<BTreeSet<u32>, rusqlite::Error>>()
1835 .context("Step 21")?;
1836
1837 {
1838 let mut stmt = transaction
1839 .prepare(
1840 "SELECT c.id, c.type, c.grpid, c.protected
1841 FROM chats c
1842 WHERE id>9",
1843 )
1844 .context("Step 22")?;
1845 let all_chats = stmt
1846 .query_map((), |row| {
1847 let id: u32 = row.get(0)?;
1848 let typ: u32 = row.get(1)?;
1849 let grpid: String = row.get(2)?;
1850 let protected: u32 = row.get(3)?;
1851 Ok((id, typ, grpid, protected))
1852 })
1853 .context("Step 23")?;
1854 let mut load_chat_contacts_stmt = transaction.prepare(
1855 "SELECT contact_id, add_timestamp>=remove_timestamp FROM chats_contacts
1856 WHERE chat_id=? AND contact_id>9",
1857 )?;
1858 let is_chatmail: Option<String> = transaction
1859 .query_row(
1860 "SELECT value FROM config WHERE keyname='is_chatmail'",
1861 (),
1862 |row| row.get(0),
1863 )
1864 .optional()
1865 .context("Step 23.1")?;
1866 let is_chatmail = is_chatmail
1867 .and_then(|s| s.parse::<i32>().ok())
1868 .unwrap_or_default()
1869 != 0;
1870 let map_to_key_contact = |old_member: &u32| {
1871 autocrypt_key_contacts
1872 .get(old_member)
1873 .or_else(|| {
1874 if is_chatmail {
1879 autocrypt_key_contacts_with_reset_peerstate.get(old_member)
1880 } else {
1881 None
1882 }
1883 })
1884 .copied()
1885 };
1886
1887 let mut update_member_stmt = transaction
1888 .prepare("UPDATE chats_contacts SET contact_id=? WHERE contact_id=? AND chat_id=?")?;
1889 let mut addr_cmp_stmt = transaction
1890 .prepare("SELECT c.addr=d.addr FROM contacts c, contacts d WHERE c.id=? AND d.id=?")?;
1891 for chat in all_chats {
1892 let (chat_id, typ, grpid, protected) = chat.context("Step 24")?;
1893 let old_members: Vec<(u32, bool)> = load_chat_contacts_stmt
1895 .query_map((chat_id,), |row| {
1896 let id: u32 = row.get(0)?;
1897 let present: bool = row.get(1)?;
1898 Ok((id, present))
1899 })
1900 .context("Step 25")?
1901 .collect::<Result<Vec<_>, _>>()
1902 .context("Step 26")?;
1903
1904 let mut keep_address_contacts = |reason: &str| -> Result<()> {
1905 info!(
1906 context,
1907 "Chat {chat_id} will be an unencrypted chat with contacts identified by email address: {reason}."
1908 );
1909 for (m, _) in &old_members {
1910 orphaned_contacts.remove(m);
1911 }
1912
1913 transaction
1917 .execute("UPDATE chats SET protected=0 WHERE id=?", (chat_id,))
1918 .context("Step 26.0")?;
1919
1920 Ok(())
1921 };
1922 let old_and_new_members: Vec<(u32, bool, Option<u32>)> = match typ {
1923 100 => {
1931 let Some((old_member, _)) = old_members.first() else {
1932 info!(
1933 context,
1934 "1:1 chat {chat_id} doesn't contain contact, probably it's self or device chat."
1935 );
1936 continue;
1937 };
1938
1939 let Some(new_contact) = map_to_key_contact(old_member) else {
1940 keep_address_contacts("No peerstate, or peerstate in 'reset' state")?;
1941 continue;
1942 };
1943 if !addr_cmp_stmt
1944 .query_row((old_member, new_contact), |row| row.get::<_, bool>(0))?
1945 {
1946 keep_address_contacts("key contact has different email")?;
1947 continue;
1948 }
1949 vec![(*old_member, true, Some(new_contact))]
1950 }
1951
1952 120 => {
1954 if grpid.is_empty() {
1955 keep_address_contacts("Empty chat-Group-ID")?;
1958 continue;
1959 } else if protected == 1 {
1960 old_members
1961 .iter()
1962 .map(|&(id, present)| {
1963 (id, present, verified_key_contacts.get(&id).copied())
1964 })
1965 .collect()
1966 } else {
1967 old_members
1968 .iter()
1969 .map(|&(id, present)| (id, present, map_to_key_contact(&id)))
1970 .collect::<Vec<(u32, bool, Option<u32>)>>()
1971 }
1972 }
1973
1974 140 => {
1976 keep_address_contacts("Mailinglist")?;
1977 continue;
1978 }
1979
1980 160 => old_members
1982 .iter()
1983 .map(|(original, _)| {
1984 (
1985 *original,
1986 true,
1987 autocrypt_key_contacts
1988 .get(original)
1989 .or_else(|| {
1993 autocrypt_key_contacts_with_reset_peerstate.get(original)
1994 })
1995 .copied(),
1996 )
1997 })
1998 .collect::<Vec<(u32, bool, Option<u32>)>>(),
1999 _ => {
2000 warn!(context, "Invalid chat type {typ}");
2001 continue;
2002 }
2003 };
2004
2005 if typ == 120
2008 && old_and_new_members
2009 .iter()
2010 .any(|&(_old, present, new)| present && new.is_none())
2011 {
2012 transaction
2013 .execute("UPDATE chats SET grpid='' WHERE id=?", (chat_id,))
2014 .context("Step 26.1")?;
2015 keep_address_contacts("Group contains contact without peerstate")?;
2016 continue;
2017 }
2018
2019 let human_readable_transitions = old_and_new_members
2020 .iter()
2021 .map(|(old, _, new)| format!("{old}->{}", new.unwrap_or_default()))
2022 .collect::<Vec<String>>()
2023 .join(" ");
2024 info!(
2025 context,
2026 "Migrating chat {chat_id} to key-contacts: {human_readable_transitions}"
2027 );
2028
2029 for (old_member, _, new_member) in old_and_new_members {
2030 if let Some(new_member) = new_member {
2031 orphaned_contacts.remove(&new_member);
2032 let res = update_member_stmt.execute((new_member, old_member, chat_id));
2033 if res.is_err() {
2034 let member_to_delete: u32 = transaction
2041 .query_row(
2042 "SELECT contact_id
2043 FROM chats_contacts
2044 WHERE chat_id=? AND contact_id IN (?,?)
2045 ORDER BY add_timestamp>=remove_timestamp, add_timestamp LIMIT 1",
2046 (chat_id, new_member, old_member),
2047 |row| row.get(0),
2048 )
2049 .context("Step 27")?;
2050 info!(
2051 context,
2052 "Chat partner is in the chat {chat_id} multiple times. \
2053 Deleting {member_to_delete}, then trying to update \
2054 {old_member}->{new_member} again"
2055 );
2056 transaction
2057 .execute(
2058 "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?",
2059 (chat_id, member_to_delete),
2060 )
2061 .context("Step 28")?;
2062 update_member_stmt.execute((new_member, old_member, chat_id))?;
2065 }
2066 } else {
2067 info!(
2068 context,
2069 "Old member {old_member} in chat {chat_id} can't be upgraded to key-contact, removing them"
2070 );
2071 transaction
2072 .execute(
2073 "DELETE FROM chats_contacts WHERE contact_id=? AND chat_id=?",
2074 (old_member, chat_id),
2075 )
2076 .context("Step 29")?;
2077 }
2078 }
2079 }
2080 }
2081
2082 {
2084 info!(
2085 context,
2086 "Marking contacts which remained in no chat at all as hidden: {orphaned_contacts:?}"
2087 );
2088 let mut mark_as_hidden_stmt = transaction
2089 .prepare("UPDATE contacts SET origin=? WHERE id=?")
2090 .context("Step 30")?;
2091 for contact in orphaned_contacts {
2092 mark_as_hidden_stmt
2093 .execute((0x8, contact))
2094 .context("Step 31")?;
2095 }
2096 }
2097
2098 {
2101 let mut contacts_map = autocrypt_key_contacts_with_reset_peerstate;
2102 for (old, new) in autocrypt_key_contacts {
2103 contacts_map.insert(old, new);
2104 }
2105 transaction
2106 .execute(
2107 "CREATE TABLE key_contacts_map (
2108 old_id INTEGER PRIMARY KEY NOT NULL,
2109 new_id INTEGER NOT NULL
2110 ) STRICT",
2111 (),
2112 )
2113 .context("Step 32")?;
2114 {
2115 let mut stmt = transaction
2116 .prepare("INSERT INTO key_contacts_map (old_id, new_id) VALUES (?, ?)")
2117 .context("Step 33")?;
2118 for ids in contacts_map {
2119 stmt.execute(ids).context("Step 34")?;
2120 }
2121 }
2122 transaction
2123 .execute(
2124 "INSERT INTO config (keyname, value) VALUES (
2125 'first_key_contacts_msg_id',
2126 IFNULL((SELECT MAX(id)+1 FROM msgs), 0)
2127 )",
2128 (),
2129 )
2130 .context("Step 35")?;
2131 }
2132
2133 Ok(())
2134}
2135
2136pub(crate) async fn msgs_to_key_contacts(context: &Context) -> Result<()> {
2138 let sql = &context.sql;
2139 if sql
2140 .get_raw_config_int64("first_key_contacts_msg_id")
2141 .await?
2142 <= Some(0)
2143 {
2144 return Ok(());
2145 }
2146 let trans_fn = |t: &mut rusqlite::Transaction| {
2147 let mut first_key_contacts_msg_id: u64 = t
2148 .query_one(
2149 "SELECT CAST(value AS INTEGER) FROM config WHERE keyname='first_key_contacts_msg_id'",
2150 (),
2151 |row| row.get(0),
2152 )
2153 .context("Get first_key_contacts_msg_id")?;
2154 let mut stmt = t
2155 .prepare(
2156 "UPDATE msgs SET
2157 from_id=IFNULL(
2158 (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.from_id),
2159 from_id
2160 ),
2161 to_id=IFNULL(
2162 (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.to_id),
2163 to_id
2164 )
2165 WHERE id>=? AND id<?
2166 AND chat_id>9
2167 AND (param GLOB '*\nc=1*' OR param GLOB 'c=1*')",
2168 )
2169 .context("Prepare stmt")?;
2170 let msgs_to_migrate = 1000;
2171 let mut msgs_migrated: u64 = 0;
2172 while first_key_contacts_msg_id > 0 && msgs_migrated < msgs_to_migrate {
2173 let start_msg_id = first_key_contacts_msg_id.saturating_sub(msgs_to_migrate);
2174 let cnt: u64 = stmt
2175 .execute((start_msg_id, first_key_contacts_msg_id))
2176 .context("UPDATE msgs")?
2177 .try_into()?;
2178 msgs_migrated += cnt;
2179 first_key_contacts_msg_id = start_msg_id;
2180 }
2181 t.execute(
2182 "UPDATE config SET value=? WHERE keyname='first_key_contacts_msg_id'",
2183 (first_key_contacts_msg_id,),
2184 )
2185 .context("Update first_key_contacts_msg_id")?;
2186 Ok((msgs_migrated, first_key_contacts_msg_id))
2187 };
2188 let start = Time::now();
2189 let mut msgs_migrated = 0;
2190 loop {
2191 let (n, first_key_contacts_msg_id) = sql.transaction(trans_fn).await?;
2192 msgs_migrated += n;
2193 if first_key_contacts_msg_id == 0 || time_elapsed(&start) >= Duration::from_millis(500) {
2194 break;
2195 }
2196 }
2197 sql.uncache_raw_config("first_key_contacts_msg_id").await;
2198 info!(
2199 context,
2200 "Rewriting {msgs_migrated} msgs to key-contacts took {:?}.",
2201 time_elapsed(&start),
2202 );
2203 Ok(())
2204}
2205
2206impl Sql {
2207 async fn set_db_version(&self, version: i32) -> Result<()> {
2208 self.set_raw_config_int(VERSION_CFG, version).await?;
2209 Ok(())
2210 }
2211
2212 fn set_db_version_trans(transaction: &mut rusqlite::Transaction, version: i32) -> Result<()> {
2214 transaction.execute(
2215 "UPDATE config SET value=? WHERE keyname=?;",
2216 (format!("{version}"), VERSION_CFG),
2217 )?;
2218 Ok(())
2219 }
2220
2221 async fn set_db_version_in_cache(&self, version: i32) -> Result<()> {
2222 let mut lock = self.config_cache.write().await;
2223 lock.insert(VERSION_CFG.to_string(), Some(format!("{version}")));
2224 Ok(())
2225 }
2226
2227 async fn execute_migration(&self, query: &str, version: i32) -> Result<()> {
2228 self.execute_migration_transaction(
2229 |transaction| {
2230 transaction.execute_batch(query)?;
2231 Ok(())
2232 },
2233 version,
2234 )
2235 .await
2236 }
2237
2238 async fn execute_migration_transaction(
2239 &self,
2240 migration: impl Send + FnOnce(&mut rusqlite::Transaction) -> Result<()>,
2241 version: i32,
2242 ) -> Result<()> {
2243 #[cfg(test)]
2244 if STOP_MIGRATIONS_AT.try_with(|stop_migrations_at| version > *stop_migrations_at)
2245 == Ok(true)
2246 {
2247 println!("Not running migration {version}, because STOP_MIGRATIONS_AT is set");
2248 return Ok(());
2249 }
2250
2251 self.transaction(move |transaction| {
2252 let curr_version: String = transaction.query_row(
2253 "SELECT IFNULL(value, ?) FROM config WHERE keyname=?;",
2254 ("0", VERSION_CFG),
2255 |row| row.get(0),
2256 )?;
2257 let curr_version: i32 = curr_version.parse()?;
2258 ensure!(curr_version < version, "Db version must be increased");
2259 Self::set_db_version_trans(transaction, version)?;
2260 migration(transaction)?;
2261
2262 Ok(())
2263 })
2264 .await
2265 .with_context(|| format!("execute_migration failed for version {version}"))?;
2266
2267 self.config_cache.write().await.clear();
2268
2269 Ok(())
2270 }
2271}
2272
2273#[cfg(test)]
2274mod migrations_tests;