OA0 = Omni AI 0
OA0 是一个探索 AI 的论坛
现在注册
已注册用户请  登录
OA0  ›  技能包  ›  ecto-migrator: 从自然语言或模式定义生成 Ecto 迁移脚本

ecto-migrator: 从自然语言或模式定义生成 Ecto 迁移脚本

 
  desktop ·  2026-02-01 09:39:02 · 3 次点击  · 0 条评论  

name: ecto-migrator
description: "根据自然语言或模式描述生成 Ecto 迁移。支持处理表、列、索引、约束、引用、枚举和分区。支持可逆迁移、数据迁移和多租户模式。适用于在 Elixir 项目中创建或修改数据库模式、添加索引、更改表、创建枚举或执行数据迁移。"


Ecto 迁移器

生成迁移

从自然语言生成

解析用户的描述并生成迁移文件。常见模式:

用户描述 迁移操作
"创建包含邮箱和姓名的用户表" create table(:users) 并添加列
"为用户表添加电话字段" alter table(:users), add :phone
"为用户表的邮箱字段添加唯一性约束" create unique_index(:users, [:email])
"为所有表添加租户ID字段" 多个 alter table 并添加索引
"将订单表中的状态字段重命名为state" rename table(:orders), :status, to: :state
"从用户表中移除 legacy_id 列" alter table(:users), remove :legacy_id
"为订单表的金额字段添加大于0的检查约束" create constraint(:orders, ...)

文件命名

mix ecto.gen.migration <名称>
# 生成: priv/repo/migrations/YYYYMMDDHHMMSS_<名称>.exs

命名约定:create_<表名>add_<列名>_to_<表名>create_<表名>_<列名>_indexalter_<表名>_add_<列名>

迁移模板

defmodule MyApp.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users, primary_key: false) do
      add :id, :binary_id, primary_key: true
      add :email, :string, null: false
      add :name, :string, null: false
      add :role, :string, null: false, default: "member"
      add :metadata, :map, default: %{}
      add :tenant_id, :binary_id, null: false

      add :team_id, references(:teams, type: :binary_id, on_delete: :delete_all)

      timestamps(type: :utc_datetime_usec)
    end

    create unique_index(:users, [:tenant_id, :email])
    create index(:users, [:tenant_id])
    create index(:users, [:team_id])
  end
end

列类型

完整的类型映射和指导请参阅 references/column-types.md

关键决策:
- ID:使用 :binary_id (UUID) — 在表上设置 primary_key: false,手动添加 :id 列。
- 金额:使用 :integer (分) 或 :decimal — 切勿使用 :float
- 时间戳:始终使用 timestamps(type: :utc_datetime_usec)
- 枚举:使用 :string 配合应用层的 Ecto.Enum — 避免使用 PostgreSQL 原生枚举(难以迁移)。
- JSON:使用 :map (映射为 jsonb)。
- 数组:使用 {:array, :string} 等。

索引策略

详细的索引指导请参阅 references/index-patterns.md

何时添加索引

始终为以下情况添加索引:
- 外键列 (_id 列)
- tenant_id (复合索引中的第一列)
- 在 WHERE 子句中使用的列
- 在 ORDER BY 子句中使用的列
- 唯一约束列

索引类型

# 标准 B-树索引
create index(:users, [:tenant_id])

# 唯一索引
create unique_index(:users, [:tenant_id, :email])

# 部分索引(条件索引)
create index(:orders, [:status], where: "status != 'completed'", name: :orders_active_status_idx)

# GIN 索引用于 JSONB
create index(:events, [:metadata], using: :gin)

# GIN 索引用于数组列
create index(:posts, [:tags], using: :gin)

# 复合索引
create index(:orders, [:tenant_id, :status, :inserted_at])

# 并发索引(无表锁 — 在单独的迁移中使用)
@disable_ddl_transaction true
@disable_migration_lock true

def change do
  create index(:users, [:email], concurrently: true)
end

约束

# 检查约束
create constraint(:orders, :amount_must_be_positive, check: "amount > 0")

# 排除约束(需要 btree_gist 扩展)
execute "CREATE EXTENSION IF NOT EXISTS btree_gist", ""
create constraint(:reservations, :no_overlapping_bookings,
  exclude: ~s|gist (room_id WITH =, tstzrange(starts_at, ends_at) WITH &&)|
)

# 唯一约束(大多数情况下与 unique_index 作用相同)
create unique_index(:accounts, [:slug])

引用(外键)

add :user_id, references(:users, type: :binary_id, on_delete: :delete_all), null: false
add :team_id, references(:teams, type: :binary_id, on_delete: :nilify_all)
add :parent_id, references(:categories, type: :binary_id, on_delete: :nothing)
on_delete 适用场景
:delete_all 子记录不能脱离父记录存在(例如成员关系、订单项)
:nilify_all 父记录删除后子记录应保留(可选关联)
:nothing 在应用代码中处理(默认值)
:restrict 如果存在子记录,则阻止父记录删除

多租户模式

每张表都包含 tenant_id

def change do
  create table(:items, primary_key: false) do
    add :id, :binary_id, primary_key: true
    add :name, :string, null: false
    add :tenant_id, :binary_id, null: false
    timestamps(type: :utc_datetime_usec)
  end

  # 始终创建以 tenant_id 为首的复合索引
  create index(:items, [:tenant_id])
  create unique_index(:items, [:tenant_id, :name])
end

为现有表添加 tenant_id

def change do
  alter table(:items) do
    add :tenant_id, :binary_id
  end

  # 在单独的数据迁移中回填数据,然后:
  # alter table(:items) do
  #   modify :tenant_id, :binary_id, null: false
  # end
end

数据迁移

规则:切勿在同一迁移中混合模式变更和数据变更。

安全的数据迁移模式

defmodule MyApp.Repo.Migrations.BackfillUserRoles do
  use Ecto.Migration

  # 不要使用模式模块 — 它们在此迁移运行后可能会改变
  def up do
    execute """
    UPDATE users SET role = 'member' WHERE role IS NULL
    """
  end

  def down do
    # 数据迁移可能不可逆
    :ok
  end
end

批量数据迁移(大表)

def up do
  execute """
  UPDATE users SET role = 'member'
  WHERE id IN (
    SELECT id FROM users WHERE role IS NULL LIMIT 10000
  )
  """

  # 对于非常大的表,请改用 Task 或 Oban 作业
end

可逆与不可逆迁移

可逆迁移(使用 change

以下操作可自动逆转:
- create tabledrop table
- add columnremove column
- create indexdrop index
- renamerename

不可逆迁移(使用 up/down

必须明确定义两个方向的操作:
- modify 列类型 — Ecto 无法推断旧类型
- execute 原始 SQL
- 数据回填
- 删除包含数据的列

def up do
  alter table(:users) do
    modify :email, :citext, from: :string  # from: 有助于实现可逆性
  end
end

def down do
  alter table(:users) do
    modify :email, :string, from: :citext
  end
end

使用带 from:modify

Phoenix 1.7+ 支持 from: 以实现可逆的 modify

def change do
  alter table(:users) do
    modify :email, :citext, null: false, from: {:string, null: true}
  end
end

PostgreSQL 扩展

def change do
  execute "CREATE EXTENSION IF NOT EXISTS citext", "DROP EXTENSION IF EXISTS citext"
  execute "CREATE EXTENSION IF NOT EXISTS pgcrypto", "DROP EXTENSION IF EXISTS pgcrypto"
  execute "CREATE EXTENSION IF NOT EXISTS pg_trgm", "DROP EXTENSION IF EXISTS pg_trgm"
end

枚举类型(PostgreSQL 原生 — 谨慎使用)

优先使用 :string 列配合 Ecto.Enum。如果必须使用 PostgreSQL 原生枚举:

def up do
  execute "CREATE TYPE order_status AS ENUM ('pending', 'confirmed', 'shipped', 'delivered')"

  alter table(:orders) do
    add :status, :order_status, null: false, default: "pending"
  end
end

def down do
  alter table(:orders) do
    remove :status
  end

  execute "DROP TYPE order_status"
end

警告: 向 PostgreSQL 枚举添加值需要使用 ALTER TYPE ... ADD VALUE,这无法在事务内运行。优先选择 :string + Ecto.Enum

检查清单

  • [ ] 主键:primary_key: false + add :id, :binary_id, primary_key: true
  • [ ] 必需列设置 null: false
  • [ ] 使用 timestamps(type: :utc_datetime_usec)
  • [ ] 外键设置合适的 on_delete 选项
  • [ ] 为每个外键列创建索引
  • [ ] 为 tenant_id 创建索引(与查询字段组成复合索引)
  • [ ] 在需要的地方添加唯一约束
  • [ ] 并发索引在单独的迁移中使用 @disable_ddl_transaction true
  • [ ] 数据迁移与模式迁移分开存放于不同文件
3 次点击  ∙  0 人收藏  
登录后收藏  
目前尚无回复
0 条回复
About   ·   Help   ·    
OA0 - Omni AI 0 一个探索 AI 的社区
沪ICP备2024103595号-2
Developed with Cursor