packages feed

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

module Sqel.Migration.Transform where

import qualified Data.Map as Map
import Hasql.Statement (Statement)
import Lens.Micro ((^.))
import Sqel (MkTableSchema (tableSchema))
import Sqel.Class.MigrationEffect (MigrationEffect (runStatement, runStatement_))
import Sqel.Data.Dd (Dd, DdType)
import qualified Sqel.Data.Migration as Migration
import Sqel.Data.Migration (
  CompAction,
  CustomMigration (customMigration),
  Mig (Mig),
  Migration,
  MigrationActions (CustomActions),
  )
import Sqel.Data.PgTypeName (PgCompName, pattern PgTypeName)
import Sqel.Data.Sql (sql, toSql)
import Sqel.Data.SqlFragment (Insert (Insert), Select (Select))
import Sqel.Data.TableSchema (TableSchema)
import Sqel.ReifyDd (ReifyDd)
import Sqel.Sql.Type (createTable)
import Sqel.Statement (plain, prepared, unprepared)

import Sqel.Migration.Ddl (DdlTypes, ddTable)
import Sqel.Migration.Run (autoKeys, runTypesMigration)
import Sqel.Migration.Table (MigrationTables (withMigrationTables))
import Sqel.Migration.Type (TypeChanges (typeChanges))

data MigrateTransform m old new =
  MigrateTransform {
    trans :: [old] -> m [new],
    types :: Map PgCompName CompAction,
    schemaOld :: TableSchema old,
    schemaNew :: TableSchema new
  }

class MkMigrateTransform m old new where
  migrateTransform ::
    Dd old ->
    Dd new ->
    ([DdType old] -> m [DdType new]) ->
    Migration ('Mig (DdType old) (DdType new) m (MigrateTransform m (DdType old) (DdType new)))

instance (
    DdlTypes 'True old (oldTable : oldTypes),
    DdlTypes 'True new (newTable : newTypes),
    TypeChanges oldTypes newTypes,
    MkTableSchema old,
    MkTableSchema new,
    ReifyDd old,
    ReifyDd new
  ) => MkMigrateTransform m old new where
    migrateTransform old new f =
      withMigrationTables (CustomActions actions) old new
      where
        actions =
          MigrateTransform {
            trans = f,
            types = Map.fromList (typeChanges oldTypes newTypes),
            ..
          }
        schemaOld = tableSchema old
        schemaNew = tableSchema new
        (_, oldTypes) = ddTable old
        (_, newTypes) = ddTable new

transformAndMigrate ::
  ∀ old new m .
  Monad m =>
  MigrationEffect m =>
  Set PgCompName ->
  MigrateTransform m old new ->
  m ()
transformAndMigrate eligible MigrateTransform {..} = do
  oldRows <- runStatement () fetchOld
  newRows <- trans oldRows
  runTypesMigration eligible types
  runPlain [sql|alter table ##{schemaOld ^. #pg . #name} rename to "##{oldName}-migration-temp"|]
  runPlain (createTable (schemaNew ^. #pg))
  for_ newRows \ row -> runStatement_ row insertNew
  where
    PgTypeName oldName = schemaOld ^. #pg . #name
    runPlain = runStatement_ () . plain
    fetchOld :: Statement () [old]
    fetchOld = unprepared [sql|##{Select schemaOld}|] (schemaOld ^. #decoder) mempty
    insertNew :: Statement new ()
    insertNew = prepared (toSql (Insert (schemaNew ^. #pg))) unit (schemaNew ^. #encoder)

instance (
    Monad m,
    MigrationEffect m
  ) => CustomMigration m ('Mig old new m (MigrateTransform m old new)) where
    customMigration _ =
      transformAndMigrate

    customTypeKeys MigrateTransform {types} =
      pure (autoKeys types)