1 parent
ecc201ef50
commit
698e3c4a1b
124 files changed
+4593
-2721
No files matched your search
+54
-51
@@ -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
@@ -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
@@ -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)]
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)]
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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
@@ -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
@@ -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]
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user