ensure schema version DOESNT FUCK THE MIGRATION OVER
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
impl Store {
|
impl Store {
|
||||||
pub async fn migrate_sqlite_state(
|
pub async fn migrate_sqlite_state(
|
||||||
@@ -12,71 +13,46 @@ impl Store {
|
|||||||
from_sqlite_state.display()
|
from_sqlite_state.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if to_state_file.is_dir() {
|
|
||||||
|
// 1. Safety check for destination
|
||||||
|
if to_state_file.exists() && !force {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"target state file {} is a directory",
|
"target state file {} already exists; use --force to overwrite",
|
||||||
to_state_file.display()
|
to_state_file.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let is_same_file = match (
|
|
||||||
std::fs::canonicalize(from_sqlite_state),
|
|
||||||
std::fs::canonicalize(to_state_file),
|
|
||||||
) {
|
|
||||||
(Ok(from_canon), Ok(to_canon)) => from_canon == to_canon,
|
|
||||||
_ => from_sqlite_state == to_state_file,
|
|
||||||
};
|
|
||||||
|
|
||||||
let actual_from_path = if is_same_file {
|
// 2. Setup temporary migration target
|
||||||
let mut bak = to_state_file.to_path_buf();
|
let mut temp_target = to_state_file.to_path_buf();
|
||||||
let mut file_name = bak.file_name().unwrap_or_default().to_os_string();
|
let mut temp_name = temp_target.file_name().unwrap_or_default().to_os_string();
|
||||||
file_name.push(".bak");
|
temp_name.push(".migration_tmp");
|
||||||
bak.set_file_name(file_name);
|
temp_target.set_file_name(temp_name);
|
||||||
|
|
||||||
if bak.exists() {
|
if temp_target.exists() {
|
||||||
if !force {
|
std::fs::remove_file(&temp_target)
|
||||||
anyhow::bail!(
|
.context("failed to clean up stale migration temp file")?;
|
||||||
"backup file {} already exists; re-run with --force to overwrite",
|
|
||||||
bak.display()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
std::fs::remove_file(&bak).with_context(|| {
|
|
||||||
format!("failed to remove existing backup {}", bak.display())
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::fs::rename(from_sqlite_state, &bak)
|
// 3. Initialize fresh schema on the temp file
|
||||||
.with_context(|| "failed to rename legacy state for in-place migration")?;
|
let store = Store::load_or_init(&temp_target, Vec::new(), Duration::from_secs(1)).await?;
|
||||||
bak
|
|
||||||
} else {
|
|
||||||
if to_state_file.exists()
|
|
||||||
&& to_state_file
|
|
||||||
.metadata()
|
|
||||||
.with_context(|| format!("failed to stat {}", to_state_file.display()))?
|
|
||||||
.len()
|
|
||||||
> 0
|
|
||||||
{
|
|
||||||
if !force {
|
|
||||||
anyhow::bail!(
|
|
||||||
"target SQLite state file {} already exists and is non-empty; re-run with --force to overwrite",
|
|
||||||
to_state_file.display()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
std::fs::remove_file(to_state_file)
|
|
||||||
.with_context(|| format!("failed to remove {}", to_state_file.display()))?;
|
|
||||||
}
|
|
||||||
from_sqlite_state.to_path_buf()
|
|
||||||
};
|
|
||||||
|
|
||||||
let store = Store::load_or_init(to_state_file, Vec::new(), Duration::from_secs(1)).await?;
|
let from_path_str = from_sqlite_state
|
||||||
|
|
||||||
let from_path_str = actual_from_path
|
|
||||||
.to_str()
|
.to_str()
|
||||||
.ok_or_else(|| anyhow::anyhow!("invalid path"))?;
|
.ok_or_else(|| anyhow::anyhow!("invalid legacy path"))?;
|
||||||
|
|
||||||
|
// 4. Attach legacy and migrate
|
||||||
sqlx::query(&format!("ATTACH DATABASE '{}' AS legacy", from_path_str))
|
sqlx::query(&format!("ATTACH DATABASE '{}' AS legacy", from_path_str))
|
||||||
.execute(&store.pool)
|
.execute(&store.pool)
|
||||||
.await
|
.await
|
||||||
.with_context(|| "failed to attach legacy database")?;
|
.with_context(|| format!("failed to attach legacy database at {}", from_path_str))?;
|
||||||
|
|
||||||
|
let legacy_tables: Vec<String> =
|
||||||
|
sqlx::query_scalar("SELECT name FROM legacy.sqlite_master WHERE type='table'")
|
||||||
|
.fetch_all(&store.pool)
|
||||||
|
.await
|
||||||
|
.context("failed to list tables in legacy database")?;
|
||||||
|
|
||||||
|
debug!(?legacy_tables, "found tables in legacy database");
|
||||||
|
|
||||||
let tables = [
|
let tables = [
|
||||||
"meta",
|
"meta",
|
||||||
@@ -91,7 +67,15 @@ impl Store {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for table in tables {
|
for table in tables {
|
||||||
let q = format!("INSERT INTO {} SELECT * FROM legacy.{}", table, table);
|
if !legacy_tables.contains(&table.to_string()) {
|
||||||
|
warn!(table, "table missing in legacy database; skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let q = format!(
|
||||||
|
"INSERT OR IGNORE INTO {} SELECT * FROM legacy.{}",
|
||||||
|
table, table
|
||||||
|
);
|
||||||
sqlx::query(&q)
|
sqlx::query(&q)
|
||||||
.execute(&store.pool)
|
.execute(&store.pool)
|
||||||
.await
|
.await
|
||||||
@@ -100,7 +84,29 @@ impl Store {
|
|||||||
|
|
||||||
sqlx::query("DETACH DATABASE legacy")
|
sqlx::query("DETACH DATABASE legacy")
|
||||||
.execute(&store.pool)
|
.execute(&store.pool)
|
||||||
.await?;
|
.await
|
||||||
|
.context("failed to detach legacy database")?;
|
||||||
|
|
||||||
|
// 5. Finalize: Force a checkpoint to roll up WAL, then close and move
|
||||||
|
sqlx::query("PRAGMA wal_checkpoint(TRUNCATE)")
|
||||||
|
.execute(&store.pool)
|
||||||
|
.await
|
||||||
|
.context("failed to checkpoint WAL before finalization")?;
|
||||||
|
|
||||||
|
store.pool.close().await;
|
||||||
|
|
||||||
|
// Clean up any "ghost" WAL/SHM files at the destination to prevent corruption
|
||||||
|
let to_path_str = to_state_file.to_string_lossy();
|
||||||
|
let _ = std::fs::remove_file(format!("{}-wal", to_path_str));
|
||||||
|
let _ = std::fs::remove_file(format!("{}-shm", to_path_str));
|
||||||
|
|
||||||
|
std::fs::rename(&temp_target, to_state_file)
|
||||||
|
.context("failed to move migrated database into final location")?;
|
||||||
|
|
||||||
|
// Also clean up any leftover temp WAL/SHM files just in case
|
||||||
|
let temp_path_str = temp_target.to_string_lossy();
|
||||||
|
let _ = std::fs::remove_file(format!("{}-wal", temp_path_str));
|
||||||
|
let _ = std::fs::remove_file(format!("{}-shm", temp_path_str));
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
from = %from_sqlite_state.display(),
|
from = %from_sqlite_state.display(),
|
||||||
|
|||||||
Reference in New Issue
Block a user