module Database.GP.Conn
  ( Conn (..),
    connect,
    Database (..),
    ConnectionPool,
    createConnPool,
    withResource,
  )
where

import           Control.Monad ((>=>))
import           Data.Pool     (Pool, PoolConfig, defaultPoolConfig, newPool,
                                withResource)
import           Database.HDBC (IConnection (..))

-- |
--  This module defines a wrapper around an HDBC IConnection.
--  Using this wrapper `Conn` simplifies the signature of the functions in the `Database.GP` module.
--  It allows to use any HDBC connection without having to define a new function for each connection type.
--  It also provides additional attributes to the connection, like the database type and the implicit commit flag.
--  These attributes can be used to implement database specific functionality, modify transaction behaviour, etc.
--
--  This code has been inspired by the HDBC ConnectionWrapper and some parts have been copied verbatim
--  from the HDBC Database.HDBC.Types module.
--
--  This module also defines a ConnectionPool type, which provides basic connection pooling functionality.

-- | A wrapper around an HDBC IConnection.
data Conn = forall conn.
  IConnection conn =>
  Conn
  { -- | The database type
    Conn -> Database
db             :: Database,
    -- | If True, the GenericPersistence functions will commit the transaction after each operation.
    Conn -> Bool
implicitCommit :: Bool,
    -- | The wrapped connection
    ()
connection     :: conn
  }

-- | An enumeration of the supported database types.
data Database = Postgres | MySQL | SQLite | Oracle | MSSQL
  deriving (Int -> Database -> ShowS
[Database] -> ShowS
Database -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Database] -> ShowS
$cshowList :: [Database] -> ShowS
show :: Database -> String
$cshow :: Database -> String
showsPrec :: Int -> Database -> ShowS
$cshowsPrec :: Int -> Database -> ShowS
Show, Database -> Database -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Database -> Database -> Bool
$c/= :: Database -> Database -> Bool
== :: Database -> Database -> Bool
$c== :: Database -> Database -> Bool
Eq, Int -> Database
Database -> Int
Database -> [Database]
Database -> Database
Database -> Database -> [Database]
Database -> Database -> Database -> [Database]
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
enumFromThenTo :: Database -> Database -> Database -> [Database]
$cenumFromThenTo :: Database -> Database -> Database -> [Database]
enumFromTo :: Database -> Database -> [Database]
$cenumFromTo :: Database -> Database -> [Database]
enumFromThen :: Database -> Database -> [Database]
$cenumFromThen :: Database -> Database -> [Database]
enumFrom :: Database -> [Database]
$cenumFrom :: Database -> [Database]
fromEnum :: Database -> Int
$cfromEnum :: Database -> Int
toEnum :: Int -> Database
$ctoEnum :: Int -> Database
pred :: Database -> Database
$cpred :: Database -> Database
succ :: Database -> Database
$csucc :: Database -> Database
Enum)

-- | a smart constructor for the Conn type.
connect :: forall conn. IConnection conn => Database -> conn -> Conn
connect :: forall conn. IConnection conn => Database -> conn -> Conn
connect Database
db = forall conn. IConnection conn => Database -> Bool -> conn -> Conn
Conn Database
db Bool
True

-- | allows to execute a function that requires an `IConnection` argument on a `Conn`.
withWConn :: forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn :: forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn (Conn Database
_db Bool
_ic conn
conn) forall conn. IConnection conn => conn -> b
f = forall conn. IConnection conn => conn -> b
f conn
conn

-- | manually implement the IConnection type class for the Conn type.
instance IConnection Conn where
  disconnect :: Conn -> IO ()
disconnect Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> IO ()
disconnect
  commit :: Conn -> IO ()
commit Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> IO ()
commit
  rollback :: Conn -> IO ()
rollback Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> IO ()
rollback
  runRaw :: Conn -> String -> IO ()
runRaw Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> String -> IO ()
runRaw
  run :: Conn -> String -> [SqlValue] -> IO Integer
run Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn.
IConnection conn =>
conn -> String -> [SqlValue] -> IO Integer
run
  prepare :: Conn -> String -> IO Statement
prepare Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> String -> IO Statement
prepare
  clone :: Conn -> IO Conn
clone w :: Conn
w@(Conn Database
db Bool
ic conn
_) = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w (forall conn. IConnection conn => conn -> IO conn
clone forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall conn. IConnection conn => Database -> Bool -> conn -> Conn
Conn Database
db Bool
ic)
  hdbcDriverName :: Conn -> String
hdbcDriverName Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> String
hdbcDriverName
  hdbcClientVer :: Conn -> String
hdbcClientVer Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> String
hdbcClientVer
  proxiedClientName :: Conn -> String
proxiedClientName Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> String
proxiedClientName
  proxiedClientVer :: Conn -> String
proxiedClientVer Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> String
proxiedClientVer
  dbServerVer :: Conn -> String
dbServerVer Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> String
dbServerVer
  dbTransactionSupport :: Conn -> Bool
dbTransactionSupport Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> Bool
dbTransactionSupport
  getTables :: Conn -> IO [String]
getTables Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn. IConnection conn => conn -> IO [String]
getTables
  describeTable :: Conn -> String -> IO [(String, SqlColDesc)]
describeTable Conn
w = forall b. Conn -> (forall conn. IConnection conn => conn -> b) -> b
withWConn Conn
w forall conn.
IConnection conn =>
conn -> String -> IO [(String, SqlColDesc)]
describeTable

-- | A pool of connections.
type ConnectionPool = Pool Conn

-- | Creates a connection pool.
createConnPool :: IConnection conn =>
  -- | the database type e.g. Postgres, MySQL, SQLite
  Database ->
  -- | the connection string
  String ->
  -- | a function that takes a connection string and returns an IConnection
  (String -> IO conn) ->
  -- | the time (in seconds) to keep idle connections open
  Double ->
  -- | the maximum number of connections to keep open
  Int ->
  -- | the resulting connection pool
  IO ConnectionPool
createConnPool :: forall conn.
IConnection conn =>
Database
-> String
-> (String -> IO conn)
-> Double
-> Int
-> IO ConnectionPool
createConnPool Database
db String
connectString String -> IO conn
connectFun Double
idle Int
numConns = forall a. PoolConfig a -> IO (Pool a)
newPool PoolConfig Conn
poolConfig
  where
    freshConnection :: IO Conn
    freshConnection :: IO Conn
freshConnection = forall conn. IConnection conn => Database -> conn -> Conn
connect Database
db forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO conn
connectFun String
connectString
    poolConfig :: PoolConfig Conn
    poolConfig :: PoolConfig Conn
poolConfig = forall a. IO a -> (a -> IO ()) -> Double -> Int -> PoolConfig a
defaultPoolConfig IO Conn
freshConnection forall conn. IConnection conn => conn -> IO ()
disconnect Double
idle Int
numConns