module Database.Persist.Sql.Migration
(
Migration
, CautiousMigration
, Sql
, showMigration
, parseMigration
, parseMigration'
, printMigration
, getMigration
, runMigration
, runMigrationQuiet
, runMigrationSilent
, runMigrationUnsafe
, runMigrationUnsafeQuiet
, migrate
, reportErrors
, reportError
, addMigrations
, addMigration
, runSqlCommand
, PersistUnsafeMigrationException(..)
) where
import Control.Exception (throwIO)
import Control.Monad (liftM, unless)
import Control.Monad.IO.Unlift
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.Trans.Reader (ReaderT(..), ask)
import Control.Monad.Trans.Writer
import Data.Text (Text, isPrefixOf, pack, snoc, unpack)
import qualified Data.Text.IO
import GHC.Stack
import System.IO
import System.IO.Silently (hSilence)
import Database.Persist.Sql.Orphan.PersistStore ()
import Database.Persist.Sql.Raw
import Database.Persist.Sql.Types
import Database.Persist.Sql.Types.Internal
import Database.Persist.Types
import Control.Exception (Exception(..))
type Sql = Text
type CautiousMigration = [(Bool, Sql)]
type Migration = WriterT [Text] (WriterT CautiousMigration (ReaderT SqlBackend IO)) ()
allSql :: CautiousMigration -> [Sql]
allSql :: CautiousMigration -> [Text]
allSql = forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd
safeSql :: CautiousMigration -> [Sql]
safeSql :: CautiousMigration -> [Text]
safeSql = CautiousMigration -> [Text]
allSql forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst)
parseMigration :: (HasCallStack, MonadIO m) => Migration -> ReaderT SqlBackend m (Either [Text] CautiousMigration)
parseMigration :: forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> ReaderT SqlBackend m (Either [Text] CautiousMigration)
parseMigration =
forall {m :: * -> *} {r} {a}.
MonadIO m =>
ReaderT r IO a -> ReaderT r m a
liftIOReader forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM forall {a} {b}. ([a], b) -> Either [a] b
go forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall w (m :: * -> *) a. WriterT w m a -> m (a, w)
runWriterT forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) w a. Monad m => WriterT w m a -> m w
execWriterT
where
go :: ([a], b) -> Either [a] b
go ([], b
sql) = forall a b. b -> Either a b
Right b
sql
go ([a]
errs, b
_) = forall a b. a -> Either a b
Left [a]
errs
liftIOReader :: ReaderT r IO a -> ReaderT r m a
liftIOReader (ReaderT r -> IO a
m) = forall r (m :: * -> *) a. (r -> m a) -> ReaderT r m a
ReaderT forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. r -> IO a
m
parseMigration' :: (HasCallStack, MonadIO m) => Migration -> ReaderT SqlBackend m CautiousMigration
parseMigration' :: forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> ReaderT SqlBackend m CautiousMigration
parseMigration' Migration
m = do
Either [Text] CautiousMigration
x <- forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> ReaderT SqlBackend m (Either [Text] CautiousMigration)
parseMigration Migration
m
case Either [Text] CautiousMigration
x of
Left [Text]
errs -> forall a. HasCallStack => [Char] -> a
error forall a b. (a -> b) -> a -> b
$ [[Char]] -> [Char]
unlines forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Text -> [Char]
unpack [Text]
errs
Right CautiousMigration
sql -> forall (m :: * -> *) a. Monad m => a -> m a
return CautiousMigration
sql
printMigration :: (HasCallStack, MonadIO m) => Migration -> ReaderT SqlBackend m ()
printMigration :: forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> ReaderT SqlBackend m ()
printMigration Migration
m = forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> ReaderT SqlBackend m [Text]
showMigration Migration
m
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> IO ()
Data.Text.IO.putStrLn)
showMigration :: (HasCallStack, MonadIO m) => Migration -> ReaderT SqlBackend m [Text]
showMigration :: forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> ReaderT SqlBackend m [Text]
showMigration Migration
m = forall a b. (a -> b) -> [a] -> [b]
map (forall a b c. (a -> b -> c) -> b -> a -> c
flip Text -> Char -> Text
snoc Char
';') forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
`liftM` forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Migration -> ReaderT SqlBackend m [Text]
getMigration Migration
m
getMigration :: (MonadIO m, HasCallStack) => Migration -> ReaderT SqlBackend m [Sql]
getMigration :: forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Migration -> ReaderT SqlBackend m [Text]
getMigration Migration
m = do
CautiousMigration
mig <- forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> ReaderT SqlBackend m CautiousMigration
parseMigration' Migration
m
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ CautiousMigration -> [Text]
allSql CautiousMigration
mig
runMigration :: MonadIO m
=> Migration
-> ReaderT SqlBackend m ()
runMigration :: forall (m :: * -> *).
MonadIO m =>
Migration -> ReaderT SqlBackend m ()
runMigration Migration
m = forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> Bool -> ReaderT SqlBackend m [Text]
runMigration' Migration
m Bool
False forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return ()
runMigrationQuiet :: MonadIO m
=> Migration
-> ReaderT SqlBackend m [Text]
runMigrationQuiet :: forall (m :: * -> *).
MonadIO m =>
Migration -> ReaderT SqlBackend m [Text]
runMigrationQuiet Migration
m = forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> Bool -> ReaderT SqlBackend m [Text]
runMigration' Migration
m Bool
True
runMigrationSilent :: MonadUnliftIO m
=> Migration
-> ReaderT SqlBackend m [Text]
runMigrationSilent :: forall (m :: * -> *).
MonadUnliftIO m =>
Migration -> ReaderT SqlBackend m [Text]
runMigrationSilent Migration
m = forall (m :: * -> *) b.
MonadUnliftIO m =>
((forall a. m a -> IO a) -> IO b) -> m b
withRunInIO forall a b. (a -> b) -> a -> b
$ \forall a. ReaderT SqlBackend m a -> IO a
run ->
forall a. [Handle] -> IO a -> IO a
hSilence [Handle
stderr] forall a b. (a -> b) -> a -> b
$ forall a. ReaderT SqlBackend m a -> IO a
run forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> Bool -> ReaderT SqlBackend m [Text]
runMigration' Migration
m Bool
True
runMigration'
:: (HasCallStack, MonadIO m)
=> Migration
-> Bool
-> ReaderT SqlBackend m [Text]
runMigration' :: forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> Bool -> ReaderT SqlBackend m [Text]
runMigration' Migration
m Bool
silent = do
CautiousMigration
mig <- forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> ReaderT SqlBackend m CautiousMigration
parseMigration' Migration
m
if forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any forall a b. (a, b) -> a
fst CautiousMigration
mig
then forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall e a. Exception e => e -> IO a
throwIO forall a b. (a -> b) -> a -> b
$ CautiousMigration -> PersistUnsafeMigrationException
PersistUnsafeMigrationException CautiousMigration
mig
else forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (forall (m :: * -> *).
MonadIO m =>
Bool -> Text -> ReaderT SqlBackend m Text
executeMigrate Bool
silent) forall a b. (a -> b) -> a -> b
$ [Text] -> [Text]
sortMigrations forall a b. (a -> b) -> a -> b
$ CautiousMigration -> [Text]
safeSql CautiousMigration
mig
runMigrationUnsafe :: MonadIO m
=> Migration
-> ReaderT SqlBackend m ()
runMigrationUnsafe :: forall (m :: * -> *).
MonadIO m =>
Migration -> ReaderT SqlBackend m ()
runMigrationUnsafe Migration
m = forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Bool -> Migration -> ReaderT SqlBackend m [Text]
runMigrationUnsafe' Bool
False Migration
m forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return ()
runMigrationUnsafeQuiet :: (HasCallStack, MonadIO m)
=> Migration
-> ReaderT SqlBackend m [Text]
runMigrationUnsafeQuiet :: forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> ReaderT SqlBackend m [Text]
runMigrationUnsafeQuiet = forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Bool -> Migration -> ReaderT SqlBackend m [Text]
runMigrationUnsafe' Bool
True
runMigrationUnsafe' :: (HasCallStack, MonadIO m)
=> Bool
-> Migration
-> ReaderT SqlBackend m [Text]
runMigrationUnsafe' :: forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Bool -> Migration -> ReaderT SqlBackend m [Text]
runMigrationUnsafe' Bool
silent Migration
m = do
CautiousMigration
mig <- forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
Migration -> ReaderT SqlBackend m CautiousMigration
parseMigration' Migration
m
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (forall (m :: * -> *).
MonadIO m =>
Bool -> Text -> ReaderT SqlBackend m Text
executeMigrate Bool
silent) forall a b. (a -> b) -> a -> b
$ [Text] -> [Text]
sortMigrations forall a b. (a -> b) -> a -> b
$ CautiousMigration -> [Text]
allSql CautiousMigration
mig
executeMigrate :: MonadIO m => Bool -> Text -> ReaderT SqlBackend m Text
executeMigrate :: forall (m :: * -> *).
MonadIO m =>
Bool -> Text -> ReaderT SqlBackend m Text
executeMigrate Bool
silent Text
s = do
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
silent forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ Handle -> [Char] -> IO ()
hPutStrLn Handle
stderr forall a b. (a -> b) -> a -> b
$ [Char]
"Migrating: " forall a. [a] -> [a] -> [a]
++ Text -> [Char]
unpack Text
s
forall (m :: * -> *) backend.
(MonadIO m, BackendCompatible SqlBackend backend) =>
Text -> [PersistValue] -> ReaderT backend m ()
rawExecute Text
s []
forall (m :: * -> *) a. Monad m => a -> m a
return Text
s
sortMigrations :: [Sql] -> [Sql]
sortMigrations :: [Text] -> [Text]
sortMigrations [Text]
x =
forall a. (a -> Bool) -> [a] -> [a]
filter Text -> Bool
isCreate [Text]
x forall a. [a] -> [a] -> [a]
++ forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Bool
isCreate) [Text]
x
where
isCreate :: Text -> Bool
isCreate Text
t = [Char] -> Text
pack [Char]
"CREATe " Text -> Text -> Bool
`isPrefixOf` Text
t
migrate :: [EntityDef]
-> EntityDef
-> Migration
migrate :: [EntityDef] -> EntityDef -> Migration
migrate [EntityDef]
allDefs EntityDef
val = do
SqlBackend
conn <- forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall a b. (a -> b) -> a -> b
$ forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall (m :: * -> *) r. Monad m => ReaderT r m r
ask
Either [Text] CautiousMigration
res <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ SqlBackend
-> [EntityDef]
-> (Text -> IO Statement)
-> EntityDef
-> IO (Either [Text] CautiousMigration)
connMigrateSql SqlBackend
conn [EntityDef]
allDefs (SqlBackend -> Text -> IO Statement
getStmtConn SqlBackend
conn) EntityDef
val
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either [Text] -> Migration
reportErrors CautiousMigration -> Migration
addMigrations Either [Text] CautiousMigration
res
reportError :: Text -> Migration
reportError :: Text -> Migration
reportError = forall (m :: * -> *) w. Monad m => w -> WriterT w m ()
tell forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a. Applicative f => a -> f a
pure
reportErrors :: [Text] -> Migration
reportErrors :: [Text] -> Migration
reportErrors = forall (m :: * -> *) w. Monad m => w -> WriterT w m ()
tell
addMigration
:: Bool
-> Sql
-> Migration
addMigration :: Bool -> Text -> Migration
addMigration Bool
isUnsafe Text
sql = forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (forall (m :: * -> *) w. Monad m => w -> WriterT w m ()
tell [(Bool
isUnsafe, Text
sql)])
addMigrations
:: CautiousMigration
-> Migration
addMigrations :: CautiousMigration -> Migration
addMigrations = forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) w. Monad m => w -> WriterT w m ()
tell
runSqlCommand :: SqlPersistT IO () -> Migration
runSqlCommand :: SqlPersistT IO () -> Migration
runSqlCommand = forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift
newtype PersistUnsafeMigrationException
= PersistUnsafeMigrationException [(Bool, Sql)]
instance Show PersistUnsafeMigrationException where
show :: PersistUnsafeMigrationException -> [Char]
show (PersistUnsafeMigrationException CautiousMigration
mig) =
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
[ [Char]
"\n\nDatabase migration: manual intervention required.\n"
, [Char]
"The unsafe actions are prefixed by '***' below:\n\n"
, [[Char]] -> [Char]
unlines forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (Bool, Text) -> [Char]
displayMigration CautiousMigration
mig
]
where
displayMigration :: (Bool, Sql) -> String
displayMigration :: (Bool, Text) -> [Char]
displayMigration (Bool
True, Text
s) = [Char]
"*** " forall a. [a] -> [a] -> [a]
++ Text -> [Char]
unpack Text
s forall a. [a] -> [a] -> [a]
++ [Char]
";"
displayMigration (Bool
False, Text
s) = [Char]
" " forall a. [a] -> [a] -> [a]
++ Text -> [Char]
unpack Text
s forall a. [a] -> [a] -> [a]
++ [Char]
";"
instance Exception PersistUnsafeMigrationException