1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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),
}
|