Skip to content
Clone
main
59 lines 2.3 KB Rust
use models::migrate::MIGRATION_LOCK_KEY;
use sea_orm::{ConnectionTrait, DatabaseConnection, DbErr, TransactionTrait};
/// 实体优先迁移:实体定义是表结构的唯一真相。
///
/// 每次 app 启动执行一次:先跑实体表达不了的兼容性 SQL,再按实体注册表同步
/// (只新增缺失的表/列/唯一键/外键,幂等),最后补实体表达不了的约束与索引。
/// 整个过程在单个事务内、同一数据库上以 advisory lock 串行化。
///
/// `sea_orm::SchemaBuilder::sync` 会把库里有、实体里没有的单列唯一键补上,也会把
/// 库里存在、实体表达不出来的唯一索引当过期索引删掉(Postgres 上走
/// `ALTER TABLE .. DROP CONSTRAINT`,普通索引会直接报错)。因此复合唯一索引、
/// 带谓词的部分唯一索引必须自己管:`before` 里 `DROP INDEX IF EXISTS`,`after` 里
/// `CREATE UNIQUE INDEX IF NOT EXISTS`。
pub struct EntityFirst<'a> {
before: Vec<&'a str>,
entities: Vec<&'a str>,
after: Vec<&'a str>,
}
impl<'a> EntityFirst<'a> {
pub fn new(entities: &[&'a str]) -> Self {
Self {
before: Vec::new(),
entities: entities.to_vec(),
after: Vec::new(),
}
}
/// 实体同步前必须完成的 SQL(老表补列等),保证实体同步能落地。
pub fn before<T: AsRef<[&'a str]>>(mut self, sql: T) -> Self {
self.before.extend_from_slice(sql.as_ref());
self
}
/// 实体表达不了的约束、复合索引等补充 SQL。
pub fn after<T: AsRef<[&'a str]>>(mut self, sql: T) -> Self {
self.after.extend_from_slice(sql.as_ref());
self
}
pub async fn run(&self, db: &DatabaseConnection) -> Result<(), DbErr> {
let txn = db.begin().await?;
txn.execute_unprepared(&format!(
"SELECT pg_advisory_xact_lock({MIGRATION_LOCK_KEY})"
))
.await?;
for statement in &self.before {
txn.execute_unprepared(statement).await?;
}
for prefix in &self.entities {
db.get_schema_registry(prefix).sync(&txn).await?;
}
for statement in &self.after {
txn.execute_unprepared(statement).await?;
}
txn.commit().await
}
}