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