diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2024-10-04 22:12:15 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2024-10-04 22:38:25 -0400 |
| commit | b422be184e01b4cc35b9c9a6921379080c24edb3 (patch) | |
| tree | b35bec7a4025e9874ba2683e3b9b8da4447c2fb0 /src | |
| parent | 9bd6d9862b1c243def02200bca2cfbf578ad2a2f (diff) | |
Start fresh with database migrations.
The migration path from the original project inception to now was complicated and buggy, and stranded _both_ Kit and I with broken databases due to oversights and incomplete migrations. We've agreed to start fresh, once.
If this is mistakenly started with an original-schema-flavour DB, startup will be aborted.
Diffstat (limited to 'src')
| -rw-r--r-- | src/cli.rs | 9 | ||||
| -rw-r--r-- | src/db.rs | 65 |
2 files changed, 67 insertions, 7 deletions
@@ -99,7 +99,7 @@ impl Args { (self.address.as_str(), self.port) } - async fn pool(&self) -> sqlx::Result<SqlitePool> { + async fn pool(&self) -> Result<SqlitePool, db::Error> { db::prepare(&self.database_url).await } } @@ -126,9 +126,6 @@ fn started_msg(listener: &net::TcpListener) -> io::Result<String> { pub enum Error { /// Failure due to `io::Error`. See [`io::Error`]. IoError(#[from] io::Error), - /// Failure due to a database error. See [`sqlx::Error`]. - DatabaseError(#[from] sqlx::Error), - /// Failure due to a database migration error. See - /// [`sqlx::migrate::MigrateError`]. - MigrateError(#[from] sqlx::migrate::MigrateError), + /// Failure due to a database initialization error. See [`db::Error`]. + Database(#[from] db::Error), } @@ -2,8 +2,13 @@ use std::str::FromStr; use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions}; -pub async fn prepare(url: &str) -> sqlx::Result<SqlitePool> { +pub async fn prepare(url: &str) -> Result<SqlitePool, Error> { let pool = create(url).await?; + + // First migration of original migration series, from commit + // 9bd6d9862b1c243def02200bca2cfbf578ad2a2f or earlier. + reject_migration(&pool, "20240831024047", "login", "9949D238C4099295EC4BEE734BFDA8D87513B2973DFB895352A11AB01DD46CB95314B7F1B3431B77E3444A165FE3DC28").await?; + sqlx::migrate!().run(&pool).await?; Ok(pool) } @@ -17,6 +22,64 @@ async fn create(database_url: &str) -> sqlx::Result<SqlitePool> { Ok(pool) } +async fn reject_migration( + pool: &SqlitePool, + version: &str, + description: &str, + checksum_hex: &str, +) -> Result<(), Error> { + if !sqlx::query_scalar!( + r#" + select count(*) as "exists: bool" + from sqlite_master + where name = '_sqlx_migrations' + "# + ) + .fetch_one(pool) + .await? + { + // No migrations table; this is a fresh DB. + return Ok(()); + } + + if !sqlx::query_scalar!( + r#" + select count(*) as "exists: bool" + from _sqlx_migrations + where version = $1 + and description = $2 + and hex(checksum) = $3 + "#, + version, + description, + checksum_hex, + ) + .fetch_one(pool) + .await? + { + // Rejected migration does not exist; this DB never ran it. + return Ok(()); + } + + Err(Error::Rejected(version.into(), description.into())) +} + +/// Errors occurring during database setup. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Failure due to a database error. See [`sqlx::Error`]. + #[error(transparent)] + Database(#[from] sqlx::Error), + /// Failure due to a database migration error. See + /// [`sqlx::migrate::MigrateError`]. + #[error(transparent)] + Migration(#[from] sqlx::migrate::MigrateError), + /// Failure because the database contains a migration from an unsupported + /// schema version. + #[error("database contains rejected migration {0}:{1}, move it aside")] + Rejected(String, String), +} + pub trait NotFound { type Ok; fn not_found<E, F>(self, map: F) -> Result<Self::Ok, E> |
