Как можно реализовать монадическую / последовательную миграцию для данных в кислотном состоянии?

Текущее состояние

У меня есть два типа данных.

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

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

Эта схема позволяет каждому Foo ссылаться на произвольный список баров. Очевидно, что бары могут быть разделены между любым количеством Foos или без Foos.

У меня уже есть данные, сохраненные в кислотном состоянии, которые используют этот тип структуры схемы.

Желаемое состояние

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

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

В желаемом состоянии каждый столбец должен иметь ровно один Foo, как в общих отношениях внешних ключей SQL многие-к-одному.

Эта проблема

Теперь, конечно, нет никакого способа совершенного перехода между этими двумя состояниями, поскольку последнее менее выразительно, чем первое. Тем не менее, я могу написать код, который имеет дело с любой неоднозначностью здесь (для дублирующих ссылок, предпочтите Foo с наименьшим fooId и просто удалите все столбцы, на которые не ссылается Foo).

Моя проблема в том, что я не вижу пути, используя Safecopy, для перехода между этими двумя схемами. Насколько я могу судить, Safecopy определяет миграции как чистые функции между типами, и я не могу запрашивать состояние кислотного состояния внутри функции миграции. Однако здесь мне нужна миграция, которая выполняется один раз в состоянии в определенный момент времени и преобразует одну схему в другую. С базой данных это было бы тривиально, но с кислотным состоянием я просто не вижу своего пути вперед.

Единственный намек на решение, которое у меня есть, это отдельная программа (или, скажем, функция командной строки, вызываемая из основной программы), скомпилированная специально для запуска нескольких строк кода, необходимых для обработки миграции данных (так, скажем, все Foov0, Barv0 преобразуются в Foov1,Barv1), а затем просто меняются новой схемой в моей основной программе.

Однако я даже не вижу, как это может работать. В моем понимании безопасной копии, если я определил миграцию в новую схему обычным способом, то, как только я попытаюсь получить доступ к данным, мне будет предоставлен экземпляр нового типа данных, который, конечно, не содержит данных, которые мне нужны на самом деле переносить данные.

Один (кажется мне неуклюжим) вариант может состоять в том, чтобы определить два дополнительных типа данных, скопировать в них данные, затем изменить схему и запустить миграцию, которая копирует данные обратно в новую схему, а затем удалить другие типы данных., Для чего требуется три компиляции программы для последовательного запуска данных, что как-то не очень элегантно!

Любые указатели будут с благодарностью.

Изменить: возможное решение

Я забыл упомянуть, что схема выше обернута в тип данных, который представляет все состояние программы, как

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

Я думаю, это означает, что все, что мне нужно сделать, это определить новую БД данных и записать миграцию из БД0 в БД, обрабатывая там мои данные без необходимости в секвенировании или монадической активности. Я буду экспериментировать с этим и отправлю это как ответ, если успешно.

1 ответ

Решение

В моих конкретных обстоятельствах, поскольку состояние было перенесено в один тип БД, решением было написать миграцию для типа верхнего уровня. Таким образом, экземпляр миграции имел доступ ко всем данным, поэтому мог выполнить необходимую логику для завершения миграции. Таким образом, решение выглядит примерно так:

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 = потрясающе

Другие вопросы по тегам