packages feed

sqel-0.0.1.0: lib/Sqel/Migration/Type.hs

module Sqel.Migration.Type where

import Generics.SOP (NP (Nil, (:*)), SListI, Top, hcfoldMap)
import Sqel.Data.Migration (CompAction, TableAction, TypeAction (AddAction, ModifyAction, RenameAction))
import Sqel.Data.PgType (PgColumn (PgColumn), PgColumns (PgColumns), pgColumnName)
import Sqel.Data.PgTypeName (PgCompName)
import Sqel.Kind (type (++))
import Sqel.SOP.Constraint (symbolText)

import Sqel.Migration.Column (ColIndex (colIndex), ColumnsChanges (columnsChanges))
import Sqel.Migration.Data.Ddl (DdlColumn (DdlColumn), DdlColumnK, DdlType (DdlType), DdlTypeK (DdlTypeK))

data OldK =
  OldK {
    table :: Bool,
    name :: Symbol,
    cols :: [DdlColumnK]
  }

data NewK =
  NewK {
    index :: Nat,
    table :: Bool,
    name :: Symbol,
    rename :: Maybe Symbol,
    cols :: [DdlColumnK]
  }

data ModK =
  KeepK
  |
  AddK
  |
  RenameK

data ActionK =
  ActionK {
    mod :: ModK,
    index :: Nat
  }
  |
  UnusedK

type family OldKs (index :: Nat) (types :: [DdlTypeK]) :: [OldK] where
  OldKs _ '[] = '[]
  OldKs index ('DdlTypeK table name _ cols : types) =
    'OldK table name cols : OldKs (index + 1) types

type family NewKs (index :: Nat) (types :: [DdlTypeK]) :: [NewK] where
  NewKs _ '[] = '[]
  NewKs index ('DdlTypeK table name rename cols : types) =
    'NewK index table name rename cols : NewKs (index + 1) types

type family MkMigrationAction (old :: OldK) (check :: [NewK]) (other :: [NewK]) :: (ActionK, [NewK]) where
  MkMigrationAction _ '[] other =
    '( 'UnusedK, other)
  MkMigrationAction ('OldK table name _) ('NewK index table name 'Nothing _ : news) other =
    '( 'ActionK 'KeepK index, news ++ other)
  MkMigrationAction ('OldK table oldName _) ('NewK index table _ ('Just oldName) _ : news) other =
    '( 'ActionK 'RenameK index, news ++ other)
  MkMigrationAction old (new : news) other =
    MkMigrationAction old news (new : other)

type family NewMigrationActions (cols :: [NewK]) :: [ActionK] where
  NewMigrationActions '[] = '[]
  NewMigrationActions ('NewK index 'False _ 'Nothing _ : news) =
    'ActionK 'AddK index : NewMigrationActions news
  NewMigrationActions cols = TypeError ("type NewMigrationActions:" % cols)

type family MigrationActionsCont (cur :: (ActionK, [NewK])) (old :: [OldK]) :: [ActionK] where
  MigrationActionsCont '(cur, new) old = cur : MigrationActions old new

type family MigrationActions (old :: [OldK]) (new :: [NewK]) :: [ActionK] where
  MigrationActions '[] rest =
    NewMigrationActions rest
  MigrationActions (old : olds) new =
    MigrationActionsCont (MkMigrationAction old new '[]) olds

type ReifyKeepAction :: Bool -> DdlTypeK -> DdlTypeK -> Constraint
class ReifyKeepAction table old new where
  reifyKeepAction :: DdlType old -> DdlType new -> TypeAction table

instance (
    ColumnsChanges colsOld colsNew
  ) => ReifyKeepAction table ('DdlTypeK table tname renameOld colsOld) ('DdlTypeK table tname renameNew colsNew) where
    reifyKeepAction (DdlType name colsOld) (DdlType _ colsNew) =
      ModifyAction name (columnsChanges colsOld colsNew)

type family ReifyModResult (table :: Bool) :: Type where
  ReifyModResult 'False =
    [(PgCompName, CompAction)]
  ReifyModResult 'True =
    TypeAction 'True

type ReifyModAction :: Bool -> ModK -> DdlTypeK -> DdlTypeK -> Constraint
class ReifyModAction table action old new where
  reifyModAction :: DdlType old -> DdlType new -> ReifyModResult table

instance (
    ReifyKeepAction 'True old new
  ) => ReifyModAction 'True 'KeepK old new where
    reifyModAction old new =
      reifyKeepAction @'True old new

instance (
    ReifyKeepAction 'False ('DdlTypeK 'False tname renameOld colsOld) new
  ) => ReifyModAction 'False 'KeepK ('DdlTypeK 'False tname renameOld colsOld) new where
    reifyModAction old@(DdlType name _) new =
      [(name, reifyKeepAction @'False old new)]

instance (
    ColumnsChanges colsOld colsNew
  ) => ReifyModAction 'False 'RenameK ('DdlTypeK 'False name renameOld colsOld) ('DdlTypeK 'False nameNew ('Just name) colsNew) where
    reifyModAction (DdlType nameOld colsOld) (DdlType nameNew colsNew) =
      [(nameOld, RenameAction nameNew (columnsChanges colsOld colsNew))]

type ReifyOldAction :: Bool -> ActionK -> DdlTypeK -> [DdlTypeK] -> Constraint
class ReifyOldAction table action old new where
  reifyOldAction :: DdlType old -> NP DdlType new -> ReifyModResult table

instance (
    ColIndex index news new,
    ReifyModAction table mod old new
  ) => ReifyOldAction table ('ActionK mod index) old news where
  reifyOldAction old news =
    reifyModAction @table @mod old (colIndex @index news)

instance ReifyOldAction 'False 'UnusedK ('DdlTypeK 'False name renameOld colsOld) new where
  reifyOldAction _ _ = []

-- -- TODO this has to check that new columns in composite types are
-- -- a) at the end of the list
-- -- b) Maybe
type ReifyNewAction :: ActionK -> [DdlTypeK] -> Constraint
class ReifyNewAction action new where
  reifyNewAction :: NP DdlType new -> (PgCompName, CompAction)

instance (
    SListI cols,
    ColIndex index news ('DdlTypeK 'False name rename cols)
  ) => ReifyNewAction ('ActionK 'AddK index) news where
  reifyNewAction news =
    let
      DdlType name ddlCols = colIndex @index news
      cols = hcfoldMap (Proxy @Top) \ (DdlColumn (Proxy :: Proxy n) t _) ->
        [PgColumn (pgColumnName (symbolText @n)) t]
    in (name, AddAction (PgColumns (cols ddlCols)))

type ReifyActions :: [ActionK] -> [DdlTypeK] -> [DdlTypeK] -> Constraint
class ReifyActions actions old new where
  reifyActions :: NP DdlType old -> NP DdlType new -> [(PgCompName, CompAction)]

instance ReifyActions '[] '[] new where
  reifyActions _ _ =
    mempty

instance (
    ReifyNewAction action new,
    ReifyActions actions '[] new
  ) => ReifyActions (action : actions) '[] new where
    reifyActions Nil new =
      reifyNewAction @action new : reifyActions @actions Nil new

instance (
    ReifyOldAction 'False action o new,
    ReifyActions actions old new
  ) => ReifyActions (action : actions) (o : old) new where
    reifyActions (o :* old) new =
      reifyOldAction @'False @action o new <> reifyActions @actions old new

type TypeChanges :: [DdlTypeK] -> [DdlTypeK] -> Constraint
class TypeChanges old new where
  typeChanges :: NP DdlType old -> NP DdlType new -> [(PgCompName, CompAction)]

instance (
    actions ~ MigrationActions (OldKs 0 old) (NewKs 0 new),
    ReifyActions actions old new
  ) => TypeChanges old new where
    typeChanges = reifyActions @actions

type TableChange :: DdlTypeK -> DdlTypeK -> Constraint
class TableChange old new where
  tableChange :: DdlType old -> DdlType new -> TableAction

instance (
    '[oldk] ~ OldKs 0 '[old],
    '(action, '[]) ~ MkMigrationAction oldk (NewKs 0 '[new]) '[],
    ReifyOldAction 'True action old '[new]
  ) => TableChange old new where
    tableChange old new =
      reifyOldAction @'True @action old (new :* Nil)