summaryrefslogtreecommitdiff
path: root/src/db/backup.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/db/backup.rs')
-rw-r--r--src/db/backup.rs76
1 files changed, 76 insertions, 0 deletions
diff --git a/src/db/backup.rs b/src/db/backup.rs
new file mode 100644
index 0000000..bb36aea
--- /dev/null
+++ b/src/db/backup.rs
@@ -0,0 +1,76 @@
+use rusqlite::{
+ backup::{self},
+ Connection,
+};
+use sqlx::sqlite::{LockedSqliteHandle, SqlitePool};
+
+pub struct Builder<'p> {
+ from: &'p SqlitePool,
+}
+
+impl<'p> Builder<'p> {
+ pub fn to(self, to: &'p SqlitePool) -> Backup<'p> {
+ Backup {
+ from: self.from,
+ to,
+ }
+ }
+}
+
+pub struct Backup<'p> {
+ from: &'p SqlitePool,
+ to: &'p SqlitePool,
+}
+
+impl<'p> Backup<'p> {
+ pub fn from(from: &'p SqlitePool) -> Builder<'p> {
+ Builder { from }
+ }
+}
+
+impl<'p> Backup<'p> {
+ pub async fn backup(&mut self) -> Result<(), Error> {
+ let mut to = self.to.acquire().await?;
+ let mut to = Self::connection(&mut to.lock_handle().await?)?;
+ let mut from = self.from.acquire().await?;
+ let from = Self::connection(&mut from.lock_handle().await?)?;
+
+ let backup = backup::Backup::new(&from, &mut to)?;
+ loop {
+ use rusqlite::backup::StepResult::{Busy, Done, Locked, More};
+ match backup.step(-1)? {
+ Done => break Ok(()),
+ // In the system as actually written, yielding does nothing: this is the
+ // only task actually in flight at the time. However, that may change; if
+ // it does, we _definitely_ want to give other tasks an opportunity to
+ // make progress before we retry.
+ More => tokio::task::consume_budget().await,
+ // If another task is actually using the DB, then it _definitely_ needs
+ // an opportunity to make progress before we retry.
+ Busy | Locked => tokio::task::yield_now().await,
+ // As of this writing, this arm is formally unreachable: all of the enum
+ // constants that actually exist in rusqlite are matched above. However,
+ // they've reserved the prerogative to add additional arms without a
+ // breaking change by making the type as non-exhaustive; if we get one,
+ // stop immediately as there's no way to guess what the correct handling
+ // will be, a priori.
+ other => panic!("unexpected backup step result: {other:?}"),
+ }
+ }
+ }
+
+ fn connection(handle: &mut LockedSqliteHandle) -> Result<Connection, rusqlite::Error> {
+ let handle = handle.as_raw_handle();
+ let connection = unsafe { Connection::from_handle(handle.as_ptr()) }?;
+
+ Ok(connection)
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error(transparent)]
+ Sqlx(#[from] sqlx::Error),
+ #[error(transparent)]
+ Rusqlite(#[from] rusqlite::Error),
+}