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>(mut self, sql: T) -> Self { self.before.extend_from_slice(sql.as_ref()); self } /// 实体表达不了的约束、复合索引等补充 SQL。 pub fn after>(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 } }