酸态数据如何实现monadic/sequential迁移?

How can a monadic/sequential migration be implemented for data in acid-state?

当前状态

我有两种数据类型。

data Foo = Foo
  {  fooId :: RecordId Foo
   , bars  :: [RecordId Bar]
   ...
  }

data Bar = Bar 
  {  barId :: RecordId  Bar
  ...
  }

此架构允许每个 Foo 引用任意的 Bars 列表。显然,Bars 可以在任意数量的 Foos 之间共享,或者没有 Foos。

我已经有使用这种模式结构的酸态数据。

期望状态

data Foo = Foo
  {  fooId :: RecordId Foo
   ...
  }

data Bar = Bar 
  {  barId :: RecordId  Bar
   , fooId :: RecordId Foo
  ...
  }

在理想状态下,每个 Bar 必须恰好有一个 Foo,就像常见的多对一 SQL 外键关系。

问题

现在当然没有办法在这两种状态之间完美过渡,因为后者的表现力不如前者。但是,我可以在此处编写处理任何歧义的代码(对于重复引用,更喜欢具有最小 fooId 的 Foo,并简单地删除任何未被 Foo 引用的 Bars)。

我的问题是我看不到任何使用 Safecopy 在这两个模式之间迁移的路径。据我所知,Safecopy 将迁移定义为类型之间的纯函数,我无法查询迁移函数内的酸态状态。不过,我在这里需要的是 运行 在特定时间点的状态上进行一次迁移,并将一种模式转换为另一种模式。对于数据库,这将是微不足道的,但是对于酸态,我看不到前进的方向。

我所拥有的解决方案的唯一线索是有一个单独的程序(或者说,可从主程序调用的命令行功能)专门编译为 运行 处理所需的几行代码数据迁移(也就是说,所有 Foov0、Barv0 都转换为 Foov1、Barv1),然后在我的主程序中简单地交换新模式。

但是,我什至不明白这是怎么回事。根据我对 safecopy 的理解,如果我以正常方式定义到新模式的迁移,那么一旦我尝试访问数据,我就会得到一个新数据类型的实例,当然它不包含我需要的数据实际迁移数据。

一个(笨拙,在我看来)选项可能是定义另外两种数据类型,将数据复制到它们,然后更改架构和 运行 将数据复制回新架构,然后删除更多数据类型。这需要程序的三个编译顺序运行在数据上,这似乎不太优雅!

任何指点将不胜感激。

编辑:可能的解决方案

我忘了提到上面的模式被包装在代表程序整个状态的数据类型中,比如

data DB = DB {
  dbFoos :: [Foo],
  dbBars :: [Bar]
}

我认为这意味着我需要做的就是定义一个新的数据 DB 并编写从 DBv0 到 DB 的迁移,在那里处理我的数据而无需任何排序或 monadic activity。如果成功,我将对此进行试验,post 作为答案。

在我的特殊情况下,因为状态由单个数据库类型包装,所以解决方案是为顶级类型编写迁移。因此,迁移实例可以访问所有数据,因此可以 运行 完成迁移所需的逻辑。所以解决方案看起来像这样:

data DB = DB {
  dbFoos :: [Foo],
  dbBars :: [Bar]
}

data DB_v0 = DB_v0 {
  v0_dbFoos :: [Foo_v0],
  v0_dbBars :: [Bar_v0]
}

data Foo = Foo
  {  fooId :: RecordId Foo
   ...
  }

data Bar = Bar 
  {  barId :: RecordId  Bar
   , fooId :: RecordId Foo
  ...
  }
data Foo_v0 = Foo_v0
  {  v0_fooId :: RecordId Foo
   , v0_bars  :: [RecordId Bar]
   ...
  }

data Bar_v0 = Bar_v0 
  {  v0_barId :: RecordId  Bar
  ...
  }

instance Migrate DB where
  type MigrateFrom DB = DB_v0
  migrate dbV0 = DB {
    dbFoos = migrateOldFoos
   ,dbBars = migrateOldBars
  }
 where 
  migrateOldFoos :: [Foo]
  -- (access to all old data possible here)
  migrateOldBars :: [Bar]
  -- (access to all old data possible here)

将 Foo_v0 迁移到 Foo 并将 Bar_v0 迁移到 Bar 的相关实例。一个潜在的陷阱是 DB_v0 的定义必须引用 Foo_v0 和 Bar_v0,否则 SafeCopy 会自动将它们迁移到 Foos 和 Bars,这意味着数据已经在您之前消失了能够在 Migrate DB class.

中使用它

SafeCopy = 很棒