packages feed

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

module Sqel.Migration.Statement where

import qualified Control.Monad.Trans.Writer.Strict as Mtl
import qualified Data.Map.Strict as Map
import qualified Exon
import Hasql.Encoders (Params)
import qualified Hasql.Session as Session
import Hasql.Session (Session)
import qualified Sqel.Data.Migration as Migration
import Sqel.Data.Migration (
  ColumnAction (AddColumn, RemoveColumn, RenameColumn, RenameColumnType),
  MigrationActions (AutoActions, CustomActions),
  TypeAction (AddAction, ModifyAction, RenameAction),
  )
import Sqel.Data.PgType (
  ColumnType (ColumnComp, ColumnPrim),
  PgColumnName (PgColumnName),
  PgComposite (PgComposite),
  PgPrimName (PgPrimName),
  PgTypeRef (PgTypeRef),
  )
import Sqel.Data.PgTypeName (
  pattern PgCompName,
  PgTableName,
  pattern PgTableName,
  pattern PgTypeName,
  PgTypeName,
  pgTableName,
  )
import Sqel.Data.Sql (Sql (Sql), sql)
import qualified Sqel.Sql.Type as Sql
import Sqel.Statement (unprepared)
import qualified Text.Show as Show

data MigrationStatement where
  MigrationStatement :: p -> Params p -> Sql -> MigrationStatement

instance Show MigrationStatement where
  show (MigrationStatement _ _ s) = show s

migrationStatementSql :: MigrationStatement -> Sql
migrationStatementSql (MigrationStatement _ _ s) =
  s

alterStatement ::
  PgTypeName table ->
  p ->
  Params p ->
  (Sql -> Sql -> Sql) ->
  Mtl.Writer [MigrationStatement] ()
alterStatement typeName p enc f =
  Mtl.tell [MigrationStatement p enc (f [sql|alter #{entity} ##{pgTableName name}|] attr)]
  where
    (entity, attr, name) = case typeName of
      PgTableName n -> ("table", "column", n)
      PgCompName n -> ("type", "attribute", n)

-- TODO maybe the default value can be null and the encoder Maybe, to unify the cases
columnStatements' ::
  PgTypeName table ->
  ColumnAction ->
  Mtl.Writer [MigrationStatement] ()
columnStatements' typeName = \case
  AddColumn (PgColumnName colName) tpe md -> do
    alter_ \ alter attr -> [sql|#{alter} add #{attr} ##{colName} #{colTypeName}|]
    case typeName of
      PgTableName _ -> do
        for_ md \ (defVal, enc) -> do
          alterStatement typeName defVal enc \ _ _ -> [sql|update ##{comp} set ##{colName} = $1|]
        for_ (nonEmpty optFrag) \ opt ->
          alter_ \ alter attr ->
            [sql|#{alter} alter #{attr} ##{colName} set #{Exon.intercalate " " opt}|]
      PgCompName _ -> unit
    where
      (optFrag, colTypeName) = case tpe of
        ColumnPrim (PgPrimName n) _ opt -> (opt, Sql n)
        ColumnComp (PgTypeRef n) _ opt -> (opt, Sql n)
  RemoveColumn (PgColumnName name) _ ->
    alter_ \ alter attr -> [sql|#{alter} drop #{attr} ##{name}|]
  RenameColumn (PgColumnName old) (PgColumnName new) ->
    alter_ \ alter attr -> [sql|#{alter} rename #{attr} ##{old} to ##{new}|]
  RenameColumnType (PgColumnName old) (PgTypeName new) ->
    alter_ \ alter attr -> [sql|#{alter} alter #{attr} ##{old} set data type ##{new}|]
  where
    alter_ = alterStatement typeName () mempty
    PgTypeName comp = typeName

typeActionStatements :: PgTypeName table -> TypeAction table -> Mtl.Writer [MigrationStatement] ()
typeActionStatements typeName = \case
  ModifyAction _ cols ->
    traverse_ (columnStatements' typeName) cols
  RenameAction newName@(PgTypeName new) cols -> do
    alterStatement typeName () mempty \ alter _ -> [sql|#{alter} rename to ##{new}|]
    traverse_ (columnStatements' newName) cols
  AddAction cols ->
    Mtl.tell [MigrationStatement () mempty (Sql.createProdType (PgComposite typeName cols))]

typeStatements :: PgTypeName table -> TypeAction table -> [MigrationStatement]
typeStatements name =
  snd .
  runIdentity .
  Mtl.runWriterT .
  typeActionStatements name

migrationStatements :: PgTableName -> MigrationActions ext -> [MigrationStatement]
migrationStatements tableName = \case
  AutoActions {..} ->
    typeStatements tableName table <> (Map.toList types >>= \ (name, actions) -> typeStatements name actions)
  CustomActions _ ->
    []

migrationSession :: [MigrationStatement] -> Session ()
migrationSession =
  traverse_ \ (MigrationStatement p enc stmt) -> Session.statement p (unprepared @() stmt unit enc)