packages feed

database-migrate 0.1.2 → 0.2.0

raw patch · 13 files changed

+297/−371 lines, 13 filesdep +lensPVP ok

version bump matches the API change (PVP)

Dependencies added: lens

API changes (from Hackage documentation)

- Database.Migrate.Data: DatabaseInitialized :: MigrationLog
- Database.Migrate.Data: Initialized :: [MigrationId] -> MigrationRecords
- Database.Migrate.Data: Migrate :: ReaderT (Migrations, c) (MaybeT (WriterT [MigrationLog] m)) a -> Migrate c m a
- Database.Migrate.Data: MigrateDatabase :: Migrate c m MigrationRecords -> Migrate c m () -> (Text -> Migrate c m ()) -> (Migration -> Migrate c m ()) -> (Migration -> Migrate c m ()) -> MigrateDatabase m c
- Database.Migrate.Data: Migration :: MigrationId -> Text -> Text -> Migration
- Database.Migrate.Data: MigrationApplied :: Migration -> MigrationLog
- Database.Migrate.Data: MigrationFailed :: Migration -> String -> MigrationLog
- Database.Migrate.Data: MigrationId :: Text -> MigrationId
- Database.Migrate.Data: MigrationRolledback :: Migration -> String -> MigrationLog
- Database.Migrate.Data: Migrations :: [Migration] -> Migrations
- Database.Migrate.Data: NotInitialized :: MigrationRecords
- Database.Migrate.Data: current :: MigrateDatabase m c -> Migrate c m MigrationRecords
- Database.Migrate.Data: data Migrate c m a
- Database.Migrate.Data: data MigrateDatabase m c
- Database.Migrate.Data: data Migration
- Database.Migrate.Data: data MigrationId
- Database.Migrate.Data: data MigrationLog
- Database.Migrate.Data: data MigrationRecords
- Database.Migrate.Data: data Migrations
- Database.Migrate.Data: down :: Migration -> Text
- Database.Migrate.Data: extract :: MigrationId -> Text
- Database.Migrate.Data: getMigrations :: Migrations -> [Migration]
- Database.Migrate.Data: initialize :: MigrateDatabase m c -> Migrate c m ()
- Database.Migrate.Data: instance Eq Migration
- Database.Migrate.Data: instance Eq MigrationId
- Database.Migrate.Data: instance Eq MigrationLog
- Database.Migrate.Data: instance Monad f => Functor (Migrate c f)
- Database.Migrate.Data: instance Monad m => Monad (Migrate c m)
- Database.Migrate.Data: instance MonadIO m => MonadIO (Migrate c m)
- Database.Migrate.Data: instance MonadTrans (Migrate c)
- Database.Migrate.Data: instance Ord Migration
- Database.Migrate.Data: instance Ord MigrationId
- Database.Migrate.Data: instance Show Migration
- Database.Migrate.Data: instance Show MigrationId
- Database.Migrate.Data: instance Show MigrationLog
- Database.Migrate.Data: migrationId :: Migration -> MigrationId
- Database.Migrate.Data: recordInstall :: MigrateDatabase m c -> Migration -> Migrate c m ()
- Database.Migrate.Data: recordRollback :: MigrateDatabase m c -> Migration -> Migrate c m ()
- Database.Migrate.Data: runMigrate :: Migrate c m a -> ReaderT (Migrations, c) (MaybeT (WriterT [MigrationLog] m)) a
- Database.Migrate.Data: runSql :: MigrateDatabase m c -> Text -> Migrate c m ()
- Database.Migrate.Data: up :: Migration -> Text
- Database.Migrate.Kernel: connection :: (Functor m, Monad m) => Migrate c m c
- Database.Migrate.Kernel: dryrun :: Monad m => MigrateDatabase m c -> MigrateDatabase m c
- Database.Migrate.Kernel: executeMigrate :: Monad m => Migrations -> c -> Migrate c m a -> m (Maybe a, [MigrationLog])
- Database.Migrate.Kernel: migrate :: (Functor m, Monad m) => MigrateDatabase m c -> Migrate c m ()
- Database.Migrate.Kernel: missing :: Migrations -> [MigrationId] -> [Migration]
- Database.Migrate.Kernel: mlog :: Monad m => MigrationLog -> Migrate c m ()
- Database.Migrate.Kernel: store :: (Functor m, Monad m) => Migrate c m Migrations
- Database.Migrate.Loader: find :: FilePath -> EitherT String IO Migrations
- Database.Migrate.Loader: hGetContents' :: Handle -> IO String
- Database.Migrate.Loader: migrationids :: FilePath -> [FilePath] -> IO [String]
- Database.Migrate.Loader: readFile' :: FilePath -> IO String
- Database.Migrate.Main: defaultMain :: Migrations -> MigrateDatabase IO c -> IO c -> IO ()
- Database.Migrate.Main: defaultMain' :: Migrations -> MigrateDatabase IO c -> IO c -> [String] -> IO ()
- Database.Migrate.Main: instance Eq Arguments
- Database.Migrate.Main: instance Eq DbMode
- Database.Migrate.Main: instance Show Arguments
- Database.Migrate.Main: instance Show DbMode
- Database.Migrate.PostgreSQL: PsqlConnectInfo :: PsqlConnectInfo
- Database.Migrate.PostgreSQL: data PsqlConnectInfo
- Database.Migrate.PostgreSQL: instance FromField MigrationId
- Database.Migrate.PostgreSQL: instance ToField MigrationId
- Database.Migrate.PostgreSQL: psqlMigrateDatabase :: MigrateDatabase IO Connection
+ Database.Migrate.Migration: Ddl :: Text -> Change
+ Database.Migrate.Migration: DdlFile :: FilePath -> Change
+ Database.Migrate.Migration: Dud :: Text -> Change
+ Database.Migrate.Migration: Migration :: MigrationId -> Change -> Change -> Migration
+ Database.Migrate.Migration: NoOp :: Change
+ Database.Migrate.Migration: _downChange :: Migration -> Change
+ Database.Migrate.Migration: _migrationId :: Migration -> MigrationId
+ Database.Migrate.Migration: _upChange :: Migration -> Change
+ Database.Migrate.Migration: data Change
+ Database.Migrate.Migration: data Migration
+ Database.Migrate.Migration: downChange :: Lens' Migration Change
+ Database.Migrate.Migration: instance Eq Change
+ Database.Migrate.Migration: instance Show Change
+ Database.Migrate.Migration: migrationId :: Lens' Migration MigrationId
+ Database.Migrate.Migration: type MigrationId = Text
+ Database.Migrate.Migration: upChange :: Lens' Migration Change
+ Database.Migrate.Migration.Ddl: down :: MigrationId -> Text -> Migration
+ Database.Migrate.Migration.Ddl: migration :: MigrationId -> Text -> Text -> Migration
+ Database.Migrate.Migration.Ddl: up :: MigrationId -> Text -> Migration
+ Database.Migrate.Migration.Directory: migration :: FilePath -> IO (Either Text [Migration])
+ Database.Migrate.Migration.File: down :: MigrationId -> FilePath -> Migration
+ Database.Migrate.Migration.File: migration :: MigrationId -> FilePath -> FilePath -> Migration
+ Database.Migrate.Migration.File: up :: MigrationId -> FilePath -> Migration
+ Database.Migrate.Migration.FileInferred: directory :: FilePath -> IO (Either Text [Migration])
+ Database.Migrate.Migration.FileInferred: down :: FilePath -> Migration
+ Database.Migrate.Migration.FileInferred: migration :: FilePath -> Migration
+ Database.Migrate.Migration.FileInferred: up :: FilePath -> Migration
+ Database.Migrate.Migration.FileStandard: down :: MigrationId -> FilePath -> Migration
+ Database.Migrate.Migration.FileStandard: migration :: MigrationId -> FilePath -> Migration
+ Database.Migrate.Migration.FileStandard: up :: MigrationId -> FilePath -> Migration
+ Database.Migrate.PostgreSQL: migrate :: DbName -> Connection -> FilePath -> [Migration] -> IO ()
+ Database.Migrate.PostgreSQL: psqlMain :: IO ()

Files

database-migrate.cabal view
@@ -1,5 +1,5 @@ Name:               database-migrate-Version:            0.1.2+Version:            0.2.0 License:            BSD3 License-File:       LICENSE Author:             Mark Hibberd <mark@hibberd.id.au>@@ -40,6 +40,7 @@                     , either                        >= 3.0.2                     , transformers                  >= 0.2                     , cmdargs                       >= 0.9.3+                    , lens                          >= 3.9.0.2    GHC-Options:                     -Wall -fno-warn-orphans@@ -49,8 +50,10 @@    Exposed-Modules:                     Database.Migrate-                    Database.Migrate.Data-                    Database.Migrate.Kernel-                    Database.Migrate.Loader-                    Database.Migrate.Main                     Database.Migrate.PostgreSQL+                    Database.Migrate.Migration+                    Database.Migrate.Migration.Ddl+                    Database.Migrate.Migration.Directory+                    Database.Migrate.Migration.File+                    Database.Migrate.Migration.FileStandard+                    Database.Migrate.Migration.FileInferred
src/Database/Migrate.hs view
@@ -16,8 +16,5 @@ -- module Database.Migrate (module X) where -import Database.Migrate.Data as X-import Database.Migrate.Kernel as X-import Database.Migrate.Loader as X-import Database.Migrate.Main as X+import Database.Migrate.Migration as X import Database.Migrate.PostgreSQL as X
− src/Database/Migrate/Data.hs
@@ -1,73 +0,0 @@-{-#LANGUAGE RankNTypes, ScopedTypeVariables #-}-module Database.Migrate.Data where--import Control.Monad.IO.Class-import Control.Monad.Trans.Class-import Control.Monad.Trans.Reader-import Control.Monad.Trans.Writer-import Control.Monad.Trans.Maybe--import Data.Text hiding (foldr, filter, reverse, length)--data MigrationId =-  MigrationId { extract :: Text } deriving (Eq, Show)--instance Ord MigrationId where-  compare a b =-   case (reads . unpack . extract $ a, reads . unpack . extract $ b) of-     ([(i :: Int, "")], [(j :: Int, "")]) -> compare i j-     _ -> compare (extract a) (extract b)--data MigrationLog =-    DatabaseInitialized-  | MigrationApplied Migration-  | MigrationFailed Migration String-  | MigrationRolledback Migration String-  deriving (Eq, Show)--data Migration =-  Migration {-      migrationId :: MigrationId-    , up :: Text-    , down :: Text-    } deriving (Eq, Show)--instance Ord Migration where-  compare a b = compare (migrationId a) (migrationId b)--data MigrationRecords =-    NotInitialized-  | Initialized [MigrationId]---- FIX should not be a list, want to index and sort migrations-data Migrations =-  Migrations { getMigrations :: [Migration] }--data Migrate c m a =-  Migrate { runMigrate :: ReaderT (Migrations, c) (MaybeT (WriterT [MigrationLog] m)) a }---- FIX consider adding history--- FIX add reverse engineer to extract current schema-data MigrateDatabase m c =-  MigrateDatabase {-      current :: Migrate c m MigrationRecords-    , initialize :: Migrate c m ()-    , runSql :: Text -> Migrate c m ()-    , recordInstall :: Migration -> Migrate c m ()-    , recordRollback :: Migration -> Migrate c m ()-    }--instance Monad f => Functor (Migrate c f) where-  fmap f a = a >>= \a' -> return (f a')--instance Monad m => Monad (Migrate c m) where-  return a = Migrate $ return a-  m >>= f = Migrate $ do-    a <- runMigrate m-    runMigrate (f a)--instance MonadTrans (Migrate c) where-  lift = Migrate . lift . lift . lift--instance MonadIO m => MonadIO (Migrate c m) where-  liftIO = lift . liftIO
− src/Database/Migrate/Kernel.hs
@@ -1,52 +0,0 @@-{-#LANGUAGE RankNTypes, ScopedTypeVariables #-}-module Database.Migrate.Kernel where--import Database.Migrate.Data--import Control.Monad-import Control.Monad.Trans.Class-import Control.Monad.Trans.Maybe-import Control.Monad.Trans.Reader-import Control.Monad.Trans.Writer--import qualified Data.Set as S-import Data.Functor--mlog :: Monad m => MigrationLog -> Migrate c m ()-mlog l = Migrate . lift . lift $ tell [l]--connection :: (Functor m, Monad m) => Migrate c m c-connection = Migrate $ fmap snd ask--store :: (Functor m, Monad m) => Migrate c m Migrations-store = Migrate $ fmap fst ask--dryrun :: Monad m => MigrateDatabase m c -> MigrateDatabase m c-dryrun db =-  MigrateDatabase {-      current = current db-    , initialize = return ()-    , runSql = \_ -> return ()-    , recordInstall = \_ -> return ()-    , recordRollback = \_ -> return ()-    }--migrate :: (Functor m, Monad m) => MigrateDatabase m c -> Migrate c m ()-migrate db =-  do records <- current db-     ms <- store-     migrations <- case records of-       NotInitialized -> getMigrations ms <$ (mlog DatabaseInitialized >> initialize db)-       Initialized mids -> return $ missing ms mids-     forM_ migrations (\m -> mlog (MigrationApplied m) >> (runSql db (up m) >> recordInstall db m))--missing :: Migrations -> [MigrationId] -> [Migration]-missing ms applied =-  let migrations = getMigrations ms-      available = foldr (S.insert . migrationId) S.empty migrations-      installed = S.fromList applied-      torun = S.difference available installed-   in filter (\m -> S.member (migrationId m) torun) migrations--executeMigrate :: Monad m => Migrations -> c -> Migrate c m a -> m (Maybe a, [MigrationLog])-executeMigrate = \ms c m -> runWriterT (runMaybeT (runReaderT (runMigrate m) (ms, c)))
− src/Database/Migrate/Loader.hs
@@ -1,35 +0,0 @@-module Database.Migrate.Loader where--import Database.Migrate.Data--import Control.Monad-import Control.Monad.IO.Class-import Control.Monad.Trans.Either--import Data.List (sort)-import Data.Text hiding (filter, length)--import System.FilePath-import System.Directory-import System.IO--find :: FilePath -> EitherT String IO Migrations-find b = liftIO (getDirectoryContents b) >>= \fs -> liftM sort (liftIO (migrationids b fs) >>=-  mapM (\p ->-         do downexists <- liftIO $ doesFileExist (b </> p <.> "down.sql")-            unless downexists (left $ "no down.sql for migration [" ++ p ++ "]")-            u <- liftIO . readFile $ b </> p <.> "up.sql"-            d <- liftIO . readFile $ b </> p <.> "down.sql"-            right (Migration (MigrationId . pack $ p) (pack u) (pack d) ))) >>= \ms -> return $ Migrations ms--migrationids :: FilePath -> [FilePath] -> IO [String]-migrationids b ps =-  filterM (\p -> doesFileExist (b </> p)) ps >>= \files ->-    ((return . fmap dropExtensions)-      (filter (\p -> takeExtensions p == ".up.sql") files))--readFile' :: FilePath -> IO String-readFile' p = withFile p ReadMode hGetContents--hGetContents' :: Handle -> IO String-hGetContents' h = hGetContents h >>= \s -> length s `seq` return s
− src/Database/Migrate/Main.hs
@@ -1,128 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}-module Database.Migrate.Main (defaultMain, defaultMain') where----import qualified Paths_database_migrate as Program (version)--import Control.Monad-import Control.Monad.Trans.Either-import Control.Monad.Trans.Reader-import Control.Monad.Trans.Writer-import Control.Monad.Trans.Maybe--import qualified Data.Text as T-import Data.Maybe---import Data.Version (showVersion)--import Database.Migrate.Data-import Database.Migrate.Kernel-import Database.Migrate.Loader-import Database.Migrate.PostgreSQL--import qualified Database.PostgreSQL.Simple as PG--import System.Console.CmdArgs.Explicit-import System.Directory-import System.FilePath-import System.Exit-import System.Environment (getArgs)-import System.IO--ignore :: Arg Arguments-ignore = flagArg (\_ a -> Right a) ""--e :: String-e = ""--usage = [-    "usage: db migrate [-v|--verbose] [-d|--dry-run]"-  , "       db up [-v|--verbose] [-d|--dry-run]"-  , "       db down [-v|--verbose] [-d|--dry-run]"-  , "       db apply [-v|--verbose] [-d|--dry-run]"-  , "       db -h|--help"-  , "       db -V|--version"-  ]---globalflags :: [Flag Arguments]-globalflags = [-    flagNone [ "h", "help" ]     (\a -> a { adbmode = HelpMode })    e-  , flagNone [ "V", "version" ]  (\a -> a { adbmode = VersionMode }) e-  ]--connectflags :: [Flag Arguments]-connectflags = [-    flagNone [ "v", "verbose" ]    (\a -> a { averbose = True }) e-  , flagNone [ "d", "dry-run" ]    (\a -> a { adry = True }) e-  ]--versionflag = (flagArg (\v a -> Right $ a { aversion = Just v }) "VERSION")--cmdmodes :: String -> Arguments -> Mode Arguments-cmdmodes cmd initial =-  modes cmd initial "" [-      mode "migrate" (initial { adbmode = MigrateMode }) "" ignore connectflags-    , mode "up" (initial { adbmode = UpMode }) "" versionflag connectflags-    , mode "down" (initial { adbmode = DownMode }) "" versionflag connectflags-    , mode "apply" (initial { adbmode = ApplyMode }) "" versionflag connectflags-    , mode "help" (initial { adbmode = HelpMode }) "" ignore []-    , mode "version" (initial { adbmode = VersionMode }) "" ignore []-    ]--data DbMode =-    HelpMode-  | VersionMode-  | MigrateMode-  | UpMode-  | DownMode-  | ApplyMode-  deriving (Eq, Show)--data Arguments = Arguments {-    adbmode :: DbMode-  , adry :: Bool-  , averbose :: Bool-  , ascripts :: String-  , aversion :: Maybe String-  } deriving (Eq, Show)--defaultArguments cwd = Arguments {-    adbmode = HelpMode-  , adry = False-  , averbose = False-  , ascripts = cwd </> "migrations"-  , aversion = Nothing-  }--defaultMain :: Migrations -> MigrateDatabase IO c -> IO c -> IO ()-defaultMain migrationstore db connector = getArgs >>= defaultMain' migrationstore db connector--defaultMain' :: Migrations -> MigrateDatabase IO c -> IO c -> [String] -> IO ()-defaultMain' migrationstore db connector args =-  getCurrentDirectory >>= \cwd ->-    case process ((cmdmodes "migrate" (defaultArguments cwd)) {modeGroupFlags = toGroup $ globalflags} ) args of-      Left x ->  hPutStrLn stderr x >> exitFailure-      Right x -> run migrationstore db connector x--run :: Migrations -> MigrateDatabase IO c -> IO c -> Arguments -> IO ()-run migrationstore db' connector args =-  let db = if adry args then dryrun db' else db'-  in case adbmode args of-    HelpMode -> mapM_ putStrLn usage-    VersionMode -> putStrLn $ "migrate 0.1.1" -- ++ showVersion Program.version-    MigrateMode -> connector  >>= \c -> (executeMigrate migrationstore c $ migrate db) >>= print-    UpMode -> connector >>= \c -> (executeMigrate migrationstore c $ upmode db) >>= print-    DownMode -> connector >>= \c -> (executeMigrate migrationstore c $ downmode db) >>= print-    ApplyMode -> connector >>= \c -> (executeMigrate migrationstore c $ applymode db) >>= print--bomb failwith =-  putStrLn failwith >> exitFailure--upmode :: MigrateDatabase m c -> Migrate c m ()-upmode = undefined--downmode :: MigrateDatabase m c -> Migrate c m ()-downmode = undefined--applymode :: MigrateDatabase m c -> Migrate c m ()-applymode = undefined
+ src/Database/Migrate/Migration.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE TemplateHaskell, LiberalTypeSynonyms #-}+module Database.Migrate.Migration where++import Data.Text+import Control.Lens++type MigrationId = Text++data Change =+    Ddl Text+  | DdlFile FilePath+  | Dud Text+  | NoOp+  deriving (Show, Eq)+-- TODO | AddTable Text TableDef+-- TODO | AddColumn Text Text ColumnDef+-- TODO | DropTable Text+-- TODO | DropColumn Text+-- TODO | AlterColumn Text Text ColumnDef+++data Migration =+    Migration { _migrationId :: MigrationId, _upChange :: Change, _downChange :: Change }++makeLenses ''Migration
+ src/Database/Migrate/Migration/Ddl.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE OverloadedStrings #-}+module Database.Migrate.Migration.Ddl (+  migration+, up+, down+) where++import Database.Migrate.Migration+import Data.Text++migration :: MigrationId -> Text -> Text -> Migration+migration mid up' down' =+  Migration mid (Ddl up') (Ddl down')++up :: MigrationId -> Text -> Migration+up mid up' =+  Migration mid (Ddl up') NoOp++down :: MigrationId -> Text -> Migration+down mid down' =+  Migration mid NoOp (Ddl down')
+ src/Database/Migrate/Migration/Directory.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE OverloadedStrings #-}+module Database.Migrate.Migration.Directory (+  migration+) where++import           Control.Applicative ((<$>))+import           Data.List (isSuffixOf, intercalate, groupBy)+import           Data.Text (Text, pack)++import           Database.Migrate.Migration+import qualified Database.Migrate.Migration.FileInferred as File+import           System.FilePath+import           System.Directory++-- TODO support baselines as well as change files++migration :: FilePath -> IO (Either Text [Migration])+migration path =+  (mkmigration . filter isMigrationFile) <$> getDirectoryContents path++unsuffix :: FilePath -> FilePath+unsuffix =+  dropExtension . dropExtension++isMigrationFile :: FilePath -> Bool+isMigrationFile p =+  isSuffixOf ".up.sql" p || isSuffixOf ".down.sql" p++isUp :: FilePath -> Bool+isUp =+  isMigrationType "up"++isMigrationType :: String -> FilePath -> Bool+isMigrationType t p =+  (takeExtension . takeExtension $ p) == t++mkmigration :: [FilePath] -> Either Text [Migration]+mkmigration paths =+  mapM (\group -> case group of+    a : _ : [] -> Right . File.migration $ unsuffix a+    a : [] ->  Right $ (if isUp a then File.up else File.down) (unsuffix a)+    _ -> Left . pack $ "Duplicate prefix detected in: " ++ (intercalate "," group)+  ) (groupBy (\a b -> unsuffix a == unsuffix b) paths)
+ src/Database/Migrate/Migration/File.hs view
@@ -0,0 +1,19 @@+module Database.Migrate.Migration.File (+  migration+, up+, down+) where++import Database.Migrate.Migration++migration :: MigrationId -> FilePath -> FilePath -> Migration+migration mid up' down' =+  Migration mid (DdlFile up') (DdlFile down')++up :: MigrationId -> FilePath -> Migration+up mid up' =+  Migration mid (DdlFile up') NoOp++down :: MigrationId -> FilePath -> Migration+down mid down' =+  Migration mid NoOp (DdlFile down')
+ src/Database/Migrate/Migration/FileInferred.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE OverloadedStrings #-}+module Database.Migrate.Migration.FileInferred (+  migration+, up+, down+, directory+) where++import           Data.List (isSuffixOf, intercalate, groupBy)+import           Data.Text (Text, pack)++import           Database.Migrate.Migration+import qualified Database.Migrate.Migration.File         as File+import qualified Database.Migrate.Migration.FileStandard as FileStandard+import           System.FilePath+import           System.Directory++directory :: FilePath -> IO (Either Text [Migration])+directory path = do+  changes <- (filter (\p -> isSuffixOf ".up.sql" p || isSuffixOf ".down.sql" p)) `fmap` getDirectoryContents path+  return $ mapM (\group ->+    case group of+      a : _ : [] -> (Right $ migration (dropExtension . dropExtension $ a))+      a : [] -> (Right $ (if ((takeExtension . takeExtension $ a) == "up") then File.up else File.down) (infer a) a)+      _ -> (Left . pack $ "Duplicate prefix detected in: " ++ (intercalate "," group))+    ) $ groupBy (\a b -> (dropExtension . dropExtension $ a) == (dropExtension . dropExtension $ b)) changes++migration :: FilePath -> Migration+migration prefix =+  FileStandard.migration (infer prefix) prefix++up :: FilePath -> Migration+up prefix =+  FileStandard.up (infer prefix) prefix++down :: FilePath -> Migration+down prefix =+  FileStandard.down (infer prefix) prefix++infer :: FilePath -> MigrationId+infer =+  pack . dropExtensions . last . splitPath
+ src/Database/Migrate/Migration/FileStandard.hs view
@@ -0,0 +1,21 @@+module Database.Migrate.Migration.FileStandard (+  migration+, up+, down+) where++import           Database.Migrate.Migration+import qualified Database.Migrate.Migration.File as File+import           System.FilePath++migration :: MigrationId -> FilePath -> Migration+migration mid prefix =+  File.migration mid (prefix <.> "up.ddl") (prefix <.> "down.ddl")++up :: MigrationId -> FilePath -> Migration+up mid prefix =+  File.up mid (prefix <.> ".up.ddl")++down :: MigrationId -> FilePath -> Migration+down mid prefix =+  File.down mid (prefix <.> "down.ddl")
src/Database/Migrate/PostgreSQL.hs view
@@ -1,90 +1,133 @@-{-#LANGUAGE OverloadedStrings #-}-module Database.Migrate.PostgreSQL where+{-# LANGUAGE OverloadedStrings, LiberalTypeSynonyms #-}+module Database.Migrate.PostgreSQL (+  psqlMain+, migrate+) where -import Database.Migrate.Data-import Database.Migrate.Kernel+import           Control.Lens                    ((^.))+import           Control.Monad                   (void, forM_)+import           Control.Monad.Trans.Reader+import           Control.Monad.IO.Class -import Control.Exception (SomeException(..), handle)-import Control.Monad-import Control.Monad.IO.Class+import qualified Data.Set as S+import           Data.String (IsString(..))+import           Data.Text                       hiding (length, foldr, filter)+import           Data.Time -import Data.Maybe-import Data.Text hiding (filter, reverse, find, null)-import Data.String (IsString(..))+import           Database.PostgreSQL.Simple+import           Database.Migrate.Migration -import Database.PostgreSQL.Simple-import Database.PostgreSQL.Simple.FromField-import Database.PostgreSQL.Simple.ToField+import           System.IO+import           System.FilePath -instance FromField MigrationId where-  fromField f m = fmap MigrationId $ fromField f m -instance ToField MigrationId where-  toField (MigrationId m) = toField m+data DbRead = DbRead { getConnection :: Connection, getDbName :: DbName,  getBase :: FilePath }+type DbName = Text+type Db a = ReaderT DbRead IO a -data PsqlConnectInfo = PsqlConnectInfo+psqlMain :: IO ()+psqlMain = putStrLn "hello" -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)-  }+migrate :: DbName -> Connection -> FilePath -> [Migration] -> IO ()+migrate n c b migrations =+  runReaderT (run_ history >> run_ versions >> migrate' migrations) (DbRead c n b) -{--  testconn =  connection >>= \c ->  query_ c "SELECT TRUE" >>= \r -> return $ maybe False fromOnly (listToMaybe r)+migrate' :: [Migration] -> Db ()+migrate' migrations = do -  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")+  installed <- current+  let nu = missing migrations installed+  forM_ nu $ \m -> do+    up m+    liftIO . putStrLn $ "Installed <" ++ (unpack . _migrationId $ m) ++ ">" +history :: Query+history =+  "CREATE TABLE IF NOT EXISTS migration_history (db VARCHAR(50), migration VARCHAR(50), action CHARACTER VARYING(20), at TIMESTAMPTZ, sql TEXT)" -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)+versions :: Query+versions =+  "CREATE TABLE IF NOT EXISTS migration_versions (db VARCHAR(50), migration VARCHAR(50) PRIMARY KEY)" -record :: Connection -> MigrationId -> IO ()-record conn mid = void $ execute conn "INSERT INTO MIGRATION_INFO VALUES (?)" (Only mid)+install :: Query+install =+  "INSERT INTO migration_versions (db, migration) VALUES (?, ?)" -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)+uninstall :: Query+uninstall =+  "DELETE FROM migration_versions (db, migration) WHERE db = ? AND migration = ?" -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)+addhistory :: Query+addhistory =+  "INSERT INTO migration_history (db, migration, action, at, sql) VALUES (?, ?, ?, ?, ?)" -run :: Connection -> (Migration -> Ddl) -> Migration -> IO MigrationId-run c f m = execute_ c (fromString . unpack $ f m) >> record c (migration m) >> return (migration m)--}+currentversions :: Query+currentversions =+  "SELECT migration FROM migration_versions WHERE db = ?"++current :: Db [MigrationId]+current = do+  n <- ask >>= return . getDbName+  unonly $ list currentversions (Only n)++up :: Migration -> Db ()+up =+  change "up" _upChange++down :: Migration -> Db ()+down =+  change "down" _downChange++change :: Text -> (Migration -> Change) -> Migration -> Db ()+change mode getChange m = do+  t <- now+  n <- ask >>= return . getDbName+  ch <- raw . getChange $ m+  run addhistory (n, m^.migrationId, mode, t, ch)+  run install (n, m^.migrationId)+  run_ . mkquery $ ch++run :: ToRow a => Query -> a -> Db ()+run q a = do+  c <- ask >>= return . getConnection+  liftIO . void $ execute c q a++run_ :: Query -> Db ()+run_ q = do+  c <- ask >>= return . getConnection+  liftIO . void $ execute_ c q++list :: (ToRow a, FromRow b) => Query -> a -> Db [b]+list q a = do+  c <- ask >>= return . getConnection+  liftIO $ query c q a++now :: Db UTCTime+now = liftIO getCurrentTime++mkquery :: Text -> Query+mkquery = fromString . unpack++raw :: Change -> Db Text+raw (Ddl q) = return q+raw (Dud msg) = error . unpack $ msg -- FIX+raw NoOp = return "select true;"+raw (DdlFile q) = do+  b <- ask >>= return . getBase+  liftIO $ readFile' (b </> q) >>= return . pack++readFile' :: FilePath -> IO String+readFile' p = withFile p ReadMode hGetContents'++hGetContents' :: Handle -> IO String+hGetContents' h = hGetContents h >>= \s -> length s `seq` return s++unonly :: Db [Only a] -> Db [a]+unonly = fmap (fmap (\(Only a) -> a))++missing :: [Migration] -> [MigrationId] -> [Migration]+missing migrations applied =+  let available = foldr (S.insert . _migrationId) S.empty migrations+      installed = S.fromList applied+      torun = S.difference available installed+   in filter (\m -> S.member (_migrationId m) torun) migrations