module Database.Schema.Migrations.Backend.MySQL
( connectMySQL
, mysqlBackend) where
import Control.Monad (when)
import Database.MySQL.Simple
import Database.Schema.Migrations.Backend
(Backend(..), rootMigrationName)
import Database.Schema.Migrations.Migration
(Migration(..), newMigration)
import Data.List.Split (wordsBy)
import Data.Char (isSpace, toLower)
import Data.Time.Clock (getCurrentTime)
import Data.String (fromString)
import Data.Maybe (fromMaybe, listToMaybe)
import qualified Database.MySQL.Base as Base
import qualified Database.MySQL.Simple as Simple
connectMySQL :: String -> IO Base.Connection
connectMySQL connectionString =
let kvs =
[(map toLower (trimlr k),trimlr v) | kvPair <-
wordsBy (== ';') connectionString :: [String]
, let (k,v) = case wordsBy (== '=') kvPair of
(k':v':_) -> (k',v')
[k'] -> (k',"")
[] -> error "impossible"]
trimlr = takeWhile (not . isSpace) . dropWhile isSpace
connInfo =
Simple.ConnectInfo
<$> lookup "host" kvs
<*> pure (read (fromMaybe "3306" (lookup "port" kvs)))
<*> lookup "user" kvs
<*> pure (fromMaybe "" (lookup "password" kvs))
<*> lookup "database" kvs
<*> pure [Base.MultiStatements]
<*> pure ""
<*> pure Nothing
in Simple.connect (fromMaybe (error "Invalid connection string. Expected form: host=hostname; user=username; port=portNumber; database=dbname; password=pwd.")
connInfo)
mysqlBackend :: Connection -> Backend
mysqlBackend conn =
Backend {isBootstrapped =
fmap ((Just migrationTableName ==) . listToMaybe . fmap fromOnly)
(query conn
(fromString "SELECT table_name FROM information_schema.tables WHERE table_name = ? AND table_schema = database()")
(Only migrationTableName) :: IO [Only String])
,getBootstrapMigration =
do ts <- getCurrentTime
return ((newMigration rootMigrationName) {mApply = createSql
,mRevert =
Just revertSql
,mDesc =
Just "Migration table installation"
,mTimestamp = Just ts})
,applyMigration =
\m ->
do execute_ conn (fromString (mApply m))
discardResults conn
execute conn
(fromString
("INSERT INTO " ++
migrationTableName ++
" (migration_id) VALUES (?)"))
(Only (mId m))
return ()
,revertMigration =
\m ->
do case mRevert m of
Nothing -> return ()
Just sql ->
do execute_ conn (fromString sql)
return ()
discardResults conn
execute
conn
(fromString
("DELETE FROM " ++
migrationTableName ++ " WHERE migration_id = ?"))
(Only (mId m))
return ()
,getMigrations =
do results <-
query_ conn
(fromString
("SELECT migration_id FROM " ++ migrationTableName))
return (map fromOnly results)
,commitBackend = commit conn
,rollbackBackend = rollback conn
,disconnectBackend = close conn}
discardResults :: Connection -> IO ()
discardResults conn =
do more <- Base.nextResult conn
when more (discardResults conn)
migrationTableName :: String
migrationTableName = "installed_migrations"
createSql :: String
createSql = "CREATE TABLE " ++ migrationTableName ++ " (migration_id TEXT)"
revertSql :: String
revertSql = "DROP TABLE " ++ migrationTableName