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::context::Context;
16use crate::key::DcKey;
17use crate::log::warn;
18use crate::provider::get_provider_info;
19use crate::sql::Sql;
20use crate::tools::{Time, inc_and_check, time_elapsed};
21use crate::transport::ConfiguredLoginParam;
22
23const DBVERSION: i32 = 68;
24const VERSION_CFG: &str = "dbversion";
25const TABLES: &str = include_str!("./tables.sql");
26
27#[cfg(test)]
28tokio::task_local! {
29 static STOP_MIGRATIONS_AT: i32;
30}
31
32fn migrate_key_contacts(
33 context: &Context,
34 transaction: &mut rusqlite::Transaction<'_>,
35) -> std::result::Result<(), anyhow::Error> {
36 info!(context, "Starting key-contact transition.");
37
38 transaction.execute_batch(
41 "ALTER TABLE contacts ADD COLUMN fingerprint TEXT NOT NULL DEFAULT '';
42
43 -- Verifier is an ID of the verifier contact.
44 -- 0 if the contact is not verified.
45 ALTER TABLE contacts ADD COLUMN verifier INTEGER NOT NULL DEFAULT 0;
46
47 CREATE INDEX contacts_fingerprint_index ON contacts (fingerprint);
48
49 CREATE TABLE public_keys (
50 id INTEGER PRIMARY KEY AUTOINCREMENT,
51 fingerprint TEXT NOT NULL UNIQUE, -- Upper-case fingerprint of the key.
52 public_key BLOB NOT NULL -- Binary key, not ASCII-armored
53 ) STRICT;
54 CREATE INDEX public_key_index ON public_keys (fingerprint);
55
56 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
57 SELECT public_key_fingerprint, public_key FROM acpeerstates
58 WHERE public_key_fingerprint IS NOT NULL AND public_key IS NOT NULL;
59
60 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
61 SELECT gossip_key_fingerprint, gossip_key FROM acpeerstates
62 WHERE gossip_key_fingerprint IS NOT NULL AND gossip_key IS NOT NULL;
63
64 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
65 SELECT verified_key_fingerprint, verified_key FROM acpeerstates
66 WHERE verified_key_fingerprint IS NOT NULL AND verified_key IS NOT NULL;
67
68 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
69 SELECT secondary_verified_key_fingerprint, secondary_verified_key FROM acpeerstates
70 WHERE secondary_verified_key_fingerprint IS NOT NULL AND secondary_verified_key IS NOT NULL;",
71 )
72 .context("Creating key-contact tables")?;
73
74 let Some(self_addr): Option<String> = transaction
75 .query_row(
76 "SELECT value FROM config WHERE keyname='configured_addr'",
77 (),
78 |row| row.get(0),
79 )
80 .optional()
81 .context("Step 0")?
82 else {
83 info!(
84 context,
85 "Not yet configured, no need to migrate key-contacts"
86 );
87 return Ok(());
88 };
89
90 let mut autocrypt_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
97 let mut autocrypt_key_contacts_with_reset_peerstate: BTreeMap<u32, u32> = BTreeMap::new();
98 let mut verified_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
99 {
100 let mut verifications: BTreeMap<u32, u32> = BTreeMap::new();
104
105 let mut load_contacts_stmt = transaction
106 .prepare(
107 "SELECT c.id, c.name, c.addr, c.origin, c.blocked, c.last_seen,
108 c.authname, c.param, c.status, c.is_bot, c.selfavatar_sent,
109 IFNULL(p.public_key, p.gossip_key),
110 p.verified_key, IFNULL(p.verifier, ''),
111 p.secondary_verified_key, p.secondary_verifier, p.prefer_encrypted
112 FROM contacts c
113 INNER JOIN acpeerstates p ON c.addr=p.addr
114 WHERE c.id > 9
115 ORDER BY p.last_seen DESC",
116 )
117 .context("Step 2")?;
118
119 let all_address_contacts: rusqlite::Result<Vec<_>> = load_contacts_stmt
120 .query_map((), |row| {
121 let id: i64 = row.get(0)?;
122 let name: String = row.get(1)?;
123 let addr: String = row.get(2)?;
124 let origin: i64 = row.get(3)?;
125 let blocked: Option<bool> = row.get(4)?;
126 let last_seen: i64 = row.get(5)?;
127 let authname: String = row.get(6)?;
128 let param: String = row.get(7)?;
129 let status: Option<String> = row.get(8)?;
130 let is_bot: bool = row.get(9)?;
131 let selfavatar_sent: i64 = row.get(10)?;
132 let autocrypt_key = row
133 .get(11)
134 .ok()
135 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
136 let verified_key = row
137 .get(12)
138 .ok()
139 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
140 let verifier: String = row.get(13)?;
141 let secondary_verified_key = row
142 .get(12)
143 .ok()
144 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
145 let secondary_verifier: String = row.get(15)?;
146 let prefer_encrypt: u8 = row.get(16)?;
147 Ok((
148 id,
149 name,
150 addr,
151 origin,
152 blocked,
153 last_seen,
154 authname,
155 param,
156 status,
157 is_bot,
158 selfavatar_sent,
159 autocrypt_key,
160 verified_key,
161 verifier,
162 secondary_verified_key,
163 secondary_verifier,
164 prefer_encrypt,
165 ))
166 })
167 .context("Step 3")?
168 .collect();
169
170 let mut insert_contact_stmt = transaction
171 .prepare(
172 "INSERT INTO contacts (name, addr, origin, blocked, last_seen,
173 authname, param, status, is_bot, selfavatar_sent, fingerprint)
174 VALUES(?,?,?,?,?,?,?,?,?,?,?)",
175 )
176 .context("Step 4")?;
177 let mut fingerprint_to_id_stmt = transaction
178 .prepare("SELECT id FROM contacts WHERE fingerprint=? AND id>9")
179 .context("Step 5")?;
180 let mut original_contact_id_from_addr_stmt = transaction
181 .prepare("SELECT id FROM contacts WHERE addr=? AND fingerprint='' AND id>9")
182 .context("Step 6")?;
183
184 for row in all_address_contacts? {
185 let (
186 original_id,
187 name,
188 addr,
189 origin,
190 blocked,
191 last_seen,
192 authname,
193 param,
194 status,
195 is_bot,
196 selfavatar_sent,
197 autocrypt_key,
198 verified_key,
199 verifier,
200 secondary_verified_key,
201 secondary_verifier,
202 prefer_encrypt,
203 ) = row;
204 let mut insert_contact = |key: SignedPublicKey| -> Result<u32> {
205 let fingerprint = key.dc_fingerprint().hex();
206 let existing_contact_id: Option<u32> = fingerprint_to_id_stmt
207 .query_row((&fingerprint,), |row| row.get(0))
208 .optional()
209 .context("Step 7")?;
210 if let Some(existing_contact_id) = existing_contact_id {
211 return Ok(existing_contact_id);
212 }
213 insert_contact_stmt
214 .execute((
215 &name,
216 &addr,
217 origin,
218 blocked,
219 last_seen,
220 &authname,
221 ¶m,
222 &status,
223 is_bot,
224 selfavatar_sent,
225 fingerprint.clone(),
226 ))
227 .context("Step 8")?;
228 let id = transaction
229 .last_insert_rowid()
230 .try_into()
231 .context("Step 9")?;
232 info!(
233 context,
234 "Inserted new contact id={id} name='{name}' addr='{addr}' fingerprint={fingerprint}"
235 );
236 Ok(id)
237 };
238 let mut original_contact_id_from_addr = |addr: &str, default: u32| -> Result<u32> {
239 if addr_cmp(addr, &self_addr) {
240 Ok(1) } else if addr.is_empty() {
242 Ok(default)
243 } else {
244 Ok(original_contact_id_from_addr_stmt
245 .query_row((addr,), |row| row.get(0))
246 .optional()
247 .with_context(|| format!("Original contact '{addr}' not found"))?
248 .unwrap_or(default))
249 }
250 };
251
252 let Some(autocrypt_key) = autocrypt_key else {
253 continue;
254 };
255 let new_id = insert_contact(autocrypt_key).context("Step 10")?;
256
257 if prefer_encrypt != 20 {
260 autocrypt_key_contacts.insert(original_id.try_into().context("Step 11")?, new_id);
261 } else {
262 autocrypt_key_contacts_with_reset_peerstate
263 .insert(original_id.try_into().context("Step 12")?, new_id);
264 }
265
266 let Some(verified_key) = verified_key else {
267 continue;
268 };
269 let new_id = insert_contact(verified_key).context("Step 13")?;
270 verified_key_contacts.insert(original_id.try_into().context("Step 14")?, new_id);
271
272 let verifier_id = if addr_cmp(&verifier, &addr) {
273 1 } else {
277 original_contact_id_from_addr(&verifier, new_id).context("Step 15")?
281 };
282 verifications.insert(new_id, verifier_id);
283
284 let Some(secondary_verified_key) = secondary_verified_key else {
285 continue;
286 };
287 let new_id = insert_contact(secondary_verified_key).context("Step 16")?;
288 let verifier_id: u32 = if addr_cmp(&secondary_verifier, &addr) {
289 1 } else {
291 original_contact_id_from_addr(&secondary_verifier, new_id).context("Step 17")?
292 };
293 verifications.entry(new_id).or_insert(verifier_id);
295 }
296 info!(
297 context,
298 "Created key-contacts identified by autocrypt key: {autocrypt_key_contacts:?}"
299 );
300 info!(
301 context,
302 "Created key-contacts with 'reset' peerstate identified by autocrypt key: {autocrypt_key_contacts_with_reset_peerstate:?}"
303 );
304 info!(
305 context,
306 "Created key-contacts identified by verified key: {verified_key_contacts:?}"
307 );
308
309 for (&new_contact, &verifier_original_contact) in &verifications {
310 let verifier = if verifier_original_contact == 1 {
311 1 } else if verifier_original_contact == new_contact {
313 new_contact } else {
315 match verified_key_contacts.get(&verifier_original_contact) {
318 Some(v) => *v,
319 None => {
320 warn!(
321 context,
322 "Couldn't find key-contact for {verifier_original_contact} who verified {new_contact}"
323 );
324 continue;
325 }
326 }
327 };
328 transaction
329 .execute(
330 "UPDATE contacts SET verifier=? WHERE id=?",
331 (verifier, new_contact),
332 )
333 .context("Step 18")?;
334 }
335 info!(context, "Migrated verifications: {verifications:?}");
336 }
337
338 let mut orphaned_contacts: BTreeSet<u32> = transaction
343 .prepare("SELECT id FROM contacts WHERE id>9")
344 .context("Step 19")?
345 .query_map((), |row| row.get::<usize, u32>(0))
346 .context("Step 20")?
347 .collect::<Result<BTreeSet<u32>, rusqlite::Error>>()
348 .context("Step 21")?;
349
350 {
351 let mut stmt = transaction
352 .prepare(
353 "SELECT c.id, c.type, c.grpid, c.protected
354 FROM chats c
355 WHERE id>9",
356 )
357 .context("Step 22")?;
358 let all_chats = stmt
359 .query_map((), |row| {
360 let id: u32 = row.get(0)?;
361 let typ: u32 = row.get(1)?;
362 let grpid: String = row.get(2)?;
363 let protected: u32 = row.get(3)?;
364 Ok((id, typ, grpid, protected))
365 })
366 .context("Step 23")?;
367 let mut load_chat_contacts_stmt = transaction.prepare(
368 "SELECT contact_id, add_timestamp>=remove_timestamp FROM chats_contacts
369 WHERE chat_id=? AND contact_id>9",
370 )?;
371 let is_chatmail: Option<String> = transaction
372 .query_row(
373 "SELECT value FROM config WHERE keyname='is_chatmail'",
374 (),
375 |row| row.get(0),
376 )
377 .optional()
378 .context("Step 23.1")?;
379 let is_chatmail = is_chatmail
380 .and_then(|s| s.parse::<i32>().ok())
381 .unwrap_or_default()
382 != 0;
383 let map_to_key_contact = |old_member: &u32| {
384 autocrypt_key_contacts
385 .get(old_member)
386 .or_else(|| {
387 if is_chatmail {
392 autocrypt_key_contacts_with_reset_peerstate.get(old_member)
393 } else {
394 None
395 }
396 })
397 .copied()
398 };
399
400 let mut update_member_stmt = transaction
401 .prepare("UPDATE chats_contacts SET contact_id=? WHERE contact_id=? AND chat_id=?")?;
402 let mut addr_cmp_stmt = transaction
403 .prepare("SELECT c.addr=d.addr FROM contacts c, contacts d WHERE c.id=? AND d.id=?")?;
404 for chat in all_chats {
405 let (chat_id, typ, grpid, protected) = chat.context("Step 24")?;
406 let old_members: Vec<(u32, bool)> = load_chat_contacts_stmt
408 .query_map((chat_id,), |row| {
409 let id: u32 = row.get(0)?;
410 let present: bool = row.get(1)?;
411 Ok((id, present))
412 })
413 .context("Step 25")?
414 .collect::<Result<Vec<_>, _>>()
415 .context("Step 26")?;
416
417 let mut keep_address_contacts = |reason: &str| -> Result<()> {
418 info!(
419 context,
420 "Chat {chat_id} will be an unencrypted chat with contacts identified by email address: {reason}."
421 );
422 for (m, _) in &old_members {
423 orphaned_contacts.remove(m);
424 }
425
426 transaction
430 .execute("UPDATE chats SET protected=0 WHERE id=?", (chat_id,))
431 .context("Step 26.0")?;
432
433 Ok(())
434 };
435 let old_and_new_members: Vec<(u32, bool, Option<u32>)> = match typ {
436 100 => {
444 let Some((old_member, _)) = old_members.first() else {
445 info!(
446 context,
447 "1:1 chat {chat_id} doesn't contain contact, probably it's self or device chat."
448 );
449 continue;
450 };
451
452 let Some(new_contact) = map_to_key_contact(old_member) else {
453 keep_address_contacts("No peerstate, or peerstate in 'reset' state")?;
454 continue;
455 };
456 if !addr_cmp_stmt
457 .query_row((old_member, new_contact), |row| row.get::<_, bool>(0))?
458 {
459 keep_address_contacts("key contact has different email")?;
460 continue;
461 }
462 vec![(*old_member, true, Some(new_contact))]
463 }
464
465 120 => {
467 if grpid.is_empty() {
468 keep_address_contacts("Empty chat-Group-ID")?;
471 continue;
472 } else if protected == 1 {
473 old_members
474 .iter()
475 .map(|&(id, present)| {
476 (id, present, verified_key_contacts.get(&id).copied())
477 })
478 .collect()
479 } else {
480 old_members
481 .iter()
482 .map(|&(id, present)| (id, present, map_to_key_contact(&id)))
483 .collect::<Vec<(u32, bool, Option<u32>)>>()
484 }
485 }
486
487 140 => {
489 keep_address_contacts("Mailinglist")?;
490 continue;
491 }
492
493 160 => old_members
495 .iter()
496 .map(|(original, _)| {
497 (
498 *original,
499 true,
500 autocrypt_key_contacts
501 .get(original)
502 .or_else(|| {
506 autocrypt_key_contacts_with_reset_peerstate.get(original)
507 })
508 .copied(),
509 )
510 })
511 .collect::<Vec<(u32, bool, Option<u32>)>>(),
512 _ => {
513 warn!(context, "Invalid chat type {typ}");
514 continue;
515 }
516 };
517
518 if typ == 120
521 && old_and_new_members
522 .iter()
523 .any(|&(_old, present, new)| present && new.is_none())
524 {
525 transaction
526 .execute("UPDATE chats SET grpid='' WHERE id=?", (chat_id,))
527 .context("Step 26.1")?;
528 keep_address_contacts("Group contains contact without peerstate")?;
529 continue;
530 }
531
532 let human_readable_transitions = old_and_new_members
533 .iter()
534 .map(|(old, _, new)| format!("{old}->{}", new.unwrap_or_default()))
535 .collect::<Vec<String>>()
536 .join(" ");
537 info!(
538 context,
539 "Migrating chat {chat_id} to key-contacts: {human_readable_transitions}"
540 );
541
542 for (old_member, _, new_member) in old_and_new_members {
543 if let Some(new_member) = new_member {
544 orphaned_contacts.remove(&new_member);
545 let res = update_member_stmt.execute((new_member, old_member, chat_id));
546 if res.is_err() {
547 let member_to_delete: u32 = transaction
554 .query_row(
555 "SELECT contact_id
556 FROM chats_contacts
557 WHERE chat_id=? AND contact_id IN (?,?)
558 ORDER BY add_timestamp>=remove_timestamp, add_timestamp LIMIT 1",
559 (chat_id, new_member, old_member),
560 |row| row.get(0),
561 )
562 .context("Step 27")?;
563 info!(
564 context,
565 "Chat partner is in the chat {chat_id} multiple times. \
566 Deleting {member_to_delete}, then trying to update \
567 {old_member}->{new_member} again"
568 );
569 transaction
570 .execute(
571 "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?",
572 (chat_id, member_to_delete),
573 )
574 .context("Step 28")?;
575 update_member_stmt.execute((new_member, old_member, chat_id))?;
578 }
579 } else {
580 info!(
581 context,
582 "Old member {old_member} in chat {chat_id} can't be upgraded to key-contact, removing them"
583 );
584 transaction
585 .execute(
586 "DELETE FROM chats_contacts WHERE contact_id=? AND chat_id=?",
587 (old_member, chat_id),
588 )
589 .context("Step 29")?;
590 }
591 }
592 }
593 }
594
595 {
597 info!(
598 context,
599 "Marking contacts which remained in no chat at all as hidden: {orphaned_contacts:?}"
600 );
601 let mut mark_as_hidden_stmt = transaction
602 .prepare("UPDATE contacts SET origin=? WHERE id=?")
603 .context("Step 30")?;
604 for contact in orphaned_contacts {
605 mark_as_hidden_stmt
606 .execute((0x8, contact))
607 .context("Step 31")?;
608 }
609 }
610
611 {
614 let mut contacts_map = autocrypt_key_contacts_with_reset_peerstate;
615 for (old, new) in autocrypt_key_contacts {
616 contacts_map.insert(old, new);
617 }
618 transaction
619 .execute(
620 "CREATE TABLE key_contacts_map (
621 old_id INTEGER PRIMARY KEY NOT NULL,
622 new_id INTEGER NOT NULL
623 ) STRICT",
624 (),
625 )
626 .context("Step 32")?;
627 {
628 let mut stmt = transaction
629 .prepare("INSERT INTO key_contacts_map (old_id, new_id) VALUES (?, ?)")
630 .context("Step 33")?;
631 for ids in contacts_map {
632 stmt.execute(ids).context("Step 34")?;
633 }
634 }
635 transaction
636 .execute(
637 "INSERT INTO config (keyname, value) VALUES (
638 'first_key_contacts_msg_id',
639 IFNULL((SELECT MAX(id)+1 FROM msgs), 0)
640 )",
641 (),
642 )
643 .context("Step 35")?;
644 }
645
646 Ok(())
647}
648
649#[expect(clippy::arithmetic_side_effects)]
651pub(crate) async fn msgs_to_key_contacts(context: &Context) -> Result<()> {
652 let sql = &context.sql;
653 if sql
654 .get_raw_config_int64("first_key_contacts_msg_id")
655 .await?
656 <= Some(0)
657 {
658 return Ok(());
659 }
660 let trans_fn = |t: &mut rusqlite::Transaction| {
661 let mut first_key_contacts_msg_id: u64 = t
662 .query_one(
663 "SELECT CAST(value AS INTEGER) FROM config WHERE keyname='first_key_contacts_msg_id'",
664 (),
665 |row| row.get(0),
666 )
667 .context("Get first_key_contacts_msg_id")?;
668 let mut stmt = t
669 .prepare(
670 "UPDATE msgs SET
671 from_id=IFNULL(
672 (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.from_id),
673 from_id
674 ),
675 to_id=IFNULL(
676 (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.to_id),
677 to_id
678 )
679 WHERE id>=? AND id<?
680 AND chat_id>9
681 AND (param GLOB '*\nc=1*' OR param GLOB 'c=1*')",
682 )
683 .context("Prepare stmt")?;
684 let msgs_to_migrate = 1000;
685 let mut msgs_migrated: u64 = 0;
686 while first_key_contacts_msg_id > 0 && msgs_migrated < msgs_to_migrate {
687 let start_msg_id = first_key_contacts_msg_id.saturating_sub(msgs_to_migrate);
688 let cnt: u64 = stmt
689 .execute((start_msg_id, first_key_contacts_msg_id))
690 .context("UPDATE msgs")?
691 .try_into()?;
692 msgs_migrated += cnt;
693 first_key_contacts_msg_id = start_msg_id;
694 }
695 t.execute(
696 "UPDATE config SET value=? WHERE keyname='first_key_contacts_msg_id'",
697 (first_key_contacts_msg_id,),
698 )
699 .context("Update first_key_contacts_msg_id")?;
700 Ok((msgs_migrated, first_key_contacts_msg_id))
701 };
702 let start = Time::now();
703 let mut msgs_migrated = 0;
704 loop {
705 let (n, first_key_contacts_msg_id) = sql.transaction(trans_fn).await?;
706 msgs_migrated += n;
707 if first_key_contacts_msg_id == 0 || time_elapsed(&start) >= Duration::from_millis(500) {
708 break;
709 }
710 }
711 sql.uncache_raw_config("first_key_contacts_msg_id").await;
712 info!(
713 context,
714 "Rewriting {msgs_migrated} msgs to key-contacts took {:?}.",
715 time_elapsed(&start),
716 );
717 Ok(())
718}
719
720impl Sql {
721 async fn set_db_version(&self, version: i32) -> Result<()> {
722 self.set_raw_config_int(VERSION_CFG, version).await?;
723 Ok(())
724 }
725
726 fn set_db_version_trans(transaction: &mut rusqlite::Transaction, version: i32) -> Result<()> {
728 transaction.execute(
729 "UPDATE config SET value=? WHERE keyname=?;",
730 (format!("{version}"), VERSION_CFG),
731 )?;
732 Ok(())
733 }
734
735 async fn execute_migration(&self, query: &str, version: i32) -> Result<()> {
736 self.execute_migration_transaction(
737 |transaction| {
738 transaction.execute_batch(query)?;
739 Ok(())
740 },
741 version,
742 )
743 .await
744 }
745
746 async fn execute_migration_transaction(
747 &self,
748 migration: impl Send + FnOnce(&mut rusqlite::Transaction) -> Result<()>,
749 version: i32,
750 ) -> Result<()> {
751 #[cfg(test)]
752 if STOP_MIGRATIONS_AT.try_with(|stop_migrations_at| version > *stop_migrations_at)
753 == Ok(true)
754 {
755 println!("Not running migration {version}, because STOP_MIGRATIONS_AT is set");
756 return Ok(());
757 }
758
759 self.transaction(move |transaction| {
760 let curr_version: String = transaction.query_row(
761 "SELECT IFNULL(value, ?) FROM config WHERE keyname=?;",
762 ("0", VERSION_CFG),
763 |row| row.get(0),
764 )?;
765 let curr_version: i32 = curr_version.parse()?;
766 ensure!(curr_version < version, "Db version must be increased");
767 Self::set_db_version_trans(transaction, version)?;
768 migration(transaction)?;
769
770 Ok(())
771 })
772 .await
773 .with_context(|| format!("execute_migration failed for version {version}"))?;
774
775 self.config_cache.write().await.clear();
776
777 Ok(())
778 }
779}
780
781#[expect(clippy::arithmetic_side_effects)]
782pub async fn run(context: &Context, sql: &Sql) -> Result<bool> {
783 let mut exists_before_update = false;
784 let mut dbversion_before_update = DBVERSION;
785
786 if !sql
787 .table_exists("config")
788 .await
789 .context("failed to check if config table exists")?
790 {
791 sql.transaction(move |transaction| {
792 transaction.execute_batch(TABLES)?;
793
794 transaction.execute(
796 "INSERT INTO config (keyname, value) VALUES (?, ?);",
797 (VERSION_CFG, format!("{dbversion_before_update}")),
798 )?;
799 Ok(())
800 })
801 .await
802 .context("Creating tables failed")?;
803
804 let mut lock = context.sql.config_cache.write().await;
805 lock.insert(
806 VERSION_CFG.to_string(),
807 Some(format!("{dbversion_before_update}")),
808 );
809 drop(lock);
810 } else {
811 exists_before_update = true;
812 dbversion_before_update = sql
813 .get_raw_config_int(VERSION_CFG)
814 .await?
815 .unwrap_or_default();
816 }
817
818 let dbversion = dbversion_before_update;
819 let mut recode_avatar = false;
820
821 if dbversion < 1 {
822 sql.execute_migration(
823 r#"
824CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');
825CREATE INDEX leftgrps_index1 ON leftgrps (grpid);"#,
826 1,
827 )
828 .await?;
829 }
830 if dbversion < 2 {
831 sql.execute_migration(
832 "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
833 2,
834 )
835 .await?;
836 }
837 if dbversion < 7 {
838 sql.execute_migration(
839 "CREATE TABLE keypairs (\
840 id INTEGER PRIMARY KEY, \
841 addr TEXT DEFAULT '' COLLATE NOCASE, \
842 is_default INTEGER DEFAULT 0, \
843 private_key, \
844 public_key, \
845 created INTEGER DEFAULT 0);",
846 7,
847 )
848 .await?;
849 }
850 if dbversion < 10 {
851 sql.execute_migration(
852 "CREATE TABLE acpeerstates (\
853 id INTEGER PRIMARY KEY, \
854 addr TEXT DEFAULT '' COLLATE NOCASE, \
855 last_seen INTEGER DEFAULT 0, \
856 last_seen_autocrypt INTEGER DEFAULT 0, \
857 public_key, \
858 prefer_encrypted INTEGER DEFAULT 0); \
859 CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);",
860 10,
861 )
862 .await?;
863 }
864 if dbversion < 12 {
865 sql.execute_migration(
866 r#"
867CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);
868CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);"#,
869 12,
870 )
871 .await?;
872 }
873 if dbversion < 17 {
874 sql.execute_migration(
875 r#"
876ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;
877CREATE INDEX chats_index2 ON chats (archived);
878ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;
879CREATE INDEX msgs_index5 ON msgs (starred);"#,
880 17,
881 )
882 .await?;
883 }
884 if dbversion < 18 {
885 sql.execute_migration(
886 r#"
887ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;
888ALTER TABLE acpeerstates ADD COLUMN gossip_key;"#,
889 18,
890 )
891 .await?;
892 }
893 if dbversion < 27 {
894 sql.execute_migration(
897 r#"
898DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;
899CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);
900ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
901ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;"#,
902 27,
903 )
904 .await?;
905 }
906 if dbversion < 34 {
907 sql.execute_migration(
908 r#"
909ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;
910ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
911ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';
912ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';
913CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
914CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);"#,
915 34,
916 )
917 .await?;
918 }
919 if dbversion < 39 {
920 sql.execute_migration(
921 r#"
922CREATE TABLE tokens (
923 id INTEGER PRIMARY KEY,
924 namespc INTEGER DEFAULT 0,
925 foreign_id INTEGER DEFAULT 0,
926 token TEXT DEFAULT '',
927 timestamp INTEGER DEFAULT 0
928);
929ALTER TABLE acpeerstates ADD COLUMN verified_key;
930ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';
931CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);"#,
932 39,
933 )
934 .await?;
935 }
936 if dbversion < 40 {
937 sql.execute_migration("ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;", 40)
938 .await?;
939 }
940 if dbversion < 44 {
941 sql.execute_migration("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", 44)
942 .await?;
943 }
944 if dbversion < 46 {
945 sql.execute_migration(
946 r#"
947ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;
948ALTER TABLE msgs ADD COLUMN mime_references TEXT;"#,
949 46,
950 )
951 .await?;
952 }
953 if dbversion < 47 {
954 sql.execute_migration("ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;", 47)
955 .await?;
956 }
957 if dbversion < 48 {
958 sql.execute_migration(
960 "ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
961 48,
962 )
963 .await?;
964 }
965 if dbversion < 49 {
966 sql.execute_migration(
967 "ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;",
968 49,
969 )
970 .await?;
971 }
972 if dbversion < 50 {
973 if exists_before_update {
977 sql.set_raw_config_int("show_emails", 2).await?;
978 }
979 sql.set_db_version(50).await?;
980 }
981 if dbversion < 53 {
982 sql.execute_migration(
985 r#"
986CREATE TABLE locations (
987 id INTEGER PRIMARY KEY AUTOINCREMENT,
988 latitude REAL DEFAULT 0.0,
989 longitude REAL DEFAULT 0.0,
990 accuracy REAL DEFAULT 0.0,
991 timestamp INTEGER DEFAULT 0,
992 chat_id INTEGER DEFAULT 0,
993 from_id INTEGER DEFAULT 0
994);"
995CREATE INDEX locations_index1 ON locations (from_id);
996CREATE INDEX locations_index2 ON locations (timestamp);
997ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;
998ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;
999ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;
1000CREATE INDEX chats_index3 ON chats (locations_send_until);"#,
1001 53,
1002 )
1003 .await?;
1004 }
1005 if dbversion < 54 {
1006 sql.execute_migration(
1007 r#"
1008ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;
1009CREATE INDEX msgs_index6 ON msgs (location_id);"#,
1010 54,
1011 )
1012 .await?;
1013 }
1014 if dbversion < 55 {
1015 sql.execute_migration(
1016 "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
1017 55,
1018 )
1019 .await?;
1020 }
1021 if dbversion < 59 {
1022 sql.execute_migration(
1025 r#"
1026CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);
1027CREATE INDEX devmsglabels_index1 ON devmsglabels (label);"#, 59)
1028 .await?;
1029 if exists_before_update && sql.get_raw_config_int("bcc_self").await?.is_none() {
1030 sql.set_raw_config_int("bcc_self", 1).await?;
1031 }
1032 }
1033
1034 if dbversion < 60 {
1035 sql.execute_migration(
1036 "ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;",
1037 60,
1038 )
1039 .await?;
1040 }
1041 if dbversion < 61 {
1042 sql.execute_migration(
1043 "ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;",
1044 61,
1045 )
1046 .await?;
1047 }
1048 if dbversion < 62 {
1049 sql.execute_migration(
1050 "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;",
1051 62,
1052 )
1053 .await?;
1054 }
1055 if dbversion < 63 {
1056 sql.execute_migration("UPDATE chats SET grpid='' WHERE type=100", 63)
1057 .await?;
1058 }
1059 if dbversion < 64 {
1060 sql.execute_migration("ALTER TABLE msgs ADD COLUMN error TEXT DEFAULT '';", 64)
1061 .await?;
1062 }
1063 if dbversion < 65 {
1064 sql.execute_migration(
1065 r#"
1066ALTER TABLE chats ADD COLUMN ephemeral_timer INTEGER;
1067ALTER TABLE msgs ADD COLUMN ephemeral_timer INTEGER DEFAULT 0;
1068ALTER TABLE msgs ADD COLUMN ephemeral_timestamp INTEGER DEFAULT 0;"#,
1069 65,
1070 )
1071 .await?;
1072 }
1073 if dbversion < 66 {
1074 sql.set_db_version(66).await?;
1075 }
1076 if dbversion < 67 {
1077 for prefix in &["", "configured_"] {
1078 if let Some(server_flags) = sql
1079 .get_raw_config_int(&format!("{prefix}server_flags"))
1080 .await?
1081 {
1082 let imap_socket_flags = server_flags & 0x700;
1083 let key = &format!("{prefix}mail_security");
1084 match imap_socket_flags {
1085 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?,
1089 }
1090 let smtp_socket_flags = server_flags & 0x70000;
1091 let key = &format!("{prefix}send_security");
1092 match smtp_socket_flags {
1093 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?,
1097 }
1098 }
1099 }
1100 sql.set_db_version(67).await?;
1101 }
1102 if dbversion < 68 {
1103 sql.execute_migration(
1105 "CREATE INDEX IF NOT EXISTS msgs_index7 ON msgs (state, hidden, chat_id);",
1106 68,
1107 )
1108 .await?;
1109 }
1110 if dbversion < 69 {
1111 sql.execute_migration(
1112 r#"
1113ALTER TABLE chats ADD COLUMN protected INTEGER DEFAULT 0;
1114-- 120=group, 130=old verified group
1115UPDATE chats SET protected=1, type=120 WHERE type=130;"#,
1116 69,
1117 )
1118 .await?;
1119 }
1120
1121 if dbversion < 71 {
1122 if let Ok(addr) = context.get_primary_self_addr().await {
1123 if let Ok(domain) = EmailAddress::new(&addr).map(|email| email.domain) {
1124 context
1125 .set_config_internal(
1126 Config::ConfiguredProvider,
1127 get_provider_info(&domain).map(|provider| provider.id),
1128 )
1129 .await?;
1130 } else {
1131 warn!(context, "Can't parse configured address: {:?}", addr);
1132 }
1133 }
1134
1135 sql.set_db_version(71).await?;
1136 }
1137 if dbversion < 72 && !sql.col_exists("msgs", "mime_modified").await? {
1138 sql.execute_migration(
1139 r#"
1140 ALTER TABLE msgs ADD COLUMN mime_modified INTEGER DEFAULT 0;"#,
1141 72,
1142 )
1143 .await?;
1144 }
1145 if dbversion < 73 {
1146 sql.execute(
1147 r#"
1148CREATE TABLE imap_sync (folder TEXT PRIMARY KEY, uidvalidity INTEGER DEFAULT 0, uid_next INTEGER DEFAULT 0);"#,
1149()
1150 )
1151 .await?;
1152 for c in [
1153 "configured_inbox_folder",
1154 "configured_sentbox_folder",
1155 "configured_mvbox_folder",
1156 ] {
1157 if let Some(folder) = context.sql.get_raw_config(c).await? {
1158 let key = format!("imap.mailbox.{folder}");
1159
1160 let (uid_validity, last_seen_uid) =
1161 if let Some(entry) = context.sql.get_raw_config(&key).await? {
1162 let mut parts = entry.split(':');
1164 (
1165 parts.next().unwrap_or_default().parse().unwrap_or(0),
1166 parts.next().unwrap_or_default().parse().unwrap_or(0),
1167 )
1168 } else {
1169 (0, 0)
1170 };
1171 if last_seen_uid > 0 {
1172 context
1173 .sql
1174 .execute(
1175 "INSERT INTO imap_sync (folder, uid_next) VALUES (?,?)
1176 ON CONFLICT(folder) DO UPDATE SET uid_next=excluded.uid_next",
1177 (&folder, last_seen_uid + 1),
1178 )
1179 .await?;
1180 context
1181 .sql
1182 .execute(
1183 "INSERT INTO imap_sync (folder, uidvalidity) VALUES (?,?)
1184 ON CONFLICT(folder) DO UPDATE SET uidvalidity=excluded.uidvalidity",
1185 (&folder, uid_validity),
1186 )
1187 .await?;
1188 }
1189 }
1190 }
1191 sql.set_db_version(73).await?;
1192 }
1193 if dbversion < 74 {
1194 sql.execute_migration("UPDATE contacts SET name='' WHERE name=authname", 74)
1195 .await?;
1196 }
1197 if dbversion < 75 {
1198 sql.execute_migration(
1199 "ALTER TABLE contacts ADD COLUMN status TEXT DEFAULT '';",
1200 75,
1201 )
1202 .await?;
1203 }
1204 if dbversion < 76 {
1205 sql.execute_migration("ALTER TABLE msgs ADD COLUMN subject TEXT DEFAULT '';", 76)
1206 .await?;
1207 }
1208 if dbversion < 77 {
1209 recode_avatar = true;
1210 sql.set_db_version(77).await?;
1211 }
1212 if dbversion < 78 {
1213 sql.execute_migration("UPDATE chats SET archived=1 WHERE blocked=2;", 78)
1216 .await?;
1217 }
1218 if dbversion < 79 {
1219 sql.execute_migration(
1220 r#"
1221 ALTER TABLE msgs ADD COLUMN download_state INTEGER DEFAULT 0;
1222 "#,
1223 79,
1224 )
1225 .await?;
1226 }
1227 if dbversion < 80 {
1228 sql.execute_migration(
1229 r#"CREATE TABLE multi_device_sync (
1230id INTEGER PRIMARY KEY AUTOINCREMENT,
1231item TEXT DEFAULT '');"#,
1232 80,
1233 )
1234 .await?;
1235 }
1236 if dbversion < 81 {
1237 sql.execute_migration("ALTER TABLE msgs ADD COLUMN hop_info TEXT;", 81)
1238 .await?;
1239 }
1240 if dbversion < 82 {
1241 sql.execute_migration(
1242 r#"CREATE TABLE imap (
1243id INTEGER PRIMARY KEY AUTOINCREMENT,
1244rfc724_mid TEXT DEFAULT '', -- Message-ID header
1245folder TEXT DEFAULT '', -- IMAP folder
1246target TEXT DEFAULT '', -- Destination folder, empty to delete.
1247uid INTEGER DEFAULT 0, -- UID
1248uidvalidity INTEGER DEFAULT 0,
1249UNIQUE (folder, uid, uidvalidity)
1250);
1251CREATE INDEX imap_folder ON imap(folder);
1252CREATE INDEX imap_messageid ON imap(rfc724_mid);
1253
1254INSERT INTO imap
1255(rfc724_mid, folder, target, uid, uidvalidity)
1256SELECT
1257rfc724_mid,
1258server_folder AS folder,
1259server_folder AS target,
1260server_uid AS uid,
1261(SELECT uidvalidity FROM imap_sync WHERE folder=server_folder) AS uidvalidity
1262FROM msgs
1263WHERE server_uid>0
1264ON CONFLICT (folder, uid, uidvalidity)
1265DO UPDATE SET rfc724_mid=excluded.rfc724_mid,
1266 target=excluded.target;
1267"#,
1268 82,
1269 )
1270 .await?;
1271 }
1272 if dbversion < 83 {
1273 sql.execute_migration(
1274 "ALTER TABLE imap_sync
1275 ADD COLUMN modseq -- Highest modification sequence
1276 INTEGER DEFAULT 0",
1277 83,
1278 )
1279 .await?;
1280 }
1281 if dbversion < 84 {
1282 sql.execute_migration(
1283 r#"CREATE TABLE msgs_status_updates (
1284id INTEGER PRIMARY KEY AUTOINCREMENT,
1285msg_id INTEGER,
1286update_item TEXT DEFAULT '',
1287update_item_read INTEGER DEFAULT 0 -- XXX unused
1288);
1289CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);"#,
1290 84,
1291 )
1292 .await?;
1293 }
1294 if dbversion < 85 {
1295 sql.execute_migration(
1296 r#"CREATE TABLE smtp (
1297id INTEGER PRIMARY KEY,
1298rfc724_mid TEXT NOT NULL, -- Message-ID
1299mime TEXT NOT NULL, -- SMTP payload
1300msg_id INTEGER NOT NULL, -- ID of the message in `msgs` table
1301recipients TEXT NOT NULL, -- List of recipients separated by space
1302retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
1303);
1304CREATE INDEX smtp_messageid ON imap(rfc724_mid);
1305"#,
1306 85,
1307 )
1308 .await?;
1309 }
1310 if dbversion < 86 {
1311 sql.execute_migration(
1312 r#"CREATE TABLE bobstate (
1313 id INTEGER PRIMARY KEY AUTOINCREMENT,
1314 invite TEXT NOT NULL,
1315 next_step INTEGER NOT NULL,
1316 chat_id INTEGER NOT NULL
1317 );"#,
1318 86,
1319 )
1320 .await?;
1321 }
1322 if dbversion < 87 {
1323 sql.execute_migration(
1325 "CREATE INDEX IF NOT EXISTS msgs_index8 ON msgs (ephemeral_timestamp);",
1326 87,
1327 )
1328 .await?;
1329 }
1330 if dbversion < 88 {
1331 sql.execute_migration("DROP TABLE IF EXISTS backup_blobs;", 88)
1332 .await?;
1333 }
1334 if dbversion < 89 {
1335 sql.execute_migration(
1336 r#"CREATE TABLE imap_markseen (
1337 id INTEGER,
1338 FOREIGN KEY(id) REFERENCES imap(id) ON DELETE CASCADE
1339 );"#,
1340 89,
1341 )
1342 .await?;
1343 }
1344 if dbversion < 90 {
1345 sql.execute_migration(
1346 r#"CREATE TABLE smtp_mdns (
1347 msg_id INTEGER NOT NULL, -- id of the message in msgs table which requested MDN (DEPRECATED 2024-06-21)
1348 from_id INTEGER NOT NULL, -- id of the contact that sent the message, MDN destination
1349 rfc724_mid TEXT NOT NULL, -- Message-ID header
1350 retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send MDN
1351 );"#,
1352 90,
1353 )
1354 .await?;
1355 }
1356 if dbversion < 91 {
1357 sql.execute_migration(
1358 r#"CREATE TABLE smtp_status_updates (
1359 msg_id INTEGER NOT NULL UNIQUE, -- msg_id of the webxdc instance with pending updates
1360 first_serial INTEGER NOT NULL, -- id in msgs_status_updates
1361 last_serial INTEGER NOT NULL, -- id in msgs_status_updates
1362 descr TEXT NOT NULL -- text to send along with the updates
1363 );"#,
1364 91,
1365 )
1366 .await?;
1367 }
1368 if dbversion < 92 {
1369 sql.execute_migration(
1370 r#"CREATE TABLE reactions (
1371 msg_id INTEGER NOT NULL, -- id of the message reacted to
1372 contact_id INTEGER NOT NULL, -- id of the contact reacting to the message
1373 reaction TEXT DEFAULT '' NOT NULL, -- a sequence of emojis separated by spaces
1374 PRIMARY KEY(msg_id, contact_id),
1375 FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE -- delete reactions when message is deleted
1376 FOREIGN KEY(contact_id) REFERENCES contacts(id) ON DELETE CASCADE -- delete reactions when contact is deleted
1377 )"#,
1378 92
1379 ).await?;
1380 }
1381 if dbversion < 93 {
1382 sql.execute_migration(
1384 "CREATE TABLE sending_domains(domain TEXT PRIMARY KEY, dkim_works INTEGER DEFAULT 0);",
1385 93,
1386 )
1387 .await?;
1388 }
1389 if dbversion < 94 {
1390 sql.execute_migration(
1391 "CREATE TABLE new_acpeerstates (
1396 id INTEGER PRIMARY KEY,
1397 addr TEXT DEFAULT '' COLLATE NOCASE,
1398 last_seen INTEGER DEFAULT 0,
1399 last_seen_autocrypt INTEGER DEFAULT 0,
1400 public_key,
1401 prefer_encrypted INTEGER DEFAULT 0,
1402 gossip_timestamp INTEGER DEFAULT 0,
1403 gossip_key,
1404 public_key_fingerprint TEXT DEFAULT '',
1405 gossip_key_fingerprint TEXT DEFAULT '',
1406 verified_key,
1407 verified_key_fingerprint TEXT DEFAULT '',
1408 UNIQUE (addr) -- Only one peerstate per address
1409 );
1410 INSERT OR IGNORE INTO new_acpeerstates SELECT
1411 id, addr, last_seen, last_seen_autocrypt, public_key, prefer_encrypted,
1412 gossip_timestamp, gossip_key, public_key_fingerprint,
1413 gossip_key_fingerprint, verified_key, verified_key_fingerprint
1414 FROM acpeerstates;
1415 DROP TABLE acpeerstates;
1416 ALTER TABLE new_acpeerstates RENAME TO acpeerstates;
1417 CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);
1418 CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
1419 CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);
1420 CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);
1421 ",
1422 94,
1423 )
1424 .await?;
1425 }
1426 if dbversion < 95 {
1427 sql.execute_migration(
1428 "CREATE TABLE new_chats_contacts (chat_id INTEGER, contact_id INTEGER, UNIQUE(chat_id, contact_id));\
1429 INSERT OR IGNORE INTO new_chats_contacts SELECT chat_id, contact_id FROM chats_contacts;\
1430 DROP TABLE chats_contacts;\
1431 ALTER TABLE new_chats_contacts RENAME TO chats_contacts;\
1432 CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);\
1433 CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
1434 95
1435 ).await?;
1436 }
1437 if dbversion < 96 {
1438 sql.execute_migration(
1439 "ALTER TABLE acpeerstates ADD COLUMN verifier TEXT DEFAULT '';",
1440 96,
1441 )
1442 .await?;
1443 }
1444 if dbversion < 97 {
1445 sql.execute_migration(
1446 "CREATE TABLE dns_cache (
1447 hostname TEXT NOT NULL,
1448 address TEXT NOT NULL, -- IPv4 or IPv6 address
1449 timestamp INTEGER NOT NULL,
1450 UNIQUE (hostname, address)
1451 )",
1452 97,
1453 )
1454 .await?;
1455 }
1456 if dbversion < 98 {
1457 if exists_before_update && sql.get_raw_config_int("show_emails").await?.is_none() {
1458 sql.set_raw_config_int("show_emails", 0).await?;
1459 }
1460 sql.set_db_version(98).await?;
1461 }
1462 if dbversion < 99 {
1463 sql.set_db_version(99).await?;
1475 }
1476 if dbversion < 100 {
1477 sql.execute_migration(
1478 "ALTER TABLE msgs ADD COLUMN mime_compressed INTEGER NOT NULL DEFAULT 0",
1479 100,
1480 )
1481 .await?;
1482 }
1483 if dbversion < 101 {
1484 sql.execute_migration(
1487 "DROP TABLE smtp;
1488 CREATE TABLE smtp (
1489 id INTEGER PRIMARY KEY AUTOINCREMENT,
1490 rfc724_mid TEXT NOT NULL, -- Message-ID
1491 mime TEXT NOT NULL, -- SMTP payload
1492 msg_id INTEGER NOT NULL, -- ID of the message in `msgs` table
1493 recipients TEXT NOT NULL, -- List of recipients separated by space
1494 retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
1495 );
1496 ",
1497 101,
1498 )
1499 .await?;
1500 }
1501
1502 if dbversion < 102 {
1503 sql.execute_migration(
1504 "CREATE TABLE download (
1505 msg_id INTEGER NOT NULL -- id of the message stub in msgs table
1506 )",
1507 102,
1508 )
1509 .await?;
1510 }
1511
1512 if dbversion < 103 {
1514 sql.execute_migration(
1515 "ALTER TABLE contacts ADD COLUMN is_bot INTEGER NOT NULL DEFAULT 0",
1516 103,
1517 )
1518 .await?;
1519 }
1520
1521 if dbversion < 104 {
1522 sql.execute_migration(
1523 "ALTER TABLE acpeerstates
1524 ADD COLUMN secondary_verified_key;
1525 ALTER TABLE acpeerstates
1526 ADD COLUMN secondary_verified_key_fingerprint TEXT DEFAULT '';
1527 ALTER TABLE acpeerstates
1528 ADD COLUMN secondary_verifier TEXT DEFAULT ''",
1529 104,
1530 )
1531 .await?;
1532 }
1533
1534 if dbversion < 105 {
1535 sql.execute_migration(
1537 r#"CREATE TABLE new_msgs_status_updates (
1538id INTEGER PRIMARY KEY AUTOINCREMENT,
1539msg_id INTEGER,
1540update_item TEXT DEFAULT '',
1541uid TEXT UNIQUE
1542);
1543INSERT OR IGNORE INTO new_msgs_status_updates SELECT
1544 id, msg_id, update_item, NULL
1545FROM msgs_status_updates;
1546DROP TABLE msgs_status_updates;
1547ALTER TABLE new_msgs_status_updates RENAME TO msgs_status_updates;
1548CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
1549CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
1550"#,
1551 105,
1552 )
1553 .await?;
1554 }
1555
1556 if dbversion < 106 {
1557 sql.execute_migration(
1559 "CREATE TABLE new_config (
1560 id INTEGER PRIMARY KEY,
1561 keyname TEXT UNIQUE,
1562 value TEXT NOT NULL
1563 );
1564 INSERT OR IGNORE INTO new_config SELECT
1565 id, keyname, value
1566 FROM config;
1567 DROP TABLE config;
1568 ALTER TABLE new_config RENAME TO config;
1569 CREATE INDEX config_index1 ON config (keyname);",
1570 106,
1571 )
1572 .await?;
1573 }
1574
1575 if dbversion < 107 {
1576 sql.execute_migration(
1577 "CREATE TABLE new_keypairs (
1578 id INTEGER PRIMARY KEY AUTOINCREMENT,
1579 private_key UNIQUE NOT NULL,
1580 public_key UNIQUE NOT NULL
1581 );
1582 INSERT OR IGNORE INTO new_keypairs SELECT id, private_key, public_key FROM keypairs;
1583
1584 INSERT OR IGNORE
1585 INTO config (keyname, value)
1586 VALUES
1587 ('key_id', (SELECT id FROM new_keypairs
1588 WHERE private_key=
1589 (SELECT private_key FROM keypairs
1590 WHERE addr=(SELECT value FROM config WHERE keyname='configured_addr')
1591 AND is_default=1)));
1592
1593 -- We do not drop the old `keypairs` table for now,
1594 -- but move it to `old_keypairs`. We can remove it later
1595 -- in next migrations. This may be needed for recovery
1596 -- in case something is wrong with the migration.
1597 ALTER TABLE keypairs RENAME TO old_keypairs;
1598 ALTER TABLE new_keypairs RENAME TO keypairs;
1599 ",
1600 107,
1601 )
1602 .await?;
1603 }
1604
1605 if dbversion < 109 {
1612 sql.execute_migration(
1613 r#"ALTER TABLE acpeerstates
1614 ADD COLUMN backward_verified_key_id -- What we think the contact has as our verified key
1615 INTEGER;
1616 UPDATE acpeerstates
1617 SET backward_verified_key_id=(SELECT value FROM config WHERE keyname='key_id')
1618 WHERE verified_key IS NOT NULL
1619 "#,
1620 109,
1621 )
1622 .await?;
1623 }
1624
1625 if dbversion < 110 {
1626 sql.execute_migration(
1627 "ALTER TABLE keypairs ADD COLUMN addr TEXT DEFAULT '' COLLATE NOCASE;
1628 ALTER TABLE keypairs ADD COLUMN is_default INTEGER DEFAULT 0;
1629 ALTER TABLE keypairs ADD COLUMN created INTEGER DEFAULT 0;
1630 UPDATE keypairs SET addr=(SELECT value FROM config WHERE keyname='configured_addr'), is_default=1;",
1631 110,
1632 )
1633 .await?;
1634 }
1635
1636 if dbversion < 111 {
1637 sql.execute_migration(
1638 "CREATE TABLE iroh_gossip_peers (msg_id TEXT not NULL, topic TEXT NOT NULL, public_key TEXT NOT NULL)",
1639 111,
1640 )
1641 .await?;
1642 }
1643
1644 if dbversion < 112 {
1645 sql.execute_migration(
1646 "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",
1647 112,
1648 )
1649 .await?;
1650 }
1651
1652 if dbversion < 113 {
1653 sql.execute_migration(
1654 "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",
1655 113,
1656 )
1657 .await?;
1658 }
1659
1660 if dbversion < 114 {
1661 sql.execute_migration("CREATE INDEX reactions_index1 ON reactions (msg_id)", 114)
1662 .await?;
1663 }
1664
1665 if dbversion < 115 {
1666 sql.execute_migration("ALTER TABLE msgs ADD COLUMN txt_normalized TEXT", 115)
1667 .await?;
1668 }
1669 let mut migration_version: i32 = 115;
1670
1671 inc_and_check(&mut migration_version, 116)?;
1672 if dbversion < migration_version {
1673 sql.execute_migration(
1676 "ALTER TABLE msgs ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0",
1677 migration_version,
1678 )
1679 .await?;
1680 }
1681
1682 inc_and_check(&mut migration_version, 117)?;
1683 if dbversion < migration_version {
1684 sql.execute_migration(
1685 "CREATE TABLE connection_history (
1686 host TEXT NOT NULL, -- server hostname
1687 port INTEGER NOT NULL, -- server port
1688 alpn TEXT NOT NULL, -- ALPN such as smtp or imap
1689 addr TEXT NOT NULL, -- IP address
1690 timestamp INTEGER NOT NULL, -- timestamp of the most recent successful connection
1691 UNIQUE (host, port, alpn, addr)
1692 ) STRICT",
1693 migration_version,
1694 )
1695 .await?;
1696 }
1697
1698 inc_and_check(&mut migration_version, 118)?;
1699 if dbversion < migration_version {
1700 sql.execute_migration(
1701 "CREATE TABLE tokens_new (
1702 id INTEGER PRIMARY KEY,
1703 namespc INTEGER DEFAULT 0,
1704 foreign_key TEXT DEFAULT '',
1705 token TEXT DEFAULT '',
1706 timestamp INTEGER DEFAULT 0
1707 ) STRICT;
1708 INSERT INTO tokens_new
1709 SELECT t.id, t.namespc, IFNULL(c.grpid, ''), t.token, t.timestamp
1710 FROM tokens t LEFT JOIN chats c ON t.foreign_id=c.id;
1711 DROP TABLE tokens;
1712 ALTER TABLE tokens_new RENAME TO tokens;",
1713 migration_version,
1714 )
1715 .await?;
1716 }
1717
1718 inc_and_check(&mut migration_version, 119)?;
1719 if dbversion < migration_version {
1720 sql.execute_migration(
1723 "CREATE TABLE imap_send (
1724 id INTEGER PRIMARY KEY AUTOINCREMENT,
1725 mime TEXT NOT NULL, -- Message content
1726 msg_id INTEGER NOT NULL, -- ID of the message in the `msgs` table
1727 attempts INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
1728 )",
1729 migration_version,
1730 )
1731 .await?;
1732 }
1733
1734 inc_and_check(&mut migration_version, 120)?;
1735 if dbversion < migration_version {
1736 sql.execute_migration(
1748 "INSERT OR IGNORE INTO config (keyname, value)
1749 SELECT 'delete_server_after', '0'
1750 FROM config WHERE keyname='configured'
1751 ",
1752 migration_version,
1753 )
1754 .await?;
1755 }
1756
1757 inc_and_check(&mut migration_version, 121)?;
1758 if dbversion < migration_version {
1759 sql.execute_migration(
1760 "CREATE INDEX chats_index4 ON chats (name)",
1761 migration_version,
1762 )
1763 .await?;
1764 }
1765
1766 inc_and_check(&mut migration_version, 122)?;
1767 if dbversion < migration_version {
1768 sql.execute_migration(
1769 "ALTER TABLE tokens ADD COLUMN foreign_id INTEGER NOT NULL DEFAULT 0",
1770 migration_version,
1771 )
1772 .await?;
1773 }
1774
1775 inc_and_check(&mut migration_version, 123)?;
1776 if dbversion < migration_version {
1777 sql.execute_migration(
1779 "CREATE TABLE new_msgs_status_updates (
1780 id INTEGER PRIMARY KEY AUTOINCREMENT,
1781 msg_id INTEGER,
1782 update_item TEXT DEFAULT '',
1783 uid TEXT UNIQUE,
1784 FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE
1785 );
1786 INSERT OR IGNORE INTO new_msgs_status_updates SELECT
1787 id, msg_id, update_item, uid
1788 FROM msgs_status_updates;
1789 DROP TABLE msgs_status_updates;
1790 ALTER TABLE new_msgs_status_updates RENAME TO msgs_status_updates;
1791 CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
1792 CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
1793 ",
1794 migration_version,
1795 )
1796 .await?;
1797 }
1798
1799 inc_and_check(&mut migration_version, 124)?;
1800 if dbversion < migration_version {
1801 sql.execute_migration(
1803 "UPDATE chats
1804 SET protected=1 -- ProtectionStatus::Protected
1805 WHERE type==100 -- Chattype::Single
1806 AND EXISTS (
1807 SELECT 1 FROM chats_contacts cc
1808 WHERE cc.chat_id==chats.id
1809 AND cc.contact_id=1
1810 )
1811 ",
1812 migration_version,
1813 )
1814 .await?;
1815 }
1816
1817 inc_and_check(&mut migration_version, 125)?;
1818 if dbversion < migration_version {
1819 sql.execute_migration(
1820 "CREATE TABLE http_cache (
1821 url TEXT PRIMARY KEY,
1822 expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1823 blobname TEXT NOT NULL,
1824 mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
1825 encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
1826 ) STRICT",
1827 migration_version,
1828 )
1829 .await?;
1830 }
1831
1832 inc_and_check(&mut migration_version, 126)?;
1833 if dbversion < migration_version {
1834 sql.execute_migration(
1836 "DROP TABLE http_cache;
1837 CREATE TABLE http_cache (
1838 url TEXT PRIMARY KEY,
1839 expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1840 stale INTEGER NOT NULL, -- When the cache entry is considered stale, timestamp in seconds.
1841 blobname TEXT NOT NULL,
1842 mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
1843 encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
1844 ) STRICT",
1845 migration_version,
1846 )
1847 .await?;
1848 }
1849
1850 inc_and_check(&mut migration_version, 127)?;
1851 if dbversion < migration_version {
1852 sql.execute_migration(
1854 "INSERT OR IGNORE INTO config (keyname, value)
1855 SELECT 'bcc_self', '1'
1856 FROM config WHERE keyname='delete_server_after' AND value='0'
1857 ",
1858 migration_version,
1859 )
1860 .await?;
1861 }
1862
1863 inc_and_check(&mut migration_version, 128)?;
1864 if dbversion < migration_version {
1865 sql.execute_migration(
1871 "ALTER TABLE chats_contacts
1872 ADD COLUMN add_timestamp NOT NULL DEFAULT 0;
1873 ALTER TABLE chats_contacts
1874 ADD COLUMN remove_timestamp NOT NULL DEFAULT 0;
1875 ",
1876 migration_version,
1877 )
1878 .await?;
1879 }
1880
1881 inc_and_check(&mut migration_version, 129)?;
1882 if dbversion < migration_version {
1883 sql.execute_migration(
1891 "INSERT OR IGNORE INTO config (keyname, value)
1892 SELECT 'bcc_self', '1'
1893 FROM config WHERE keyname='delete_server_after' AND value!='1'
1894 ",
1895 migration_version,
1896 )
1897 .await?;
1898 }
1899
1900 inc_and_check(&mut migration_version, 130)?;
1901 if dbversion < migration_version {
1902 sql.execute_migration(
1903 "
1904CREATE TABLE gossip_timestamp (
1905 chat_id INTEGER NOT NULL,
1906 fingerprint TEXT NOT NULL, -- Upper-case fingerprint of the key.
1907 timestamp INTEGER NOT NULL,
1908 UNIQUE (chat_id, fingerprint)
1909) STRICT;
1910CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint);
1911",
1912 migration_version,
1913 )
1914 .await?;
1915 }
1916
1917 inc_and_check(&mut migration_version, 131)?;
1918 if dbversion < migration_version {
1919 let entered_param = EnteredLoginParam::load_legacy(context).await?;
1920 let configured_param = ConfiguredLoginParam::load_legacy(context).await?;
1921
1922 sql.execute_migration_transaction(
1923 |transaction| {
1924 transaction.execute(
1925 "CREATE TABLE transports (
1926 id INTEGER PRIMARY KEY AUTOINCREMENT,
1927 addr TEXT NOT NULL,
1928 entered_param TEXT NOT NULL,
1929 configured_param TEXT NOT NULL,
1930 UNIQUE(addr)
1931 )",
1932 (),
1933 )?;
1934 if let Some(configured_param) = configured_param {
1935 transaction.execute(
1936 "INSERT INTO transports (addr, entered_param, configured_param)
1937 VALUES (?, ?, ?)",
1938 (
1939 configured_param.addr.clone(),
1940 serde_json::to_string(&entered_param)?,
1941 configured_param.into_json()?,
1942 ),
1943 )?;
1944 }
1945
1946 Ok(())
1947 },
1948 migration_version,
1949 )
1950 .await?;
1951 }
1952
1953 inc_and_check(&mut migration_version, 132)?;
1954 if dbversion < migration_version {
1955 let start = Time::now();
1956 sql.execute_migration_transaction(|t| migrate_key_contacts(context, t), migration_version)
1957 .await?;
1958 info!(
1959 context,
1960 "key-contacts migration took {:?} in total.",
1961 time_elapsed(&start),
1962 );
1963 context
1965 .set_config_internal(Config::LastHousekeeping, None)
1966 .await?;
1967 }
1968
1969 inc_and_check(&mut migration_version, 133)?;
1970 if dbversion < migration_version {
1971 sql.execute_migration(
1973 "UPDATE chats SET protected=0 WHERE protected!=1",
1974 migration_version,
1975 )
1976 .await?;
1977 }
1978
1979 inc_and_check(&mut migration_version, 134)?; inc_and_check(&mut migration_version, 135)?;
1982 if dbversion < migration_version {
1983 sql.execute_migration(
1984 "CREATE TABLE stats_securejoin_sources(
1985 source INTEGER PRIMARY KEY,
1986 count INTEGER NOT NULL DEFAULT 0
1987 ) STRICT;
1988 CREATE TABLE stats_securejoin_uipaths(
1989 uipath INTEGER PRIMARY KEY,
1990 count INTEGER NOT NULL DEFAULT 0
1991 ) STRICT;
1992 CREATE TABLE stats_securejoin_invites(
1993 already_existed INTEGER NOT NULL,
1994 already_verified INTEGER NOT NULL,
1995 type TEXT NOT NULL
1996 ) STRICT;
1997 CREATE TABLE stats_msgs(
1998 chattype INTEGER PRIMARY KEY,
1999 verified INTEGER NOT NULL DEFAULT 0,
2000 unverified_encrypted INTEGER NOT NULL DEFAULT 0,
2001 unencrypted INTEGER NOT NULL DEFAULT 0,
2002 only_to_self INTEGER NOT NULL DEFAULT 0,
2003 last_counted_msg_id INTEGER NOT NULL DEFAULT 0
2004 ) STRICT;",
2005 migration_version,
2006 )
2007 .await?;
2008 }
2009
2010 inc_and_check(&mut migration_version, 136)?;
2011 if dbversion < migration_version {
2012 sql.execute_migration(
2013 "CREATE TABLE stats_sending_enabled_events(timestamp INTEGER NOT NULL) STRICT;
2014 CREATE TABLE stats_sending_disabled_events(timestamp INTEGER NOT NULL) STRICT;",
2015 migration_version,
2016 )
2017 .await?;
2018 }
2019
2020 inc_and_check(&mut migration_version, 137)?;
2021 if dbversion < migration_version {
2022 sql.execute_migration(
2023 "DELETE FROM config WHERE keyname IN (
2024 'configured',
2025 'configured_imap_certificate_checks',
2026 'configured_imap_servers',
2027 'configured_mail_port',
2028 'configured_mail_pw',
2029 'configured_mail_security',
2030 'configured_mail_server',
2031 'configured_mail_user',
2032 'configured_send_port',
2033 'configured_send_pw',
2034 'configured_send_security',
2035 'configured_send_server',
2036 'configured_send_user',
2037 'configured_server_flags',
2038 'configured_smtp_certificate_checks',
2039 'configured_smtp_servers'
2040 )",
2041 migration_version,
2042 )
2043 .await?;
2044 }
2045
2046 inc_and_check(&mut migration_version, 138)?;
2047 if dbversion < migration_version {
2048 sql.execute_migration(
2049 "CREATE TABLE broadcast_secrets(
2050 chat_id INTEGER PRIMARY KEY NOT NULL,
2051 secret TEXT NOT NULL
2052 ) STRICT",
2053 migration_version,
2054 )
2055 .await?;
2056 }
2057
2058 inc_and_check(&mut migration_version, 139)?;
2059 if dbversion < migration_version {
2060 sql.execute_migration_transaction(
2061 |transaction| {
2062 if exists_before_update {
2063 let is_chatmail = transaction
2064 .query_row(
2065 "SELECT value FROM config WHERE keyname='is_chatmail'",
2066 (),
2067 |row| {
2068 let value: String = row.get(0)?;
2069 Ok(value)
2070 },
2071 )
2072 .optional()?
2073 .as_deref()
2074 == Some("1");
2075
2076 if !is_chatmail {
2083 transaction.execute(
2084 "INSERT OR IGNORE
2085 INTO config (keyname, value)
2086 VALUES (?, ?)",
2087 ("bcc_self", "1"),
2088 )?;
2089 }
2090 }
2091 Ok(())
2092 },
2093 migration_version,
2094 )
2095 .await?;
2096 }
2097
2098 inc_and_check(&mut migration_version, 140)?;
2099 if dbversion < migration_version {
2100 sql.execute_migration(
2101 "
2102CREATE TABLE new_imap (
2103id INTEGER PRIMARY KEY AUTOINCREMENT,
2104transport_id INTEGER NOT NULL, -- ID of the transport in the `transports` table.
2105rfc724_mid TEXT NOT NULL, -- Message-ID header
2106folder TEXT NOT NULL, -- IMAP folder
2107target TEXT NOT NULL, -- Destination folder. Empty string means that the message shall be deleted.
2108uid INTEGER NOT NULL, -- UID
2109uidvalidity INTEGER NOT NULL,
2110UNIQUE (transport_id, folder, uid, uidvalidity)
2111) STRICT;
2112
2113INSERT OR IGNORE INTO new_imap SELECT
2114 id, 1, rfc724_mid, folder, target, uid, uidvalidity
2115FROM imap;
2116DROP TABLE imap;
2117ALTER TABLE new_imap RENAME TO imap;
2118CREATE INDEX imap_folder ON imap(transport_id, folder);
2119CREATE INDEX imap_rfc724_mid ON imap(transport_id, rfc724_mid);
2120
2121CREATE TABLE new_imap_sync (
2122 transport_id INTEGER NOT NULL, -- ID of the transport in the `transports` table.
2123 folder TEXT NOT NULL,
2124 uidvalidity INTEGER NOT NULL DEFAULT 0,
2125 uid_next INTEGER NOT NULL DEFAULT 0,
2126 modseq INTEGER NOT NULL DEFAULT 0,
2127 UNIQUE (transport_id, folder)
2128) STRICT;
2129INSERT OR IGNORE INTO new_imap_sync SELECT
2130 1, folder, uidvalidity, uid_next, modseq
2131FROM imap_sync;
2132DROP TABLE imap_sync;
2133ALTER TABLE new_imap_sync RENAME TO imap_sync;
2134CREATE INDEX imap_sync_index ON imap_sync(transport_id, folder);
2135",
2136 migration_version,
2137 )
2138 .await?;
2139 }
2140
2141 inc_and_check(&mut migration_version, 141)?;
2142 if dbversion < migration_version {
2143 sql.execute_migration(
2144 "CREATE INDEX imap_only_rfc724_mid ON imap(rfc724_mid)",
2145 migration_version,
2146 )
2147 .await?;
2148 }
2149
2150 inc_and_check(&mut migration_version, 142)?;
2151 if dbversion < migration_version {
2152 sql.execute_migration(
2153 "ALTER TABLE transports
2154 ADD COLUMN add_timestamp INTEGER NOT NULL DEFAULT 0;
2155 CREATE TABLE removed_transports (
2156 addr TEXT NOT NULL,
2157 remove_timestamp INTEGER NOT NULL,
2158 UNIQUE(addr)
2159 ) STRICT;",
2160 migration_version,
2161 )
2162 .await?;
2163 }
2164
2165 inc_and_check(&mut migration_version, 143)?;
2166 if dbversion < migration_version {
2167 sql.execute_migration(
2168 "
2169ALTER TABLE chats ADD COLUMN name_normalized TEXT;
2170ALTER TABLE contacts ADD COLUMN name_normalized TEXT;
2171 ",
2172 migration_version,
2173 )
2174 .await?;
2175 }
2176
2177 inc_and_check(&mut migration_version, 144)?;
2178 if dbversion < migration_version {
2179 sql.execute_migration_transaction(
2180 |transaction| {
2181 let is_chatmail = transaction
2182 .query_row(
2183 "SELECT value FROM config WHERE keyname='is_chatmail'",
2184 (),
2185 |row| {
2186 let value: String = row.get(0)?;
2187 Ok(value)
2188 },
2189 )
2190 .optional()?
2191 .as_deref()
2192 == Some("1");
2193
2194 if is_chatmail {
2195 transaction.execute_batch(
2196 "DELETE FROM config WHERE keyname='only_fetch_mvbox';
2197 DELETE FROM config WHERE keyname='show_emails';
2198 UPDATE config SET value='0' WHERE keyname='mvbox_move'",
2199 )?;
2200 }
2201 Ok(())
2202 },
2203 migration_version,
2204 )
2205 .await?;
2206 }
2207
2208 inc_and_check(&mut migration_version, 145)?;
2209 if dbversion < migration_version {
2210 sql.execute_migration(
2214 "CREATE TABLE download_new (
2215 rfc724_mid TEXT PRIMARY KEY,
2216 msg_id INTEGER NOT NULL DEFAULT 0
2217 ) STRICT;
2218 INSERT OR IGNORE INTO download_new (rfc724_mid, msg_id)
2219 SELECT m.rfc724_mid, d.msg_id FROM download d
2220 LEFT JOIN msgs m ON d.msg_id = m.id
2221 WHERE m.rfc724_mid IS NOT NULL AND m.rfc724_mid != '';
2222 DROP TABLE download;
2223 ALTER TABLE download_new RENAME TO download;
2224 CREATE TABLE available_post_msgs (
2225 rfc724_mid TEXT PRIMARY KEY
2226 ) STRICT;
2227 ALTER TABLE msgs ADD COLUMN pre_rfc724_mid TEXT DEFAULT '';
2228 CREATE INDEX msgs_index9 ON msgs (pre_rfc724_mid);",
2229 migration_version,
2230 )
2231 .await?;
2232 }
2233
2234 inc_and_check(&mut migration_version, 146)?;
2237 if dbversion < migration_version {
2238 sql.execute_migration_transaction(
2239 |transaction| {
2240 let is_chatmail = transaction
2241 .query_row(
2242 "SELECT value FROM config WHERE keyname='is_chatmail'",
2243 (),
2244 |row| {
2245 let value: String = row.get(0)?;
2246 Ok(value)
2247 },
2248 )
2249 .optional()?
2250 .as_deref()
2251 == Some("1");
2252
2253 if is_chatmail {
2254 transaction.execute_batch(
2255 "DELETE FROM config WHERE keyname='only_fetch_mvbox';
2256 DELETE FROM config WHERE keyname='show_emails';
2257 INSERT OR REPLACE INTO config (keyname, value) VALUES ('mvbox_move', '0')",
2258 )?;
2259 }
2260 Ok(())
2261 },
2262 migration_version,
2263 )
2264 .await?;
2265 }
2266
2267 inc_and_check(&mut migration_version, 147)?;
2268 if dbversion < migration_version {
2269 sql.execute_migration(
2270 "CREATE TABLE chats_descriptions (chat_id INTEGER PRIMARY KEY AUTOINCREMENT, description TEXT NOT NULL DEFAULT '') STRICT;",
2271 migration_version,
2272 )
2273 .await?;
2274 }
2275
2276 inc_and_check(&mut migration_version, 148)?;
2278 if dbversion < migration_version {
2279 sql.execute_migration(
2280 "CREATE TABLE tokens_new (
2281 id INTEGER PRIMARY KEY,
2282 namespc INTEGER NOT NULL,
2283 foreign_key TEXT DEFAULT '' NOT NULL,
2284 token TEXT NOT NULL UNIQUE,
2285 timestamp INTEGER DEFAULT 0 NOT NULL
2286 ) STRICT;
2287 INSERT OR IGNORE INTO tokens_new
2288 SELECT id, namespc, foreign_key, token, timestamp FROM tokens;
2289 DROP TABLE tokens;
2290 ALTER TABLE tokens_new RENAME TO tokens;",
2291 migration_version,
2292 )
2293 .await?;
2294 }
2295
2296 inc_and_check(&mut migration_version, 149)?;
2305 if dbversion < migration_version {
2306 sql.execute_migration(
2307 "ALTER TABLE transports ADD COLUMN is_published INTEGER DEFAULT 1 NOT NULL;
2308 UPDATE transports SET is_published=0 WHERE addr!=(
2309 SELECT value FROM config WHERE keyname='configured_addr'
2310 )",
2311 migration_version,
2312 )
2313 .await?;
2314 }
2315
2316 inc_and_check(&mut migration_version, 150)?;
2317 if dbversion < migration_version {
2318 sql.execute_migration_transaction(
2319 |transaction| {
2320 let only_fetch_mvbox = transaction
2321 .query_row(
2322 "SELECT value FROM config WHERE keyname='only_fetch_mvbox'",
2323 (),
2324 |row| {
2325 let value: String = row.get(0)?;
2326 Ok(value)
2327 },
2328 )
2329 .optional()?
2330 .as_deref()
2331 == Some("1");
2332
2333 if only_fetch_mvbox {
2334 let mvbox_folder = transaction
2335 .query_row(
2336 "SELECT value FROM config WHERE keyname='configured_mvbox_folder'",
2337 (),
2338 |row| {
2339 let value: String = row.get(0)?;
2340 Ok(value)
2341 },
2342 )
2343 .optional()?
2344 .unwrap_or_else(|| "DeltaChat".to_string());
2345
2346 transaction.execute(
2347 "UPDATE transports
2348 SET entered_param=json_set(entered_param, '$.imap.folder', ?1),
2349 configured_param=json_set(configured_param', '$.imap_folder', ?1)",
2350 (mvbox_folder,),
2351 )?;
2352 }
2353 Ok(())
2354 },
2355 migration_version,
2356 )
2357 .await?;
2358 }
2359
2360 inc_and_check(&mut migration_version, 151)?;
2361 if dbversion < migration_version {
2362 sql.execute_migration(
2363 "CREATE TABLE tls_spki (
2364 host TEXT NOT NULL UNIQUE,
2365 spki_hash TEXT NOT NULL, -- base64 of SPKI SHA-256 hash
2366 timestamp INTEGER NOT NULL -- timestamp of the last time we have seen this key
2367 ) STRICT;
2368 -- Index on host column is created implicitly because of UNIQUE constraint.
2369 CREATE INDEX tls_spki_index_timestamp ON tls_spki (timestamp);
2370 ",
2371 migration_version,
2372 )
2373 .await?;
2374 }
2375
2376 let new_version = sql
2377 .get_raw_config_int(VERSION_CFG)
2378 .await?
2379 .unwrap_or_default();
2380 if new_version != dbversion || !exists_before_update {
2381 let created_db = if exists_before_update {
2382 ""
2383 } else {
2384 "Created new database. "
2385 };
2386 info!(context, "{}Migration done from v{}.", created_db, dbversion);
2387 }
2388 info!(context, "Database version: v{new_version}.");
2389
2390 Ok(recode_avatar)
2391}
2392
2393#[cfg(test)]
2394mod migrations_tests;