Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

编写迁移

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

你在 updown 函数中使用的 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。文档提到了两种方法,区别非常大:

  1. 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?;
  2. 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_tableadd_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 的 原子性事务 确保数据安全的一整套工程化方案。