feat(saas): add down migrations for all incremental schema changes (AUD3-DB-01)
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
- 16 down SQL files in migrations/down/ for each incremental migration - db::run_down_migrations() executes rollback files in reverse order - migrate_down CLI task: task=migrate_down timestamp=20260402 - Initial schema and seed data excluded (would be destructive)
This commit is contained in:
@@ -123,6 +123,50 @@ async fn run_migration_files(pool: &PgPool, dir: &std::path::Path) -> SaasResult
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 从 down/ 目录加载并执行回滚迁移文件(按文件名倒序)
|
||||
///
|
||||
/// 用法: `task=migrate_down timestamp=20260402000001`
|
||||
/// 会回滚 >= 该时间戳的所有增量迁移。不包含初始 schema 和 seed data。
|
||||
pub async fn run_down_migrations(pool: &PgPool, target: &str) -> SaasResult<()> {
|
||||
let down_dir = std::path::Path::new("crates/zclaw-saas/migrations/down");
|
||||
let alt_dir = std::path::Path::new("migrations/down");
|
||||
let dir = if down_dir.exists() { down_dir } else if alt_dir.exists() { alt_dir } else {
|
||||
tracing::warn!("No down migrations directory found");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let mut entries: Vec<std::path::PathBuf> = std::fs::read_dir(dir)?
|
||||
.filter_map(|e| e.ok())
|
||||
.map(|e| e.path())
|
||||
.filter(|p| {
|
||||
p.extension().map(|ext| ext == "sql").unwrap_or(false)
|
||||
&& p.file_name()
|
||||
.map(|n| n.to_string_lossy().starts_with(target))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.collect();
|
||||
entries.sort_by(|a, b| b.cmp(a)); // Reverse order (newest first)
|
||||
|
||||
if entries.is_empty() {
|
||||
tracing::info!("No down migrations match prefix '{}'", target);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for path in &entries {
|
||||
let filename = path.file_name().unwrap_or_default().to_string_lossy();
|
||||
tracing::info!("Running down migration: {}", filename);
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
for stmt in split_sql_statements(&content) {
|
||||
let trimmed = stmt.trim();
|
||||
if !trimmed.is_empty() && !trimmed.starts_with("--") {
|
||||
sqlx::query(trimmed).execute(pool).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
tracing::info!("Down migrations complete (prefix={})", target);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 按语句分割 SQL 文件内容,正确处理:
|
||||
/// - 单引号字符串 `'...'`
|
||||
/// - 双引号标识符 `"..."`
|
||||
|
||||
Reference in New Issue
Block a user