migrated migration logic from rusqlite
to sqlx
and updated relevant async methods for better database interaction
This commit is contained in:
1955
backend-rust/Cargo.lock
generated
1955
backend-rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,5 +6,7 @@ edition = "2024"
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-rusqlite = "0.6.0"
|
||||
rusqlite = "=0.32.0"
|
||||
axum = "0.8.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-native-tls", "sqlite", "macros", "migrate", "chrono", "json"] }
|
||||
|
@@ -1,13 +1,16 @@
|
||||
use anyhow::Result;
|
||||
use std::path::{Path};
|
||||
use tokio_rusqlite::Connection as AsyncConn;
|
||||
use crate::migrations::Migrator;
|
||||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
use sqlx::sqlite::{SqliteConnectOptions, SqliteConnection};
|
||||
use sqlx::Connection;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub async fn initialize_db(db_path: &Path, migrations_dir: &Path) -> Result<AsyncConn> {
|
||||
let conn = AsyncConn::open(db_path).await?;
|
||||
pub async fn initialize_db(db_path: &Path, migrations_dir: &Path) -> Result<SqliteConnection> {
|
||||
let options = SqliteConnectOptions::from_str(&format!("sqlite:{}", db_path.display()))?.create_if_missing(true);
|
||||
let mut conn = sqlx::SqliteConnection::connect_with(&options).await?;
|
||||
let migrator = Migrator::new(migrations_dir.to_path_buf())?;
|
||||
|
||||
migrator.migrate_up_async(&conn).await?;
|
||||
migrator.migrate_up_async(&mut conn).await?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
@@ -1,10 +1,9 @@
|
||||
use anyhow::{Context, Result};
|
||||
use rusqlite::{Connection, params};
|
||||
use sqlx::sqlite::SqliteConnection;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tokio::fs;
|
||||
use tokio_rusqlite::Connection as AsyncConn;
|
||||
|
||||
pub struct Migration {
|
||||
pub version: i64,
|
||||
@@ -24,33 +23,40 @@ impl Migrator {
|
||||
Ok(Migrator { migrations_dir })
|
||||
}
|
||||
|
||||
fn initialize(&self, conn: &mut Connection) -> Result<()> {
|
||||
let tx = conn
|
||||
.transaction()
|
||||
async fn initialize(&self, conn: &mut SqliteConnection) -> Result<()> {
|
||||
let mut tx = sqlx::Connection::begin(conn)
|
||||
.await
|
||||
.context("Failed to start transaction for initialization")?;
|
||||
|
||||
tx.execute(
|
||||
"CREATE TABLE IF NOT EXISTS migrations (version INTEGER PRIMARY KEY)",
|
||||
[],
|
||||
)
|
||||
.context("Failed to create migrations table")?;
|
||||
sqlx::query("CREATE TABLE IF NOT EXISTS migrations (version INTEGER PRIMARY KEY)")
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.context("Failed to create migrations table")?;
|
||||
|
||||
let columns: HashSet<String> = {
|
||||
let mut stmt = tx.prepare("PRAGMA table_info(migrations)")?;
|
||||
stmt.query_map([], |row| row.get(1))?
|
||||
.collect::<Result<HashSet<String>, _>>()?
|
||||
let rows: Vec<(i32, String, String, i32, Option<String>, i32)> =
|
||||
sqlx::query_as("PRAGMA table_info(migrations)")
|
||||
.fetch_all(&mut *tx)
|
||||
.await
|
||||
.context("Failed to get migrations table info")?;
|
||||
rows.into_iter().map(|row| row.1).collect()
|
||||
};
|
||||
|
||||
if !columns.contains("name") {
|
||||
tx.execute("ALTER TABLE migrations ADD COLUMN name TEXT", [])
|
||||
sqlx::query("ALTER TABLE migrations ADD COLUMN name TEXT")
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.context("Failed to add 'name' column to migrations table")?;
|
||||
}
|
||||
if !columns.contains("applied_at") {
|
||||
tx.execute("ALTER TABLE migrations ADD COLUMN applied_at INTEGER", [])
|
||||
sqlx::query("ALTER TABLE migrations ADD COLUMN applied_at INTEGER")
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.context("Failed to add 'applied_at' column to migrations table")?;
|
||||
}
|
||||
|
||||
tx.commit()
|
||||
.await
|
||||
.context("Failed to commit migrations table initialization")?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -100,70 +106,60 @@ impl Migrator {
|
||||
Ok(migrations)
|
||||
}
|
||||
|
||||
pub fn get_applied_migrations(&self, conn: &mut Connection) -> Result<HashSet<i64>> {
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT version FROM migrations ORDER BY version")
|
||||
.context("Failed to prepare query for applied migrations")?;
|
||||
let versions = stmt
|
||||
.query_map([], |row| row.get(0))?
|
||||
.collect::<Result<HashSet<i64>, _>>()?;
|
||||
pub async fn get_applied_migrations<'a, E>(&self, executor: E) -> Result<HashSet<i64>>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Sqlite>,
|
||||
{
|
||||
let versions =
|
||||
sqlx::query_as::<_, (i64,)>("SELECT version FROM migrations ORDER BY version")
|
||||
.fetch_all(executor)
|
||||
.await
|
||||
.context("Failed to get applied migrations")?
|
||||
.into_iter()
|
||||
.map(|row| row.0)
|
||||
.collect();
|
||||
Ok(versions)
|
||||
}
|
||||
|
||||
pub async fn migrate_up_async(&self, async_conn: &AsyncConn) -> Result<()> {
|
||||
pub async fn migrate_up_async(&self, conn: &mut SqliteConnection) -> Result<()> {
|
||||
let migrations = self.load_migrations_async().await?;
|
||||
let migrator = self.clone();
|
||||
|
||||
// Perform all database operations within a blocking-safe context
|
||||
async_conn
|
||||
.call(move |conn| {
|
||||
migrator.initialize(conn).expect("TODO: panic message");
|
||||
let applied = migrator
|
||||
.get_applied_migrations(conn)
|
||||
.expect("TODO: panic message");
|
||||
self.initialize(conn).await?;
|
||||
let applied = self.get_applied_migrations(&mut *conn).await?;
|
||||
|
||||
let tx = conn
|
||||
.transaction()
|
||||
.context("Failed to start transaction for migrations")
|
||||
.expect("TODO: panic message");
|
||||
let mut tx = sqlx::Connection::begin(conn)
|
||||
.await
|
||||
.context("Failed to start transaction for migrations")?;
|
||||
|
||||
for migration in migrations {
|
||||
if !applied.contains(&migration.version) {
|
||||
println!(
|
||||
"Applying migration {}: {}",
|
||||
for migration in migrations {
|
||||
if !applied.contains(&migration.version) {
|
||||
println!(
|
||||
"Applying migration {}: {}",
|
||||
migration.version, migration.name
|
||||
);
|
||||
|
||||
sqlx::query(&migration.sql_up)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to apply migration {}: {}",
|
||||
migration.version, migration.name
|
||||
);
|
||||
|
||||
tx.execute_batch(&migration.sql_up)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to execute migration {}: {}",
|
||||
migration.version, migration.name
|
||||
)
|
||||
})
|
||||
.expect("TODO: panic message");
|
||||
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("TODO: panic message")
|
||||
.as_secs() as i64;
|
||||
tx.execute(
|
||||
"INSERT INTO migrations (version, name, applied_at) VALUES (?, ?, ?)",
|
||||
params![migration.version, migration.name, now],
|
||||
)
|
||||
.with_context(|| {
|
||||
format!("Failed to record migration {}", migration.version)
|
||||
})
|
||||
.expect("TODO: panic message");
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
tx.commit()
|
||||
.context("Failed to commit migrations")
|
||||
.expect("TODO: panic message");
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;
|
||||
sqlx::query("INSERT INTO migrations (version, name, applied_at) VALUES (?, ?, ?)")
|
||||
.bind(migration.version)
|
||||
.bind(&migration.name.clone())
|
||||
.bind(now)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.with_context(|| format!("Failed to record migration {}", migration.version))?;
|
||||
}
|
||||
}
|
||||
|
||||
tx.commit().await.context("Failed to commit migrations")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -171,74 +167,61 @@ impl Migrator {
|
||||
#[allow(dead_code)]
|
||||
pub async fn migrate_down_async(
|
||||
&self,
|
||||
async_conn: &AsyncConn,
|
||||
conn: &mut SqliteConnection,
|
||||
target_version: Option<i64>,
|
||||
) -> Result<()> {
|
||||
let migrations = self.load_migrations_async().await?;
|
||||
let migrator = self.clone();
|
||||
|
||||
// Perform all database operations within a blocking-safe context
|
||||
async_conn
|
||||
.call(move |conn| {
|
||||
migrator.initialize(conn).expect("TODO: panic message");
|
||||
let applied = migrator
|
||||
.get_applied_migrations(conn)
|
||||
.expect("TODO: panic message");
|
||||
self.initialize(conn).await?;
|
||||
let applied = self.get_applied_migrations(&mut *conn).await?;
|
||||
|
||||
// If no target specified, roll back only the latest migration
|
||||
let max_applied = *applied.iter().max().unwrap_or(&0);
|
||||
let target =
|
||||
target_version.unwrap_or(if max_applied > 0 { max_applied - 1 } else { 0 });
|
||||
// If no target specified, roll back only the latest migration
|
||||
let max_applied = *applied.iter().max().unwrap_or(&0);
|
||||
let target = target_version.unwrap_or(if max_applied > 0 { max_applied - 1 } else { 0 });
|
||||
|
||||
let tx = conn
|
||||
.transaction()
|
||||
.context("Failed to start transaction for migrations")
|
||||
.expect("TODO: panic message");
|
||||
let mut tx = sqlx::Connection::begin(conn)
|
||||
.await
|
||||
.context("Failed to start transaction for migrations")?;
|
||||
|
||||
// Find migrations to roll back (in reverse order)
|
||||
let mut to_rollback: Vec<&Migration> = migrations
|
||||
.iter()
|
||||
.filter(|m| applied.contains(&m.version) && m.version > target)
|
||||
.collect();
|
||||
// Find migrations to roll back (in reverse order)
|
||||
let mut to_rollback: Vec<&Migration> = migrations
|
||||
.iter()
|
||||
.filter(|m| applied.contains(&m.version) && m.version > target)
|
||||
.collect();
|
||||
|
||||
to_rollback.sort_by_key(|m| std::cmp::Reverse(m.version));
|
||||
to_rollback.sort_by_key(|m| std::cmp::Reverse(m.version));
|
||||
|
||||
for migration in to_rollback {
|
||||
println!(
|
||||
"Rolling back migration {}: {}",
|
||||
migration.version, migration.name
|
||||
);
|
||||
for migration in to_rollback {
|
||||
println!(
|
||||
"Rolling back migration {}: {}",
|
||||
migration.version, migration.name
|
||||
);
|
||||
|
||||
if !migration.sql_down.is_empty() {
|
||||
tx.execute_batch(&migration.sql_down)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to rollback migration {}: {}",
|
||||
migration.version, migration.name
|
||||
)
|
||||
})
|
||||
.expect("TODO: panic message");
|
||||
} else {
|
||||
println!("Warning: No down migration defined for {}", migration.name);
|
||||
}
|
||||
|
||||
// Remove the migration record
|
||||
tx.execute(
|
||||
"DELETE FROM migrations WHERE version = ?",
|
||||
[&migration.version],
|
||||
)
|
||||
if !migration.sql_down.is_empty() {
|
||||
sqlx::query(&migration.sql_down)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to remove migration record {}", migration.version)
|
||||
})
|
||||
.expect("TODO: panic message");
|
||||
}
|
||||
format!(
|
||||
"Failed to rollback migration {}: {}",
|
||||
migration.version, migration.name
|
||||
)
|
||||
})?;
|
||||
} else {
|
||||
println!("Warning: No down migration defined for {}", migration.name);
|
||||
}
|
||||
|
||||
tx.commit()
|
||||
.context("Failed to commit rollback")
|
||||
.expect("TODO: panic message");
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
// Remove the migration record
|
||||
sqlx::query("DELETE FROM migrations WHERE version = ?")
|
||||
.bind(migration.version)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to remove migration record {}", migration.version)
|
||||
})?;
|
||||
}
|
||||
|
||||
tx.commit().await.context("Failed to commit rollback")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Reference in New Issue
Block a user