core: add sqlite savepoints
parent
28156fdb75
commit
243f4af198
|
@ -30,6 +30,7 @@ use mailpot::{
|
|||
melib::{backends::maildir::MaildirPathTrait, smol, smtp::*, Envelope, EnvelopeHash},
|
||||
models::{changesets::*, *},
|
||||
queue::{Queue, QueueEntry},
|
||||
transaction::TransactionBehavior,
|
||||
Configuration, Connection, Error, ErrorKind, Result, *,
|
||||
};
|
||||
use mailpot_cli::*;
|
||||
|
@ -507,6 +508,7 @@ fn run_app(opt: Opt) -> Result<()> {
|
|||
println!("post dry_run{:?}", dry_run);
|
||||
}
|
||||
|
||||
let tx = db.transaction(TransactionBehavior::Exclusive).unwrap();
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_to_string(&mut input)?;
|
||||
match Envelope::from_bytes(input.as_bytes(), None) {
|
||||
|
@ -514,7 +516,7 @@ fn run_app(opt: Opt) -> Result<()> {
|
|||
if opt.debug {
|
||||
eprintln!("{:?}", &env);
|
||||
}
|
||||
db.post(&env, input.as_bytes(), dry_run)?;
|
||||
tx.post(&env, input.as_bytes(), dry_run)?;
|
||||
}
|
||||
Err(err) if input.trim().is_empty() => {
|
||||
eprintln!("Empty input, abort.");
|
||||
|
@ -522,21 +524,28 @@ fn run_app(opt: Opt) -> Result<()> {
|
|||
}
|
||||
Err(err) => {
|
||||
eprintln!("Could not parse message: {}", err);
|
||||
let p = db.conf().save_message(input)?;
|
||||
let p = tx.conf().save_message(input)?;
|
||||
eprintln!("Message saved at {}", p.display());
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
tx.commit()?;
|
||||
}
|
||||
FlushQueue { dry_run } => {
|
||||
let tx = db.transaction(TransactionBehavior::Exclusive).unwrap();
|
||||
let messages = if opt.debug {
|
||||
println!("flush-queue dry_run {:?}", dry_run);
|
||||
db.queue(Queue::Out)?
|
||||
tx.queue(Queue::Out)?
|
||||
.into_iter()
|
||||
.map(DbVal::into_inner)
|
||||
.chain(
|
||||
tx.queue(Queue::Deferred)?
|
||||
.into_iter()
|
||||
.map(DbVal::into_inner),
|
||||
)
|
||||
.collect()
|
||||
} else {
|
||||
db.delete_from_queue(Queue::Out, vec![])?
|
||||
tx.delete_from_queue(Queue::Out, vec![])?
|
||||
};
|
||||
if opt.verbose > 0 || opt.debug {
|
||||
println!("Queue out has {} messages.", messages.len());
|
||||
|
@ -544,7 +553,7 @@ fn run_app(opt: Opt) -> Result<()> {
|
|||
|
||||
let mut failures = Vec::with_capacity(messages.len());
|
||||
|
||||
let send_mail = db.conf().send_mail.clone();
|
||||
let send_mail = tx.conf().send_mail.clone();
|
||||
match send_mail {
|
||||
mailpot::SendMail::ShellCommand(cmd) => {
|
||||
fn submit(cmd: &str, msg: &QueueEntry) -> Result<()> {
|
||||
|
@ -589,18 +598,27 @@ fn run_app(opt: Opt) -> Result<()> {
|
|||
}
|
||||
}
|
||||
mailpot::SendMail::Smtp(_) => {
|
||||
let conn_future = db.new_smtp_connection()?;
|
||||
smol::future::block_on(smol::spawn(async move {
|
||||
let conn_future = tx.new_smtp_connection()?;
|
||||
failures = smol::future::block_on(smol::spawn(async move {
|
||||
let mut conn = conn_future.await?;
|
||||
for msg in messages {
|
||||
if let Err(err) = Connection::submit(&mut conn, &msg, dry_run).await {
|
||||
failures.push((err, msg));
|
||||
}
|
||||
}
|
||||
Ok::<(), Error>(())
|
||||
Ok::<_, Error>(failures)
|
||||
}))?;
|
||||
}
|
||||
}
|
||||
|
||||
for (err, mut msg) in failures {
|
||||
log::error!("Message {msg:?} failed with: {err}. Inserting to Deferred queue.");
|
||||
|
||||
msg.queue = Queue::Deferred;
|
||||
tx.insert_to_queue(msg)?;
|
||||
}
|
||||
|
||||
tx.commit()?;
|
||||
}
|
||||
ErrorQueue { cmd } => match cmd {
|
||||
ErrorQueueCommand::List => {
|
||||
|
|
|
@ -136,7 +136,7 @@ fn test_out_queue_flush() {
|
|||
log::info!("Subscribe two users, Αλίκη and Χαραλάμπης to foo-chat.");
|
||||
|
||||
{
|
||||
let mut db = Connection::open_or_create_db(config.clone())
|
||||
let db = Connection::open_or_create_db(config.clone())
|
||||
.unwrap()
|
||||
.trusted();
|
||||
|
||||
|
@ -204,7 +204,7 @@ fn test_out_queue_flush() {
|
|||
);
|
||||
|
||||
{
|
||||
let mut db = Connection::open_or_create_db(config.clone())
|
||||
let db = Connection::open_or_create_db(config.clone())
|
||||
.unwrap()
|
||||
.trusted();
|
||||
let mail = generate_mail("Χαραλάμπης", "", "hello world", "Hello there.", &mut seq);
|
||||
|
@ -332,7 +332,7 @@ fn test_list_requests_submission() {
|
|||
log::info!("User Αλίκη sends to foo-chat+request with subject 'help'.");
|
||||
|
||||
{
|
||||
let mut db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
let db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
|
||||
let mail = generate_mail("Αλίκη", "+request", "help", "", &mut seq);
|
||||
let subenvelope = mailpot::melib::Envelope::from_bytes(mail.as_bytes(), None)
|
||||
|
|
|
@ -17,7 +17,7 @@ error-chain = { version = "0.12.4", default-features = false }
|
|||
log = "0.4"
|
||||
melib = { version = "*", default-features = false, features = ["smtp", "unicode_algorithms", "maildir_backend"], git = "https://github.com/meli/meli", rev = "2447a2c" }
|
||||
minijinja = { version = "0.31.0", features = ["source", ] }
|
||||
rusqlite = { version = "^0.28", features = ["bundled", "trace", "hooks", "serde_json", "array", "chrono"] }
|
||||
rusqlite = { version = "^0.28", features = ["bundled", "trace", "hooks", "serde_json", "array", "chrono", "unlock_notify"] }
|
||||
serde = { version = "^1", features = ["derive", ] }
|
||||
serde_json = "^1"
|
||||
toml = "^0.5"
|
||||
|
|
|
@ -199,7 +199,7 @@ impl Connection {
|
|||
conn.busy_timeout(core::time::Duration::from_millis(500))?;
|
||||
conn.busy_handler(Some(|times: i32| -> bool { times < 5 }))?;
|
||||
|
||||
let mut ret = Self {
|
||||
let ret = Self {
|
||||
conf,
|
||||
connection: conn,
|
||||
};
|
||||
|
@ -232,13 +232,13 @@ impl Connection {
|
|||
/// Migrate from version `from` to `to`.
|
||||
///
|
||||
/// See [Self::MIGRATIONS].
|
||||
pub fn migrate(&mut self, mut from: u32, to: u32) -> Result<()> {
|
||||
pub fn migrate(&self, mut from: u32, to: u32) -> Result<()> {
|
||||
if from == to {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let undo = from > to;
|
||||
let tx = self.connection.transaction()?;
|
||||
let tx = self.savepoint(Some(stringify!(migrate)))?;
|
||||
|
||||
while from != to {
|
||||
log::trace!(
|
||||
|
@ -247,15 +247,18 @@ impl Connection {
|
|||
);
|
||||
if undo {
|
||||
trace!("{}", Self::MIGRATIONS[from as usize].2);
|
||||
tx.execute(Self::MIGRATIONS[from as usize].2, [])?;
|
||||
tx.connection
|
||||
.execute(Self::MIGRATIONS[from as usize].2, [])?;
|
||||
from -= 1;
|
||||
} else {
|
||||
trace!("{}", Self::MIGRATIONS[from as usize].1);
|
||||
tx.execute(Self::MIGRATIONS[from as usize].1, [])?;
|
||||
tx.connection
|
||||
.execute(Self::MIGRATIONS[from as usize].1, [])?;
|
||||
from += 1;
|
||||
}
|
||||
}
|
||||
tx.pragma_update(None, "user_version", Self::MIGRATIONS[to as usize - 1].0)?;
|
||||
tx.connection
|
||||
.pragma_update(None, "user_version", Self::MIGRATIONS[to as usize - 1].0)?;
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
|
@ -354,10 +357,10 @@ impl Connection {
|
|||
}
|
||||
|
||||
/// Loads archive databases from [`Configuration::data_path`], if any.
|
||||
pub fn load_archives(&mut self) -> Result<()> {
|
||||
let tx = self.connection.transaction()?;
|
||||
pub fn load_archives(&self) -> Result<()> {
|
||||
let tx = self.savepoint(Some(stringify!(load_archives)))?;
|
||||
{
|
||||
let mut stmt = tx.prepare("ATTACH ? AS ?;")?;
|
||||
let mut stmt = tx.connection.prepare("ATTACH ? AS ?;")?;
|
||||
for archive in std::fs::read_dir(&self.conf.data_path)? {
|
||||
let archive = archive?;
|
||||
let path = archive.path();
|
||||
|
@ -611,7 +614,7 @@ impl Connection {
|
|||
}
|
||||
|
||||
/// Update a mailing list.
|
||||
pub fn update_list(&mut self, change_set: MailingListChangeset) -> Result<()> {
|
||||
pub fn update_list(&self, change_set: MailingListChangeset) -> Result<()> {
|
||||
if matches!(
|
||||
change_set,
|
||||
MailingListChangeset {
|
||||
|
@ -644,12 +647,12 @@ impl Connection {
|
|||
hidden,
|
||||
enabled,
|
||||
} = change_set;
|
||||
let tx = self.connection.transaction()?;
|
||||
let tx = self.savepoint(Some(stringify!(update_list)))?;
|
||||
|
||||
macro_rules! update {
|
||||
($field:tt) => {{
|
||||
if let Some($field) = $field {
|
||||
tx.execute(
|
||||
tx.connection.execute(
|
||||
concat!("UPDATE list SET ", stringify!($field), " = ? WHERE pk = ?;"),
|
||||
rusqlite::params![&$field, &pk],
|
||||
)?;
|
||||
|
@ -673,7 +676,7 @@ impl Connection {
|
|||
|
||||
/// Execute operations inside an SQL transaction.
|
||||
pub fn transaction(
|
||||
&'_ self,
|
||||
&'_ mut self,
|
||||
behavior: transaction::TransactionBehavior,
|
||||
) -> Result<transaction::Transaction<'_>> {
|
||||
use transaction::*;
|
||||
|
@ -689,6 +692,30 @@ impl Connection {
|
|||
drop_behavior: DropBehavior::Rollback,
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute operations inside an SQL savepoint.
|
||||
pub fn savepoint(&'_ self, name: Option<&'static str>) -> Result<transaction::Savepoint<'_>> {
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use transaction::*;
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
let name = name
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| Err(COUNTER.fetch_add(1, Ordering::Relaxed)));
|
||||
|
||||
match name {
|
||||
Ok(ref n) => self.connection.execute_batch(&format!("SAVEPOINT {n}"))?,
|
||||
Err(ref i) => self.connection.execute_batch(&format!("SAVEPOINT _{i}"))?,
|
||||
};
|
||||
|
||||
Ok(Savepoint {
|
||||
conn: self,
|
||||
drop_behavior: DropBehavior::Rollback,
|
||||
name,
|
||||
committed: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute operations inside an SQL transaction.
|
||||
|
@ -698,7 +725,7 @@ pub mod transaction {
|
|||
/// A transaction handle.
|
||||
#[derive(Debug)]
|
||||
pub struct Transaction<'conn> {
|
||||
pub(super) conn: &'conn Connection,
|
||||
pub(super) conn: &'conn mut Connection,
|
||||
pub(super) drop_behavior: DropBehavior,
|
||||
}
|
||||
|
||||
|
@ -778,10 +805,10 @@ pub mod transaction {
|
|||
/// DEFERRED means that the transaction does not actually start until
|
||||
/// the database is first accessed.
|
||||
Deferred,
|
||||
#[default]
|
||||
/// IMMEDIATE cause the database connection to start a new write
|
||||
/// immediately, without waiting for a writes statement.
|
||||
Immediate,
|
||||
#[default]
|
||||
/// EXCLUSIVE prevents other database connections from reading the
|
||||
/// database while the transaction is underway.
|
||||
Exclusive,
|
||||
|
@ -806,6 +833,106 @@ pub mod transaction {
|
|||
/// Panic. Used to enforce intentional behavior during development.
|
||||
Panic,
|
||||
}
|
||||
|
||||
/// A savepoint handle.
|
||||
#[derive(Debug)]
|
||||
pub struct Savepoint<'conn> {
|
||||
pub(super) conn: &'conn Connection,
|
||||
pub(super) drop_behavior: DropBehavior,
|
||||
pub(super) name: std::result::Result<&'static str, usize>,
|
||||
pub(super) committed: bool,
|
||||
}
|
||||
|
||||
impl Drop for Savepoint<'_> {
|
||||
fn drop(&mut self) {
|
||||
_ = self.finish_();
|
||||
}
|
||||
}
|
||||
|
||||
impl Savepoint<'_> {
|
||||
/// Commit and consume savepoint.
|
||||
pub fn commit(mut self) -> Result<()> {
|
||||
self.commit_()
|
||||
}
|
||||
|
||||
fn commit_(&mut self) -> Result<()> {
|
||||
if !self.committed {
|
||||
match self.name {
|
||||
Ok(ref n) => self
|
||||
.conn
|
||||
.connection
|
||||
.execute_batch(&format!("RELEASE SAVEPOINT {n}"))?,
|
||||
Err(ref i) => self
|
||||
.conn
|
||||
.connection
|
||||
.execute_batch(&format!("RELEASE SAVEPOINT _{i}"))?,
|
||||
};
|
||||
self.committed = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Configure the savepoint to perform the specified action when it is
|
||||
/// dropped.
|
||||
#[inline]
|
||||
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
|
||||
self.drop_behavior = drop_behavior;
|
||||
}
|
||||
|
||||
/// A convenience method which consumes and rolls back a savepoint.
|
||||
#[inline]
|
||||
pub fn rollback(mut self) -> Result<()> {
|
||||
self.rollback_()
|
||||
}
|
||||
|
||||
fn rollback_(&mut self) -> Result<()> {
|
||||
if !self.committed {
|
||||
match self.name {
|
||||
Ok(ref n) => self
|
||||
.conn
|
||||
.connection
|
||||
.execute_batch(&format!("ROLLBACK TO SAVEPOINT {n}"))?,
|
||||
Err(ref i) => self
|
||||
.conn
|
||||
.connection
|
||||
.execute_batch(&format!("ROLLBACK TO SAVEPOINT _{i}"))?,
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Consumes the savepoint, committing or rolling back according to
|
||||
/// the current setting (see `drop_behavior`).
|
||||
///
|
||||
/// Functionally equivalent to the `Drop` implementation, but allows
|
||||
/// callers to see any errors that occur.
|
||||
#[inline]
|
||||
pub fn finish(mut self) -> Result<()> {
|
||||
self.finish_()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn finish_(&mut self) -> Result<()> {
|
||||
if self.conn.connection.is_autocommit() {
|
||||
return Ok(());
|
||||
}
|
||||
match self.drop_behavior {
|
||||
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()),
|
||||
DropBehavior::Rollback => self.rollback_(),
|
||||
DropBehavior::Ignore => Ok(()),
|
||||
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Savepoint<'_> {
|
||||
type Target = Connection;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Connection {
|
||||
self.conn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -842,4 +969,124 @@ mod tests {
|
|||
|
||||
_ = Connection::open_or_create_db(config).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transactions() {
|
||||
use melib::smtp::{SmtpAuth, SmtpSecurity, SmtpServerConf};
|
||||
use tempfile::TempDir;
|
||||
|
||||
use super::transaction::*;
|
||||
use crate::SendMail;
|
||||
|
||||
let tmp_dir = TempDir::new().unwrap();
|
||||
let db_path = tmp_dir.path().join("mpot.db");
|
||||
let data_path = tmp_dir.path().to_path_buf();
|
||||
let config = Configuration {
|
||||
send_mail: SendMail::Smtp(SmtpServerConf {
|
||||
hostname: "127.0.0.1".into(),
|
||||
port: 25,
|
||||
envelope_from: "foo-chat@example.com".into(),
|
||||
auth: SmtpAuth::None,
|
||||
security: SmtpSecurity::None,
|
||||
extensions: Default::default(),
|
||||
}),
|
||||
db_path,
|
||||
data_path,
|
||||
administrators: vec![],
|
||||
};
|
||||
let list = MailingList {
|
||||
pk: 0,
|
||||
name: "".into(),
|
||||
id: "".into(),
|
||||
description: None,
|
||||
address: "".into(),
|
||||
archive_url: None,
|
||||
};
|
||||
let mut db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
|
||||
/* drop rollback */
|
||||
let mut tx = db.transaction(Default::default()).unwrap();
|
||||
tx.set_drop_behavior(DropBehavior::Rollback);
|
||||
let _new = tx.create_list(list.clone()).unwrap();
|
||||
drop(tx);
|
||||
assert_eq!(&db.lists().unwrap(), &[]);
|
||||
|
||||
/* drop commit */
|
||||
let mut tx = db.transaction(Default::default()).unwrap();
|
||||
tx.set_drop_behavior(DropBehavior::Commit);
|
||||
let new = tx.create_list(list.clone()).unwrap();
|
||||
drop(tx);
|
||||
assert_eq!(&db.lists().unwrap(), &[new.clone()]);
|
||||
|
||||
/* rollback with drop commit */
|
||||
let mut tx = db.transaction(Default::default()).unwrap();
|
||||
tx.set_drop_behavior(DropBehavior::Commit);
|
||||
let _new2 = tx
|
||||
.create_list(MailingList {
|
||||
id: "1".into(),
|
||||
address: "1".into(),
|
||||
..list.clone()
|
||||
})
|
||||
.unwrap();
|
||||
tx.rollback().unwrap();
|
||||
assert_eq!(&db.lists().unwrap(), &[new.clone()]);
|
||||
|
||||
/* tx and then savepoint */
|
||||
let tx = db.transaction(Default::default()).unwrap();
|
||||
let sv = tx.savepoint(None).unwrap();
|
||||
let new2 = sv
|
||||
.create_list(MailingList {
|
||||
id: "2".into(),
|
||||
address: "2".into(),
|
||||
..list.clone()
|
||||
})
|
||||
.unwrap();
|
||||
sv.commit().unwrap();
|
||||
tx.commit().unwrap();
|
||||
assert_eq!(&db.lists().unwrap(), &[new.clone(), new2.clone()]);
|
||||
|
||||
/* tx and then rollback savepoint */
|
||||
let tx = db.transaction(Default::default()).unwrap();
|
||||
let sv = tx.savepoint(None).unwrap();
|
||||
let _new3 = sv
|
||||
.create_list(MailingList {
|
||||
id: "3".into(),
|
||||
address: "3".into(),
|
||||
..list.clone()
|
||||
})
|
||||
.unwrap();
|
||||
sv.rollback().unwrap();
|
||||
tx.commit().unwrap();
|
||||
assert_eq!(&db.lists().unwrap(), &[new.clone(), new2.clone()]);
|
||||
|
||||
/* tx, commit savepoint and then rollback commit */
|
||||
let tx = db.transaction(Default::default()).unwrap();
|
||||
let sv = tx.savepoint(None).unwrap();
|
||||
let _new3 = sv
|
||||
.create_list(MailingList {
|
||||
id: "3".into(),
|
||||
address: "3".into(),
|
||||
..list.clone()
|
||||
})
|
||||
.unwrap();
|
||||
sv.commit().unwrap();
|
||||
tx.rollback().unwrap();
|
||||
assert_eq!(&db.lists().unwrap(), &[new.clone(), new2.clone()]);
|
||||
|
||||
/* nested savepoints */
|
||||
let tx = db.transaction(Default::default()).unwrap();
|
||||
let sv = tx.savepoint(None).unwrap();
|
||||
let sv1 = sv.savepoint(None).unwrap();
|
||||
let new3 = sv1
|
||||
.create_list(MailingList {
|
||||
id: "3".into(),
|
||||
address: "3".into(),
|
||||
..list
|
||||
})
|
||||
.unwrap();
|
||||
sv1.commit().unwrap();
|
||||
sv.commit().unwrap();
|
||||
tx.commit().unwrap();
|
||||
assert_eq!(&db.lists().unwrap(), &[new, new2, new3]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -189,7 +189,7 @@ pub mod subscriptions;
|
|||
mod templates;
|
||||
|
||||
pub use config::{Configuration, SendMail};
|
||||
pub use connection::*;
|
||||
pub use connection::{transaction, *};
|
||||
pub use errors::*;
|
||||
use models::*;
|
||||
pub use templates::*;
|
||||
|
|
|
@ -84,7 +84,11 @@ impl Connection {
|
|||
}
|
||||
|
||||
/// Process a new mailing list post.
|
||||
pub fn post(&mut self, env: &Envelope, raw: &[u8], _dry_run: bool) -> Result<()> {
|
||||
///
|
||||
/// In case multiple processes can access the database at any time, use an
|
||||
/// `EXCLUSIVE` transaction before calling this function.
|
||||
/// See [`Connection::transaction`].
|
||||
pub fn post(&self, env: &Envelope, raw: &[u8], _dry_run: bool) -> Result<()> {
|
||||
let result = self.inner_post(env, raw, _dry_run);
|
||||
if let Err(err) = result {
|
||||
return match self.insert_to_queue(QueueEntry::new(
|
||||
|
@ -115,7 +119,7 @@ impl Connection {
|
|||
result
|
||||
}
|
||||
|
||||
fn inner_post(&mut self, env: &Envelope, raw: &[u8], _dry_run: bool) -> Result<()> {
|
||||
fn inner_post(&self, env: &Envelope, raw: &[u8], _dry_run: bool) -> Result<()> {
|
||||
trace!("Received envelope to post: {:#?}", &env);
|
||||
let tos = env.to().to_vec();
|
||||
if tos.is_empty() {
|
||||
|
@ -295,7 +299,7 @@ impl Connection {
|
|||
|
||||
/// Process a new mailing list request.
|
||||
pub fn request(
|
||||
&mut self,
|
||||
&self,
|
||||
list: &DbVal<MailingList>,
|
||||
request: ListRequest,
|
||||
env: &Envelope,
|
||||
|
|
|
@ -214,8 +214,8 @@ impl Connection {
|
|||
}
|
||||
|
||||
/// Delete queue entries returning the deleted values.
|
||||
pub fn delete_from_queue(&mut self, queue: Queue, index: Vec<i64>) -> Result<Vec<QueueEntry>> {
|
||||
let tx = self.connection.transaction()?;
|
||||
pub fn delete_from_queue(&self, queue: Queue, index: Vec<i64>) -> Result<Vec<QueueEntry>> {
|
||||
let tx = self.savepoint(Some(stringify!(delete_from_queue)))?;
|
||||
|
||||
let cl = |row: &rusqlite::Row<'_>| {
|
||||
Ok(QueueEntry {
|
||||
|
@ -233,9 +233,11 @@ impl Connection {
|
|||
})
|
||||
};
|
||||
let mut stmt = if index.is_empty() {
|
||||
tx.prepare("DELETE FROM queue WHERE which = ? RETURNING *;")?
|
||||
tx.connection
|
||||
.prepare("DELETE FROM queue WHERE which = ? RETURNING *;")?
|
||||
} else {
|
||||
tx.prepare("DELETE FROM queue WHERE which = ? AND pk IN rarray(?) RETURNING *;")?
|
||||
tx.connection
|
||||
.prepare("DELETE FROM queue WHERE which = ? AND pk IN rarray(?) RETURNING *;")?
|
||||
};
|
||||
let iter = if index.is_empty() {
|
||||
stmt.query_map([&queue.as_str()], cl)?
|
||||
|
@ -279,7 +281,7 @@ mod tests {
|
|||
administrators: vec![],
|
||||
};
|
||||
|
||||
let mut db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
let db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
for i in 0..5 {
|
||||
db.insert_to_queue(
|
||||
QueueEntry::new(
|
||||
|
|
|
@ -253,7 +253,7 @@ impl Connection {
|
|||
}
|
||||
|
||||
/// Accept subscription candidate.
|
||||
pub fn accept_candidate_subscription(&mut self, pk: i64) -> Result<DbVal<ListSubscription>> {
|
||||
pub fn accept_candidate_subscription(&self, pk: i64) -> Result<DbVal<ListSubscription>> {
|
||||
let val = self.connection.query_row(
|
||||
"INSERT INTO subscription(list, address, name, enabled, digest, verified, \
|
||||
hide_address, receive_duplicates, receive_own_posts, receive_confirmation) SELECT \
|
||||
|
@ -311,7 +311,7 @@ impl Connection {
|
|||
}
|
||||
|
||||
/// Update a mailing list subscription.
|
||||
pub fn update_subscription(&mut self, change_set: ListSubscriptionChangeset) -> Result<()> {
|
||||
pub fn update_subscription(&self, change_set: ListSubscriptionChangeset) -> Result<()> {
|
||||
let pk = self
|
||||
.list_subscription_by_address(change_set.list, &change_set.address)?
|
||||
.pk;
|
||||
|
@ -347,12 +347,12 @@ impl Connection {
|
|||
receive_own_posts,
|
||||
receive_confirmation,
|
||||
} = change_set;
|
||||
let tx = self.connection.transaction()?;
|
||||
let tx = self.savepoint(Some(stringify!(update_subscription)))?;
|
||||
|
||||
macro_rules! update {
|
||||
($field:tt) => {{
|
||||
if let Some($field) = $field {
|
||||
tx.execute(
|
||||
tx.connection.execute(
|
||||
concat!(
|
||||
"UPDATE subscription SET ",
|
||||
stringify!($field),
|
||||
|
@ -547,7 +547,7 @@ impl Connection {
|
|||
}
|
||||
|
||||
/// Update an account.
|
||||
pub fn update_account(&mut self, change_set: AccountChangeset) -> Result<()> {
|
||||
pub fn update_account(&self, change_set: AccountChangeset) -> Result<()> {
|
||||
let Some(acc) = self.account_by_address(&change_set.address)? else {
|
||||
return Err(NotFound("account with this address not found!").into());
|
||||
};
|
||||
|
@ -572,12 +572,12 @@ impl Connection {
|
|||
password,
|
||||
enabled,
|
||||
} = change_set;
|
||||
let tx = self.connection.transaction()?;
|
||||
let tx = self.savepoint(Some(stringify!(update_account)))?;
|
||||
|
||||
macro_rules! update {
|
||||
($field:tt) => {{
|
||||
if let Some($field) = $field {
|
||||
tx.execute(
|
||||
tx.connection.execute(
|
||||
concat!(
|
||||
"UPDATE account SET ",
|
||||
stringify!($field),
|
||||
|
@ -616,7 +616,7 @@ mod tests {
|
|||
administrators: vec![],
|
||||
};
|
||||
|
||||
let mut db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
let db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
let list = db
|
||||
.create_list(MailingList {
|
||||
pk: -1,
|
||||
|
|
|
@ -70,7 +70,7 @@ fn test_accounts() {
|
|||
assert_eq!(db.queue(Queue::Error).unwrap().len(), 0);
|
||||
assert_eq!(db.list_subscriptions(foo_chat.pk()).unwrap().len(), 0);
|
||||
|
||||
let mut db = db.untrusted();
|
||||
let db = db.untrusted();
|
||||
|
||||
let subscribe_bytes = b"From: Name <user@example.com>
|
||||
To: <foo-chat+subscribe@example.com>
|
||||
|
|
|
@ -76,7 +76,7 @@ fn test_error_queue() {
|
|||
assert_eq!(db.queue(Queue::Error).unwrap().len(), 0);
|
||||
|
||||
// drop privileges
|
||||
let mut db = db.untrusted();
|
||||
let db = db.untrusted();
|
||||
|
||||
let input_bytes = include_bytes!("./test_sample_longmessage.eml");
|
||||
let envelope = melib::Envelope::from_bytes(input_bytes, None).expect("Could not parse message");
|
||||
|
|
|
@ -34,7 +34,7 @@ fn test_init_empty() {
|
|||
administrators: vec![],
|
||||
};
|
||||
|
||||
let mut db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
let db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
|
||||
let migrations = Connection::MIGRATIONS;
|
||||
if migrations.is_empty() {
|
||||
|
|
|
@ -39,7 +39,7 @@ fn test_smtp() {
|
|||
administrators: vec![],
|
||||
};
|
||||
|
||||
let mut db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
let db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
assert!(db.lists().unwrap().is_empty());
|
||||
let foo_chat = db
|
||||
.create_list(MailingList {
|
||||
|
@ -193,7 +193,7 @@ fn test_smtp_mailcrab() {
|
|||
administrators: vec![],
|
||||
};
|
||||
|
||||
let mut db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
let db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
assert!(db.lists().unwrap().is_empty());
|
||||
let foo_chat = db
|
||||
.create_list(MailingList {
|
||||
|
|
|
@ -68,7 +68,7 @@ fn test_list_subscription() {
|
|||
assert_eq!(db.queue(Queue::Error).unwrap().len(), 0);
|
||||
assert_eq!(db.list_subscriptions(foo_chat.pk()).unwrap().len(), 0);
|
||||
|
||||
let mut db = db.untrusted();
|
||||
let db = db.untrusted();
|
||||
|
||||
let post_bytes = b"From: Name <user@example.com>
|
||||
To: <foo-chat@example.com>
|
||||
|
@ -193,7 +193,7 @@ fn test_post_rejection() {
|
|||
assert_eq!(db.queue(Queue::Error).unwrap().len(), 0);
|
||||
assert_eq!(db.list_subscriptions(foo_chat.pk()).unwrap().len(), 0);
|
||||
|
||||
let mut db = db.untrusted();
|
||||
let db = db.untrusted();
|
||||
|
||||
let post_bytes = b"From: Name <user@example.com>
|
||||
To: <foo-chat@example.com>
|
||||
|
|
|
@ -346,7 +346,7 @@ pub async fn list_edit_post(
|
|||
));
|
||||
};
|
||||
|
||||
let mut db = db.trusted();
|
||||
let db = db.trusted();
|
||||
match payload {
|
||||
ChangeSetting::PostPolicy {
|
||||
delete_post_policy: _,
|
||||
|
|
|
@ -96,7 +96,7 @@ pub async fn settings_post(
|
|||
Form(payload): Form<ChangeSetting>,
|
||||
state: Arc<AppState>,
|
||||
) -> Result<Redirect, ResponseError> {
|
||||
let mut db = Connection::open_db(state.conf.clone())?;
|
||||
let db = Connection::open_db(state.conf.clone())?;
|
||||
let acc = db
|
||||
.account_by_address(&user.address)
|
||||
.with_status(StatusCode::BAD_REQUEST)?
|
||||
|
@ -338,7 +338,7 @@ pub async fn user_list_subscription_post(
|
|||
Form(payload): Form<SubscriptionFormPayload>,
|
||||
state: Arc<AppState>,
|
||||
) -> Result<Redirect, ResponseError> {
|
||||
let mut db = Connection::open_db(state.conf.clone())?;
|
||||
let db = Connection::open_db(state.conf.clone())?;
|
||||
|
||||
let Some(list) = (match id {
|
||||
ListPathIdentifier::Pk(id) => db.list(id)?,
|
||||
|
|
Loading…
Reference in New Issue