Fix migration checksum mismatch on startup
Add repair_migrations Tauri command that deletes stale migration 1 checksum from _sqlx_migrations before Database.load(). Migration 1 is idempotent (CREATE IF NOT EXISTS) so re-applying is safe. Fixes "migration 1 was previously applied but has been modified". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4328c2f929
commit
15d626cbbb
4 changed files with 55 additions and 0 deletions
|
|
@ -25,6 +25,7 @@ tauri-plugin-dialog = "2"
|
||||||
tauri-plugin-updater = "2"
|
tauri-plugin-updater = "2"
|
||||||
tauri-plugin-process = "2"
|
tauri-plugin-process = "2"
|
||||||
libsqlite3-sys = { version = "0.30", features = ["bundled"] }
|
libsqlite3-sys = { version = "0.30", features = ["bundled"] }
|
||||||
|
rusqlite = { version = "0.32", features = ["bundled"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
|
|
|
||||||
|
|
@ -155,3 +155,46 @@ pub fn verify_pin(pin: String, stored_hash: String) -> Result<bool, String> {
|
||||||
fn hex_encode(bytes: &[u8]) -> String {
|
fn hex_encode(bytes: &[u8]) -> String {
|
||||||
bytes.iter().map(|b| format!("{:02x}", b)).collect()
|
bytes.iter().map(|b| format!("{:02x}", b)).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Repair migration checksums for a profile database.
|
||||||
|
/// Deletes migration records that have mismatched checksums so they can be re-applied.
|
||||||
|
/// This fixes the "migration X was previously applied but has been modified" error.
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn repair_migrations(app: tauri::AppHandle, db_filename: String) -> Result<bool, String> {
|
||||||
|
let app_dir = app
|
||||||
|
.path()
|
||||||
|
.app_data_dir()
|
||||||
|
.map_err(|e| format!("Cannot get app data dir: {}", e))?;
|
||||||
|
let db_path = app_dir.join(&db_filename);
|
||||||
|
|
||||||
|
if !db_path.exists() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = rusqlite::Connection::open(&db_path)
|
||||||
|
.map_err(|e| format!("Cannot open database: {}", e))?;
|
||||||
|
|
||||||
|
// Check if _sqlx_migrations table exists
|
||||||
|
let table_exists: bool = conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT COUNT(*) > 0 FROM sqlite_master WHERE type='table' AND name='_sqlx_migrations'",
|
||||||
|
[],
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if !table_exists {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete migration 1 record so it can be cleanly re-applied
|
||||||
|
// Migration 1 is idempotent (CREATE IF NOT EXISTS + INSERT OR IGNORE)
|
||||||
|
let deleted = conn
|
||||||
|
.execute(
|
||||||
|
"DELETE FROM _sqlx_migrations WHERE version = 1",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.map_err(|e| format!("Cannot repair migrations: {}", e))?;
|
||||||
|
|
||||||
|
Ok(deleted > 0)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ pub fn run() {
|
||||||
commands::get_new_profile_init_sql,
|
commands::get_new_profile_init_sql,
|
||||||
commands::hash_pin,
|
commands::hash_pin,
|
||||||
commands::verify_pin,
|
commands::verify_pin,
|
||||||
|
commands::repair_migrations,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Database from "@tauri-apps/plugin-sql";
|
import Database from "@tauri-apps/plugin-sql";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
let dbInstance: Database | null = null;
|
let dbInstance: Database | null = null;
|
||||||
|
|
||||||
|
|
@ -14,6 +15,15 @@ export async function connectToProfile(dbFilename: string): Promise<void> {
|
||||||
await dbInstance.close();
|
await dbInstance.close();
|
||||||
dbInstance = null;
|
dbInstance = null;
|
||||||
}
|
}
|
||||||
|
// Repair migration checksums before loading (fixes "migration was modified" error)
|
||||||
|
try {
|
||||||
|
const repaired = await invoke<boolean>("repair_migrations", { dbFilename });
|
||||||
|
if (repaired) {
|
||||||
|
console.warn("Migration checksums repaired for", dbFilename);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Migration repair failed:", e);
|
||||||
|
}
|
||||||
dbInstance = await Database.load(`sqlite:${dbFilename}`);
|
dbInstance = await Database.load(`sqlite:${dbFilename}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue