{-# LANGUAGE AllowAmbiguousTypes #-}
module Database.Beam.Migrate.Log where
import Database.Beam
import Database.Beam.Backend.SQL
import Database.Beam.Migrate
import Database.Beam.Migrate.Backend
import Control.Monad (when)
import Data.String (fromString)
import Data.Text (Text)
import Data.Time (LocalTime)
import Data.UUID.Types (UUID)
data LogEntryT f
= LogEntry
{ _logEntryId :: C f Int
, _logEntryCommitId :: C f Text
, _logEntryDate :: C f LocalTime
} deriving Generic
instance Beamable LogEntryT
type LogEntry = LogEntryT Identity
deriving instance Show LogEntry
instance Table LogEntryT where
data PrimaryKey LogEntryT f = LogEntryKey (C f Int)
deriving Generic
primaryKey = LogEntryKey <$> _logEntryId
instance Beamable (PrimaryKey LogEntryT)
type LogEntryKey = PrimaryKey LogEntryT Identity
deriving instance Show LogEntryKey
newtype BeamMigrateVersionT f
= BeamMigrateVersion
{ _beamMigrateVersion :: C f Int
} deriving Generic
instance Beamable BeamMigrateVersionT
type BeamMigrateVersion = BeamMigrateVersionT Identity
deriving instance Show BeamMigrateVersion
instance Table BeamMigrateVersionT where
data PrimaryKey BeamMigrateVersionT f = BeamMigrateVersionKey (C f Int)
deriving Generic
primaryKey = BeamMigrateVersionKey <$> _beamMigrateVersion
instance Beamable (PrimaryKey BeamMigrateVersionT)
type BeamMigrateVersionKey = PrimaryKey BeamMigrateVersionT Identity
deriving instance Show BeamMigrateVersionKey
data BeamMigrateDb entity
= BeamMigrateDb
{ _beamMigrateVersionTbl :: entity (TableEntity BeamMigrateVersionT)
, _beamMigrateLogEntries :: entity (TableEntity LogEntryT)
} deriving Generic
instance Database be BeamMigrateDb
beamMigratableDb :: forall cmd be hdl m
. ( Sql92SaneDdlCommandSyntax cmd
, Sql92SerializableDataTypeSyntax (Sql92DdlCommandDataTypeSyntax cmd)
, MonadBeam cmd be hdl m )
=> CheckedDatabaseSettings be BeamMigrateDb
beamMigratableDb = runMigrationSilenced $ beamMigrateDbMigration @cmd @be @hdl @m
beamMigrateDb :: forall be cmd hdl m
. ( Sql92SaneDdlCommandSyntax cmd
, Sql92SerializableDataTypeSyntax (Sql92DdlCommandDataTypeSyntax cmd)
, MonadBeam cmd be hdl m )
=> DatabaseSettings be BeamMigrateDb
beamMigrateDb = unCheckDatabase $ beamMigratableDb @cmd @be @hdl @m
beamMigrateDbMigration :: forall cmd be hdl m
. ( Sql92SaneDdlCommandSyntax cmd
, Sql92SerializableDataTypeSyntax (Sql92DdlCommandDataTypeSyntax cmd)
, MonadBeam cmd be hdl m )
=> Migration cmd (CheckedDatabaseSettings be BeamMigrateDb)
beamMigrateDbMigration =
BeamMigrateDb <$> createTable "beam_version"
(BeamMigrateVersion (field "version" int notNull))
<*> createTable "beam_migration"
(LogEntry (field "id" int notNull) (field "commitId" (varchar Nothing) notNull)
(field "date" timestamp notNull))
beamMigrateSchemaVersion :: Int
beamMigrateSchemaVersion = 1
getLatestLogEntry :: forall be cmd hdl m
. ( IsSql92Syntax cmd
, HasQBuilder (Sql92SelectSyntax cmd)
, Sql92ReasonableMarshaller be
, Sql92SanityCheck cmd
, Sql92SaneDdlCommandSyntax cmd
, Sql92SerializableDataTypeSyntax (Sql92DdlCommandDataTypeSyntax cmd)
, MonadBeam cmd be hdl m )
=> m (Maybe LogEntry)
getLatestLogEntry =
runSelectReturningOne (select $
limit_ 1 $
orderBy_ (desc_ . _logEntryId) $
all_ (_beamMigrateLogEntries (beamMigrateDb @be @cmd @hdl @m)))
updateSchemaToCurrent :: forall be cmd hdl m
. ( IsSql92Syntax cmd
, Sql92SanityCheck cmd
, Sql92ReasonableMarshaller be
, Sql92SaneDdlCommandSyntax cmd
, Sql92SerializableDataTypeSyntax (Sql92DdlCommandDataTypeSyntax cmd)
, MonadBeam cmd be hdl m )
=> m ()
updateSchemaToCurrent =
runInsert (insert (_beamMigrateVersionTbl (beamMigrateDb @be @cmd @hdl @m)) (insertValues [BeamMigrateVersion beamMigrateSchemaVersion]))
recordCommit :: forall be cmd hdl m
. ( IsSql92Syntax cmd
, Sql92SanityCheck cmd
, Sql92SaneDdlCommandSyntax cmd
, HasQBuilder (Sql92SelectSyntax cmd)
, Sql92SerializableDataTypeSyntax (Sql92DdlCommandDataTypeSyntax cmd)
, HasSqlValueSyntax (Sql92ValueSyntax cmd) Text
, Sql92ReasonableMarshaller be
, MonadBeam cmd be hdl m )
=> UUID -> m ()
recordCommit commitId = do
let commitIdTxt = fromString (show commitId)
logEntry <- getLatestLogEntry
let nextLogEntryId = maybe 0 (succ . _logEntryId) logEntry
runInsert (insert (_beamMigrateLogEntries (beamMigrateDb @be @cmd @hdl @m))
(insertExpressions
[ LogEntry (val_ nextLogEntryId)
(val_ commitIdTxt)
currentTimestamp_]))
ensureBackendTables :: forall be cmd hdl m
. BeamMigrationBackend cmd be hdl m
-> m ()
ensureBackendTables be@BeamMigrationBackend { backendGetDbConstraints = getCs } =
do backendSchemaBuilt <- checkForBackendTables be
if backendSchemaBuilt
then continueMigrate
else createSchema
where
doStep cmd = runNoReturn cmd
continueMigrate = do
maxVersion <-
runSelectReturningOne $ select $
aggregate_ (\v -> max_ (_beamMigrateVersion v)) $
all_ (_beamMigrateVersionTbl (beamMigrateDb @be @cmd @hdl @m))
case maxVersion of
Nothing -> cleanAndCreateSchema
Just Nothing -> cleanAndCreateSchema
Just (Just maxVersion')
| maxVersion' > beamMigrateSchemaVersion ->
fail "This database is being managed by a newer version of beam-migrate"
| maxVersion' < beamMigrateSchemaVersion ->
fail "This database is being managed by an older version of beam-migrate, but there are no older versions"
| otherwise -> pure ()
cleanAndCreateSchema = do
cs <- getCs
let migrationLogExists = any (== p (TableExistsPredicate "beam_migration")) cs
when migrationLogExists $ do
Just totalCnt <-
runSelectReturningOne $ select $
aggregate_ (\_ -> as_ @Int countAll_) $
all_ (_beamMigrateLogEntries (beamMigrateDb @be @cmd @hdl @m))
when (totalCnt > 0) (fail "beam-migrate: No versioning information, but log entries present")
runNoReturn (dropTableCmd (dropTableSyntax "beam_migration"))
runNoReturn (dropTableCmd (dropTableSyntax "beam_version"))
createSchema
createSchema = do
_ <- executeMigration doStep (beamMigrateDbMigration @cmd @be @hdl @m)
updateSchemaToCurrent
checkForBackendTables :: BeamMigrationBackend cmd be hdl m -> m Bool
checkForBackendTables BeamMigrationBackend { backendGetDbConstraints = getCs } =
do cs <- getCs
pure (any (== p (TableExistsPredicate "beam_version")) cs)