"db newer than app" guard
This commit is contained in:
+82
-1
@@ -1,9 +1,12 @@
|
|||||||
|
use sqlx::migrate::Migrator;
|
||||||
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};
|
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};
|
||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use tauri::{AppHandle, Manager};
|
use tauri::{AppHandle, Manager};
|
||||||
|
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind};
|
||||||
|
|
||||||
const DATABASE_FILE: &str = "friendolls.sqlite";
|
const DATABASE_FILE: &str = "friendolls.sqlite";
|
||||||
|
static MIGRATOR: Migrator = sqlx::migrate!("./migrations");
|
||||||
|
|
||||||
pub struct AppDatabase {
|
pub struct AppDatabase {
|
||||||
pool: Pool<Sqlite>,
|
pool: Pool<Sqlite>,
|
||||||
@@ -35,14 +38,92 @@ pub async fn init(app: &AppHandle) -> Result<(), Box<dyn Error>> {
|
|||||||
.connect_with(options)
|
.connect_with(options)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
sqlx::migrate!("./migrations").run(&pool).await?;
|
if database_requires_newer_app(&pool).await? {
|
||||||
|
show_outdated_app_dialog(app);
|
||||||
|
return std::future::pending().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
MIGRATOR.run(&pool).await?;
|
||||||
|
|
||||||
app.manage(AppDatabase::new(pool));
|
app.manage(AppDatabase::new(pool));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn database_requires_newer_app(pool: &Pool<Sqlite>) -> Result<bool, sqlx::Error> {
|
||||||
|
let has_migration_history = sqlx::query_scalar::<_, bool>(
|
||||||
|
"SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '_sqlx_migrations')",
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !has_migration_history {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let latest_applied =
|
||||||
|
sqlx::query_scalar::<_, Option<i64>>("SELECT MAX(version) FROM _sqlx_migrations")
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
let latest_supported = MIGRATOR.iter().map(|migration| migration.version).max();
|
||||||
|
|
||||||
|
Ok(latest_applied
|
||||||
|
.zip(latest_supported)
|
||||||
|
.is_some_and(|(applied, supported)| applied > supported))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_outdated_app_dialog(app: &AppHandle) {
|
||||||
|
app.dialog()
|
||||||
|
.message(
|
||||||
|
"Your on-device data was created by a newer version of Friendolls and cannot be opened safely by this version.\n\nDownload and install the latest version of Friendolls, then reopen the app. Your data has not been changed.",
|
||||||
|
)
|
||||||
|
.title("Friendolls is out of date")
|
||||||
|
.kind(MessageDialogKind::Warning)
|
||||||
|
.buttons(MessageDialogButtons::OkCustom("Close Friendolls".into()))
|
||||||
|
.show(move |_| std::process::exit(0));
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn command_error(error: impl std::fmt::Display) -> String {
|
pub fn command_error(error: impl std::fmt::Display) -> String {
|
||||||
error.to_string()
|
error.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn database_without_migration_history_does_not_require_newer_app() {
|
||||||
|
let pool = SqlitePoolOptions::new()
|
||||||
|
.connect("sqlite::memory:")
|
||||||
|
.await
|
||||||
|
.expect("connect to in-memory database");
|
||||||
|
|
||||||
|
assert!(!database_requires_newer_app(&pool).await.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn database_with_newer_migration_requires_newer_app() {
|
||||||
|
let pool = SqlitePoolOptions::new()
|
||||||
|
.connect("sqlite::memory:")
|
||||||
|
.await
|
||||||
|
.expect("connect to in-memory database");
|
||||||
|
sqlx::query("CREATE TABLE _sqlx_migrations (version BIGINT PRIMARY KEY)")
|
||||||
|
.execute(&pool)
|
||||||
|
.await
|
||||||
|
.expect("create migration history");
|
||||||
|
let newer_version = MIGRATOR
|
||||||
|
.iter()
|
||||||
|
.map(|migration| migration.version)
|
||||||
|
.max()
|
||||||
|
.expect("at least one embedded migration")
|
||||||
|
+ 1;
|
||||||
|
sqlx::query("INSERT INTO _sqlx_migrations (version) VALUES (?)")
|
||||||
|
.bind(newer_version)
|
||||||
|
.execute(&pool)
|
||||||
|
.await
|
||||||
|
.expect("insert newer migration");
|
||||||
|
|
||||||
|
assert!(database_requires_newer_app(&pool).await.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user