sqel-0.0.1.0: lib/Sqel/Migration/Column.hs
module Sqel.Migration.Column where
import Generics.SOP (NP (Nil, (:*)), hd, tl)
import qualified Hasql.Encoders as Encoders
import Sqel.Class.Mods (OptMod (optMod))
import Sqel.Codec (PrimColumn (primEncoder))
import Sqel.Data.Migration (ColumnAction (AddColumn, RemoveColumn, RenameColumn))
import Sqel.Data.MigrationParams (MigrationDefault (MigrationDefault))
import Sqel.Data.PgType (ColumnType, PgColumnName, pgColumnName)
import Sqel.Kind (type (++))
import Sqel.SOP.Constraint (symbolText)
import Sqel.Migration.Data.Ddl (DdlColumn (DdlColumn), DdlColumnK (DdlColumnK))
data OldK =
OldK {
name :: Symbol,
comp :: Maybe Symbol,
delete :: Bool
}
data NewK =
NewK {
index :: Nat,
name :: Symbol,
comp :: Maybe Symbol,
rename :: Maybe Symbol,
renameType :: Maybe Symbol
}
data ModK =
KeepK
|
AddK
|
RenameK
data ActionK =
ActionK {
mod :: ModK,
index :: Nat
}
|
RemoveK
type family OldKs (index :: Nat) (cols :: [DdlColumnK]) :: [OldK] where
OldKs _ '[] = '[]
OldKs index ('DdlColumnK name comp _ _ _ delete _ : cols) =
'OldK name comp delete : OldKs (index + 1) cols
type family NewKs (index :: Nat) (cols :: [DdlColumnK]) :: [NewK] where
NewKs _ '[] = '[]
NewKs index ('DdlColumnK name comp _ rename renameType _ _ : cols) =
'NewK index name comp rename renameType : NewKs (index + 1) cols
-- TODO this reverses the other list every time
type family MkMigrationAction (old :: OldK) (check :: [NewK]) (other :: [NewK]) :: (ActionK, [NewK]) where
MkMigrationAction ('OldK _ _ 'True) '[] other =
'( 'RemoveK, other)
MkMigrationAction ('OldK name comp 'False) ('NewK index name comp 'Nothing 'Nothing : news) other =
'( 'ActionK 'KeepK index, news ++ other)
MkMigrationAction ('OldK oldName comp 'False) ('NewK index _ comp ('Just oldName) 'Nothing : news) other =
'( 'ActionK 'RenameK index, news ++ other)
MkMigrationAction ('OldK oldName ('Just oldComp) 'False) ('NewK index newName _ rename ('Just oldComp) : news) other =
MkMigrationAction ('OldK oldName ('Just oldComp) 'False) ('NewK index newName ('Just oldComp) rename 'Nothing : news) other
MkMigrationAction old (new : news) other =
MkMigrationAction old news (new : other)
MkMigrationAction old '[] other =
TypeError ("MkMigrationAction:" % old % other)
type family NewMigrationActions (cols :: [NewK]) :: [ActionK] where
NewMigrationActions '[] = '[]
NewMigrationActions ('NewK index _ _ 'Nothing 'Nothing : news) =
'ActionK 'AddK index : NewMigrationActions news
NewMigrationActions cols = TypeError ("NewMigrationActions:" % cols)
type family MigrationActionsCont (cur :: (ActionK, [NewK])) (old :: [OldK]) :: [ActionK] where
MigrationActionsCont '(cur, new) old = cur : MigrationActions old new
-- TODO removing could be done implicitly, given that only renaming really _necessitates_ explicit marking.
-- The only reason to not do that is to avoid mistakes, but that seems exaggerated since we use unit tests for checking
-- consistency anyway, and integration tests to ensure the tables work
type family MigrationActions (old :: [OldK]) (new :: [NewK]) :: [ActionK] where
MigrationActions '[] rest =
NewMigrationActions rest
MigrationActions (old : olds) new =
MigrationActionsCont (MkMigrationAction old new '[]) olds
class ColumnAddition (comp :: Maybe Symbol) (def :: Type) where
columnAddition :: def -> PgColumnName -> ColumnType -> [ColumnAction]
instance ColumnAddition ('Just tname) () where
columnAddition () n t = [AddColumn n t Nothing]
instance ColumnAddition 'Nothing () where
columnAddition () n t = [AddColumn n t Nothing]
-- TODO error message when no migration default was specified for new column
-- TODO this encoder should be taken from the builder derivation
instance (
PrimColumn a
) => ColumnAddition 'Nothing (MigrationDefault a) where
columnAddition (MigrationDefault a) n t =
[AddColumn n t md]
where
md = Just (a, Encoders.param (Encoders.nonNullable (primEncoder @a)))
class ColIndex index cols col | index cols -> col where
colIndex :: NP f cols -> f col
instance ColIndex 0 (col : cols) col where
colIndex = hd
instance {-# overlappable #-} (
ColIndex (n - 1) cols col
) => ColIndex n (c : cols) col where
colIndex = colIndex @(n - 1) . tl
type ReifyModAction :: ModK -> DdlColumnK -> DdlColumnK -> Constraint
class ReifyModAction action old new where
reifyModAction :: DdlColumn old -> DdlColumn new -> [ColumnAction]
instance ReifyModAction 'KeepK old new where
reifyModAction _ _ = []
instance ReifyModAction 'RenameK ('DdlColumnK name compOld modsOld renameOld renameTOld deleteOld typeOld) ('DdlColumnK nameNew compNew modsNew ('Just name) renameTNew delNew typeNew) where
reifyModAction (DdlColumn Proxy _ _) (DdlColumn Proxy _ _) =
[RenameColumn (pgColumnName (symbolText @name)) (pgColumnName (symbolText @nameNew))]
type ReifyOldAction :: ActionK -> DdlColumnK -> [DdlColumnK] -> Constraint
class ReifyOldAction action old new where
reifyOldAction :: DdlColumn old -> NP DdlColumn new -> [ColumnAction]
instance (
ColIndex index news new,
ReifyModAction mod old new
) => ReifyOldAction ('ActionK mod index) old news where
reifyOldAction old news =
reifyModAction @mod old new
where
new = colIndex @index news
instance ReifyOldAction 'RemoveK old new where
reifyOldAction (DdlColumn (Proxy :: Proxy name) t _) _ =
[RemoveColumn (pgColumnName (symbolText @name)) t]
-- -- TODO this has to check that new columns in composite types are
-- -- a) at the end of the list
-- -- b) Maybe
type ReifyNewAction :: ActionK -> [DdlColumnK] -> Constraint
class ReifyNewAction action new where
reifyNewAction :: NP DdlColumn new -> [ColumnAction]
instance (
ColIndex index news ('DdlColumnK name comp mods rename renameT delete tpe),
OptMod (MigrationDefault tpe) mods def,
ColumnAddition comp def
) => ReifyNewAction ('ActionK 'AddK index) news where
reifyNewAction news =
case colIndex @index news of
DdlColumn (Proxy :: Proxy name) t mods ->
columnAddition @comp @def (optMod @(MigrationDefault tpe) mods) (pgColumnName (symbolText @name)) t
type ReifyActions :: [ActionK] -> [DdlColumnK] -> [DdlColumnK] -> Constraint
class ReifyActions actions old new where
reifyActions :: NP DdlColumn old -> NP DdlColumn new -> [ColumnAction]
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 action o new,
ReifyActions actions old new
) => ReifyActions (action : actions) (o : old) new where
reifyActions (o :* old) new =
reifyOldAction @action o new <> reifyActions @actions old new
type ColumnsChanges :: [DdlColumnK] -> [DdlColumnK] -> Constraint
class ColumnsChanges old new where
columnsChanges :: NP DdlColumn old -> NP DdlColumn new -> [ColumnAction]
instance (
actions ~ MigrationActions (OldKs 0 old) (NewKs 0 new),
ReifyActions actions old new
) => ColumnsChanges old new where
columnsChanges = reifyActions @actions