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

迁移系统

迁移系统(Migration)简单来说就是数据库的版本控制工具。就像 Git 记录代码改动一样,Migration 记录数据库表结构的改动(新建表、加字段、删索引等)。


1. 完整的工作区 (Workspace) 结构

在 Rust 中,SeaORM 官方强烈建议使用 Workspace 模式。这能让你的“业务代码”和“数据库迁移代码”彻底解耦。

推荐的目录树如下:

my_project/
├── Cargo.toml          # 根目录虚拟清单
├── src/                # 你的业务逻辑 (App Crate)
│   └── main.rs
└── migration/          # 迁移逻辑 (Migration Crate)
    ├── Cargo.toml
    └── src/
        ├── lib.rs      # 定义迁移器
        ├── main.rs     # CLI 入口
        └── m20240110_000001_create_user_table.rs  # 具体的表变动记录

2. 第一步:初始化与配置

首先,安装工具并创建迁移目录。

安装 CLI

cargo install sea-orm-cli

初始化

在你的项目根目录下运行:

sea-orm-cli migrate init

配置 migration/Cargo.toml

这是最容易出错的地方。你需要为迁移脚本开启 PostgreSQL 支持:

[dependencies.sea-orm-migration]
version = "1.1.0" 
features = [
  "runtime-tokio-rustls", # 异步运行时
  "sqlx-postgres",        # 明确指定使用 Postgres
]

3. 第二步:编写迁移逻辑 (核心细节)

迁移文件(如 m..._create_table.rs)是纯 Rust 代码。SeaORM 使用 SeaQuery(一种 DSL)来描述 SQL。

up 函数:创建表

假设我们要创建一个用户表:

use sea_orm_migration::prelude::*;

//SeaORM 需要给每个迁移任务起个名字(存到 PostgreSQL 的 seaql_migrations 表里)。这个宏会自动把你的文件名(比如 m20240110_...)提取出来作为名字,省得你手动写。
#[derive(DeriveMigrationName)]
pub struct Migration;

// 实现 MigrationTrait 来定义具体的迁移逻辑
#[async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        // 使用 manager 创建表
        manager
            .create_table(
                Table::create()
                    .table(User::Table) // 表名
                    .if_not_exists()    // 防御性编程
                    .col(
                        ColumnDef::new(User::Id)
                            .integer()
                            .not_null()
                            .auto_increment()
                            .primary_key(),
                    )
                    .col(ColumnDef::new(User::Username).string().not_null().unique_key())
                    .col(ColumnDef::new(User::CreatedAt).date_time().default(Expr::current_timestamp()))
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        // 回滚操作:删除表
        manager
            .drop_table(Table::drop().table(User::Table).to_owned())
            .await
    }
}

// 定义表和列的枚举,方便在 Rust 里引用. 在 SQL 里我们需要写 user(表名)、id(列名),如果不加这个,你得手写字符串 "user", "id"。加了它,你就可以写 User::Table 或 User::Id。如果有拼写错误,编译器会直接报错,这非常安全。
#[derive(DeriveIden)]
enum User {
    Table,
    Id,
    Username,
    CreatedAt,
}

在数据库迁移(Migration)中,updown 就像是版本控制里的 “前进”“后退”。你可以把它们想象成一对“反义词”操作:up 负责建立down 负责拆除

1. fn up

当你运行 sea-orm-cli migrate up 时,执行的就是这个函数。

  • 目的:改变数据库的结构,让它变成你想要的新样子。
  • 常见操作
    • 创建新表(create_table)。
    • 给现有的表增加新列(alter_table)。
    • 创建索引或外键。

2. fn down

当你发现这次改动有问题,或者想回到上一个数据库版本,运行 sea-orm-cli migrate down,执行的就是这个函数。

  • 目的:把 up 函数做过的事情原样撤销,让数据库回到没改之前的状态。
  • 常见操作
    • 如果 up 是创建表,down 就是删除表(drop_table)。
    • 如果 up 是加了一列,down 就是删掉那一列。
  • 重要性对称性。为了保证数据库的整洁,down 逻辑必须写得准确。如果 up 建了表但 down 没写删表,数据库就会留下“垃圾”。

3. 它们是如何工作的?

SeaORM 会在你的 PostgreSQL 数据库里自动维护一张表,叫 seaql_migrations

版本号 (Version)应用时间 (Applied At)
m20220101_0000012024-05-20 …
  • **当你执行 up**:SeaORM 运行 up 函数。如果成功,就把这个文件名(版本号)记入这张表。下次再跑 up 时,发现表里已经有了,它就会跳过,不会重复执行。
  • **当你执行 down**:SeaORM 查找表里最后一条记录,找到对应的文件,执行它的 down 函数。如果成功,就从表里删掉这条记录。

4. 深入代码细节

让我们看看你刚才提供的代码块里,这两个函数具体在干什么:

// --- UP: 增加功能 ---
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
    manager
        .create_table(               // 动作:创建表
            Table::create()          // 建造者模式开始
                .table(User::Table)  // 表名:user
                .col(...)            // 具体的列定义
                .to_owned()
        )
        .await
}

// --- DOWN: 撤销功能 ---
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
    manager
        .drop_table(                 // 动作:删除表(撤销 up 的创建)
            Table::drop()
                .table(User::Table)  // 指定要删哪张表
                .to_owned()
        )
        .await
}

4. 第三步:如何执行迁移

你有两个选择:命令行代码内运行

A. 命令行方式 (手动)

你需要设置环境变量来告诉 CLI 你的 PostgreSQL 连接串:

# 设置环境变量 (Linux/macOS)
export DATABASE_URL="postgres://user:pass@localhost:5432/my_db"
# 查看当前迁移状态
sea-orm-cli migrate status
# 执行所有待处理的迁移
sea-orm-cli migrate up
# 撤销最后一次迁移
sea-orm-cli migrate down

B. 代码方式 (自动化)

src/main.rs 中,连接数据库后立即执行迁移。这样每次你部署新代码,数据库都会自动更新。

use migration::{Migrator, MigratorTrait}; // 引入你的 migration crate
#[tokio::main]
async fn main() {
    let db = sea_orm::Database::connect("postgres://...").await.unwrap();
    // 这一行会自动运行所有未执行的 .rs 迁移文件
    Migrator::up(&db, None).await.expect("迁移失败");
    println!("数据库已就绪");
}

5. 迁移系统的生命周期 (关键点)

为了防止弄乱 PostgreSQL,请务必理解这三个概念:

  1. seaql_migrations:SeaORM 会自动在你的数据库里建这张表。它记录了哪些 .rs 文件已经跑过了。不要手动删它。
  2. 不可变性:一旦某个迁移文件已经 up 到了生产环境,不要修改它。如果你想加字段,应该建一个的迁移文件。
  3. Iden 宏:代码最后的 enum User { Table, Id... } 是为了避免在代码里写硬编码字符串(如 "username"),增加类型安全性。