当前状态
我有两种数据类型。
data Foo = Foo
{ fooId :: RecordId Foo
, bars :: [RecordId Bar]
...
}
data Bar = Bar
{ barId :: RecordId Bar
...
}
此模式允许每个 Foo 引用任意柱线列表。显然,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 将迁移定义为类型之间的纯函数,我无法查询迁移函数内的酸态状态。但是,我在这里需要的是一种迁移,它在特定时间点的状态上运行一次,并将一个架构转换为另一个架构。有了数据库,这将是微不足道的,但是对于酸态,我看不到前进的道路。
我拥有的解决方案的唯一迹象是专门编译一个单独的程序(或者说,可从主程序调用的命令行功能)以运行处理数据迁移所需的几行代码(因此,例如,所有 Foov0、Barv0 都转换为 Foov1,Barv1),然后简单地在我的主程序中交换新模式。
但是,我什至不明白这是如何工作的。根据我对 safecopy 的理解,如果我以正常方式定义到新架构的迁移,那么一旦我尝试访问数据,我就会得到一个新数据类型的实例,当然它不包含我实际迁移数据所需的数据。
一个(在我看来很笨拙)选项可能是定义另外两种数据类型,将数据复制到它们,然后更改架构并运行将数据复制回新架构的迁移,然后删除其他数据类型。这需要程序的三个编译才能按顺序运行数据,这在某种程度上看起来不是很优雅!
任何指示将不胜感激。
编辑:可能的解决方案
我忽略了上面的架构包装在表示程序整个状态的数据类型中,例如
data DB = DB {
dbFoos :: [Foo],
dbBars :: [Bar]
}
我认为这意味着我需要做的就是定义一个新的数据数据库并编写从 DBv0 到 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 和 Bar_v0 迁移到 Bar 的相关实例Foo_v0。一个潜在的问题是DB_v0的定义必须引用Foo_v0和Bar_v0,否则SafeCopy会自动将它们迁移到Foos和Bars,这意味着在您无法在迁移数据库类中使用它之前,数据已经消失了。
安全复制 = 很棒