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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
mod backup;
use std::str::FromStr;
use sqlx::{
error::{DatabaseError, ErrorKind},
migrate::MigrateDatabase as _,
sqlite::{Sqlite, SqliteConnectOptions, SqlitePool, SqlitePoolOptions},
};
pub async fn prepare(url: &str, backup_url: &str) -> Result<SqlitePool, Error> {
if backup_url != "sqlite::memory:" && Sqlite::database_exists(backup_url).await? {
return Err(Error::BackupExists(backup_url.into()));
}
let pool = create(url).await?;
let backup_pool = create(backup_url).await?;
backup::Backup::from(&pool)
.to(&backup_pool)
.backup()
.await?;
if let Err(migrate_error) = sqlx::migrate!().run(&pool).await {
if let Err(restore_error) = backup::Backup::from(&backup_pool).to(&pool).backup().await {
Err(Error::Restore(restore_error, migrate_error))?;
} else if let Err(drop_error) = Sqlite::drop_database(backup_url).await {
Err(Error::Drop(drop_error, migrate_error))?;
} else {
Err(migrate_error)?;
}
}
Sqlite::drop_database(backup_url).await?;
Ok(pool)
}
async fn create(database_url: &str) -> sqlx::Result<SqlitePool> {
let options = SqliteConnectOptions::from_str(database_url)?
.create_if_missing(true)
.optimize_on_close(true, /* analysis_limit */ None);
let pool = SqlitePoolOptions::new().connect_with(options).await?;
Ok(pool)
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Database(#[from] sqlx::Error),
#[error("backup from a previous failed migration already exists: {0}")]
BackupExists(String),
#[error(transparent)]
Backup(#[from] backup::Error),
#[error("migration failed: {1}\nrestoring backup failed: {0}")]
Restore(backup::Error, sqlx::migrate::MigrateError),
#[error(
"migration failed: {1}\nrestoring from backup succeeded, but deleting backup failed: {0}"
)]
Drop(sqlx::Error, sqlx::migrate::MigrateError),
#[error(transparent)]
Migration(#[from] sqlx::migrate::MigrateError),
}
pub trait NotFound: Sized {
type Ok;
type Error;
fn not_found<E, F>(self, map: F) -> Result<Self::Ok, E>
where
E: From<Self::Error>,
F: FnOnce() -> E,
{
self.optional()?.ok_or_else(map)
}
fn optional(self) -> Result<Option<Self::Ok>, Self::Error>;
}
impl<T> NotFound for Result<T, sqlx::Error> {
type Ok = T;
type Error = sqlx::Error;
fn optional(self) -> Result<Option<T>, sqlx::Error> {
match self {
Ok(value) => Ok(Some(value)),
Err(sqlx::Error::RowNotFound) => Ok(None),
Err(other) => Err(other),
}
}
}
pub trait Duplicate {
type Ok;
type Error;
fn duplicate<E, F>(self, map: F) -> Result<Self::Ok, E>
where
E: From<Self::Error>,
F: FnOnce() -> E;
}
impl<T> Duplicate for Result<T, sqlx::Error> {
type Ok = T;
type Error = sqlx::Error;
fn duplicate<E, F>(self, map: F) -> Result<T, E>
where
E: From<sqlx::Error>,
F: FnOnce() -> E,
{
match self {
Ok(value) => Ok(value),
Err(error) => match error.as_database_error().map(DatabaseError::kind) {
Some(ErrorKind::UniqueViolation) => Err(map()),
_ => Err(error.into()),
},
}
}
}
|