v0.1.1
RustBox CI / rustbox (push) Canceled after 0s

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-31 00:37:16 +01:00
1 parent ecc201ef50
commit 698e3c4a1b
124 files changed
+4593 -2721

No files matched your search

+54 -51
View File
@@ -1,5 +1,5 @@
use crate::db::pool::{Connection, DbValue, ToDbValue, params};
use crate::queries::sqlite::queries::db_calendar::{EVENT_SELECT_COLS, SETTINGS_SELECT_COLS};
use rusqlite::{Connection, params};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
@@ -126,7 +126,7 @@ pub struct MeetingTemplate {
pub created_at: String,
}
fn row_to_event(row: &rusqlite::Row<'_>) -> rusqlite::Result<CalendarEvent> {
fn row_to_event(row: &crate::db::pool::Row<'_>) -> Result<CalendarEvent, crate::db::pool::DbError> {
let all_day_int: i32 = row.get(6)?;
Ok(CalendarEvent {
id: row.get(0)?,
@@ -152,7 +152,9 @@ fn row_to_event(row: &rusqlite::Row<'_>) -> rusqlite::Result<CalendarEvent> {
})
}
fn row_to_settings(row: &rusqlite::Row<'_>) -> rusqlite::Result<CalendarSettings> {
fn row_to_settings(
row: &crate::db::pool::Row<'_>,
) -> Result<CalendarSettings, crate::db::pool::DbError> {
let caldav_enabled_int: i32 = row.get(10)?;
Ok(CalendarSettings {
default_view: row.get(0)?,
@@ -170,7 +172,9 @@ fn row_to_settings(row: &rusqlite::Row<'_>) -> rusqlite::Result<CalendarSettings
})
}
fn row_to_template(row: &rusqlite::Row<'_>) -> rusqlite::Result<MeetingTemplate> {
fn row_to_template(
row: &crate::db::pool::Row<'_>,
) -> Result<MeetingTemplate, crate::db::pool::DbError> {
let is_default_int: i32 = row.get(4)?;
Ok(MeetingTemplate {
id: row.get(0)?,
@@ -187,7 +191,8 @@ pub fn list_events(
start: &str,
end: &str,
) -> Result<Vec<CalendarEvent>, String> {
let sql = crate::rustbox_query_db_calendar_001!();
let sql =
crate::dbq!(db_calendar::FORMAT_001).replace("{EVENT_SELECT_COLS}", EVENT_SELECT_COLS);
let mut stmt = conn
.prepare(&sql)
.map_err(|e| format!("Failed to prepare list_events: {e}"))?;
@@ -203,7 +208,8 @@ pub fn list_events(
}
pub fn list_all_events(conn: &Connection) -> Result<Vec<CalendarEvent>, String> {
let sql = crate::rustbox_query_db_calendar_002!();
let sql =
crate::dbq!(db_calendar::FORMAT_002).replace("{EVENT_SELECT_COLS}", EVENT_SELECT_COLS);
let mut stmt = conn
.prepare(&sql)
.map_err(|e| format!("Failed to prepare list_all_events: {e}"))?;
@@ -219,12 +225,13 @@ pub fn list_all_events(conn: &Connection) -> Result<Vec<CalendarEvent>, String>
}
pub fn get_event(conn: &Connection, id: &str) -> Result<Option<CalendarEvent>, String> {
let sql = crate::rustbox_query_db_calendar_003!();
let sql =
crate::dbq!(db_calendar::FORMAT_003).replace("{EVENT_SELECT_COLS}", EVENT_SELECT_COLS);
let result = conn.query_row(&sql, params![id], row_to_event);
match result {
Ok(e) => Ok(Some(e)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get event: {e}")),
}
}
@@ -232,15 +239,11 @@ pub fn get_event(conn: &Connection, id: &str) -> Result<Option<CalendarEvent>, S
pub fn create_event(conn: &Connection, data: &CreateEvent) -> Result<CalendarEvent, String> {
let id = Uuid::new_v4().to_string();
let now: String = conn
.query_row(
crate::queries::sqlite::queries::db_calendar::Q_001,
[],
|row| row.get(0),
)
.query_row(crate::dbq!(db_calendar::Q_001), [], |row| row.get(0))
.map_err(|e| format!("Failed to get current time: {e}"))?;
conn.execute(
crate::queries::sqlite::queries::db_calendar::Q_002,
crate::dbq!(db_calendar::Q_002),
params![
id,
data.title,
@@ -275,14 +278,14 @@ pub fn update_event(
data: &UpdateEvent,
) -> Result<Option<CalendarEvent>, String> {
let mut sets = Vec::new();
let mut values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
let mut values: Vec<DbValue> = Vec::new();
let mut idx = 1u32;
macro_rules! maybe_set {
($field:ident, $col:expr) => {
if let Some(ref v) = data.$field {
sets.push(format!("{} = ?{idx}", $col));
values.push(Box::new(v.clone()));
values.push(v.to_db_value());
idx += 1;
}
};
@@ -302,13 +305,13 @@ pub fn update_event(
if let Some(v) = data.all_day {
sets.push(format!("all_day = ?{idx}"));
values.push(Box::new(v as i32));
values.push((v as i32).to_db_value());
idx += 1;
}
if let Some(v) = data.reminder_minutes {
sets.push(format!("reminder_minutes = ?{idx}"));
values.push(Box::new(v));
values.push(v.to_db_value());
idx += 1;
}
@@ -318,12 +321,15 @@ pub fn update_event(
sets.push("updated_at = datetime('now')".to_string());
let sql = crate::rustbox_query_db_calendar_004!(sets.join(", "), idx);
values.push(Box::new(id.to_string()));
let sql = format!(
"UPDATE calendar_events SET {} WHERE id = ?{}",
sets.join(", "),
idx
);
values.push(id.to_db_value());
let params_refs: Vec<&dyn rusqlite::types::ToSql> = values.iter().map(|b| b.as_ref()).collect();
let updated = conn
.execute(&sql, params_refs.as_slice())
.execute(&sql, values)
.map_err(|e| format!("Failed to update event: {e}"))?;
if updated == 0 {
@@ -335,10 +341,7 @@ pub fn update_event(
pub fn delete_event(conn: &Connection, id: &str) -> Result<bool, String> {
let deleted = conn
.execute(
crate::queries::sqlite::queries::db_calendar::Q_003,
params![id],
)
.execute(crate::dbq!(db_calendar::Q_003), params![id])
.map_err(|e| format!("Failed to delete event: {e}"))?;
Ok(deleted > 0)
}
@@ -347,21 +350,23 @@ pub fn find_event_by_source_uid(
conn: &Connection,
source_uid: &str,
) -> Result<Option<CalendarEvent>, String> {
let sql = crate::rustbox_query_db_calendar_005!();
let sql =
crate::dbq!(db_calendar::FORMAT_005).replace("{EVENT_SELECT_COLS}", EVENT_SELECT_COLS);
let result = conn.query_row(&sql, params![source_uid], row_to_event);
match result {
Ok(e) => Ok(Some(e)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to find event by source_uid: {e}")),
}
}
pub fn get_calendar_settings(conn: &Connection) -> Result<CalendarSettings, String> {
conn.execute(crate::queries::sqlite::queries::db_calendar::Q_004, [])
conn.execute(crate::dbq!(db_calendar::Q_004), [])
.map_err(|e| format!("Failed to ensure calendar_settings row: {e}"))?;
let sql = crate::rustbox_query_db_calendar_006!();
let sql = crate::dbq!(db_calendar::FORMAT_006)
.replace("{SETTINGS_SELECT_COLS}", SETTINGS_SELECT_COLS);
conn.query_row(&sql, [], row_to_settings)
.map_err(|e| format!("Failed to get calendar settings: {e}"))
}
@@ -370,18 +375,18 @@ pub fn update_calendar_settings(
conn: &Connection,
data: &UpdateCalendarSettings,
) -> Result<CalendarSettings, String> {
conn.execute(crate::queries::sqlite::queries::db_calendar::Q_005, [])
conn.execute(crate::dbq!(db_calendar::Q_005), [])
.map_err(|e| format!("Failed to ensure calendar_settings row: {e}"))?;
let mut sets = Vec::new();
let mut values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
let mut values: Vec<DbValue> = Vec::new();
let mut idx = 1u32;
macro_rules! maybe_set {
($field:ident, $col:expr) => {
if let Some(ref v) = data.$field {
sets.push(format!("{} = ?{idx}", $col));
values.push(Box::new(v.clone()));
values.push(v.to_db_value());
idx += 1;
}
};
@@ -400,13 +405,13 @@ pub fn update_calendar_settings(
if let Some(v) = data.week_starts_on {
sets.push(format!("week_starts_on = ?{idx}"));
values.push(Box::new(v));
values.push(v.to_db_value());
idx += 1;
}
if let Some(v) = data.caldav_enabled {
sets.push(format!("caldav_enabled = ?{idx}"));
values.push(Box::new(v as i32));
values.push((v as i32).to_db_value());
#[allow(unused_assignments)]
{
idx += 1;
@@ -415,10 +420,11 @@ pub fn update_calendar_settings(
if !sets.is_empty() {
sets.push("updated_at = datetime('now')".to_string());
let sql = crate::rustbox_query_db_calendar_007!(sets.join(", "));
let params_refs: Vec<&dyn rusqlite::types::ToSql> =
values.iter().map(|b| b.as_ref()).collect();
conn.execute(&sql, params_refs.as_slice())
let sql = format!(
"UPDATE calendar_settings SET {} WHERE id = 1",
sets.join(", ")
);
conn.execute(&sql, values)
.map_err(|e| format!("Failed to update calendar settings: {e}"))?;
}
@@ -427,7 +433,7 @@ pub fn update_calendar_settings(
pub fn list_meeting_templates(conn: &Connection) -> Result<Vec<MeetingTemplate>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_calendar::Q_006)
.prepare(crate::dbq!(db_calendar::Q_006))
.map_err(|e| format!("Failed to prepare list_meeting_templates: {e}"))?;
let rows = stmt
.query_map([], row_to_template)
@@ -446,24 +452,21 @@ pub fn create_meeting_template(
url_template: &str,
icon: &str,
) -> Result<MeetingTemplate, String> {
conn.execute(
crate::queries::sqlite::queries::db_calendar::Q_007,
params![name, url_template, icon],
)
.map_err(|e| format!("Failed to create meeting template: {e}"))?;
let id = conn
.insert_id(
crate::dbq!(db_calendar::Q_007),
params![name, url_template, icon],
)
.map_err(|e| format!("Failed to create meeting template: {e}"))?;
let id = conn.last_insert_rowid();
let sql = crate::queries::sqlite::queries::db_calendar::Q_008;
let sql = crate::dbq!(db_calendar::Q_008);
conn.query_row(sql, params![id], row_to_template)
.map_err(|e| format!("Failed to read created template: {e}"))
}
pub fn delete_meeting_template(conn: &Connection, id: i64) -> Result<bool, String> {
let deleted = conn
.execute(
crate::queries::sqlite::queries::db_calendar::Q_009,
params![id],
)
.execute(crate::dbq!(db_calendar::Q_009), params![id])
.map_err(|e| format!("Failed to delete meeting template: {e}"))?;
Ok(deleted > 0)
}
+10 -19
View File
@@ -1,4 +1,4 @@
use rusqlite::{Connection, params};
use crate::db::pool::{Connection, params};
use serde::Serialize;
use crate::db::contacts::Contact;
@@ -13,17 +13,14 @@ pub struct ContactGroup {
}
pub fn create_group(conn: &Connection, id: &str, name: &str) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_contact_groups::Q_001,
params![id, name],
)
.map_err(|e| format!("Failed to create contact group: {e}"))?;
conn.execute(crate::dbq!(db_contact_groups::Q_001), params![id, name])
.map_err(|e| format!("Failed to create contact group: {e}"))?;
Ok(())
}
pub fn list_groups(conn: &Connection) -> Result<Vec<ContactGroup>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_contact_groups::Q_002)
.prepare(crate::dbq!(db_contact_groups::Q_002))
.map_err(|e| format!("Failed to prepare list_groups: {e}"))?;
let rows = stmt
@@ -47,27 +44,21 @@ pub fn list_groups(conn: &Connection) -> Result<Vec<ContactGroup>, String> {
pub fn update_group(conn: &Connection, id: &str, name: &str) -> Result<bool, String> {
let updated = conn
.execute(
crate::queries::sqlite::queries::db_contact_groups::Q_003,
params![name, id],
)
.execute(crate::dbq!(db_contact_groups::Q_003), params![name, id])
.map_err(|e| format!("Failed to update contact group: {e}"))?;
Ok(updated > 0)
}
pub fn delete_group(conn: &Connection, id: &str) -> Result<bool, String> {
let deleted = conn
.execute(
crate::queries::sqlite::queries::db_contact_groups::Q_004,
params![id],
)
.execute(crate::dbq!(db_contact_groups::Q_004), params![id])
.map_err(|e| format!("Failed to delete contact group: {e}"))?;
Ok(deleted > 0)
}
pub fn add_member(conn: &Connection, group_id: &str, contact_id: &str) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_contact_groups::Q_005,
crate::dbq!(db_contact_groups::Q_005),
params![group_id, contact_id],
)
.map_err(|e| format!("Failed to add group member: {e}"))?;
@@ -77,7 +68,7 @@ pub fn add_member(conn: &Connection, group_id: &str, contact_id: &str) -> Result
pub fn remove_member(conn: &Connection, group_id: &str, contact_id: &str) -> Result<bool, String> {
let deleted = conn
.execute(
crate::queries::sqlite::queries::db_contact_groups::Q_006,
crate::dbq!(db_contact_groups::Q_006),
params![group_id, contact_id],
)
.map_err(|e| format!("Failed to remove group member: {e}"))?;
@@ -86,7 +77,7 @@ pub fn remove_member(conn: &Connection, group_id: &str, contact_id: &str) -> Res
pub fn list_group_members(conn: &Connection, group_id: &str) -> Result<Vec<Contact>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_contact_groups::Q_007)
.prepare(crate::dbq!(db_contact_groups::Q_007))
.map_err(|e| format!("Failed to prepare list_group_members: {e}"))?;
let rows = stmt
@@ -131,7 +122,7 @@ pub fn list_contact_groups(
contact_id: &str,
) -> Result<Vec<ContactGroup>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_contact_groups::Q_008)
.prepare(crate::dbq!(db_contact_groups::Q_008))
.map_err(|e| format!("Failed to prepare list_contact_groups: {e}"))?;
let rows = stmt
+33 -43
View File
@@ -1,5 +1,5 @@
use crate::db::pool::{Connection, params};
use crate::queries::sqlite::queries::db_contacts::CONTACT_SELECT_COLS;
use rusqlite::{Connection, params};
use serde::Serialize;
use uuid::Uuid;
@@ -33,7 +33,7 @@ pub struct Contact {
pub has_photo: bool,
}
fn row_to_contact(row: &rusqlite::Row<'_>) -> rusqlite::Result<Contact> {
fn row_to_contact(row: &crate::db::pool::Row<'_>) -> Result<Contact, crate::db::pool::DbError> {
Ok(Contact {
id: row.get(0)?,
email: row.get(1)?,
@@ -60,14 +60,14 @@ fn row_to_contact(row: &rusqlite::Row<'_>) -> rusqlite::Result<Contact> {
}
fn escape_like(term: &str) -> String {
term.replace('\\', "\\\\")
.replace('%', "\\%")
.replace('_', "\\_")
term.replace('!', "!!")
.replace('%', "!%")
.replace('_', "!_")
}
pub fn upsert_contact(conn: &Connection, contact: &Contact) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_contacts::Q_001,
crate::dbq!(db_contacts::Q_001),
params![
contact.id,
contact.email,
@@ -95,23 +95,25 @@ pub fn upsert_contact(conn: &Connection, contact: &Contact) -> Result<(), String
}
pub fn get_contact(conn: &Connection, id: &str) -> Result<Option<Contact>, String> {
let sql = crate::rustbox_query_db_contacts_001!();
let sql =
crate::dbq!(db_contacts::FORMAT_001).replace("{CONTACT_SELECT_COLS}", CONTACT_SELECT_COLS);
let result = conn.query_row(&sql, params![id], row_to_contact);
match result {
Ok(c) => Ok(Some(c)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get contact: {e}")),
}
}
pub fn get_contact_by_email(conn: &Connection, email: &str) -> Result<Option<Contact>, String> {
let sql = crate::rustbox_query_db_contacts_002!();
let sql =
crate::dbq!(db_contacts::FORMAT_002).replace("{CONTACT_SELECT_COLS}", CONTACT_SELECT_COLS);
let result = conn.query_row(&sql, params![email], row_to_contact);
match result {
Ok(c) => Ok(Some(c)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get contact by email: {e}")),
}
}
@@ -126,13 +128,15 @@ pub fn list_contacts(
Some(q) if !q.is_empty() => {
let escaped = escape_like(q);
let pattern = format!("%{escaped}%");
let sql = crate::rustbox_query_db_contacts_003!();
let sql = crate::dbq!(db_contacts::FORMAT_003)
.replace("{CONTACT_SELECT_COLS}", CONTACT_SELECT_COLS);
drop(pattern);
(sql, Some(escaped))
}
_ => {
let sql = crate::rustbox_query_db_contacts_004!();
let sql = crate::dbq!(db_contacts::FORMAT_004)
.replace("{CONTACT_SELECT_COLS}", CONTACT_SELECT_COLS);
(sql, None)
}
};
@@ -170,10 +174,7 @@ pub fn list_contacts(
pub fn delete_contact(conn: &Connection, id: &str) -> Result<bool, String> {
let deleted = conn
.execute(
crate::queries::sqlite::queries::db_contacts::Q_002,
params![id],
)
.execute(crate::dbq!(db_contacts::Q_002), params![id])
.map_err(|e| format!("Failed to delete contact: {e}"))?;
Ok(deleted > 0)
}
@@ -182,7 +183,8 @@ pub fn search_contacts(conn: &Connection, query: &str, limit: u32) -> Result<Vec
let escaped = escape_like(query);
let pattern = format!("%{escaped}%");
let sql = crate::rustbox_query_db_contacts_005!();
let sql =
crate::dbq!(db_contacts::FORMAT_005).replace("{CONTACT_SELECT_COLS}", CONTACT_SELECT_COLS);
let mut stmt = conn
.prepare(&sql)
@@ -206,7 +208,7 @@ pub fn search_known_addresses(
let escaped = escape_like(query);
let pattern = format!("%{escaped}%");
let sql = crate::queries::sqlite::queries::db_contacts::Q_003;
let sql = crate::dbq!(db_contacts::Q_003);
let mut stmt = conn
.prepare(sql)
@@ -237,7 +239,7 @@ pub fn populate_known_addresses(
) -> Result<(), String> {
if !from_address.is_empty() {
conn.execute(
crate::queries::sqlite::queries::db_contacts::Q_004,
crate::dbq!(db_contacts::Q_004),
params![from_address, from_name],
)
.map_err(|e| format!("Failed to insert known address (from): {e}"))?;
@@ -262,11 +264,8 @@ fn insert_addresses_from_json(conn: &Connection, json: &str) -> Result<(), Strin
continue;
}
let name = entry.get("name").and_then(|v| v.as_str()).unwrap_or("");
conn.execute(
crate::queries::sqlite::queries::db_contacts::Q_005,
params![email, name],
)
.map_err(|e| format!("Failed to insert known address (json): {e}"))?;
conn.execute(crate::dbq!(db_contacts::Q_005), params![email, name])
.map_err(|e| format!("Failed to insert known address (json): {e}"))?;
}
Ok(())
@@ -274,22 +273,16 @@ fn insert_addresses_from_json(conn: &Connection, json: &str) -> Result<(), Strin
#[allow(dead_code)]
pub fn increment_contact_count(conn: &Connection, email: &str) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_contacts::Q_006,
params![email],
)
.map_err(|e| format!("Failed to increment contact count: {e}"))?;
conn.execute(crate::dbq!(db_contacts::Q_006), params![email])
.map_err(|e| format!("Failed to increment contact count: {e}"))?;
Ok(())
}
#[allow(dead_code)]
pub fn auto_add_contact(conn: &Connection, email: &str, name: &str) -> Result<(), String> {
let id = Uuid::new_v4().to_string();
conn.execute(
crate::queries::sqlite::queries::db_contacts::Q_007,
params![id, email, name],
)
.map_err(|e| format!("Failed to auto-add contact: {e}"))?;
conn.execute(crate::dbq!(db_contacts::Q_007), params![id, email, name])
.map_err(|e| format!("Failed to auto-add contact: {e}"))?;
Ok(())
}
@@ -300,7 +293,7 @@ pub fn set_contact_photo(
data: &[u8],
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_contacts::Q_008,
crate::dbq!(db_contacts::Q_008),
params![contact_id, content_type, data],
)
.map_err(|e| format!("Failed to save contact photo: {e}"))?;
@@ -312,23 +305,20 @@ pub fn get_contact_photo(
contact_id: &str,
) -> Result<Option<(String, Vec<u8>)>, String> {
match conn.query_row(
crate::queries::sqlite::queries::db_contacts::Q_009,
crate::dbq!(db_contacts::Q_009),
params![contact_id],
|row| Ok((row.get(0)?, row.get(1)?)),
) {
Ok(value) => Ok(Some(value)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to load contact photo: {e}")),
}
}
pub fn delete_contact_photo(conn: &Connection, contact_id: &str) -> Result<bool, String> {
conn.execute(
crate::queries::sqlite::queries::db_contacts::Q_010,
params![contact_id],
)
.map(|n| n > 0)
.map_err(|e| format!("Failed to delete contact photo: {e}"))
conn.execute(crate::dbq!(db_contacts::Q_010), params![contact_id])
.map(|n| n > 0)
.map_err(|e| format!("Failed to delete contact photo: {e}"))
}
#[cfg(test)]
+73 -77
View File
@@ -1,4 +1,4 @@
use rusqlite::Connection;
use crate::db::pool::{Connection, DbValue, ToDbValue};
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, Clone, Serialize)]
@@ -184,70 +184,66 @@ fn defaults() -> DisplayPreferences {
}
pub fn get_preferences(conn: &Connection) -> Result<DisplayPreferences, String> {
let result = conn.query_row(
crate::queries::sqlite::queries::db_display_preferences::Q_001,
[],
|row| {
Ok(DisplayPreferences {
density: row.get(0)?,
theme: row.get(1)?,
language: row.get(2)?,
compose_format: row.get(3)?,
deep_index: row.get::<_, i32>(4)? != 0,
animation_mode: row.get(5)?,
preview_pane: row.get(6)?,
ui_scale: row.get(7)?,
color_preset: row.get(8)?,
accent_color: row.get(9)?,
background_color: row.get(10)?,
panel_color: row.get(11)?,
text_color: row.get(12)?,
font_family: row.get(13)?,
font_size: row.get(14)?,
row_height: row.get(15)?,
folder_pane_width: row.get(16)?,
message_pane_width: row.get(17)?,
border_radius: row.get(18)?,
contrast: row.get(19)?,
show_avatars: row.get::<_, i32>(20)? != 0,
show_attachment_icons: row.get::<_, i32>(21)? != 0,
show_flags: row.get::<_, i32>(22)? != 0,
reduce_motion: row.get::<_, i32>(23)? != 0,
date_format: row.get(24)?,
time_format: row.get(25)?,
compose_autosave_ms: row.get(26)?,
default_priority: row.get(27)?,
request_mdn_default: row.get::<_, i32>(28)? != 0,
attachment_reminder: row.get::<_, i32>(29)? != 0,
icon_size: row.get(30)?,
toolbar_density: row.get(31)?,
sidebar_item_height: row.get(32)?,
reader_max_width: row.get(33)?,
composer_width: row.get(34)?,
selection_color: row.get(35)?,
unread_font_weight: row.get(36)?,
unread_badge_style: row.get(37)?,
compact_headers: row.get::<_, i32>(38)? != 0,
show_borders: row.get::<_, i32>(39)? != 0,
scrollbar_density: row.get(40)?,
monochrome: row.get::<_, i32>(41)? != 0,
folder_background: row.get(42)?,
message_background: row.get(43)?,
reader_background: row.get(44)?,
compose_font_family: row.get(45)?,
reply_position: row.get(46)?,
quote_style: row.get(47)?,
forward_mode: row.get(48)?,
spellcheck_enabled: row.get::<_, i32>(49)? != 0,
interface_skin: row.get(50)?,
preset_scale_unlocked: row.get::<_, i32>(51)? != 0,
updated_at: row.get(52)?,
})
},
);
let result = conn.query_row(crate::dbq!(db_display_preferences::Q_001), [], |row| {
Ok(DisplayPreferences {
density: row.get(0)?,
theme: row.get(1)?,
language: row.get(2)?,
compose_format: row.get(3)?,
deep_index: row.get::<_, i32>(4)? != 0,
animation_mode: row.get(5)?,
preview_pane: row.get(6)?,
ui_scale: row.get(7)?,
color_preset: row.get(8)?,
accent_color: row.get(9)?,
background_color: row.get(10)?,
panel_color: row.get(11)?,
text_color: row.get(12)?,
font_family: row.get(13)?,
font_size: row.get(14)?,
row_height: row.get(15)?,
folder_pane_width: row.get(16)?,
message_pane_width: row.get(17)?,
border_radius: row.get(18)?,
contrast: row.get(19)?,
show_avatars: row.get::<_, i32>(20)? != 0,
show_attachment_icons: row.get::<_, i32>(21)? != 0,
show_flags: row.get::<_, i32>(22)? != 0,
reduce_motion: row.get::<_, i32>(23)? != 0,
date_format: row.get(24)?,
time_format: row.get(25)?,
compose_autosave_ms: row.get(26)?,
default_priority: row.get(27)?,
request_mdn_default: row.get::<_, i32>(28)? != 0,
attachment_reminder: row.get::<_, i32>(29)? != 0,
icon_size: row.get(30)?,
toolbar_density: row.get(31)?,
sidebar_item_height: row.get(32)?,
reader_max_width: row.get(33)?,
composer_width: row.get(34)?,
selection_color: row.get(35)?,
unread_font_weight: row.get(36)?,
unread_badge_style: row.get(37)?,
compact_headers: row.get::<_, i32>(38)? != 0,
show_borders: row.get::<_, i32>(39)? != 0,
scrollbar_density: row.get(40)?,
monochrome: row.get::<_, i32>(41)? != 0,
folder_background: row.get(42)?,
message_background: row.get(43)?,
reader_background: row.get(44)?,
compose_font_family: row.get(45)?,
reply_position: row.get(46)?,
quote_style: row.get(47)?,
forward_mode: row.get(48)?,
spellcheck_enabled: row.get::<_, i32>(49)? != 0,
interface_skin: row.get(50)?,
preset_scale_unlocked: row.get::<_, i32>(51)? != 0,
updated_at: row.get(52)?,
})
});
match result {
Ok(value) => Ok(value),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(defaults()),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(defaults()),
Err(error) => Err(format!("Failed to get display preferences: {error}")),
}
}
@@ -274,20 +270,17 @@ pub fn update_preferences(
conn: &Connection,
data: &UpdateDisplayPreferences,
) -> Result<DisplayPreferences, String> {
conn.execute(
crate::queries::sqlite::queries::db_display_preferences::Q_002,
[],
)
.map_err(|e| format!("Failed to ensure preferences row: {e}"))?;
conn.execute(crate::dbq!(db_display_preferences::Q_002), [])
.map_err(|e| format!("Failed to ensure preferences row: {e}"))?;
let mut sets = Vec::new();
let mut values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
let mut values: Vec<DbValue> = Vec::new();
let mut idx = 1usize;
macro_rules! set_text {
($field:ident, $column:literal) => {
if let Some(ref value) = data.$field {
sets.push(format!(concat!($column, " = ?{}"), idx));
values.push(Box::new(value.clone()));
values.push(value.to_db_value());
idx += 1;
}
};
@@ -296,7 +289,7 @@ pub fn update_preferences(
($field:ident, $column:literal) => {
if let Some(value) = data.$field {
sets.push(format!(concat!($column, " = ?{}"), idx));
values.push(Box::new(value));
values.push(value.to_db_value());
idx += 1;
}
};
@@ -305,7 +298,7 @@ pub fn update_preferences(
($field:ident, $column:literal) => {
if let Some(value) = data.$field {
sets.push(format!(concat!($column, " = ?{}"), idx));
values.push(Box::new(value as i32));
values.push((value as i32).to_db_value());
idx += 1;
}
};
@@ -499,7 +492,7 @@ pub fn update_preferences(
set_bool!(deep_index, "deep_index");
if let Some(ref value) = data.animation_mode {
sets.push(format!("animation_mode = ?{}", idx));
values.push(Box::new(value.clone()));
values.push(value.to_db_value());
idx += 1;
}
set_text!(preview_pane, "preview_pane");
@@ -549,10 +542,13 @@ pub fn update_preferences(
return get_preferences(conn);
}
sets.push("updated_at = datetime('now')".to_string());
let sql = crate::rustbox_query_db_display_preferences_001!(sets.join(", "), idx);
values.push(Box::new(1_i32));
let refs: Vec<&dyn rusqlite::types::ToSql> = values.iter().map(|v| v.as_ref()).collect();
conn.execute(&sql, refs.as_slice())
let sql = format!(
"UPDATE display_preferences SET {} WHERE id = ?{}",
sets.join(", "),
idx
);
values.push(1_i32.to_db_value());
conn.execute(&sql, values)
.map_err(|e| format!("Failed to update display preferences: {e}"))?;
get_preferences(conn)
}
+34 -44
View File
@@ -1,4 +1,4 @@
use rusqlite::{Connection, params};
use crate::db::pool::{Connection, params};
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
@@ -72,7 +72,7 @@ pub fn upsert_draft(
) -> Result<(), String> {
validate_draft_id(id)?;
conn.execute(
crate::queries::sqlite::queries::db_drafts::Q_001,
crate::dbq!(db_drafts::Q_001),
params![
id,
to_addresses,
@@ -97,46 +97,42 @@ pub fn upsert_draft(
}
pub fn get_draft(conn: &Connection, id: &str) -> Result<Option<Draft>, String> {
let result = conn.query_row(
crate::queries::sqlite::queries::db_drafts::Q_002,
params![id],
|row| {
Ok(Draft {
id: row.get(0)?,
to_addresses: row.get(1)?,
cc_addresses: row.get(2)?,
bcc_addresses: row.get(3)?,
subject: row.get(4)?,
text_body: row.get(5)?,
html_body: row.get(6)?,
in_reply_to: row.get(7)?,
references_header: row.get(8)?,
request_mdn: row.get::<_, i64>(9)? != 0,
priority: row.get(10)?,
compose_format: row.get(11)?,
pgp_mode: row.get(12)?,
pgp_inline: row.get::<_, i64>(13)? != 0,
pgp_attach_public_key: row.get::<_, i64>(14)? != 0,
pgp_attach_recipient_keys: row.get::<_, i64>(15)? != 0,
remote_folder: row.get(16)?,
remote_uid: row.get(17)?,
remote_uid_validity: row.get(18)?,
created_at: row.get(19)?,
updated_at: row.get(20)?,
})
},
);
let result = conn.query_row(crate::dbq!(db_drafts::Q_002), params![id], |row| {
Ok(Draft {
id: row.get(0)?,
to_addresses: row.get(1)?,
cc_addresses: row.get(2)?,
bcc_addresses: row.get(3)?,
subject: row.get(4)?,
text_body: row.get(5)?,
html_body: row.get(6)?,
in_reply_to: row.get(7)?,
references_header: row.get(8)?,
request_mdn: row.get::<_, i64>(9)? != 0,
priority: row.get(10)?,
compose_format: row.get(11)?,
pgp_mode: row.get(12)?,
pgp_inline: row.get::<_, i64>(13)? != 0,
pgp_attach_public_key: row.get::<_, i64>(14)? != 0,
pgp_attach_recipient_keys: row.get::<_, i64>(15)? != 0,
remote_folder: row.get(16)?,
remote_uid: row.get(17)?,
remote_uid_validity: row.get(18)?,
created_at: row.get(19)?,
updated_at: row.get(20)?,
})
});
match result {
Ok(draft) => Ok(Some(draft)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get draft: {e}")),
}
}
pub fn list_drafts(conn: &Connection) -> Result<Vec<Draft>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_drafts::Q_003)
.prepare(crate::dbq!(db_drafts::Q_003))
.map_err(|e| format!("Failed to prepare list_drafts: {e}"))?;
let rows = stmt
@@ -183,7 +179,7 @@ pub fn set_remote_location(
) -> Result<(), String> {
let updated = conn
.execute(
crate::queries::sqlite::queries::db_drafts::Q_004,
crate::dbq!(db_drafts::Q_004),
params![id, folder, uid, uid_validity],
)
.map_err(|e| format!("Failed to update remote draft location: {e}"))?;
@@ -195,10 +191,7 @@ pub fn set_remote_location(
pub fn delete_draft(conn: &Connection, id: &str) -> Result<bool, String> {
let deleted = conn
.execute(
crate::queries::sqlite::queries::db_drafts::Q_005,
params![id],
)
.execute(crate::dbq!(db_drafts::Q_005), params![id])
.map_err(|e| format!("Failed to delete draft: {e}"))?;
Ok(deleted > 0)
}
@@ -213,7 +206,7 @@ pub fn add_draft_attachment(
file_path: &str,
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_drafts::Q_006,
crate::dbq!(db_drafts::Q_006),
params![id, draft_id, filename, content_type, size, file_path],
)
.map_err(|e| format!("Failed to add draft attachment: {e}"))?;
@@ -225,7 +218,7 @@ pub fn get_draft_attachments(
draft_id: &str,
) -> Result<Vec<DraftAttachment>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_drafts::Q_007)
.prepare(crate::dbq!(db_drafts::Q_007))
.map_err(|e| format!("Failed to prepare get_draft_attachments: {e}"))?;
let rows = stmt
@@ -251,10 +244,7 @@ pub fn get_draft_attachments(
pub fn delete_draft_attachment(conn: &Connection, id: &str) -> Result<bool, String> {
let deleted = conn
.execute(
crate::queries::sqlite::queries::db_drafts::Q_008,
params![id],
)
.execute(crate::dbq!(db_drafts::Q_008), params![id])
.map_err(|e| format!("Failed to delete draft attachment: {e}"))?;
Ok(deleted > 0)
}
+19 -32
View File
@@ -1,4 +1,4 @@
use rusqlite::{Connection, OptionalExtension, params};
use crate::db::pool::{Connection, OptionalExtension, params};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize)]
@@ -13,11 +13,9 @@ pub struct UpdateFolderPreferences {
}
fn get_override(conn: &Connection, folder: &str) -> Result<Option<bool>, String> {
conn.query_row(
crate::queries::sqlite::queries::db_folder_preferences::Q_001,
[folder],
|row| row.get::<_, Option<i32>>(0),
)
conn.query_row(crate::dbq!(db_folder_preferences::Q_001), [folder], |row| {
row.get::<_, Option<i32>>(0)
})
.optional()
.map(|value| value.flatten().map(|value| value != 0))
.map_err(|error| format!("Failed to read folder preferences: {error}"))
@@ -28,33 +26,27 @@ pub fn effective_flagged_on_top(conn: &Connection, folder: &str) -> Result<bool,
return Ok(value);
}
let global = conn
.query_row(
crate::queries::sqlite::queries::db_folder_preferences::Q_002,
[],
|row| row.get::<_, i32>(0),
)
.query_row(crate::dbq!(db_folder_preferences::Q_002), [], |row| {
row.get::<_, i32>(0)
})
.unwrap_or(0);
Ok(global != 0)
}
pub fn get_preferences(conn: &Connection, folder: &str) -> Result<FolderPreferences, String> {
let exists: bool = conn
.query_row(
crate::queries::sqlite::queries::db_folder_preferences::Q_003,
[folder],
|row| row.get(0),
)
.query_row(crate::dbq!(db_folder_preferences::Q_003), [folder], |row| {
row.get(0)
})
.map_err(|error| format!("Failed to validate folder: {error}"))?;
if !exists {
return Err(format!("Folder does not exist: {folder}"));
}
let flagged_on_top = get_override(conn, folder)?;
let effective_flagged_on_top = flagged_on_top.unwrap_or(
conn.query_row(
crate::queries::sqlite::queries::db_folder_preferences::Q_004,
[],
|row| row.get::<_, i32>(0),
)
conn.query_row(crate::dbq!(db_folder_preferences::Q_004), [], |row| {
row.get::<_, i32>(0)
})
.unwrap_or(0)
!= 0,
);
@@ -70,11 +62,9 @@ pub fn update_preferences(
data: &UpdateFolderPreferences,
) -> Result<FolderPreferences, String> {
let exists: bool = conn
.query_row(
crate::queries::sqlite::queries::db_folder_preferences::Q_005,
[folder],
|row| row.get(0),
)
.query_row(crate::dbq!(db_folder_preferences::Q_005), [folder], |row| {
row.get(0)
})
.map_err(|error| format!("Failed to validate folder: {error}"))?;
if !exists {
return Err(format!("Folder does not exist: {folder}"));
@@ -83,17 +73,14 @@ pub fn update_preferences(
match data.flagged_on_top {
Some(value) => {
conn.execute(
crate::queries::sqlite::queries::db_folder_preferences::Q_006,
crate::dbq!(db_folder_preferences::Q_006),
params![folder, value as i32],
)
.map_err(|error| format!("Failed to update folder preferences: {error}"))?;
}
None => {
conn.execute(
crate::queries::sqlite::queries::db_folder_preferences::Q_007,
[folder],
)
.map_err(|error| format!("Failed to clear folder preferences: {error}"))?;
conn.execute(crate::dbq!(db_folder_preferences::Q_007), [folder])
.map_err(|error| format!("Failed to clear folder preferences: {error}"))?;
}
}
+63 -107
View File
@@ -1,4 +1,4 @@
use rusqlite::{Connection, OptionalExtension, params};
use crate::db::pool::{Connection, DbValue, OptionalExtension, ToDbValue, params};
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
@@ -28,7 +28,7 @@ pub fn upsert_folder(
highest_modseq: u64,
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_folders::Q_001,
crate::dbq!(db_folders::Q_001),
params![
folder_name,
delimiter,
@@ -52,7 +52,7 @@ pub fn insert_folder_if_new(
flags_csv: &str,
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_folders::Q_002,
crate::dbq!(db_folders::Q_002),
params![folder_name, delimiter, flags_csv],
)
.map_err(|e| format!("Failed to insert folder: {e}"))?;
@@ -61,7 +61,7 @@ pub fn insert_folder_if_new(
pub fn get_all_folders(conn: &Connection) -> Result<Vec<CachedFolder>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_folders::Q_003)
.prepare(crate::dbq!(db_folders::Q_003))
.map_err(|e| format!("Failed to prepare get_all_folders: {e}"))?;
let rows = stmt
@@ -92,28 +92,28 @@ pub fn get_all_folders(conn: &Connection) -> Result<Vec<CachedFolder>, String> {
pub fn remove_stale_folders(conn: &Connection, current_names: &[String]) -> Result<usize, String> {
if current_names.is_empty() {
let deleted = conn
.execute(crate::queries::sqlite::queries::db_folders::Q_004, [])
.execute(crate::dbq!(db_folders::Q_004), [])
.map_err(|e| format!("Failed to delete all folders: {e}"))?;
return Ok(deleted);
}
let placeholders: Vec<String> = (1..=current_names.len()).map(|i| format!("?{i}")).collect();
let sql = crate::rustbox_query_db_folders_001!(placeholders.join(", "));
let sql = format!(
"DELETE FROM folders WHERE name NOT IN ({})",
placeholders.join(", ")
);
let params: Vec<&dyn rusqlite::types::ToSql> = current_names
.iter()
.map(|n| n as &dyn rusqlite::types::ToSql)
.collect();
let params: Vec<DbValue> = current_names.iter().map(ToDbValue::to_db_value).collect();
let deleted = conn
.execute(&sql, params.as_slice())
.execute(&sql, params)
.map_err(|e| format!("Failed to remove stale folders: {e}"))?;
Ok(deleted)
}
pub fn touch_all_folders(conn: &Connection) -> Result<(), String> {
conn.execute(crate::queries::sqlite::queries::db_folders::Q_005, [])
conn.execute(crate::dbq!(db_folders::Q_005), [])
.map_err(|e| format!("Failed to touch folders: {e}"))?;
Ok(())
}
@@ -125,7 +125,7 @@ pub fn update_folder_status(
total_count: u32,
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_folders::Q_006,
crate::dbq!(db_folders::Q_006),
params![uid_validity, total_count, name],
)
.map_err(|e| format!("Failed to update folder status: {e}"))?;
@@ -140,7 +140,7 @@ pub fn update_folder_sync_status(
highest_modseq: u64,
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_folders::Q_007,
crate::dbq!(db_folders::Q_007),
params![uid_validity, total_count, highest_modseq as i64, name],
)
.map_err(|e| format!("Failed to update folder sync status: {e}"))?;
@@ -150,35 +150,29 @@ pub fn update_folder_sync_status(
pub fn refresh_unread_count(conn: &Connection, folder_name: &str) -> Result<(), String> {
let unread: u32 = conn
.query_row(
crate::queries::sqlite::queries::db_folders::Q_008,
crate::dbq!(db_folders::Q_008),
params![folder_name],
|row| row.get(0),
)
.map_err(|e| format!("Failed to count unread messages: {e}"))?;
conn.execute(
crate::queries::sqlite::queries::db_folders::Q_009,
params![unread, folder_name],
)
.map_err(|e| format!("Failed to update unread_count: {e}"))?;
conn.execute(crate::dbq!(db_folders::Q_009), params![unread, folder_name])
.map_err(|e| format!("Failed to update unread_count: {e}"))?;
Ok(())
}
pub fn adjust_unread_count(conn: &Connection, folder_name: &str, delta: i32) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_folders::Q_010,
params![delta, folder_name],
)
.map_err(|e| format!("Failed to adjust unread_count: {e}"))?;
conn.execute(crate::dbq!(db_folders::Q_010), params![delta, folder_name])
.map_err(|e| format!("Failed to adjust unread_count: {e}"))?;
Ok(())
}
pub fn is_folder_cache_fresh(conn: &Connection, max_age_secs: u32) -> Result<bool, String> {
let fresh: bool = conn
.query_row(
crate::queries::sqlite::queries::db_folders::Q_011,
params![format!("-{max_age_secs} seconds")],
crate::dbq!(db_folders::Q_011),
params![max_age_secs],
|row| row.get(0),
)
.map_err(|e| format!("Failed to check folder cache freshness: {e}"))?;
@@ -192,8 +186,8 @@ pub fn is_folder_fresh(
) -> Result<bool, String> {
let fresh: bool = conn
.query_row(
crate::queries::sqlite::queries::db_folders::Q_012,
params![folder_name, format!("-{max_age_secs} seconds")],
crate::dbq!(db_folders::Q_012),
params![folder_name, max_age_secs],
|row| row.get(0),
)
.map_err(|e| format!("Failed to check folder freshness: {e}"))?;
@@ -206,7 +200,7 @@ pub fn is_folder_messages_invalidated(
) -> Result<bool, String> {
let invalidated: bool = conn
.query_row(
crate::queries::sqlite::queries::db_folders::Q_013,
crate::dbq!(db_folders::Q_013),
params![folder_name],
|row| row.get(0),
)
@@ -215,53 +209,40 @@ pub fn is_folder_messages_invalidated(
}
pub fn invalidate_folder_freshness(conn: &Connection, folder_name: &str) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_folders::Q_014,
params![folder_name],
)
.map_err(|e| format!("Failed to invalidate folder freshness: {e}"))?;
conn.execute(crate::dbq!(db_folders::Q_014), params![folder_name])
.map_err(|e| format!("Failed to invalidate folder freshness: {e}"))?;
Ok(())
}
pub fn get_folder(conn: &Connection, name: &str) -> Result<Option<CachedFolder>, String> {
let result = conn.query_row(
crate::queries::sqlite::queries::db_folders::Q_015,
params![name],
|row| {
let is_subscribed_int: i32 = row.get(4)?;
let highest_modseq_int: i64 = row.get(8)?;
Ok(CachedFolder {
name: row.get(0)?,
delimiter: row.get(1)?,
parent: row.get(2)?,
flags: row.get(3)?,
is_subscribed: is_subscribed_int != 0,
total_count: row.get(5)?,
unread_count: row.get(6)?,
uid_validity: row.get(7)?,
highest_modseq: highest_modseq_int as u64,
})
},
);
let result = conn.query_row(crate::dbq!(db_folders::Q_015), params![name], |row| {
let is_subscribed_int: i32 = row.get(4)?;
let highest_modseq_int: i64 = row.get(8)?;
Ok(CachedFolder {
name: row.get(0)?,
delimiter: row.get(1)?,
parent: row.get(2)?,
flags: row.get(3)?,
is_subscribed: is_subscribed_int != 0,
total_count: row.get(5)?,
unread_count: row.get(6)?,
uid_validity: row.get(7)?,
highest_modseq: highest_modseq_int as u64,
})
});
match result {
Ok(folder) => Ok(Some(folder)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get folder: {e}")),
}
}
pub fn delete_folder_and_messages(conn: &Connection, folder_name: &str) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_folders::Q_016,
params![folder_name],
)
.map_err(|e| format!("Failed to delete folder messages: {e}"))?;
conn.execute(
crate::queries::sqlite::queries::db_folders::Q_017,
params![folder_name],
)
.map_err(|e| format!("Failed to delete folder: {e}"))?;
conn.execute(crate::dbq!(db_folders::Q_016), params![folder_name])
.map_err(|e| format!("Failed to delete folder messages: {e}"))?;
conn.execute(crate::dbq!(db_folders::Q_017), params![folder_name])
.map_err(|e| format!("Failed to delete folder: {e}"))?;
Ok(())
}
@@ -273,44 +254,26 @@ pub fn rename_folder_in_cache(
let tx = conn
.unchecked_transaction()
.map_err(|e| format!("Failed to start folder rename transaction: {e}"))?;
tx.execute_batch(crate::queries::sqlite::queries::db_folders::Q_018)
tx.execute_batch(crate::dbq!(db_folders::Q_018))
.map_err(|e| format!("Failed to defer folder rename foreign keys: {e}"))?;
let inserted = tx
.execute(
crate::queries::sqlite::queries::db_folders::Q_019,
params![new_name, old_name],
)
.execute(crate::dbq!(db_folders::Q_019), params![new_name, old_name])
.map_err(|e| format!("Failed to create renamed folder cache entry: {e}"))?;
if inserted == 0 {
return Err(format!("Folder not found in cache: {old_name}"));
}
tx.execute(
crate::queries::sqlite::queries::db_folders::Q_020,
params![new_name, old_name],
)
.map_err(|e| format!("Failed to rename messages folder: {e}"))?;
tx.execute(
crate::queries::sqlite::queries::db_folders::Q_021,
params![new_name, old_name],
)
.map_err(|e| format!("Failed to rename message tag folder references: {e}"))?;
tx.execute(
crate::queries::sqlite::queries::db_folders::Q_022,
params![new_name, old_name],
)
.ok();
tx.execute(
crate::queries::sqlite::queries::db_folders::Q_023,
params![new_name, old_name],
)
.map_err(|e| format!("Failed to update child folder parent: {e}"))?;
tx.execute(
crate::queries::sqlite::queries::db_folders::Q_024,
params![old_name],
)
.map_err(|e| format!("Failed to remove old folder cache entry: {e}"))?;
tx.execute(crate::dbq!(db_folders::Q_020), params![new_name, old_name])
.map_err(|e| format!("Failed to rename messages folder: {e}"))?;
tx.execute(crate::dbq!(db_folders::Q_021), params![new_name, old_name])
.map_err(|e| format!("Failed to rename message tag folder references: {e}"))?;
tx.execute(crate::dbq!(db_folders::Q_022), params![new_name, old_name])
.ok();
tx.execute(crate::dbq!(db_folders::Q_023), params![new_name, old_name])
.map_err(|e| format!("Failed to update child folder parent: {e}"))?;
tx.execute(crate::dbq!(db_folders::Q_024), params![old_name])
.map_err(|e| format!("Failed to remove old folder cache entry: {e}"))?;
tx.commit()
.map_err(|e| format!("Failed to commit folder rename: {e}"))?;
Ok(())
@@ -320,11 +283,9 @@ pub fn find_folder_by_attribute(
conn: &Connection,
attribute: &str,
) -> Result<Option<String>, String> {
conn.query_row(
crate::queries::sqlite::queries::db_folders::Q_025,
[attribute],
|row| row.get(0),
)
conn.query_row(crate::dbq!(db_folders::Q_025), [attribute], |row| {
row.get(0)
})
.optional()
.map_err(|e| e.to_string())
}
@@ -442,17 +403,12 @@ mod tests {
fn sync_metadata_preserves_message_sync_timestamp() {
let conn = open_test_db();
insert_folder_if_new(&conn, "Sent", None, "\\Sent").unwrap();
conn.execute(crate::queries::sqlite::queries::db_folders::Q_026, [])
.unwrap();
conn.execute(crate::dbq!(db_folders::Q_026), []).unwrap();
update_folder_sync_status(&conn, "Sent", 1, 5, 42).unwrap();
let timestamp: String = conn
.query_row(
crate::queries::sqlite::queries::db_folders::Q_027,
[],
|row| row.get(0),
)
.query_row(crate::dbq!(db_folders::Q_027), [], |row| row.get(0))
.unwrap();
assert_eq!(timestamp, "2000-01-01 00:00:00");
assert!(!is_folder_fresh(&conn, "Sent", 30).unwrap());
+9 -12
View File
@@ -1,4 +1,4 @@
use rusqlite::{Connection, OptionalExtension, Transaction, TransactionBehavior};
use crate::db::pool::{Connection, OptionalExtension, Transaction};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) struct HeaderSync {
@@ -8,11 +8,9 @@ pub(crate) struct HeaderSync {
impl HeaderSync {
pub(crate) fn capture(conn: &Connection) -> Result<Self, String> {
let generation = conn
.query_row(
crate::queries::sqlite::queries::db_header_sync::Q_001,
[],
|row| row.get::<_, String>(0),
)
.query_row(crate::dbq!(db_header_sync::Q_001), [], |row| {
row.get::<_, String>(0)
})
.optional()
.map_err(|e| e.to_string())?
.map(|value| value.parse::<u64>().map_err(|e| e.to_string()))
@@ -21,18 +19,17 @@ impl HeaderSync {
Ok(Self { generation })
}
pub(crate) fn begin_write(self, conn: &Connection) -> Result<Transaction<'_>, String> {
let transaction = Transaction::new_unchecked(conn, TransactionBehavior::Immediate)
.map_err(|e| e.to_string())?;
if Self::capture(conn)?.generation != self.generation {
pub(crate) fn begin_write(self, conn: &Connection) -> Result<Transaction, String> {
let transaction = conn.unchecked_transaction().map_err(|e| e.to_string())?;
if Self::capture(&transaction)?.generation != self.generation {
return Err("Header sync superseded by cache removal; retry on next sync".into());
}
Ok(transaction)
}
}
pub(crate) fn invalidate(conn: &Transaction<'_>) -> Result<(), String> {
conn.execute(crate::queries::sqlite::queries::db_header_sync::Q_002, [])
pub(crate) fn invalidate(conn: &Transaction) -> Result<(), String> {
conn.execute(crate::dbq!(db_header_sync::Q_002), [])
.map_err(|e| e.to_string())?;
Ok(())
}
+47 -49
View File
@@ -1,5 +1,5 @@
use crate::db::pool::{Connection, DbValue, ToDbValue, params};
use crate::queries::sqlite::queries::db_identities::IDENTITY_SELECT_COLS;
use rusqlite::{Connection, params};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize)]
@@ -95,7 +95,7 @@ fn validate_signature_position(value: &str) -> Result<(), String> {
}
}
fn row_to_identity(row: &rusqlite::Row<'_>) -> rusqlite::Result<Identity> {
fn row_to_identity(row: &crate::db::pool::Row<'_>) -> Result<Identity, crate::db::pool::DbError> {
Ok(Identity {
id: row.get(0)?,
display_name: row.get(1)?,
@@ -120,7 +120,8 @@ fn row_to_identity(row: &rusqlite::Row<'_>) -> rusqlite::Result<Identity> {
}
pub fn list_identities(conn: &Connection) -> Result<Vec<Identity>, String> {
let sql = crate::rustbox_query_db_identities_001!();
let sql = crate::dbq!(db_identities::FORMAT_001)
.replace("{IDENTITY_SELECT_COLS}", IDENTITY_SELECT_COLS);
let mut stmt = conn
.prepare(&sql)
.map_err(|e| format!("Failed to prepare list_identities: {e}"))?;
@@ -132,20 +133,22 @@ pub fn list_identities(conn: &Connection) -> Result<Vec<Identity>, String> {
}
pub fn get_identity(conn: &Connection, id: i64) -> Result<Option<Identity>, String> {
let sql = crate::rustbox_query_db_identities_002!();
let sql = crate::dbq!(db_identities::FORMAT_002)
.replace("{IDENTITY_SELECT_COLS}", IDENTITY_SELECT_COLS);
match conn.query_row(&sql, params![id], row_to_identity) {
Ok(identity) => Ok(Some(identity)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get identity: {e}")),
}
}
#[allow(dead_code)]
pub fn get_default_identity(conn: &Connection) -> Result<Option<Identity>, String> {
let sql = crate::rustbox_query_db_identities_003!();
let sql = crate::dbq!(db_identities::FORMAT_003)
.replace("{IDENTITY_SELECT_COLS}", IDENTITY_SELECT_COLS);
match conn.query_row(&sql, [], row_to_identity) {
Ok(identity) => Ok(Some(identity)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get default identity: {e}")),
}
}
@@ -155,29 +158,29 @@ pub fn create_identity(conn: &Connection, data: &CreateIdentity) -> Result<Ident
if data.is_default {
clear_default(conn)?;
}
conn.execute(
crate::queries::sqlite::queries::db_identities::Q_001,
params![
data.display_name,
data.email,
data.signature_html,
data.is_default as i32,
data.reply_to,
data.bcc,
data.organization,
data.signature_text,
data.signature_new as i32,
data.signature_reply as i32,
data.signature_forward as i32,
data.signature_position,
data.pgp_fingerprint,
data.pgp_sign_default as i32,
data.pgp_encrypt_default as i32,
data.pgp_inline_default as i32
],
)
.map_err(|e| format!("Failed to create identity: {e}"))?;
let id = conn.last_insert_rowid();
let id = conn
.insert_id(
crate::dbq!(db_identities::Q_001),
params![
data.display_name,
data.email,
data.signature_html,
data.is_default as i32,
data.reply_to,
data.bcc,
data.organization,
data.signature_text,
data.signature_new as i32,
data.signature_reply as i32,
data.signature_forward as i32,
data.signature_position,
data.pgp_fingerprint,
data.pgp_sign_default as i32,
data.pgp_encrypt_default as i32,
data.pgp_inline_default as i32
],
)
.map_err(|e| format!("Failed to create identity: {e}"))?;
get_identity(conn, id)?.ok_or_else(|| "Failed to read back created identity".to_string())
}
@@ -196,13 +199,13 @@ pub fn update_identity(
validate_signature_position(value)?;
}
let mut sets = Vec::new();
let mut values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
let mut values: Vec<DbValue> = Vec::new();
let mut idx = 1usize;
macro_rules! text {
($field:ident,$column:literal) => {
if let Some(ref value) = data.$field {
sets.push(format!(concat!($column, " = ?{}"), idx));
values.push(Box::new(value.clone()));
values.push(value.to_db_value());
idx += 1;
}
};
@@ -211,7 +214,7 @@ pub fn update_identity(
($field:ident,$column:literal) => {
if let Some(value) = data.$field {
sets.push(format!(concat!($column, " = ?{}"), idx));
values.push(Box::new(value as i32));
values.push((value as i32).to_db_value());
idx += 1;
}
};
@@ -230,7 +233,7 @@ pub fn update_identity(
text!(signature_position, "signature_position");
if let Some(ref value) = data.pgp_fingerprint {
sets.push(format!("pgp_fingerprint = ?{}", idx));
values.push(Box::new(value.clone()));
values.push(value.to_db_value());
idx += 1;
}
boolean!(pgp_sign_default, "pgp_sign_default");
@@ -240,38 +243,33 @@ pub fn update_identity(
return get_identity(conn, id);
}
sets.push("updated_at = datetime('now')".to_string());
let sql = crate::rustbox_query_db_identities_004!(sets.join(", "), idx);
values.push(Box::new(id));
let refs: Vec<&dyn rusqlite::types::ToSql> =
values.iter().map(|value| value.as_ref()).collect();
conn.execute(&sql, refs.as_slice())
let sql = format!(
"UPDATE identities SET {} WHERE id = ?{}",
sets.join(", "),
idx
);
values.push(id.to_db_value());
conn.execute(&sql, values)
.map_err(|e| format!("Failed to update identity: {e}"))?;
get_identity(conn, id)
}
pub fn delete_identity(conn: &Connection, id: i64) -> Result<bool, String> {
Ok(conn
.execute(
crate::queries::sqlite::queries::db_identities::Q_002,
params![id],
)
.execute(crate::dbq!(db_identities::Q_002), params![id])
.map_err(|e| format!("Failed to delete identity: {e}"))?
> 0)
}
pub fn has_identities(conn: &Connection) -> Result<bool, String> {
let count: i64 = conn
.query_row(
crate::queries::sqlite::queries::db_identities::Q_003,
[],
|row| row.get(0),
)
.query_row(crate::dbq!(db_identities::Q_003), [], |row| row.get(0))
.map_err(|e| format!("Failed to count identities: {e}"))?;
Ok(count > 0)
}
fn clear_default(conn: &Connection) -> Result<(), String> {
conn.execute(crate::queries::sqlite::queries::db_identities::Q_004, [])
conn.execute(crate::dbq!(db_identities::Q_004), [])
.map_err(|e| format!("Failed to clear default identity: {e}"))?;
Ok(())
}
+20 -27
View File
@@ -1,4 +1,4 @@
use rusqlite::Connection;
use crate::db::pool::{Connection, params};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize)]
@@ -31,25 +31,21 @@ pub struct UpdateMailPreferences {
}
pub fn get_preferences(conn: &Connection) -> Result<MailPreferences, String> {
conn.query_row(
crate::queries::sqlite::queries::db_mail_preferences::Q_001,
[],
|row| {
Ok(MailPreferences {
messages_per_page: row.get::<_, u32>(0)?,
mark_read_delay_ms: row.get::<_, u64>(1)?,
remote_images: row.get(2)?,
preferred_body: row.get(3)?,
thread_mode: row.get(4)?,
collapse_quotes: row.get::<_, i32>(5)? != 0,
show_snippets: row.get::<_, i32>(6)? != 0,
default_sort: row.get(7)?,
flagged_on_top: row.get::<_, i32>(8)? != 0,
sync_imap_keywords: row.get::<_, i32>(9)? != 0,
updated_at: row.get(10)?,
})
},
)
conn.query_row(crate::dbq!(db_mail_preferences::Q_001), [], |row| {
Ok(MailPreferences {
messages_per_page: row.get::<_, u32>(0)?,
mark_read_delay_ms: row.get::<_, u64>(1)?,
remote_images: row.get(2)?,
preferred_body: row.get(3)?,
thread_mode: row.get(4)?,
collapse_quotes: row.get::<_, i32>(5)? != 0,
show_snippets: row.get::<_, i32>(6)? != 0,
default_sort: row.get(7)?,
flagged_on_top: row.get::<_, i32>(8)? != 0,
sync_imap_keywords: row.get::<_, i32>(9)? != 0,
updated_at: row.get(10)?,
})
})
.map_err(|error| format!("Failed to read mail preferences: {error}"))
}
@@ -57,11 +53,8 @@ pub fn update_preferences(
conn: &Connection,
data: &UpdateMailPreferences,
) -> Result<MailPreferences, String> {
conn.execute(
crate::queries::sqlite::queries::db_mail_preferences::Q_002,
[],
)
.map_err(|error| format!("Failed to initialize mail preferences: {error}"))?;
conn.execute(crate::dbq!(db_mail_preferences::Q_002), [])
.map_err(|error| format!("Failed to initialize mail preferences: {error}"))?;
if let Some(value) = data.messages_per_page
&& !(20..=200).contains(&value)
@@ -130,8 +123,8 @@ pub fn update_preferences(
.unwrap_or(current.sync_imap_keywords);
conn.execute(
crate::queries::sqlite::queries::db_mail_preferences::Q_003,
rusqlite::params![
crate::dbq!(db_mail_preferences::Q_003),
params![
messages_per_page,
mark_read_delay_ms,
remote_images,
+76 -74
View File
@@ -1,5 +1,7 @@
use crate::db::pool::{
Connection, DatabaseBackend, DbValue, ToDbValue, configured_backend, params,
};
use crate::queries::sqlite::queries::db_messages::MSG_SELECT_COLS;
use rusqlite::{Connection, params};
use serde::Serialize;
#[derive(Debug, Clone)]
@@ -35,7 +37,9 @@ pub struct CachedMessage {
pub reaction: Option<String>,
}
fn row_to_cached_message(row: &rusqlite::Row<'_>) -> rusqlite::Result<CachedMessage> {
fn row_to_cached_message(
row: &crate::db::pool::Row<'_>,
) -> Result<CachedMessage, crate::db::pool::DbError> {
let has_attachments_int: i32 = row.get(13)?;
Ok(CachedMessage {
uid: row.get(0)?,
@@ -79,7 +83,7 @@ pub fn upsert_message(
) -> Result<(), String> {
let date_epoch = parse_date_to_epoch(date);
conn.execute(
crate::queries::sqlite::queries::db_messages::Q_001,
crate::dbq!(db_messages::Q_001),
params![
uid,
folder,
@@ -105,37 +109,30 @@ pub fn upsert_message(
}
pub fn remove_after_mutation(conn: &Connection, folder: &str, uid: u32) -> Result<(), String> {
let transaction =
rusqlite::Transaction::new_unchecked(conn, rusqlite::TransactionBehavior::Immediate)
.map_err(|e| e.to_string())?;
delete_message(conn, folder, uid)?;
let transaction = conn.unchecked_transaction().map_err(|e| e.to_string())?;
delete_message(&transaction, folder, uid)?;
super::header_sync::invalidate(&transaction)?;
super::folders::invalidate_folder_freshness(conn, folder)?;
super::folders::invalidate_folder_freshness(&transaction, folder)?;
transaction.commit().map_err(|e| e.to_string())
}
pub fn delete_folder_messages(conn: &Connection, folder: &str) -> Result<usize, String> {
let deleted = conn
.execute(
crate::queries::sqlite::queries::db_messages::Q_002,
params![folder],
)
.execute(crate::dbq!(db_messages::Q_002), params![folder])
.map_err(|e| format!("Failed to delete folder messages: {e}"))?;
Ok(deleted)
}
pub fn max_uid(conn: &Connection, folder: &str) -> Result<u32, String> {
conn.query_row(
crate::queries::sqlite::queries::db_messages::Q_003,
params![folder],
|row| row.get(0),
)
conn.query_row(crate::dbq!(db_messages::Q_003), params![folder], |row| {
row.get(0)
})
.map_err(|e| format!("Failed to get max uid: {e}"))
}
pub fn list_index_source_messages(conn: &Connection) -> Result<Vec<IndexSourceMessage>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_messages::Q_004)
.prepare(crate::dbq!(db_messages::Q_004))
.map_err(|e| format!("Failed to prepare index rebuild query: {e}"))?;
let rows = stmt
@@ -253,7 +250,7 @@ pub fn get_messages(
per_page: u32,
) -> Result<Vec<CachedMessage>, String> {
let offset = page * per_page;
let sql = crate::rustbox_query_db_messages_001!();
let sql = crate::dbq!(db_messages::FORMAT_001).replace("{MSG_SELECT_COLS}", MSG_SELECT_COLS);
let mut stmt = conn
.prepare(&sql)
@@ -298,13 +295,20 @@ pub fn get_messages_sorted(
_ => return Err(format!("Unsupported message sort: {sort}")),
};
let order_by = if flagged_on_top {
format!("CASE WHEN instr(flags, '\\Flagged') > 0 THEN 0 ELSE 1 END ASC, {base_order}")
let flagged_test = match configured_backend() {
DatabaseBackend::Mysql => "LOCATE(CONCAT(CHAR(92), 'Flagged'), flags) > 0",
DatabaseBackend::Pgsql => "strpos(flags, '\\Flagged') > 0",
DatabaseBackend::Sqlite => "instr(flags, '\\Flagged') > 0",
};
format!("CASE WHEN {flagged_test} THEN 0 ELSE 1 END ASC, {base_order}")
} else {
base_order.to_string()
};
let offset = page * per_page;
let sql = crate::rustbox_query_db_messages_002!(&order_by);
let sql = crate::dbq!(db_messages::FORMAT_002)
.replace("{MSG_SELECT_COLS}", MSG_SELECT_COLS)
.replace("{order_by}", &order_by);
let mut stmt = conn
.prepare(&sql)
.map_err(|e| format!("Failed to prepare sorted get_messages: {e}"))?;
@@ -316,21 +320,15 @@ pub fn get_messages_sorted(
}
pub fn count_messages(conn: &Connection, folder: &str) -> Result<u32, String> {
conn.query_row(
crate::queries::sqlite::queries::db_messages::Q_005,
params![folder],
|row| row.get(0),
)
conn.query_row(crate::dbq!(db_messages::Q_005), params![folder], |row| {
row.get(0)
})
.map_err(|e| format!("Failed to count messages: {e}"))
}
pub fn count_all_messages(conn: &Connection) -> Result<usize, String> {
let count: i64 = conn
.query_row(
crate::queries::sqlite::queries::db_messages::Q_006,
[],
|row| row.get(0),
)
.query_row(crate::dbq!(db_messages::Q_006), [], |row| row.get(0))
.map_err(|e| format!("Failed to count cached messages: {e}"))?;
usize::try_from(count).map_err(|_| "Cached message count is out of range".to_string())
}
@@ -342,7 +340,7 @@ pub fn update_message_flags(
flags_csv: &str,
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_messages::Q_007,
crate::dbq!(db_messages::Q_007),
params![flags_csv, folder, uid],
)
.map_err(|e| format!("Failed to update message flags: {e}"))?;
@@ -361,7 +359,7 @@ pub fn cache_message_body(
email_theme: Option<i32>,
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_messages::Q_008,
crate::dbq!(db_messages::Q_008),
params![
html,
text,
@@ -383,7 +381,7 @@ pub fn update_email_theme(
email_theme: i32,
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_messages::Q_009,
crate::dbq!(db_messages::Q_009),
params![email_theme, folder, uid],
)
.map_err(|e| format!("Failed to update email theme: {e}"))?;
@@ -404,7 +402,7 @@ pub fn get_cached_body(
uid: u32,
) -> Result<Option<CachedBody>, String> {
let result = conn.query_row(
crate::queries::sqlite::queries::db_messages::Q_010,
crate::dbq!(db_messages::Q_010),
params![folder, uid],
|row| {
let cached: i32 = row.get(0)?;
@@ -424,7 +422,7 @@ pub fn get_cached_body(
match result {
Ok(body) => Ok(body),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get cached body: {e}")),
}
}
@@ -434,29 +432,26 @@ pub fn get_single_message(
folder: &str,
uid: u32,
) -> Result<Option<CachedMessage>, String> {
let sql = crate::rustbox_query_db_messages_003!();
let sql = crate::dbq!(db_messages::FORMAT_003).replace("{MSG_SELECT_COLS}", MSG_SELECT_COLS);
let result = conn.query_row(&sql, params![folder, uid], row_to_cached_message);
match result {
Ok(msg) => Ok(Some(msg)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get single message: {e}")),
}
}
pub fn delete_message(conn: &Connection, folder: &str, uid: u32) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_messages::Q_011,
params![folder, uid],
)
.map_err(|e| format!("Failed to delete message: {e}"))?;
conn.execute(crate::dbq!(db_messages::Q_011), params![folder, uid])
.map_err(|e| format!("Failed to delete message: {e}"))?;
Ok(())
}
pub fn get_unindexed_messages(conn: &Connection, limit: u32) -> Result<Vec<(String, u32)>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_messages::Q_012)
.prepare(crate::dbq!(db_messages::Q_012))
.map_err(|e| format!("Failed to prepare get_unindexed_messages: {e}"))?;
let rows = stmt
@@ -477,7 +472,7 @@ pub fn get_all_uids_and_flags(
folder: &str,
) -> Result<Vec<(u32, String)>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_messages::Q_013)
.prepare(crate::dbq!(db_messages::Q_013))
.map_err(|e| format!("Failed to prepare get_all_uids_and_flags: {e}"))?;
let rows = stmt
.query_map(params![folder], |row| {
@@ -496,9 +491,12 @@ pub fn get_thread_messages(
conn: &Connection,
target_message_id: &str,
) -> Result<Vec<CachedMessage>, String> {
let escaped_target = target_message_id.replace("%", "\\%").replace("_", "\\_");
let escaped_target = target_message_id
.replace('!', "!!")
.replace("%", "!%")
.replace("_", "!_");
let like_pattern = format!("%{}%", escaped_target);
let sql = crate::rustbox_query_db_messages_004!();
let sql = crate::dbq!(db_messages::FORMAT_004).replace("{MSG_SELECT_COLS}", MSG_SELECT_COLS);
let mut stmt = conn
.prepare(&sql)
@@ -606,7 +604,7 @@ pub fn search_messages_sqlite(
limit: usize,
) -> Result<Vec<CachedMessage>, String> {
let mut conditions = Vec::new();
let mut param_values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
let mut param_values: Vec<DbValue> = Vec::new();
let mut idx = 1u32;
conditions.push("LOWER(folder) NOT IN ('trash', 'spam', 'junk')".to_string());
@@ -616,19 +614,19 @@ pub fn search_messages_sqlite(
conditions.push(format!(
"(subject LIKE ?{idx} OR from_name LIKE ?{idx} OR from_address LIKE ?{idx} OR to_addresses LIKE ?{idx})"
));
param_values.push(Box::new(pattern));
param_values.push(pattern.to_db_value());
idx += 1;
}
if let Some(subject) = subject.filter(|value| !value.trim().is_empty()) {
conditions.push(format!("subject LIKE ?{idx}"));
param_values.push(Box::new(format!("%{subject}%")));
param_values.push(format!("%{subject}%").to_db_value());
idx += 1;
}
if let Some(f) = folder {
conditions.push(format!("folder = ?{idx}"));
param_values.push(Box::new(f.to_string()));
param_values.push(f.to_db_value());
idx += 1;
}
@@ -643,47 +641,57 @@ pub fn search_messages_sqlite(
} else {
format!("%{f}%")
};
param_values.push(Box::new(val));
param_values.push(val.to_db_value());
idx += 1;
}
if let Some(t) = to {
conditions.push(format!("to_addresses LIKE ?{idx}"));
param_values.push(Box::new(format!("%{t}%")));
param_values.push(format!("%{t}%").to_db_value());
idx += 1;
}
if let Some(df) = date_from {
conditions.push(format!("date_epoch >= ?{idx}"));
param_values.push(Box::new(df));
param_values.push(df.to_db_value());
idx += 1;
}
if let Some(dt) = date_to {
conditions.push(format!("date_epoch <= ?{idx}"));
param_values.push(Box::new(dt));
param_values.push(dt.to_db_value());
idx += 1;
}
if let Some(ha) = has_attachment {
conditions.push(format!("has_attachments = ?{idx}"));
param_values.push(Box::new(ha as i32));
param_values.push((ha as i32).to_db_value());
idx += 1;
}
if let Some(want_unread) = unread {
let seen = match configured_backend() {
DatabaseBackend::Mysql => "LOCATE(CONCAT(CHAR(92), 'Seen'), flags)",
DatabaseBackend::Pgsql => "strpos(flags, '\\Seen')",
DatabaseBackend::Sqlite => "instr(flags, '\\Seen')",
};
conditions.push(if want_unread {
"flags NOT LIKE '%\\Seen%'".to_string()
format!("{seen} = 0")
} else {
"flags LIKE '%\\Seen%'".to_string()
format!("{seen} > 0")
});
}
if let Some(want_flagged) = flagged {
let flagged = match configured_backend() {
DatabaseBackend::Mysql => "LOCATE(CONCAT(CHAR(92), 'Flagged'), flags)",
DatabaseBackend::Pgsql => "strpos(flags, '\\Flagged')",
DatabaseBackend::Sqlite => "instr(flags, '\\Flagged')",
};
conditions.push(if want_flagged {
"flags LIKE '%\\Flagged%'".to_string()
format!("{flagged} > 0")
} else {
"flags NOT LIKE '%\\Flagged%'".to_string()
format!("{flagged} = 0")
});
}
@@ -695,21 +703,19 @@ pub fn search_messages_sqlite(
format!("WHERE {}", conditions.join(" AND "))
};
let sql = crate::rustbox_query_db_messages_005!(&where_clause, param_values.len() + 1);
let sql = format!(
"SELECT {MSG_SELECT_COLS} FROM messages {where_clause} ORDER BY date_epoch DESC LIMIT ?{}",
param_values.len() + 1
);
param_values.push(Box::new(limit as i64));
let params_refs: Vec<&dyn rusqlite::types::ToSql> =
param_values.iter().map(|b| b.as_ref()).collect();
param_values.push((limit as i64).to_db_value());
let mut stmt = conn
.prepare(&sql)
.map_err(|e| format!("Failed to prepare search query: {e}"))?;
let rows = stmt
.query_map(rusqlite::params_from_iter(params_refs), |row| {
row_to_cached_message(row)
})
.query_map(param_values, row_to_cached_message)
.map_err(|e| format!("Failed to execute search query: {e}"))?;
let mut results = Vec::new();
@@ -720,12 +726,8 @@ pub fn search_messages_sqlite(
}
pub fn cached_storage_bytes(conn: &Connection) -> Result<Option<u64>, String> {
conn.query_row(
crate::queries::sqlite::queries::db_messages::Q_014,
[],
|row| row.get(0),
)
.map_err(|e| e.to_string())
conn.query_row(crate::dbq!(db_messages::Q_014), [], |row| row.get(0))
.map_err(|e| e.to_string())
}
#[cfg(test)]
+32 -41
View File
@@ -1,4 +1,4 @@
use rusqlite::Connection;
use crate::db::pool::{Connection, DbValue, ToDbValue};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize)]
@@ -23,28 +23,24 @@ pub struct UpdateNotificationPreferences {
}
pub fn get_preferences(conn: &Connection) -> Result<NotificationPreferences, String> {
let result = conn.query_row(
crate::queries::sqlite::queries::db_notification_preferences::Q_001,
[],
|row| {
let enabled_int: i32 = row.get(0)?;
let sound_int: i32 = row.get(1)?;
let folders_json: String = row.get(2)?;
let dnd_enabled_int: i32 = row.get(3)?;
let dnd_start: String = row.get(4)?;
let dnd_end: String = row.get(5)?;
let updated_at: String = row.get(6)?;
Ok((
enabled_int,
sound_int,
folders_json,
dnd_enabled_int,
dnd_start,
dnd_end,
updated_at,
))
},
);
let result = conn.query_row(crate::dbq!(db_notification_preferences::Q_001), [], |row| {
let enabled_int: i32 = row.get(0)?;
let sound_int: i32 = row.get(1)?;
let folders_json: String = row.get(2)?;
let dnd_enabled_int: i32 = row.get(3)?;
let dnd_start: String = row.get(4)?;
let dnd_end: String = row.get(5)?;
let updated_at: String = row.get(6)?;
Ok((
enabled_int,
sound_int,
folders_json,
dnd_enabled_int,
dnd_start,
dnd_end,
updated_at,
))
});
match result {
Ok((
@@ -68,7 +64,7 @@ pub fn get_preferences(conn: &Connection) -> Result<NotificationPreferences, Str
updated_at,
})
}
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(NotificationPreferences {
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(NotificationPreferences {
enabled: true,
sound: false,
folders: vec!["INBOX".to_string()],
@@ -85,36 +81,33 @@ pub fn update_preferences(
conn: &Connection,
data: &UpdateNotificationPreferences,
) -> Result<NotificationPreferences, String> {
conn.execute(
crate::queries::sqlite::queries::db_notification_preferences::Q_002,
[],
)
.map_err(|e| format!("Failed to ensure preferences row: {e}"))?;
conn.execute(crate::dbq!(db_notification_preferences::Q_002), [])
.map_err(|e| format!("Failed to ensure preferences row: {e}"))?;
let mut sets = Vec::new();
let mut values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
let mut values: Vec<DbValue> = Vec::new();
let mut idx = 1;
if let Some(enabled) = data.enabled {
sets.push(format!("enabled = ?{idx}"));
values.push(Box::new(enabled as i32));
values.push((enabled as i32).to_db_value());
idx += 1;
}
if let Some(sound) = data.sound {
sets.push(format!("sound = ?{idx}"));
values.push(Box::new(sound as i32));
values.push((sound as i32).to_db_value());
idx += 1;
}
if let Some(ref folders) = data.folders {
let folders_json = serde_json::to_string(folders)
.map_err(|e| format!("Failed to serialize folders: {e}"))?;
sets.push(format!("folders = ?{idx}"));
values.push(Box::new(folders_json));
values.push(folders_json.to_db_value());
idx += 1;
}
if let Some(dnd_enabled) = data.dnd_enabled {
sets.push(format!("dnd_enabled = ?{idx}"));
values.push(Box::new(dnd_enabled as i32));
values.push((dnd_enabled as i32).to_db_value());
idx += 1;
}
if let Some(ref dnd_start) = data.dnd_start {
@@ -122,7 +115,7 @@ pub fn update_preferences(
return Err("Invalid DND start time".to_string());
}
sets.push(format!("dnd_start = ?{idx}"));
values.push(Box::new(dnd_start.clone()));
values.push(dnd_start.to_db_value());
idx += 1;
}
if let Some(ref dnd_end) = data.dnd_end {
@@ -130,7 +123,7 @@ pub fn update_preferences(
return Err("Invalid DND end time".to_string());
}
sets.push(format!("dnd_end = ?{idx}"));
values.push(Box::new(dnd_end.clone()));
values.push(dnd_end.to_db_value());
idx += 1;
}
@@ -140,12 +133,10 @@ pub fn update_preferences(
sets.push("updated_at = datetime('now')".to_string());
let set_clause = sets.join(", ");
let sql = crate::rustbox_query_db_notification_preferences_001!(&set_clause, idx);
values.push(Box::new(1_i32));
let sql = format!("UPDATE notification_preferences SET {set_clause} WHERE id = ?{idx}");
values.push(1_i32.to_db_value());
let params_refs: Vec<&dyn rusqlite::types::ToSql> = values.iter().map(|v| v.as_ref()).collect();
conn.execute(&sql, params_refs.as_slice())
conn.execute(&sql, values)
.map_err(|e| format!("Failed to update notification preferences: {e}"))?;
get_preferences(conn)
+14 -14
View File
@@ -1,5 +1,5 @@
use crate::db::pool::{Connection, params};
use crate::queries::sqlite::queries::db_pgp::KEY_COLS;
use rusqlite::{Connection, params};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize)]
@@ -31,7 +31,7 @@ pub struct PgpPreferences {
pub require_trusted_encryption: bool,
}
fn row_to_key(row: &rusqlite::Row<'_>) -> rusqlite::Result<PgpKeyInternal> {
fn row_to_key(row: &crate::db::pool::Row<'_>) -> Result<PgpKeyInternal, crate::db::pool::DbError> {
Ok(PgpKeyInternal {
record: PgpKeyRecord {
fingerprint: row.get(0)?,
@@ -51,7 +51,7 @@ fn row_to_key(row: &rusqlite::Row<'_>) -> rusqlite::Result<PgpKeyInternal> {
pub fn list_keys(conn: &Connection) -> Result<Vec<PgpKeyRecord>, String> {
let mut stmt = conn
.prepare(&crate::rustbox_query_db_pgp_001!())
.prepare(&crate::dbq!(db_pgp::FORMAT_001).replace("{KEY_COLS}", KEY_COLS))
.map_err(|e| format!("Failed to prepare PGP key list: {e}"))?;
let rows = stmt
.query_map([], row_to_key)
@@ -65,17 +65,17 @@ pub fn list_keys(conn: &Connection) -> Result<Vec<PgpKeyRecord>, String> {
pub fn get_key(conn: &Connection, fingerprint: &str) -> Result<Option<PgpKeyInternal>, String> {
let normalized = normalize_fingerprint(fingerprint);
let sql = crate::rustbox_query_db_pgp_002!();
let sql = crate::dbq!(db_pgp::FORMAT_002).replace("{KEY_COLS}", KEY_COLS);
match conn.query_row(&sql, params![normalized], row_to_key) {
Ok(v) => Ok(Some(v)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get PGP key: {e}")),
}
}
pub fn find_keys_by_email(conn: &Connection, email: &str) -> Result<Vec<PgpKeyRecord>, String> {
let mut stmt = conn
.prepare(&crate::rustbox_query_db_pgp_003!())
.prepare(&crate::dbq!(db_pgp::FORMAT_003).replace("{KEY_COLS}", KEY_COLS))
.map_err(|e| format!("Failed to prepare PGP email lookup: {e}"))?;
let rows = stmt
.query_map(params![email.trim()], row_to_key)
@@ -93,7 +93,7 @@ pub fn upsert_key(
secret_key_path: Option<&str>,
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_pgp::Q_001,
crate::dbq!(db_pgp::Q_001),
params![
record.fingerprint,
record.key_id,
@@ -113,7 +113,7 @@ pub fn upsert_key(
pub fn delete_key(conn: &Connection, fingerprint: &str) -> Result<bool, String> {
Ok(conn
.execute(
crate::queries::sqlite::queries::db_pgp::Q_002,
crate::dbq!(db_pgp::Q_002),
params![normalize_fingerprint(fingerprint)],
)
.map_err(|e| format!("Failed to delete PGP key: {e}"))?
@@ -126,7 +126,7 @@ pub fn set_trust(conn: &Connection, fingerprint: &str, trust: &str) -> Result<bo
}
Ok(conn
.execute(
crate::queries::sqlite::queries::db_pgp::Q_003,
crate::dbq!(db_pgp::Q_003),
params![normalize_fingerprint(fingerprint), trust],
)
.map_err(|e| format!("Failed to update PGP trust: {e}"))?
@@ -134,7 +134,7 @@ pub fn set_trust(conn: &Connection, fingerprint: &str, trust: &str) -> Result<bo
}
pub fn get_preferences(conn: &Connection) -> Result<PgpPreferences, String> {
conn.query_row(crate::queries::sqlite::queries::db_pgp::Q_004, [], |row| {
conn.query_row(crate::dbq!(db_pgp::Q_004), [], |row| {
Ok(PgpPreferences {
wkd_enabled: row.get::<_, i32>(0)? != 0,
hkp_enabled: row.get::<_, i32>(1)? != 0,
@@ -149,7 +149,7 @@ pub fn update_preferences(
prefs: &PgpPreferences,
) -> Result<PgpPreferences, String> {
conn.execute(
crate::queries::sqlite::queries::db_pgp::Q_005,
crate::dbq!(db_pgp::Q_005),
params![
prefs.wkd_enabled as i32,
prefs.hkp_enabled as i32,
@@ -172,17 +172,17 @@ pub fn find_key_internal_by_email(
conn: &Connection,
email: &str,
) -> Result<Option<PgpKeyInternal>, String> {
let sql = crate::rustbox_query_db_pgp_004!();
let sql = crate::dbq!(db_pgp::FORMAT_004).replace("{KEY_COLS}", KEY_COLS);
match conn.query_row(&sql, params![email.trim()], row_to_key) {
Ok(value) => Ok(Some(value)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to query PGP key by email: {e}")),
}
}
pub fn list_secret_keys(conn: &Connection) -> Result<Vec<PgpKeyInternal>, String> {
let mut stmt = conn
.prepare(&crate::rustbox_query_db_pgp_005!())
.prepare(&crate::dbq!(db_pgp::FORMAT_005).replace("{KEY_COLS}", KEY_COLS))
.map_err(|e| format!("Failed to prepare PGP secret key list: {e}"))?;
let rows = stmt
.query_map([], row_to_key)
+2361 -290
View File
File diff suppressed because it is too large. Load diff
+20 -23
View File
@@ -1,4 +1,4 @@
use rusqlite::{Connection, params};
use crate::db::pool::{Connection, params};
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
@@ -11,7 +11,7 @@ pub struct ResponseTemplate {
pub updated_at: String,
}
fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<ResponseTemplate> {
fn from_row(row: &crate::db::pool::Row<'_>) -> Result<ResponseTemplate, crate::db::pool::DbError> {
Ok(ResponseTemplate {
id: row.get(0)?,
name: row.get(1)?,
@@ -24,7 +24,7 @@ fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<ResponseTemplate> {
pub fn list(conn: &Connection) -> Result<Vec<ResponseTemplate>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_response_templates::Q_001)
.prepare(crate::dbq!(db_response_templates::Q_001))
.map_err(|e| format!("Failed to prepare response templates: {e}"))?;
let rows = stmt
.query_map([], from_row)
@@ -35,12 +35,12 @@ pub fn list(conn: &Connection) -> Result<Vec<ResponseTemplate>, String> {
pub fn get(conn: &Connection, id: i64) -> Result<Option<ResponseTemplate>, String> {
match conn.query_row(
crate::queries::sqlite::queries::db_response_templates::Q_002,
crate::dbq!(db_response_templates::Q_002),
params![id],
from_row,
) {
Ok(value) => Ok(Some(value)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get response template: {e}")),
}
}
@@ -66,19 +66,19 @@ pub fn create(
"text" | "" => "text",
_ => return Err("Invalid response template format".to_string()),
};
conn.execute(
crate::queries::sqlite::queries::db_response_templates::Q_003,
params![name, body, format],
)
.map_err(|e| {
if e.to_string().contains("UNIQUE constraint failed") {
"A response template with this name already exists".to_string()
} else {
format!("Failed to create response template: {e}")
}
})?;
get(conn, conn.last_insert_rowid())?
.ok_or_else(|| "Failed to read response template".to_string())
let id = conn
.insert_id(
crate::dbq!(db_response_templates::Q_003),
params![name, body, format],
)
.map_err(|e| {
if e.to_string().contains("UNIQUE constraint failed") {
"A response template with this name already exists".to_string()
} else {
format!("Failed to create response template: {e}")
}
})?;
get(conn, id)?.ok_or_else(|| "Failed to read response template".to_string())
}
pub fn update(
@@ -105,7 +105,7 @@ pub fn update(
};
let changed = conn
.execute(
crate::queries::sqlite::queries::db_response_templates::Q_004,
crate::dbq!(db_response_templates::Q_004),
params![name, body, format, id],
)
.map_err(|e| {
@@ -123,10 +123,7 @@ pub fn update(
pub fn delete(conn: &Connection, id: i64) -> Result<bool, String> {
Ok(conn
.execute(
crate::queries::sqlite::queries::db_response_templates::Q_005,
params![id],
)
.execute(crate::dbq!(db_response_templates::Q_005), params![id])
.map_err(|e| format!("Failed to delete response template: {e}"))?
> 0)
}
+13 -16
View File
@@ -1,4 +1,4 @@
use rusqlite::{Connection, params};
use crate::db::pool::{Connection, params};
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
@@ -10,7 +10,7 @@ pub struct SavedSearch {
pub updated_at: String,
}
fn row_to_saved(row: &rusqlite::Row<'_>) -> rusqlite::Result<SavedSearch> {
fn row_to_saved(row: &crate::db::pool::Row<'_>) -> Result<SavedSearch, crate::db::pool::DbError> {
Ok(SavedSearch {
id: row.get(0)?,
name: row.get(1)?,
@@ -22,7 +22,7 @@ fn row_to_saved(row: &rusqlite::Row<'_>) -> rusqlite::Result<SavedSearch> {
pub fn list(conn: &Connection) -> Result<Vec<SavedSearch>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_saved_searches::Q_001)
.prepare(crate::dbq!(db_saved_searches::Q_001))
.map_err(|e| format!("Failed to prepare saved searches: {e}"))?;
let rows = stmt
.query_map([], row_to_saved)
@@ -37,12 +37,12 @@ pub fn create(conn: &Connection, name: &str, query_string: &str) -> Result<Saved
if name.is_empty() || query_string.is_empty() {
return Err("Saved search name and query are required".to_string());
}
conn.execute(
crate::queries::sqlite::queries::db_saved_searches::Q_002,
params![name, query_string],
)
.map_err(|e| format!("Failed to save search: {e}"))?;
let id = conn.last_insert_rowid();
let id = conn
.insert_id(
crate::dbq!(db_saved_searches::Q_002),
params![name, query_string],
)
.map_err(|e| format!("Failed to save search: {e}"))?;
get(conn, id)?.ok_or_else(|| "Failed to read saved search".to_string())
}
@@ -59,7 +59,7 @@ pub fn update(
}
let changed = conn
.execute(
crate::queries::sqlite::queries::db_saved_searches::Q_003,
crate::dbq!(db_saved_searches::Q_003),
params![name, query_string, id],
)
.map_err(|e| format!("Failed to update saved search: {e}"))?;
@@ -71,22 +71,19 @@ pub fn update(
pub fn get(conn: &Connection, id: i64) -> Result<Option<SavedSearch>, String> {
match conn.query_row(
crate::queries::sqlite::queries::db_saved_searches::Q_004,
crate::dbq!(db_saved_searches::Q_004),
params![id],
row_to_saved,
) {
Ok(value) => Ok(Some(value)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(crate::db::pool::DbError::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(format!("Failed to get saved search: {e}")),
}
}
pub fn delete(conn: &Connection, id: i64) -> Result<bool, String> {
Ok(conn
.execute(
crate::queries::sqlite::queries::db_saved_searches::Q_005,
params![id],
)
.execute(crate::dbq!(db_saved_searches::Q_005), params![id])
.map_err(|e| format!("Failed to delete saved search: {e}"))?
> 0)
}
+13 -23
View File
@@ -1,4 +1,4 @@
use rusqlite::{Connection, OptionalExtension, params};
use crate::db::pool::{Connection, OptionalExtension, params};
use serde::{Deserialize, Serialize};
const ROLES: &[&str] = &["sent", "drafts", "trash", "junk", "archive"];
@@ -20,11 +20,9 @@ pub fn get_role(conn: &Connection, role: &str) -> Result<Option<String>, String>
if !valid_role(role) {
return Err(format!("Invalid special folder role: {role}"));
}
conn.query_row(
crate::queries::sqlite::queries::db_special_folders::Q_001,
[role],
|row| row.get(0),
)
conn.query_row(crate::dbq!(db_special_folders::Q_001), [role], |row| {
row.get(0)
})
.optional()
.map_err(|e| format!("Failed to read special folder {role}: {e}"))
}
@@ -46,27 +44,22 @@ pub fn set_role(conn: &Connection, role: &str, folder_name: Option<&str>) -> Res
match folder_name.map(str::trim).filter(|value| !value.is_empty()) {
Some(folder) => {
let exists: bool = conn
.query_row(
crate::queries::sqlite::queries::db_special_folders::Q_002,
[folder],
|row| row.get(0),
)
.query_row(crate::dbq!(db_special_folders::Q_002), [folder], |row| {
row.get(0)
})
.map_err(|e| format!("Failed to validate folder {folder}: {e}"))?;
if !exists {
return Err(format!("Folder does not exist: {folder}"));
}
conn.execute(
crate::queries::sqlite::queries::db_special_folders::Q_003,
crate::dbq!(db_special_folders::Q_003),
params![role, folder],
)
.map_err(|e| format!("Failed to set special folder {role}: {e}"))?;
}
None => {
conn.execute(
crate::queries::sqlite::queries::db_special_folders::Q_004,
[role],
)
.map_err(|e| format!("Failed to clear special folder {role}: {e}"))?;
conn.execute(crate::dbq!(db_special_folders::Q_004), [role])
.map_err(|e| format!("Failed to clear special folder {role}: {e}"))?;
}
}
Ok(())
@@ -87,7 +80,7 @@ pub fn rename_folder_reference(
new_name: &str,
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_special_folders::Q_005,
crate::dbq!(db_special_folders::Q_005),
params![new_name, old_name],
)
.map_err(|e| format!("Failed to update special folder reference: {e}"))?;
@@ -95,11 +88,8 @@ pub fn rename_folder_reference(
}
pub fn clear_folder_reference(conn: &Connection, folder_name: &str) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_special_folders::Q_006,
[folder_name],
)
.map_err(|e| format!("Failed to clear special folder reference: {e}"))?;
conn.execute(crate::dbq!(db_special_folders::Q_006), [folder_name])
.map_err(|e| format!("Failed to clear special folder reference: {e}"))?;
Ok(())
}
+26 -43
View File
@@ -1,6 +1,6 @@
use std::collections::HashMap;
use rusqlite::{Connection, params};
use crate::db::pool::{Connection, DbValue, ToDbValue, params};
use serde::Serialize;
use crate::db::messages::CachedMessage;
@@ -33,7 +33,7 @@ pub fn imap_keyword_for_tag_id(id: &str) -> String {
pub fn create_tag(conn: &Connection, id: &str, name: &str, color: &str) -> Result<(), String> {
let imap_keyword = imap_keyword_for_tag_id(id);
conn.execute(
crate::queries::sqlite::queries::db_tags::Q_001,
crate::dbq!(db_tags::Q_001),
params![id, name, color, imap_keyword],
)
.map_err(|e| format!("Failed to create tag: {e}"))?;
@@ -42,7 +42,7 @@ pub fn create_tag(conn: &Connection, id: &str, name: &str, color: &str) -> Resul
pub fn list_tags(conn: &Connection) -> Result<Vec<Tag>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_tags::Q_002)
.prepare(crate::dbq!(db_tags::Q_002))
.map_err(|e| format!("Failed to prepare list_tags: {e}"))?;
let rows = stmt
@@ -76,7 +76,7 @@ pub fn get_message_refs_by_tag(
tag_id: &str,
) -> Result<Vec<(u32, String)>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_tags::Q_003)
.prepare(crate::dbq!(db_tags::Q_003))
.map_err(|e| format!("Failed to prepare tag message refs: {e}"))?;
let rows = stmt
.query_map(params![tag_id], |row| Ok((row.get(0)?, row.get(1)?)))
@@ -146,17 +146,14 @@ pub fn reconcile_imap_keywords(
pub fn update_tag(conn: &Connection, id: &str, name: &str, color: &str) -> Result<bool, String> {
let updated = conn
.execute(
crate::queries::sqlite::queries::db_tags::Q_004,
params![name, color, id],
)
.execute(crate::dbq!(db_tags::Q_004), params![name, color, id])
.map_err(|e| format!("Failed to update tag: {e}"))?;
Ok(updated > 0)
}
pub fn delete_tag(conn: &Connection, id: &str) -> Result<bool, String> {
let deleted = conn
.execute(crate::queries::sqlite::queries::db_tags::Q_005, params![id])
.execute(crate::dbq!(db_tags::Q_005), params![id])
.map_err(|e| format!("Failed to delete tag: {e}"))?;
Ok(deleted > 0)
}
@@ -167,11 +164,8 @@ pub fn add_tag_to_message(
uid: u32,
folder: &str,
) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_tags::Q_006,
params![tag_id, uid, folder],
)
.map_err(|e| format!("Failed to add tag to message: {e}"))?;
conn.execute(crate::dbq!(db_tags::Q_006), params![tag_id, uid, folder])
.map_err(|e| format!("Failed to add tag to message: {e}"))?;
Ok(())
}
@@ -182,10 +176,7 @@ pub fn remove_tag_from_message(
folder: &str,
) -> Result<bool, String> {
let deleted = conn
.execute(
crate::queries::sqlite::queries::db_tags::Q_007,
params![tag_id, uid, folder],
)
.execute(crate::dbq!(db_tags::Q_007), params![tag_id, uid, folder])
.map_err(|e| format!("Failed to remove tag from message: {e}"))?;
Ok(deleted > 0)
}
@@ -196,7 +187,7 @@ pub fn get_message_tags(
folder: &str,
) -> Result<Vec<MessageTag>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_tags::Q_008)
.prepare(crate::dbq!(db_tags::Q_008))
.map_err(|e| format!("Failed to prepare get_message_tags: {e}"))?;
let rows = stmt
@@ -236,22 +227,22 @@ pub fn get_tags_for_messages(
})
.collect();
let sql = crate::rustbox_query_db_tags_001!(conditions.join(" OR "));
let sql = format!(
"SELECT mt.message_uid, mt.message_folder, t.id, t.name, t.color FROM message_tags mt JOIN tags t ON t.id = mt.tag_id WHERE {} ORDER BY t.name ASC",
conditions.join(" OR ")
);
let mut stmt = conn
.prepare(&sql)
.map_err(|e| format!("Failed to prepare get_tags_for_messages: {e}"))?;
let mut param_values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
let mut param_values: Vec<DbValue> = Vec::new();
for &(uid, folder) in messages {
param_values.push(Box::new(uid));
param_values.push(Box::new(folder.to_string()));
param_values.push(uid.to_db_value());
param_values.push(folder.to_db_value());
}
let params_ref: Vec<&dyn rusqlite::types::ToSql> =
param_values.iter().map(|p| p.as_ref()).collect();
let rows = stmt
.query_map(params_ref.as_slice(), |row| {
.query_map(param_values, |row| {
let uid: u32 = row.get(0)?;
let folder: String = row.get(1)?;
let tag = MessageTag {
@@ -279,7 +270,7 @@ pub fn get_messages_by_tag(
) -> Result<Vec<CachedMessage>, String> {
let offset = page * per_page;
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_tags::Q_009)
.prepare(crate::dbq!(db_tags::Q_009))
.map_err(|e| format!("Failed to prepare get_messages_by_tag: {e}"))?;
let rows = stmt
@@ -314,11 +305,9 @@ pub fn get_messages_by_tag(
}
pub fn count_messages_by_tag(conn: &Connection, tag_id: &str) -> Result<u32, String> {
conn.query_row(
crate::queries::sqlite::queries::db_tags::Q_010,
params![tag_id],
|row| row.get(0),
)
conn.query_row(crate::dbq!(db_tags::Q_010), params![tag_id], |row| {
row.get(0)
})
.map_err(|e| format!("Failed to count messages by tag: {e}"))
}
@@ -328,16 +317,10 @@ mod tests {
use crate::db::pool::open_test_db;
fn insert_test_message(conn: &Connection, folder: &str, uid: u32) {
conn.execute(
crate::queries::sqlite::queries::db_tags::Q_011,
params![folder],
)
.unwrap();
conn.execute(
crate::queries::sqlite::queries::db_tags::Q_012,
params![uid, folder],
)
.unwrap();
conn.execute(crate::dbq!(db_tags::Q_011), params![folder])
.unwrap();
conn.execute(crate::dbq!(db_tags::Q_012), params![uid, folder])
.unwrap();
}
#[test]
+6 -12
View File
@@ -1,8 +1,8 @@
use rusqlite::Connection;
use crate::db::pool::Connection;
pub fn list(conn: &Connection) -> Result<Vec<String>, String> {
let mut stmt = conn
.prepare(crate::queries::sqlite::queries::db_trusted_senders::Q_001)
.prepare(crate::dbq!(db_trusted_senders::Q_001))
.map_err(|e| format!("Failed to prepare trusted sender list: {e}"))?;
let rows = stmt
.query_map([], |row| row.get::<_, String>(0))
@@ -12,20 +12,14 @@ pub fn list(conn: &Connection) -> Result<Vec<String>, String> {
}
pub fn add(conn: &Connection, email: &str) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_trusted_senders::Q_002,
[email],
)
.map_err(|e| format!("Failed to trust sender: {e}"))?;
conn.execute(crate::dbq!(db_trusted_senders::Q_002), [email])
.map_err(|e| format!("Failed to trust sender: {e}"))?;
Ok(())
}
pub fn remove(conn: &Connection, email: &str) -> Result<(), String> {
conn.execute(
crate::queries::sqlite::queries::db_trusted_senders::Q_003,
[email],
)
.map_err(|e| format!("Failed to remove trusted sender: {e}"))?;
conn.execute(crate::dbq!(db_trusted_senders::Q_003), [email])
.map_err(|e| format!("Failed to remove trusted sender: {e}"))?;
Ok(())
}