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