【发布时间】:2018-01-22 22:20:13
【问题描述】:
我有一个 Ecto 迁移,我想修改一些列,但也迁移一些数据。例如:
import Ecto.Query
defmodule MyApp.Repo.Migrations.AddStatus do
alter table(:foo) do
add(:status, :text)
end
foos = from(f in MyApp.Foo, where: ...)
|> MyApp.Repo.all
Enum.each(foos, fn(foo) ->
# There's then some complex logic here to work
# out how to set the status based on other attributes of `foo`
end)
end
现在,这里的问题是,通过调用 MyApp.Repo.all,迁移实质上使用了一个单独的数据库连接到 alter table... 语句使用的那个(EDIT:这个假设是错误的,请参阅接受的答案)。因此,没有status 列,所以整个迁移都被炸毁了!请注意,我们使用的是 postgres 数据库,因此 DDL 语句是事务性的。
我可以将此作为两个单独的迁移或mix 任务来设置数据,仅将迁移保留为架构更改,但为了确保数据一致性,我不希望这样做。
对于如何以这种方式为MyApp.Repo 查询使用相同的数据库连接有什么想法吗?
编辑:注意,我正在处理一小组数据,在我的用例中停机时间是可以接受的。如果情况并非如此,请参阅下面 José 的回复,了解一些好的建议。
【问题讨论】:
-
在
alter之后添加对flush()的调用是否有效? -
是的,
flush()应该可以工作。整个迁移都包含在一个事务中,这意味着任何 DDL 语句以及 Repo 表达式都在同一个连接/事务上工作。问题是我们将 ddl 语句组合在一起,直到事务结束或刷新。 @Dogbert 应该提交一个答案。 :) 我将编辑我的答案以删除错误的建议。 -
那么
flush()会提交事务吗?如果在迁移的其余部分出现异常怎么办?flush()之前的更改也会回滚吗? -
不,
flush()不提交事务,它只是执行在那之前已经排队的语句。将迁移中的每个语句视为被推入队列,然后在所有内容都排队后全部运行。flush()本质上让您在添加后续语句之前执行已经排队的内容。整个迁移都包含在一个事务中,因此如果发生异常,所有已执行的内容(不管flush())都会回滚。 -
对于添加上下文@ŠtefanĽupták,将事物添加到队列的方式是通过
Ecto.MigrationDSL 函数,如alter()或create()。我需要flush()的原因是,在上面的示例中,我正在做其他事情,假设上面的所有内容都已运行。所以迁移失败了,因为当我尝试随后在Enum.each()中使用它时,我将东西放入队列但实际上并没有在数据库上运行它。
标签: elixir database-migration ecto