transfer-db-0.3.1.0: lib/Database/TransferDB/Commons.hs
{-- |
Module : Commons
Description : Common definitions used by other modules
Copyright : (c) Mihai Giurgeanu, 2017
License : GPL-3
Maintainer : mihai.giurgeanu@gmail.com
Stability : experimental
Portability : Portable
--}
module Database.TransferDB.Commons where
import Prelude hiding (fail, log)
import Control.Monad.Trans.Maybe (MaybeT(MaybeT), runMaybeT)
import Control.Monad.Trans.Reader (ReaderT, runReaderT, ask, asks)
import Control.Monad.Trans.Class (lift)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Fail (MonadFail, fail)
import Control.Logging(loggingLogger, LogLevel(LevelError), log)
import Data.Text (Text)
import Data.String (fromString)
import SQL.CLI (SQLHENV, SQLHDBC, sql_handle_env, sql_handle_stmt, sql_null_data, sql_char)
import SQL.CLI.Utils (connect, freeHandle, disconnect, tables, allocHandle, getData, forAllRecords)
import SQL.CLI.ODBC (setupEnv)
import System.IO (hPutStrLn, stderr)
import Foreign.Marshal.Alloc (alloca, allocaBytes)
import Foreign.Storable (peek, poke)
import Foreign.C.String (peekCString)
import Foreign.Ptr (castPtr)
-- | runs the 'run' action, then, runs the 'afterRun' action no matter if the
-- 'run' action failed or succeeded
finally :: Monad m => m a -> ReaderT r (MaybeT m) b -> ReaderT r (MaybeT m) b
finally afterRun run = do
env <- ask
lift $ MaybeT $ runMaybeT (runReaderT run env) >>= (\ result -> afterRun >> return result)
-- | a 'MaybeT' only variant of 'finally'; it runs the second action and, then
-- the first action and returns the result of the second action
finally' :: (MonadIO m, MonadFail m) => IO a -> MaybeT IO b -> m b
finally' afterRun run = do
result <- liftIO $ runMaybeT run >>= (\result -> afterRun >> return result)
maybe (fail "action failed in finally'") return result
-- | calls fail on the MonadFail logging an error message
faillog :: (MonadIO m, MonadFail m) => String -> m a
faillog = faillogS $ fromString ""
-- | the variant of 'faillog' taking a log source as parameter
faillogS :: (MonadIO m, MonadFail m) => Text -> String -> m a
faillogS source msg = (liftIO $ loggingLogger LevelError source msg) >> fail msg
-- | setup db environment and connect to database
withConnection :: (MonadIO m)
=> String -- ^ datasource name
-> String -- ^ user name
-> String -- ^ password
-> (SQLHENV -> SQLHDBC -> (ReaderT r (MaybeT m) a)) -- ^ a function that gets the newly allocated environment and connection handlers
-> ReaderT r (MaybeT m) a
withConnection d u p f = do
liftIO $ log $ fromString $ "connect to " ++ d
henv <- setupEnv
let freeEnvHandle = do
liftIO $ log $ fromString "free environment handle"
liftIO $ freeHandle sql_handle_env henv
in finally freeEnvHandle $ do
hdbc <- connect henv d u p
let freeHDBC = do
liftIO $ log $ fromString $ "disconnect from " ++ d
liftIO $ disconnect hdbc
in finally freeHDBC $ f henv hdbc
withConnection' :: (MonadIO m, HasDBInfo r) => (SQLHENV -> SQLHDBC -> (ReaderT r (MaybeT m) a)) -> ReaderT r (MaybeT m) a
withConnection' f = do
dbi <- asks extractDBInfo
let d = dbi_Datasource dbi
u = dbi_User dbi
p = dbi_Password dbi
withConnection d u p f
-- | connect to database in an existing db environment
withEnvConnection :: (MonadIO m)
=> SQLHENV -- ^ handle to environment
-> String -- ^ datasource name
-> String -- ^ user name
-> String -- ^ password
-> (SQLHENV -> SQLHDBC -> (ReaderT r (MaybeT m) a)) -- ^ a function that gets the newly allocated environment and connection handlers
-> ReaderT r (MaybeT m) a
withEnvConnection henv d u p f = do
liftIO $ log $ fromString $ "withEnvConnection' using environment handle " ++ (show henv)
hdbc <- connect henv d u p
let freeHDBC = do
liftIO $ log $ fromString $ "disconnect from " ++ d
liftIO $ disconnect hdbc
in finally freeHDBC $ f henv hdbc
-- | call 'withEnvConnect' within a 'ReaderT' environment containing database connnetion info
withEnvConnection' :: (MonadIO m, HasDBInfo r) => SQLHENV -> (SQLHENV -> SQLHDBC -> (ReaderT r (MaybeT m) a)) -> ReaderT r (MaybeT m) a
withEnvConnection' henv f = do
dbi <- asks extractDBInfo
let d = dbi_Datasource dbi
u = dbi_User dbi
p = dbi_Password dbi
withEnvConnection henv d u p f
-- | call 'connect' within a ReaderT environment containing database connnetion info
connect' :: (MonadIO m, MonadFail m, HasDBInfo r) => SQLHENV -> ReaderT r m SQLHDBC
connect' henv = do
dbi <- asks extractDBInfo
let d = dbi_Datasource dbi
u = dbi_User dbi
p = dbi_Password dbi
connect henv d u p
-- | the environment used to run the program
data ProgramOptions = ProgramOptions {
po_Source :: DBInfo,
po_Dest :: DBInfo
}
-- | Information about source or destination db
data DBInfo = DBInfo {
dbi_Datasource :: String,
dbi_User :: String,
dbi_Password :: String,
dbi_Schema :: String
}
class HasDBInfo a where
extractDBInfo :: a -> DBInfo
instance HasDBInfo DBInfo where
extractDBInfo = id
-- | an instance that deals only with source db
instance HasDBInfo ProgramOptions where
extractDBInfo = po_Source
-- | run an action in the current environment on each table name from the current schema,
-- passing an accumulator value; returns the value of the accumulor
forAllTables :: (MonadFail m, MonadIO m, HasDBInfo r) => SQLHDBC -> a -> (a -> String -> ReaderT r (MaybeT m) a) -> ReaderT r (MaybeT m) a
forAllTables hdbc arg f = do
tables_stmt <- allocHandle sql_handle_stmt hdbc
liftIO $ log $ fromString $ "forAllTables allocated tables statement: " ++ (show tables_stmt)
schema <- asks $ dbi_Schema.extractDBInfo
finally (liftIO $ freeHandle sql_handle_stmt tables_stmt) $ do
result <- liftIO $ runMaybeT $ tables tables_stmt Nothing (Just schema) Nothing (Just "TABLE")
let readTableName = liftIO $ allocaBytes 255
(\ p_tableName ->
alloca
(\ p_tableName_ind -> do
poke p_tableName_ind 0
_ <- getData tables_stmt 3 sql_char p_tableName 255 p_tableName_ind
tableName_ind <- liftIO $ peek p_tableName_ind
tableName <- if tableName_ind == sql_null_data
then return Nothing
else (liftIO . peekCString . castPtr) p_tableName >>= (return.Just)
return tableName))
withTableName arg' = do
tableName <- readTableName
maybe (return arg') (f arg') tableName
result <- forAllRecords tables_stmt withTableName arg
return result