mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-21 13:14:04 +02:00
feat: database migration system
This commit is contained in:
parent
2652f26271
commit
8f0d53d355
3 changed files with 86 additions and 15 deletions
|
@ -2,9 +2,15 @@ use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
use tokio::runtime::Runtime as TokioRuntime;
|
use tokio::runtime::Runtime as TokioRuntime;
|
||||||
|
use include_dir::{include_dir, Dir};
|
||||||
|
|
||||||
|
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/db/migrations");
|
||||||
|
|
||||||
pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let rt = TokioRuntime::new().expect("Failed to create Tokio runtime");
|
let rt = TokioRuntime::new().expect("Failed to create Tokio runtime");
|
||||||
|
app.manage(rt);
|
||||||
|
|
||||||
|
let rt = app.state::<TokioRuntime>();
|
||||||
|
|
||||||
let app_data_dir = app.path().app_data_dir().unwrap();
|
let app_data_dir = app.path().app_data_dir().unwrap();
|
||||||
fs::create_dir_all(&app_data_dir).expect("Failed to create app data directory");
|
fs::create_dir_all(&app_data_dir).expect("Failed to create app data directory");
|
||||||
|
@ -24,8 +30,10 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.expect("Failed to create pool")
|
.expect("Failed to create pool")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.manage(pool.clone());
|
||||||
|
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
apply_schema(&pool).await?;
|
apply_migrations(&pool).await?;
|
||||||
if is_new_db {
|
if is_new_db {
|
||||||
if let Err(e) = super::history::initialize_history(&pool).await {
|
if let Err(e) = super::history::initialize_history(&pool).await {
|
||||||
eprintln!("Failed to initialize history: {}", e);
|
eprintln!("Failed to initialize history: {}", e);
|
||||||
|
@ -37,26 +45,75 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
Ok::<(), Box<dyn std::error::Error>>(())
|
Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
app.manage(pool);
|
|
||||||
app.manage(rt);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn apply_schema(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
|
async fn apply_migrations(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let schema = include_str!("scheme.sql");
|
println!("Starting migration process");
|
||||||
|
|
||||||
let statements: Vec<&str> = schema
|
// Create schema_version table
|
||||||
.split(';')
|
sqlx::query(
|
||||||
.map(|s| s.trim())
|
"CREATE TABLE IF NOT EXISTS schema_version (
|
||||||
.filter(|s| !s.is_empty())
|
version INTEGER PRIMARY KEY,
|
||||||
|
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);"
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let current_version: Option<i64> = sqlx::query_scalar(
|
||||||
|
"SELECT MAX(version) FROM schema_version"
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let current_version = current_version.unwrap_or(0);
|
||||||
|
println!("Current database version: {}", current_version);
|
||||||
|
|
||||||
|
let mut migration_files: Vec<(i64, &str)> = MIGRATIONS_DIR
|
||||||
|
.files()
|
||||||
|
.filter_map(|file| {
|
||||||
|
let file_name = file.path().file_name()?.to_str()?;
|
||||||
|
println!("Processing file: {}", file_name);
|
||||||
|
if file_name.ends_with(".sql") && file_name.starts_with("migration") {
|
||||||
|
let version: i64 = file_name
|
||||||
|
.trim_start_matches("migration")
|
||||||
|
.trim_end_matches(".sql")
|
||||||
|
.parse()
|
||||||
|
.ok()?;
|
||||||
|
println!("Found migration version: {}", version);
|
||||||
|
Some((version, file.contents_utf8()?))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for statement in statements {
|
migration_files.sort_by_key(|(version, _)| *version);
|
||||||
sqlx::query(statement)
|
|
||||||
.execute(pool)
|
for (version, content) in migration_files {
|
||||||
.await
|
if version > current_version {
|
||||||
.map_err(|e| format!("Failed to execute schema statement: {}", e))?;
|
println!("Applying migration {}", version);
|
||||||
|
|
||||||
|
let statements: Vec<&str> = content
|
||||||
|
.split(';')
|
||||||
|
.map(|s| s.trim())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for statement in statements {
|
||||||
|
println!("Executing statement: {}", statement);
|
||||||
|
sqlx::query(statement)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to execute migration {}: {}", version, e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query("INSERT INTO schema_version (version) VALUES (?)")
|
||||||
|
.bind(version)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
12
src-tauri/src/db/migrations/migration1.sql
Normal file
12
src-tauri/src/db/migrations/migration1.sql
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS history (
|
||||||
|
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
||||||
|
content_type TEXT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
favicon TEXT,
|
||||||
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
2
src-tauri/src/db/migrations/migration2.sql
Normal file
2
src-tauri/src/db/migrations/migration2.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE history ADD COLUMN source TEXT DEFAULT 'System' NOT NULL;
|
||||||
|
ALTER TABLE history ADD COLUMN source_icon TEXT;
|
Loading…
Add table
Add a link
Reference in a new issue