1use anyhow::{ensure, Context as _, Result};
4use deltachat_contact_tools::EmailAddress;
5use rusqlite::OptionalExtension;
6
7use crate::config::Config;
8use crate::configure::EnteredLoginParam;
9use crate::constants::ShowEmails;
10use crate::context::Context;
11use crate::imap;
12use crate::login_param::ConfiguredLoginParam;
13use crate::message::MsgId;
14use crate::provider::get_provider_by_domain;
15use crate::sql::Sql;
16use crate::tools::inc_and_check;
17
18const DBVERSION: i32 = 68;
19const VERSION_CFG: &str = "dbversion";
20const TABLES: &str = include_str!("./tables.sql");
21
22pub async fn run(context: &Context, sql: &Sql) -> Result<(bool, bool, bool, bool)> {
23 let mut recalc_fingerprints = false;
24 let mut exists_before_update = false;
25 let mut dbversion_before_update = DBVERSION;
26
27 if !sql
28 .table_exists("config")
29 .await
30 .context("failed to check if config table exists")?
31 {
32 sql.transaction(move |transaction| {
33 transaction.execute_batch(TABLES)?;
34
35 transaction.execute(
37 "INSERT INTO config (keyname, value) VALUES (?, ?);",
38 (VERSION_CFG, format!("{dbversion_before_update}")),
39 )?;
40 Ok(())
41 })
42 .await
43 .context("Creating tables failed")?;
44
45 let mut lock = context.sql.config_cache.write().await;
46 lock.insert(
47 VERSION_CFG.to_string(),
48 Some(format!("{dbversion_before_update}")),
49 );
50 drop(lock);
51 } else {
52 exists_before_update = true;
53 dbversion_before_update = sql
54 .get_raw_config_int(VERSION_CFG)
55 .await?
56 .unwrap_or_default();
57 }
58
59 let dbversion = dbversion_before_update;
60 let mut update_icons = !exists_before_update;
61 let mut disable_server_delete = false;
62 let mut recode_avatar = false;
63
64 if dbversion < 1 {
65 sql.execute_migration(
66 r#"
67CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');
68CREATE INDEX leftgrps_index1 ON leftgrps (grpid);"#,
69 1,
70 )
71 .await?;
72 }
73 if dbversion < 2 {
74 sql.execute_migration(
75 "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
76 2,
77 )
78 .await?;
79 }
80 if dbversion < 7 {
81 sql.execute_migration(
82 "CREATE TABLE keypairs (\
83 id INTEGER PRIMARY KEY, \
84 addr TEXT DEFAULT '' COLLATE NOCASE, \
85 is_default INTEGER DEFAULT 0, \
86 private_key, \
87 public_key, \
88 created INTEGER DEFAULT 0);",
89 7,
90 )
91 .await?;
92 }
93 if dbversion < 10 {
94 sql.execute_migration(
95 "CREATE TABLE acpeerstates (\
96 id INTEGER PRIMARY KEY, \
97 addr TEXT DEFAULT '' COLLATE NOCASE, \
98 last_seen INTEGER DEFAULT 0, \
99 last_seen_autocrypt INTEGER DEFAULT 0, \
100 public_key, \
101 prefer_encrypted INTEGER DEFAULT 0); \
102 CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);",
103 10,
104 )
105 .await?;
106 }
107 if dbversion < 12 {
108 sql.execute_migration(
109 r#"
110CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);
111CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);"#,
112 12,
113 )
114 .await?;
115 }
116 if dbversion < 17 {
117 sql.execute_migration(
118 r#"
119ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;
120CREATE INDEX chats_index2 ON chats (archived);
121ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;
122CREATE INDEX msgs_index5 ON msgs (starred);"#,
123 17,
124 )
125 .await?;
126 }
127 if dbversion < 18 {
128 sql.execute_migration(
129 r#"
130ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;
131ALTER TABLE acpeerstates ADD COLUMN gossip_key;"#,
132 18,
133 )
134 .await?;
135 }
136 if dbversion < 27 {
137 sql.execute_migration(
140 r#"
141DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;
142CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);
143ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
144ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;"#,
145 27,
146 )
147 .await?;
148 }
149 if dbversion < 34 {
150 sql.execute_migration(
151 r#"
152ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;
153ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
154ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';
155ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';
156CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
157CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);"#,
158 34,
159 )
160 .await?;
161 recalc_fingerprints = true;
162 }
163 if dbversion < 39 {
164 sql.execute_migration(
165 r#"
166CREATE TABLE tokens (
167 id INTEGER PRIMARY KEY,
168 namespc INTEGER DEFAULT 0,
169 foreign_id INTEGER DEFAULT 0,
170 token TEXT DEFAULT '',
171 timestamp INTEGER DEFAULT 0
172);
173ALTER TABLE acpeerstates ADD COLUMN verified_key;
174ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';
175CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);"#,
176 39,
177 )
178 .await?;
179 }
180 if dbversion < 40 {
181 sql.execute_migration("ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;", 40)
182 .await?;
183 }
184 if dbversion < 44 {
185 sql.execute_migration("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", 44)
186 .await?;
187 }
188 if dbversion < 46 {
189 sql.execute_migration(
190 r#"
191ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;
192ALTER TABLE msgs ADD COLUMN mime_references TEXT;"#,
193 46,
194 )
195 .await?;
196 }
197 if dbversion < 47 {
198 sql.execute_migration("ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;", 47)
199 .await?;
200 }
201 if dbversion < 48 {
202 sql.execute_migration(
204 "ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
205 48,
206 )
207 .await?;
208 }
209 if dbversion < 49 {
210 sql.execute_migration(
211 "ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;",
212 49,
213 )
214 .await?;
215 }
216 if dbversion < 50 {
217 if exists_before_update {
221 sql.set_raw_config_int("show_emails", ShowEmails::All as i32)
222 .await?;
223 }
224 sql.set_db_version(50).await?;
225 }
226 if dbversion < 53 {
227 sql.execute_migration(
230 r#"
231CREATE TABLE locations (
232 id INTEGER PRIMARY KEY AUTOINCREMENT,
233 latitude REAL DEFAULT 0.0,
234 longitude REAL DEFAULT 0.0,
235 accuracy REAL DEFAULT 0.0,
236 timestamp INTEGER DEFAULT 0,
237 chat_id INTEGER DEFAULT 0,
238 from_id INTEGER DEFAULT 0
239);"
240CREATE INDEX locations_index1 ON locations (from_id);
241CREATE INDEX locations_index2 ON locations (timestamp);
242ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;
243ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;
244ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;
245CREATE INDEX chats_index3 ON chats (locations_send_until);"#,
246 53,
247 )
248 .await?;
249 }
250 if dbversion < 54 {
251 sql.execute_migration(
252 r#"
253ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;
254CREATE INDEX msgs_index6 ON msgs (location_id);"#,
255 54,
256 )
257 .await?;
258 }
259 if dbversion < 55 {
260 sql.execute_migration(
261 "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
262 55,
263 )
264 .await?;
265 }
266 if dbversion < 59 {
267 sql.execute_migration(
270 r#"
271CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);
272CREATE INDEX devmsglabels_index1 ON devmsglabels (label);"#, 59)
273 .await?;
274 if exists_before_update && sql.get_raw_config_int("bcc_self").await?.is_none() {
275 sql.set_raw_config_int("bcc_self", 1).await?;
276 }
277 }
278
279 if dbversion < 60 {
280 sql.execute_migration(
281 "ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;",
282 60,
283 )
284 .await?;
285 }
286 if dbversion < 61 {
287 sql.execute_migration(
288 "ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;",
289 61,
290 )
291 .await?;
292 update_icons = true;
293 }
294 if dbversion < 62 {
295 sql.execute_migration(
296 "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;",
297 62,
298 )
299 .await?;
300 }
301 if dbversion < 63 {
302 sql.execute_migration("UPDATE chats SET grpid='' WHERE type=100", 63)
303 .await?;
304 }
305 if dbversion < 64 {
306 sql.execute_migration("ALTER TABLE msgs ADD COLUMN error TEXT DEFAULT '';", 64)
307 .await?;
308 }
309 if dbversion < 65 {
310 sql.execute_migration(
311 r#"
312ALTER TABLE chats ADD COLUMN ephemeral_timer INTEGER;
313ALTER TABLE msgs ADD COLUMN ephemeral_timer INTEGER DEFAULT 0;
314ALTER TABLE msgs ADD COLUMN ephemeral_timestamp INTEGER DEFAULT 0;"#,
315 65,
316 )
317 .await?;
318 }
319 if dbversion < 66 {
320 update_icons = true;
321 sql.set_db_version(66).await?;
322 }
323 if dbversion < 67 {
324 for prefix in &["", "configured_"] {
325 if let Some(server_flags) = sql
326 .get_raw_config_int(&format!("{prefix}server_flags"))
327 .await?
328 {
329 let imap_socket_flags = server_flags & 0x700;
330 let key = &format!("{prefix}mail_security");
331 match imap_socket_flags {
332 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?,
336 }
337 let smtp_socket_flags = server_flags & 0x70000;
338 let key = &format!("{prefix}send_security");
339 match smtp_socket_flags {
340 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?,
344 }
345 }
346 }
347 sql.set_db_version(67).await?;
348 }
349 if dbversion < 68 {
350 sql.execute_migration(
352 "CREATE INDEX IF NOT EXISTS msgs_index7 ON msgs (state, hidden, chat_id);",
353 68,
354 )
355 .await?;
356 }
357 if dbversion < 69 {
358 sql.execute_migration(
359 r#"
360ALTER TABLE chats ADD COLUMN protected INTEGER DEFAULT 0;
361-- 120=group, 130=old verified group
362UPDATE chats SET protected=1, type=120 WHERE type=130;"#,
363 69,
364 )
365 .await?;
366 }
367
368 if dbversion < 71 {
369 if let Ok(addr) = context.get_primary_self_addr().await {
370 if let Ok(domain) = EmailAddress::new(&addr).map(|email| email.domain) {
371 context
372 .set_config_internal(
373 Config::ConfiguredProvider,
374 get_provider_by_domain(&domain).map(|provider| provider.id),
375 )
376 .await?;
377 } else {
378 warn!(context, "Can't parse configured address: {:?}", addr);
379 }
380 }
381
382 sql.set_db_version(71).await?;
383 }
384 if dbversion < 72 && !sql.col_exists("msgs", "mime_modified").await? {
385 sql.execute_migration(
386 r#"
387 ALTER TABLE msgs ADD COLUMN mime_modified INTEGER DEFAULT 0;"#,
388 72,
389 )
390 .await?;
391 }
392 if dbversion < 73 {
393 use Config::*;
394 sql.execute(
395 r#"
396CREATE TABLE imap_sync (folder TEXT PRIMARY KEY, uidvalidity INTEGER DEFAULT 0, uid_next INTEGER DEFAULT 0);"#,
397()
398 )
399 .await?;
400 for c in &[
401 ConfiguredInboxFolder,
402 ConfiguredSentboxFolder,
403 ConfiguredMvboxFolder,
404 ] {
405 if let Some(folder) = context.get_config(*c).await? {
406 let (uid_validity, last_seen_uid) =
407 imap::get_config_last_seen_uid(context, &folder).await?;
408 if last_seen_uid > 0 {
409 imap::set_uid_next(context, &folder, last_seen_uid + 1).await?;
410 imap::set_uidvalidity(context, &folder, uid_validity).await?;
411 }
412 }
413 }
414 if exists_before_update {
415 disable_server_delete = true;
416
417 if let Some(provider) = context.get_configured_provider().await? {
419 if let Some(defaults) = &provider.config_defaults {
420 if defaults.iter().any(|d| d.key == Config::DeleteServerAfter) {
421 disable_server_delete = false;
422 }
423 }
424 }
425 }
426 sql.set_db_version(73).await?;
427 }
428 if dbversion < 74 {
429 sql.execute_migration("UPDATE contacts SET name='' WHERE name=authname", 74)
430 .await?;
431 }
432 if dbversion < 75 {
433 sql.execute_migration(
434 "ALTER TABLE contacts ADD COLUMN status TEXT DEFAULT '';",
435 75,
436 )
437 .await?;
438 }
439 if dbversion < 76 {
440 sql.execute_migration("ALTER TABLE msgs ADD COLUMN subject TEXT DEFAULT '';", 76)
441 .await?;
442 }
443 if dbversion < 77 {
444 recode_avatar = true;
445 sql.set_db_version(77).await?;
446 }
447 if dbversion < 78 {
448 sql.execute_migration("UPDATE chats SET archived=1 WHERE blocked=2;", 78)
451 .await?;
452 }
453 if dbversion < 79 {
454 sql.execute_migration(
455 r#"
456 ALTER TABLE msgs ADD COLUMN download_state INTEGER DEFAULT 0;
457 "#,
458 79,
459 )
460 .await?;
461 }
462 if dbversion < 80 {
463 sql.execute_migration(
464 r#"CREATE TABLE multi_device_sync (
465id INTEGER PRIMARY KEY AUTOINCREMENT,
466item TEXT DEFAULT '');"#,
467 80,
468 )
469 .await?;
470 }
471 if dbversion < 81 {
472 sql.execute_migration("ALTER TABLE msgs ADD COLUMN hop_info TEXT;", 81)
473 .await?;
474 }
475 if dbversion < 82 {
476 sql.execute_migration(
477 r#"CREATE TABLE imap (
478id INTEGER PRIMARY KEY AUTOINCREMENT,
479rfc724_mid TEXT DEFAULT '', -- Message-ID header
480folder TEXT DEFAULT '', -- IMAP folder
481target TEXT DEFAULT '', -- Destination folder, empty to delete.
482uid INTEGER DEFAULT 0, -- UID
483uidvalidity INTEGER DEFAULT 0,
484UNIQUE (folder, uid, uidvalidity)
485);
486CREATE INDEX imap_folder ON imap(folder);
487CREATE INDEX imap_messageid ON imap(rfc724_mid);
488
489INSERT INTO imap
490(rfc724_mid, folder, target, uid, uidvalidity)
491SELECT
492rfc724_mid,
493server_folder AS folder,
494server_folder AS target,
495server_uid AS uid,
496(SELECT uidvalidity FROM imap_sync WHERE folder=server_folder) AS uidvalidity
497FROM msgs
498WHERE server_uid>0
499ON CONFLICT (folder, uid, uidvalidity)
500DO UPDATE SET rfc724_mid=excluded.rfc724_mid,
501 target=excluded.target;
502"#,
503 82,
504 )
505 .await?;
506 }
507 if dbversion < 83 {
508 sql.execute_migration(
509 "ALTER TABLE imap_sync
510 ADD COLUMN modseq -- Highest modification sequence
511 INTEGER DEFAULT 0",
512 83,
513 )
514 .await?;
515 }
516 if dbversion < 84 {
517 sql.execute_migration(
518 r#"CREATE TABLE msgs_status_updates (
519id INTEGER PRIMARY KEY AUTOINCREMENT,
520msg_id INTEGER,
521update_item TEXT DEFAULT '',
522update_item_read INTEGER DEFAULT 0 -- XXX unused
523);
524CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);"#,
525 84,
526 )
527 .await?;
528 }
529 if dbversion < 85 {
530 sql.execute_migration(
531 r#"CREATE TABLE smtp (
532id INTEGER PRIMARY KEY,
533rfc724_mid TEXT NOT NULL, -- Message-ID
534mime TEXT NOT NULL, -- SMTP payload
535msg_id INTEGER NOT NULL, -- ID of the message in `msgs` table
536recipients TEXT NOT NULL, -- List of recipients separated by space
537retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
538);
539CREATE INDEX smtp_messageid ON imap(rfc724_mid);
540"#,
541 85,
542 )
543 .await?;
544 }
545 if dbversion < 86 {
546 sql.execute_migration(
547 r#"CREATE TABLE bobstate (
548 id INTEGER PRIMARY KEY AUTOINCREMENT,
549 invite TEXT NOT NULL,
550 next_step INTEGER NOT NULL,
551 chat_id INTEGER NOT NULL
552 );"#,
553 86,
554 )
555 .await?;
556 }
557 if dbversion < 87 {
558 sql.execute_migration(
560 "CREATE INDEX IF NOT EXISTS msgs_index8 ON msgs (ephemeral_timestamp);",
561 87,
562 )
563 .await?;
564 }
565 if dbversion < 88 {
566 sql.execute_migration("DROP TABLE IF EXISTS backup_blobs;", 88)
567 .await?;
568 }
569 if dbversion < 89 {
570 sql.execute_migration(
571 r#"CREATE TABLE imap_markseen (
572 id INTEGER,
573 FOREIGN KEY(id) REFERENCES imap(id) ON DELETE CASCADE
574 );"#,
575 89,
576 )
577 .await?;
578 }
579 if dbversion < 90 {
580 sql.execute_migration(
581 r#"CREATE TABLE smtp_mdns (
582 msg_id INTEGER NOT NULL, -- id of the message in msgs table which requested MDN (DEPRECATED 2024-06-21)
583 from_id INTEGER NOT NULL, -- id of the contact that sent the message, MDN destination
584 rfc724_mid TEXT NOT NULL, -- Message-ID header
585 retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send MDN
586 );"#,
587 90,
588 )
589 .await?;
590 }
591 if dbversion < 91 {
592 sql.execute_migration(
593 r#"CREATE TABLE smtp_status_updates (
594 msg_id INTEGER NOT NULL UNIQUE, -- msg_id of the webxdc instance with pending updates
595 first_serial INTEGER NOT NULL, -- id in msgs_status_updates
596 last_serial INTEGER NOT NULL, -- id in msgs_status_updates
597 descr TEXT NOT NULL -- text to send along with the updates
598 );"#,
599 91,
600 )
601 .await?;
602 }
603 if dbversion < 92 {
604 sql.execute_migration(
605 r#"CREATE TABLE reactions (
606 msg_id INTEGER NOT NULL, -- id of the message reacted to
607 contact_id INTEGER NOT NULL, -- id of the contact reacting to the message
608 reaction TEXT DEFAULT '' NOT NULL, -- a sequence of emojis separated by spaces
609 PRIMARY KEY(msg_id, contact_id),
610 FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE -- delete reactions when message is deleted
611 FOREIGN KEY(contact_id) REFERENCES contacts(id) ON DELETE CASCADE -- delete reactions when contact is deleted
612 )"#,
613 92
614 ).await?;
615 }
616 if dbversion < 93 {
617 sql.execute_migration(
619 "CREATE TABLE sending_domains(domain TEXT PRIMARY KEY, dkim_works INTEGER DEFAULT 0);",
620 93,
621 )
622 .await?;
623 }
624 if dbversion < 94 {
625 sql.execute_migration(
626 "CREATE TABLE new_acpeerstates (
631 id INTEGER PRIMARY KEY,
632 addr TEXT DEFAULT '' COLLATE NOCASE,
633 last_seen INTEGER DEFAULT 0,
634 last_seen_autocrypt INTEGER DEFAULT 0,
635 public_key,
636 prefer_encrypted INTEGER DEFAULT 0,
637 gossip_timestamp INTEGER DEFAULT 0,
638 gossip_key,
639 public_key_fingerprint TEXT DEFAULT '',
640 gossip_key_fingerprint TEXT DEFAULT '',
641 verified_key,
642 verified_key_fingerprint TEXT DEFAULT '',
643 UNIQUE (addr) -- Only one peerstate per address
644 );
645 INSERT OR IGNORE INTO new_acpeerstates SELECT
646 id, addr, last_seen, last_seen_autocrypt, public_key, prefer_encrypted,
647 gossip_timestamp, gossip_key, public_key_fingerprint,
648 gossip_key_fingerprint, verified_key, verified_key_fingerprint
649 FROM acpeerstates;
650 DROP TABLE acpeerstates;
651 ALTER TABLE new_acpeerstates RENAME TO acpeerstates;
652 CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);
653 CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
654 CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);
655 CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);
656 ",
657 94,
658 )
659 .await?;
660 }
661 if dbversion < 95 {
662 sql.execute_migration(
663 "CREATE TABLE new_chats_contacts (chat_id INTEGER, contact_id INTEGER, UNIQUE(chat_id, contact_id));\
664 INSERT OR IGNORE INTO new_chats_contacts SELECT chat_id, contact_id FROM chats_contacts;\
665 DROP TABLE chats_contacts;\
666 ALTER TABLE new_chats_contacts RENAME TO chats_contacts;\
667 CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);\
668 CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
669 95
670 ).await?;
671 }
672 if dbversion < 96 {
673 sql.execute_migration(
674 "ALTER TABLE acpeerstates ADD COLUMN verifier TEXT DEFAULT '';",
675 96,
676 )
677 .await?;
678 }
679 if dbversion < 97 {
680 sql.execute_migration(
681 "CREATE TABLE dns_cache (
682 hostname TEXT NOT NULL,
683 address TEXT NOT NULL, -- IPv4 or IPv6 address
684 timestamp INTEGER NOT NULL,
685 UNIQUE (hostname, address)
686 )",
687 97,
688 )
689 .await?;
690 }
691 if dbversion < 98 {
692 if exists_before_update && sql.get_raw_config_int("show_emails").await?.is_none() {
693 sql.set_raw_config_int("show_emails", ShowEmails::Off as i32)
694 .await?;
695 }
696 sql.set_db_version(98).await?;
697 }
698 if dbversion < 99 {
699 sql.set_db_version(99).await?;
711 }
712 if dbversion < 100 {
713 sql.execute_migration(
714 "ALTER TABLE msgs ADD COLUMN mime_compressed INTEGER NOT NULL DEFAULT 0",
715 100,
716 )
717 .await?;
718 }
719 if dbversion < 101 {
720 sql.execute_migration(
723 "DROP TABLE smtp;
724 CREATE TABLE smtp (
725 id INTEGER PRIMARY KEY AUTOINCREMENT,
726 rfc724_mid TEXT NOT NULL, -- Message-ID
727 mime TEXT NOT NULL, -- SMTP payload
728 msg_id INTEGER NOT NULL, -- ID of the message in `msgs` table
729 recipients TEXT NOT NULL, -- List of recipients separated by space
730 retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
731 );
732 ",
733 101,
734 )
735 .await?;
736 }
737
738 if dbversion < 102 {
739 sql.execute_migration(
740 "CREATE TABLE download (
741 msg_id INTEGER NOT NULL -- id of the message stub in msgs table
742 )",
743 102,
744 )
745 .await?;
746 }
747
748 if dbversion < 103 {
750 sql.execute_migration(
751 "ALTER TABLE contacts ADD COLUMN is_bot INTEGER NOT NULL DEFAULT 0",
752 103,
753 )
754 .await?;
755 }
756
757 if dbversion < 104 {
758 sql.execute_migration(
759 "ALTER TABLE acpeerstates
760 ADD COLUMN secondary_verified_key;
761 ALTER TABLE acpeerstates
762 ADD COLUMN secondary_verified_key_fingerprint TEXT DEFAULT '';
763 ALTER TABLE acpeerstates
764 ADD COLUMN secondary_verifier TEXT DEFAULT ''",
765 104,
766 )
767 .await?;
768 }
769
770 if dbversion < 105 {
771 sql.execute_migration(
773 r#"CREATE TABLE new_msgs_status_updates (
774id INTEGER PRIMARY KEY AUTOINCREMENT,
775msg_id INTEGER,
776update_item TEXT DEFAULT '',
777uid TEXT UNIQUE
778);
779INSERT OR IGNORE INTO new_msgs_status_updates SELECT
780 id, msg_id, update_item, NULL
781FROM msgs_status_updates;
782DROP TABLE msgs_status_updates;
783ALTER TABLE new_msgs_status_updates RENAME TO msgs_status_updates;
784CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
785CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
786"#,
787 105,
788 )
789 .await?;
790 }
791
792 if dbversion < 106 {
793 sql.execute_migration(
795 "CREATE TABLE new_config (
796 id INTEGER PRIMARY KEY,
797 keyname TEXT UNIQUE,
798 value TEXT NOT NULL
799 );
800 INSERT OR IGNORE INTO new_config SELECT
801 id, keyname, value
802 FROM config;
803 DROP TABLE config;
804 ALTER TABLE new_config RENAME TO config;
805 CREATE INDEX config_index1 ON config (keyname);",
806 106,
807 )
808 .await?;
809 }
810
811 if dbversion < 107 {
812 sql.execute_migration(
813 "CREATE TABLE new_keypairs (
814 id INTEGER PRIMARY KEY AUTOINCREMENT,
815 private_key UNIQUE NOT NULL,
816 public_key UNIQUE NOT NULL
817 );
818 INSERT OR IGNORE INTO new_keypairs SELECT id, private_key, public_key FROM keypairs;
819
820 INSERT OR IGNORE
821 INTO config (keyname, value)
822 VALUES
823 ('key_id', (SELECT id FROM new_keypairs
824 WHERE private_key=
825 (SELECT private_key FROM keypairs
826 WHERE addr=(SELECT value FROM config WHERE keyname='configured_addr')
827 AND is_default=1)));
828
829 -- We do not drop the old `keypairs` table for now,
830 -- but move it to `old_keypairs`. We can remove it later
831 -- in next migrations. This may be needed for recovery
832 -- in case something is wrong with the migration.
833 ALTER TABLE keypairs RENAME TO old_keypairs;
834 ALTER TABLE new_keypairs RENAME TO keypairs;
835 ",
836 107,
837 )
838 .await?;
839 }
840
841 if dbversion < 108 {
842 let version = 108;
843 let chunk_size = context.get_max_smtp_rcpt_to().await?;
844 sql.transaction(move |trans| {
845 Sql::set_db_version_trans(trans, version)?;
846 let id_max =
847 trans.query_row("SELECT IFNULL((SELECT MAX(id) FROM smtp), 0)", (), |row| {
848 let id_max: i64 = row.get(0)?;
849 Ok(id_max)
850 })?;
851 while let Some((id, rfc724_mid, mime, msg_id, recipients, retries)) = trans
852 .query_row(
853 "SELECT id, rfc724_mid, mime, msg_id, recipients, retries FROM smtp \
854 WHERE id<=? LIMIT 1",
855 (id_max,),
856 |row| {
857 let id: i64 = row.get(0)?;
858 let rfc724_mid: String = row.get(1)?;
859 let mime: String = row.get(2)?;
860 let msg_id: MsgId = row.get(3)?;
861 let recipients: String = row.get(4)?;
862 let retries: i64 = row.get(5)?;
863 Ok((id, rfc724_mid, mime, msg_id, recipients, retries))
864 },
865 )
866 .optional()?
867 {
868 trans.execute("DELETE FROM smtp WHERE id=?", (id,))?;
869 let recipients = recipients.split(' ').collect::<Vec<_>>();
870 for recipients in recipients.chunks(chunk_size) {
871 let recipients = recipients.join(" ");
872 trans.execute(
873 "INSERT INTO smtp (rfc724_mid, mime, msg_id, recipients, retries) \
874 VALUES (?, ?, ?, ?, ?)",
875 (&rfc724_mid, &mime, msg_id, recipients, retries),
876 )?;
877 }
878 }
879 Ok(())
880 })
881 .await
882 .with_context(|| format!("migration failed for version {version}"))?;
883
884 sql.set_db_version_in_cache(version).await?;
885 }
886
887 if dbversion < 109 {
888 sql.execute_migration(
889 r#"ALTER TABLE acpeerstates
890 ADD COLUMN backward_verified_key_id -- What we think the contact has as our verified key
891 INTEGER;
892 UPDATE acpeerstates
893 SET backward_verified_key_id=(SELECT value FROM config WHERE keyname='key_id')
894 WHERE verified_key IS NOT NULL
895 "#,
896 109,
897 )
898 .await?;
899 }
900
901 if dbversion < 110 {
902 sql.execute_migration(
903 "ALTER TABLE keypairs ADD COLUMN addr TEXT DEFAULT '' COLLATE NOCASE;
904 ALTER TABLE keypairs ADD COLUMN is_default INTEGER DEFAULT 0;
905 ALTER TABLE keypairs ADD COLUMN created INTEGER DEFAULT 0;
906 UPDATE keypairs SET addr=(SELECT value FROM config WHERE keyname='configured_addr'), is_default=1;",
907 110,
908 )
909 .await?;
910 }
911
912 if dbversion < 111 {
913 sql.execute_migration(
914 "CREATE TABLE iroh_gossip_peers (msg_id TEXT not NULL, topic TEXT NOT NULL, public_key TEXT NOT NULL)",
915 111,
916 )
917 .await?;
918 }
919
920 if dbversion < 112 {
921 sql.execute_migration(
922 "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",
923 112,
924 )
925 .await?;
926 }
927
928 if dbversion < 113 {
929 sql.execute_migration(
930 "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",
931 113,
932 )
933 .await?;
934 }
935
936 if dbversion < 114 {
937 sql.execute_migration("CREATE INDEX reactions_index1 ON reactions (msg_id)", 114)
938 .await?;
939 }
940
941 if dbversion < 115 {
942 sql.execute_migration("ALTER TABLE msgs ADD COLUMN txt_normalized TEXT", 115)
943 .await?;
944 }
945 let mut migration_version: i32 = 115;
946
947 inc_and_check(&mut migration_version, 116)?;
948 if dbversion < migration_version {
949 sql.execute_migration(
952 "ALTER TABLE msgs ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0",
953 migration_version,
954 )
955 .await?;
956 }
957
958 inc_and_check(&mut migration_version, 117)?;
959 if dbversion < migration_version {
960 sql.execute_migration(
961 "CREATE TABLE connection_history (
962 host TEXT NOT NULL, -- server hostname
963 port INTEGER NOT NULL, -- server port
964 alpn TEXT NOT NULL, -- ALPN such as smtp or imap
965 addr TEXT NOT NULL, -- IP address
966 timestamp INTEGER NOT NULL, -- timestamp of the most recent successful connection
967 UNIQUE (host, port, alpn, addr)
968 ) STRICT",
969 migration_version,
970 )
971 .await?;
972 }
973
974 inc_and_check(&mut migration_version, 118)?;
975 if dbversion < migration_version {
976 sql.execute_migration(
977 "CREATE TABLE tokens_new (
978 id INTEGER PRIMARY KEY,
979 namespc INTEGER DEFAULT 0,
980 foreign_key TEXT DEFAULT '',
981 token TEXT DEFAULT '',
982 timestamp INTEGER DEFAULT 0
983 ) STRICT;
984 INSERT INTO tokens_new
985 SELECT t.id, t.namespc, IFNULL(c.grpid, ''), t.token, t.timestamp
986 FROM tokens t LEFT JOIN chats c ON t.foreign_id=c.id;
987 DROP TABLE tokens;
988 ALTER TABLE tokens_new RENAME TO tokens;",
989 migration_version,
990 )
991 .await?;
992 }
993
994 inc_and_check(&mut migration_version, 119)?;
995 if dbversion < migration_version {
996 sql.execute_migration(
997 "CREATE TABLE imap_send (
998 id INTEGER PRIMARY KEY AUTOINCREMENT,
999 mime TEXT NOT NULL, -- Message content
1000 msg_id INTEGER NOT NULL, -- ID of the message in the `msgs` table
1001 attempts INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
1002 )",
1003 migration_version,
1004 )
1005 .await?;
1006 }
1007
1008 inc_and_check(&mut migration_version, 120)?;
1009 if dbversion < migration_version {
1010 sql.execute_migration(
1022 "INSERT OR IGNORE INTO config (keyname, value)
1023 SELECT 'delete_server_after', '0'
1024 FROM config WHERE keyname='configured'
1025 ",
1026 migration_version,
1027 )
1028 .await?;
1029 }
1030
1031 inc_and_check(&mut migration_version, 121)?;
1032 if dbversion < migration_version {
1033 sql.execute_migration(
1034 "CREATE INDEX chats_index4 ON chats (name)",
1035 migration_version,
1036 )
1037 .await?;
1038 }
1039
1040 inc_and_check(&mut migration_version, 122)?;
1041 if dbversion < migration_version {
1042 sql.execute_migration(
1043 "ALTER TABLE tokens ADD COLUMN foreign_id INTEGER NOT NULL DEFAULT 0",
1044 migration_version,
1045 )
1046 .await?;
1047 }
1048
1049 inc_and_check(&mut migration_version, 123)?;
1050 if dbversion < migration_version {
1051 sql.execute_migration(
1053 "CREATE TABLE new_msgs_status_updates (
1054 id INTEGER PRIMARY KEY AUTOINCREMENT,
1055 msg_id INTEGER,
1056 update_item TEXT DEFAULT '',
1057 uid TEXT UNIQUE,
1058 FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE
1059 );
1060 INSERT OR IGNORE INTO new_msgs_status_updates SELECT
1061 id, msg_id, update_item, uid
1062 FROM msgs_status_updates;
1063 DROP TABLE msgs_status_updates;
1064 ALTER TABLE new_msgs_status_updates RENAME TO msgs_status_updates;
1065 CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
1066 CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
1067 ",
1068 migration_version,
1069 )
1070 .await?;
1071 }
1072
1073 inc_and_check(&mut migration_version, 124)?;
1074 if dbversion < migration_version {
1075 sql.execute_migration(
1077 "UPDATE chats
1078 SET protected=1 -- ProtectionStatus::Protected
1079 WHERE type==100 -- Chattype::Single
1080 AND EXISTS (
1081 SELECT 1 FROM chats_contacts cc
1082 WHERE cc.chat_id==chats.id
1083 AND cc.contact_id=1
1084 )
1085 ",
1086 migration_version,
1087 )
1088 .await?;
1089 }
1090
1091 inc_and_check(&mut migration_version, 125)?;
1092 if dbversion < migration_version {
1093 sql.execute_migration(
1094 "CREATE TABLE http_cache (
1095 url TEXT PRIMARY KEY,
1096 expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1097 blobname TEXT NOT NULL,
1098 mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
1099 encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
1100 ) STRICT",
1101 migration_version,
1102 )
1103 .await?;
1104 }
1105
1106 inc_and_check(&mut migration_version, 126)?;
1107 if dbversion < migration_version {
1108 sql.execute_migration(
1110 "DROP TABLE http_cache;
1111 CREATE TABLE http_cache (
1112 url TEXT PRIMARY KEY,
1113 expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1114 stale INTEGER NOT NULL, -- When the cache entry is considered stale, 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, 127)?;
1125 if dbversion < migration_version {
1126 sql.execute_migration(
1128 "INSERT OR IGNORE INTO config (keyname, value)
1129 SELECT 'bcc_self', '1'
1130 FROM config WHERE keyname='delete_server_after' AND value='0'
1131 ",
1132 migration_version,
1133 )
1134 .await?;
1135 }
1136
1137 inc_and_check(&mut migration_version, 128)?;
1138 if dbversion < migration_version {
1139 sql.execute_migration(
1145 "ALTER TABLE chats_contacts
1146 ADD COLUMN add_timestamp NOT NULL DEFAULT 0;
1147 ALTER TABLE chats_contacts
1148 ADD COLUMN remove_timestamp NOT NULL DEFAULT 0;
1149 ",
1150 migration_version,
1151 )
1152 .await?;
1153 }
1154
1155 inc_and_check(&mut migration_version, 129)?;
1156 if dbversion < migration_version {
1157 sql.execute_migration(
1165 "INSERT OR IGNORE INTO config (keyname, value)
1166 SELECT 'bcc_self', '1'
1167 FROM config WHERE keyname='delete_server_after' AND value!='1'
1168 ",
1169 migration_version,
1170 )
1171 .await?;
1172 }
1173
1174 inc_and_check(&mut migration_version, 130)?;
1175 if dbversion < migration_version {
1176 sql.execute_migration(
1177 "
1178CREATE TABLE gossip_timestamp (
1179 chat_id INTEGER NOT NULL,
1180 fingerprint TEXT NOT NULL, -- Upper-case fingerprint of the key.
1181 timestamp INTEGER NOT NULL,
1182 UNIQUE (chat_id, fingerprint)
1183) STRICT;
1184CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint);
1185",
1186 migration_version,
1187 )
1188 .await?;
1189 }
1190
1191 inc_and_check(&mut migration_version, 131)?;
1192 if dbversion < migration_version {
1193 let entered_param = EnteredLoginParam::load(context).await?;
1194 let configured_param = ConfiguredLoginParam::load_legacy(context).await?;
1195
1196 sql.execute_migration_transaction(
1197 |transaction| {
1198 transaction.execute(
1199 "CREATE TABLE transports (
1200 id INTEGER PRIMARY KEY AUTOINCREMENT,
1201 addr TEXT NOT NULL,
1202 entered_param TEXT NOT NULL,
1203 configured_param TEXT NOT NULL,
1204 UNIQUE(addr)
1205 )",
1206 (),
1207 )?;
1208 if let Some(configured_param) = configured_param {
1209 transaction.execute(
1210 "INSERT INTO transports (addr, entered_param, configured_param)
1211 VALUES (?, ?, ?)",
1212 (
1213 configured_param.addr.clone(),
1214 serde_json::to_string(&entered_param)?,
1215 configured_param.into_json()?,
1216 ),
1217 )?;
1218 }
1219
1220 Ok(())
1221 },
1222 migration_version,
1223 )
1224 .await?;
1225 }
1226
1227 let new_version = sql
1228 .get_raw_config_int(VERSION_CFG)
1229 .await?
1230 .unwrap_or_default();
1231 if new_version != dbversion || !exists_before_update {
1232 let created_db = if exists_before_update {
1233 ""
1234 } else {
1235 "Created new database. "
1236 };
1237 info!(context, "{}Migration done from v{}.", created_db, dbversion);
1238 }
1239 info!(context, "Database version: v{new_version}.");
1240
1241 Ok((
1242 recalc_fingerprints,
1243 update_icons,
1244 disable_server_delete,
1245 recode_avatar,
1246 ))
1247}
1248
1249impl Sql {
1250 async fn set_db_version(&self, version: i32) -> Result<()> {
1251 self.set_raw_config_int(VERSION_CFG, version).await?;
1252 Ok(())
1253 }
1254
1255 fn set_db_version_trans(transaction: &mut rusqlite::Transaction, version: i32) -> Result<()> {
1257 transaction.execute(
1258 "UPDATE config SET value=? WHERE keyname=?;",
1259 (format!("{version}"), VERSION_CFG),
1260 )?;
1261 Ok(())
1262 }
1263
1264 async fn set_db_version_in_cache(&self, version: i32) -> Result<()> {
1265 let mut lock = self.config_cache.write().await;
1266 lock.insert(VERSION_CFG.to_string(), Some(format!("{version}")));
1267 Ok(())
1268 }
1269
1270 async fn execute_migration(&self, query: &str, version: i32) -> Result<()> {
1271 self.execute_migration_transaction(
1272 |transaction| {
1273 transaction.execute_batch(query)?;
1274 Ok(())
1275 },
1276 version,
1277 )
1278 .await
1279 }
1280
1281 async fn execute_migration_transaction(
1282 &self,
1283 migration: impl Send + FnOnce(&mut rusqlite::Transaction) -> Result<()>,
1284 version: i32,
1285 ) -> Result<()> {
1286 self.transaction(move |transaction| {
1287 let curr_version: String = transaction.query_row(
1288 "SELECT IFNULL(value, ?) FROM config WHERE keyname=?;",
1289 ("0", VERSION_CFG),
1290 |row| row.get(0),
1291 )?;
1292 let curr_version: i32 = curr_version.parse()?;
1293 ensure!(curr_version < version, "Db version must be increased");
1294 Self::set_db_version_trans(transaction, version)?;
1295 migration(transaction)?;
1296
1297 Ok(())
1298 })
1299 .await
1300 .with_context(|| format!("execute_migration failed for version {version}"))?;
1301
1302 self.config_cache.write().await.clear();
1303
1304 Ok(())
1305 }
1306}
1307
1308#[cfg(test)]
1309mod tests {
1310 use super::*;
1311 use crate::config::Config;
1312 use crate::test_utils::TestContext;
1313
1314 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1315 async fn test_clear_config_cache() -> anyhow::Result<()> {
1316 let t = TestContext::new().await;
1320 assert_eq!(t.get_config_bool(Config::IsChatmail).await?, false);
1321
1322 t.sql
1323 .execute_migration(
1324 "INSERT INTO config (keyname, value) VALUES ('is_chatmail', '1')",
1325 1000,
1326 )
1327 .await?;
1328 assert_eq!(t.get_config_bool(Config::IsChatmail).await?, true);
1329 assert_eq!(t.sql.get_raw_config_int(VERSION_CFG).await?.unwrap(), 1000);
1330
1331 Ok(())
1332 }
1333}