【问题标题】:How can a monadic/sequential migration be implemented for data in acid-state?如何对酸性状态的数据实施一元/顺序迁移?
【发布时间】:2015-03-12 13:45:14
【问题描述】:

当前状态

我有两种数据类型。

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

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

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

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

想要的状态

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,Brv1),然后在我的主程序中简单地交换新模式。

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

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

任何指针将不胜感激。

编辑:可能的解决方案

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

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

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

【问题讨论】:

  • 您可以使用位于它们之间的“多对多连接表”系统C: (Id C, Id A, Id B)。然后,您也许可以“清除”在事务上共享 B 的 C,而不必将所有这些逻辑都放在迁移中。但我没有使用过 Data.SafeCopy 和 Data.Acid,所以我无法深入回答你。
  • 非常感谢您的意见。沿着这些思路可能会起作用,因为我可以用对 Foo 中 Bar 的所有引用填充连接表,但我认为这将具有相同的问题,即在安全复制中,从 Foov0 到 Foov1 的迁移函数会有效地掩盖了原始数据,所以我仍然会遇到一个问题,即我需要使用旧模式运行一次计算以填充连接表,然后使用新模式重新编译并继续。

标签: haskell acid-state


【解决方案1】:

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

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 类中使用之前就已经消失了。

SafeCopy = 真棒

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-21
    • 2011-07-09
    • 1970-01-01
    相关资源
    最近更新 更多