{-#LANGUAGE OverloadedStrings #-} module Database.Migrate.PostgreSQL where import Database.Migrate.Data import Database.Migrate.Kernel import Control.Exception (SomeException(..), handle) import Control.Monad import Control.Monad.IO.Class import Data.Maybe import Data.Text hiding (filter, reverse, find, null) import Data.String (IsString(..)) import Database.PostgreSQL.Simple import Database.PostgreSQL.Simple.FromField import Database.PostgreSQL.Simple.ToField instance FromField MigrationId where fromField f m = fmap MigrationId $ fromField f m instance ToField MigrationId where toField (MigrationId m) = toField m data PsqlConnectInfo = PsqlConnectInfo psqlMigrateDatabase :: MigrateDatabase IO Connection psqlMigrateDatabase = MigrateDatabase { current = connection >>= \c -> liftIO (query_ c "SELECT TRUE FROM pg_tables WHERE schemaname='public' AND tablename = 'migration_info'") >>= \r -> return (maybe False fromOnly (listToMaybe r)) >>= \rr -> if rr then liftIO (fmap Initialized (fmap (fmap fromOnly) (query_ c "SELECT migration FROM migration_info"))) else return NotInitialized , initialize = connection >>= \c -> liftIO . void $ execute_ c "CREATE TABLE IF NOT EXISTS migration_info (migration VARCHAR(50) PRIMARY KEY)" , runSql = \sql -> connection >>= \c -> liftIO . void $ execute_ c (fromString . unpack $ sql) , recordInstall = \m -> connection >>= \c -> liftIO . void $ (begin c >> execute c "INSERT INTO migration_info (migration) VALUES (?)" (Only $ migrationId m) >> commit c) , recordRollback = \m -> connection >>= \c -> liftIO . void $ (begin c >> execute c "DELETE FROM migration_info WHERE migration = ?" (Only $ migrationId m) >> commit c) } {- testconn = connection >>= \c -> query_ c "SELECT TRUE" >>= \r -> return $ maybe False fromOnly (listToMaybe r) initialize = connection >>= \c -> void $ execute_ c "CREATE TABLE IF NOT EXISTS MIGRATION_INFO (MIGRATION VARCHAR(50) PRIMARY KEY)" initialized = connection >>= \c -> query_ c "SELECT TRUE FROM pg_tables WHERE schemaname='public' AND tablename = MIGRATION_INFO" >>= \r -> return $ maybe False fromOnly (listToMaybe r) runMigration = runall getMigrations c = fmap (fmap fromOnly) (query_ c "SELECT MIGRATION FROM MIGRATION_INFO") migratePostgres :: Connection -> FilePath -> (String -> IO ()) -> IO () -> IO () migratePostgres c path logger bomb = do initialize c ems <- runEitherT $ find path case ems of Left e -> logger e >> bomb Right ms -> runEitherT (latest c ms) >>= \er -> case er of Left (Context s f m r) -> forM_ s (\mid -> logger ("migration:applied: " ++ (unpack . extract $ mid))) >> logger ("migration:failed:" ++ (unpack . extract $ f) ++ ":" ++ unpack m) >> logger ("migration:rolledback:" ++ show r) >> bomb Right mids -> if null mids then logger "migration:up-to-date" else forM_ mids $ \mid -> logger $ "migration:applied: " ++ (unpack . extract $ mid) record :: Connection -> MigrationId -> IO () record conn mid = void $ execute conn "INSERT INTO MIGRATION_INFO VALUES (?)" (Only mid) runall :: Connection -> (Migration -> Ddl) -> [Migration] -> MigrationResultT IO [MigrationId] runall c f ms = liftIO (begin c) >> foldM (\rs m -> EitherT $ do e <- runEitherT (saferun c f m) case e of Left emsg -> rollback c >> (return . Left $ Context (reverse rs) (migration m) emsg True) Right r -> return . Right $ r:rs) [] ms >>= \result -> liftIO (commit c) >> return (reverse result) saferun :: Connection -> (Migration -> Ddl) -> Migration -> EitherT Text IO MigrationId saferun c f m = EitherT $ handle (\e -> return (Left (pack . show $ (e :: SomeException)))) (fmap Right $ run c f m) run :: Connection -> (Migration -> Ddl) -> Migration -> IO MigrationId run c f m = execute_ c (fromString . unpack $ f m) >> record c (migration m) >> return (migration m) -}