编写迁移
1. 迁移系统的核心配置:MigratorTrait
迁移文件写好后,如果不进行 注册 ,系统是找不到它们的。这是通过 migration/src/lib.rs 实现的。
Migrator结构体 :它是整个迁移的控制塔。migrations函数 :- 它返回一个
Vec<Box<dyn MigrationTrait>>。 - 重要细节 :必须手动将每一个迁移文件(如
m20220101_...::Migration)用Box::new()包裹并放入数组。 - 顺序要求 :数组内的顺序必须是 时间顺序 。SeaORM 依赖这个顺序来依次执行。
- 它返回一个
// migration/src/lib.rs 完整还原
pub struct Migrator;
#[async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Box::new(m20220101_000001_create_table::Migration),
// 每一个新生成的迁移文件,都要手动在这里“挂号”
]
}
}
2. 深入 SchemaManager
你在 up 和 down 函数中使用的 manager 对象提供了极丰富的 API。
A. 结构创建 (Creation)
use sea_orm_migration::{prelude::*, schema::*};
manager
.create_table(
Table::create()
.table("post")
.if_not_exists()
.col(pk_auto("id"))
.col(string("title"))
.col(string("text"))
.col(enumeration_null("category", "category", ["Feed", "Store"]))
)
.await
除了 create_table,还有这些你可能忽略的细节:
create_index:创建索引,eg:manager.create_index(sea_query::Index::create()..)。create_foreign_key:创建外键约束, eg:manager.create_foreign_key(sea_query::ForeignKey::create()..)。create_type(PostgreSQL 专有) :
use sea_orm_migration::prelude::extension::postgres::Type;
manager
.create_type(
Type::create()
.as_enum(CategoryEnum)
.values(["feed", "story"])
.to_owned()
)
.await?;
B. 结构变更与删除
drop_table:删除表,eg:manager.drop_table(sea_query::Table::drop()..)alter_table:修改表结构(加字段、改类型)eg:manager.alter_table(sea_query::Table::alter()..)rename_table:重命名。eg:manager.rename_table(sea_query::Table::rename()..)truncate_table:清空表数据但保留结构。eg:manager.truncate_table(sea_query::Table::truncate()..)drop_index/drop_foreign_key:删除索引或外键,eg:manager.drop_index(sea_query::Index::drop()..);manager.drop_foreign_key(sea_query::ForeignKey::drop()..)truncate_table:截断表数据,eg:manager.truncate_table(sea_query::Table::truncate()..)alter_type:修改数据类型(仅限 PostgreSQL),eg:manager.alter_type(sea_query::Type::alter()..)drop_type:删除数据类型(仅限 PostgreSQL),eg:manager.drop_type(sea_query::extension::postgres::Type::drop()..)
C. 结构检查
在执行高风险操作前,可以使用检查方法防止报错:
has_table("name"):是否存在某表,eg:manager.has_table("table_name").await?。has_column("table", "col"):是否存在某列,eg:manager.has_column("table_name", "column_name")。has_index("table", "idx"):是否存在某索引,eg:manager.has_index("table_name", "index_name")。
3. 原始 SQL 的两种执行方式
虽然 SeaQuery 很好,但有时候你必须写原始 SQL。文档提到了两种方法,区别非常大:
-
execute_unprepared(sql):- 用于不带参数的纯 SQL(如
CREATE TABLE)。 - 它是最直接的字符串执行。
// Use `execute_unprepared` if the SQL statement doesn't have value bindings db.execute_unprepared( "CREATE TABLE `cake` ( `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` varchar(255) NOT NULL )" ) .await?; - 用于不带参数的纯 SQL(如
-
execute_raw(Statement):
- 用于带参数绑定的 SQL(如
INSERT INTO table (name) VALUES (?))。 - 原理 :它通过
Statement::from_sql_and_values构建,能有效防御 SQL 注入。
// Construct a `Statement` if the SQL contains value bindings
db.execute_raw(Statement::from_sql_and_values(
manager.get_database_backend(),
r#"INSERT INTO `cake` (`name`) VALUES (?)"#,
["Cheese Cake".into()]
)).await?;
4. 高级技巧与最佳实践 (Tips)
Tip 1:多原子操作组合
你可以在一个 up 函数里写多个操作。注意: 因为你用的是 PostgreSQL ,这些操作会自动被包裹在一个 Transaction(事务) 中。如果第 3 个操作失败了,前 2 个会自动回滚。
Tip 2:手动实现 ADD COLUMN IF NOT EXISTS
MySQL 不支持这个 SQL 语法,所以 SeaORM 建议通过代码逻辑实现:
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
if !manager.has_column("user", "email").await? {
// 只有不存在时才执行 alter_table
}
Ok(())
}
初学者暂时可以不考虑,直接写 create_table 和 add_column。等以后遇到报错了,再回来学这个“手动检查”的方法。
Tip 3:数据种子填充
你可以在 up 逻辑里直接使用 ActiveModel 插入初始数据。迁移文件不仅能改表结构,还能利用你已经写好的实体模型来操作数据。 它是将“结构变更”与“数据初始化”合二为一的优雅手段。
- 逻辑 :先
create_table,紧接着insert(db)。
#[async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// 1. 先用 SeaQuery 建表
manager.create_table(
Table::create().table(cake::Entity).if_not_exists()
.col(ColumnDef::new(cake::Column::Id).integer().not_null().auto_increment().primary_key())
.col(ColumnDef::new(cake::Column::Name).string().not_null())
.to_owned()
).await?;
// 2. 表建好了,立刻塞入一条初始数据
let db = manager.get_connection();
cake::ActiveModel {
name: Set("Cheesecake".to_owned()),
..Default::default() // 其他字段用默认值
}
.insert(db)// 真正的插入动作
.await?;
Ok(())
}
}
迁移系统不仅仅是建表。它是通过 MigratorTrait 注册、利用 SchemaManager 丰富的 API 进行结构演进、并利用 PostgreSQL 的 原子性事务 确保数据安全的一整套工程化方案。