修改 Ecto 中的外键

Modify foreign key in Ecto

我有这个原始迁移已经 运行 并发送到上游:

create table(:videos) do
  add :url, :string
  add :title, :string
  add :description, :text
  add :user_id, references(:users, on_delete: :nothing)

  timestamps
end
create index(:videos, [:user_id])

现在我想将 user_id 上的外键更改为级联删除,这样当用户被删除时,他的所有关联视频也将被删除。

我尝试了以下迁移:

alter table(:videos) do
  modify :user_id, references(:users, on_delete: :delete_all)
end

但这会引发错误:

(Postgrex.Error) ERROR (duplicate_object): constraint "videos_user_id_fkey" for relation "videos" already exists

如何制定一个迁移脚本来根据我的要求更改这个外键?


更新

我最终得到了以下解决方案:

def up do
  execute "ALTER TABLE videos DROP CONSTRAINT videos_user_id_fkey"
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :delete_all)
  end
end

def down do
  execute "ALTER TABLE videos DROP CONSTRAINT videos_user_id_fkey"
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :nothing)
  end
end

这会在 ecto 尝试重新创建约束之前删除约束。

我认为 alter table 无法实现。例如根据 this answer Postgres doesn't allow modifying constraints in ALTER TABLE statement. MySQL also doesn't allow modifying constraints.

最简单的方法是删除该字段,如果您没有任何数据,再将其添加回去。否则,您需要使用 SQL 和 execute

您可以在调用之前删除索引 alter:

drop_if_exists index(:videos, [:user_id])
alter table(:videos) do
  modify :user_id, references(:users, on_delete: :delete_all)
end

反其道而行之有点棘手:

execute "ALTER TABLE videos DROP CONSTRAINT videos_user_id_fkey"
create_if_not_exists index(:videos, [:user_id])

我最终得到了以下解决方案:

def up do
  execute "ALTER TABLE videos DROP CONSTRAINT videos_user_id_fkey"
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :delete_all)
  end
end

def down do
  execute "ALTER TABLE videos DROP CONSTRAINT videos_user_id_fkey"
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :nothing)
  end
end

这会在 ecto 尝试重新创建约束之前删除约束

从问题中复制。

我不确定这是什么时候添加到 Ecto 的,但至少在 2.1.6 中不再需要原始的 SQL。 drop/1 现在 supports constraintsdrop_if_exists/1 没有):

def up do
  drop constraint(:videos, "videos_user_id_fkey")
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :delete_all)
  end
end

def down do
  drop constraint(:videos, "videos_user_id_fkey")
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :nothing)
  end
end

Ecto SQL 3.4.3中:

"If the :from value is a %Reference{}, the adapter will try to drop the corresponding foreign key constraints before modifying the type."

modify :user_id, references(:users, on_delete: :delete_all), from: references(:users)

应该可以。在进行回滚时,我发现这可以清除 FK 并删除列:

remove :user_id, references(:users)

from 选项在 Ecto 3.0 中引入,因此您可以以更优雅的方式设置迁移:

defmodule App.Migrations.ChangeVideosUserConstraint do
  use Ecto.Migration
  def change do
    alter table(:videos) do
      modify(:user_id, references(:users, on_delete: :delete_all), from: references(:users))
    end
  end
end