module Database.PostgreSQL.PQTypes.Internal.Connection (
Connection(..)
, ConnectionData(..)
, withConnectionData
, ConnectionStats(..)
, ConnectionSettings(..)
, def
, ConnectionSourceM(..)
, ConnectionSource(..)
, simpleSource
, poolSource
, connect
, disconnect
) where
import Control.Applicative
import Control.Arrow
import Control.Concurrent.MVar
import Control.Monad
import Control.Monad.Base
import Control.Monad.Catch
import Data.Default.Class
import Data.Pool
import Data.Time.Clock
import Foreign.ForeignPtr
import Foreign.Ptr
import Foreign.Storable
import GHC.Exts
import Prelude
import qualified Control.Exception as E
import qualified Data.ByteString as BS
import qualified Data.Foldable as F
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Database.PostgreSQL.PQTypes.Internal.C.Interface
import Database.PostgreSQL.PQTypes.Internal.C.Types
import Database.PostgreSQL.PQTypes.Internal.Composite
import Database.PostgreSQL.PQTypes.Internal.Error
import Database.PostgreSQL.PQTypes.Internal.Utils
data ConnectionSettings = ConnectionSettings {
csConnInfo :: !T.Text
, csClientEncoding :: !(Maybe T.Text)
, csComposites :: ![T.Text]
} deriving (Eq, Ord, Show)
instance Default ConnectionSettings where
def = ConnectionSettings {
csConnInfo = T.empty
, csClientEncoding = Just "UTF-8"
, csComposites = []
}
data ConnectionStats = ConnectionStats {
statsQueries :: !Int
, statsRows :: !Int
, statsValues :: !Int
, statsParams :: !Int
} deriving (Eq, Ord, Show)
initialStats :: ConnectionStats
initialStats = ConnectionStats {
statsQueries = 0
, statsRows = 0
, statsValues = 0
, statsParams = 0
}
data ConnectionData = ConnectionData {
cdFrgnPtr :: !(ForeignPtr (Ptr PGconn))
, cdPtr :: !(Ptr PGconn)
, cdStats :: !ConnectionStats
}
newtype Connection = Connection {
unConnection :: MVar (Maybe ConnectionData)
}
withConnectionData
:: Connection
-> String
-> (ConnectionData -> IO (ConnectionData, r))
-> IO r
withConnectionData (Connection mvc) fname f =
modifyMVar mvc $ \mc -> case mc of
Nothing -> hpqTypesError $ fname ++ ": no connection"
Just cd -> first Just <$> f cd
newtype ConnectionSourceM m = ConnectionSourceM {
withConnection :: forall r. (Connection -> m r) -> m r
}
newtype ConnectionSource (cs :: [(* -> *) -> Constraint]) = ConnectionSource {
unConnectionSource :: forall m. MkConstraint m cs => ConnectionSourceM m
}
simpleSource
:: ConnectionSettings
-> ConnectionSource [MonadBase IO, MonadMask]
simpleSource cs = ConnectionSource $ ConnectionSourceM {
withConnection = bracket (liftBase $ connect cs) (liftBase . disconnect)
}
poolSource
:: ConnectionSettings
-> Int
-> NominalDiffTime
-> Int
-> IO (ConnectionSource [MonadBase IO, MonadMask])
poolSource cs numStripes idleTime maxResources = do
pool <- createPool (connect cs) disconnect numStripes idleTime maxResources
return $ ConnectionSource $ ConnectionSourceM {
withConnection = withResource' pool . (clearStats >=>)
}
where
withResource' pool m = mask $ \restore -> do
(resource, local) <- liftBase $ takeResource pool
ret <- restore (m resource) `onException`
liftBase (destroyResource pool local resource)
liftBase $ putResource local resource
return ret
clearStats conn@(Connection mv) = do
liftBase . modifyMVar_ mv $ \mconn ->
return $ (\cd -> cd { cdStats = initialStats }) <$> mconn
return conn
connect :: ConnectionSettings -> IO Connection
connect ConnectionSettings{..} = do
fconn <- BS.useAsCString (T.encodeUtf8 csConnInfo) c_PQconnectdb
withForeignPtr fconn $ \connPtr -> do
conn <- peek connPtr
status <- c_PQstatus conn
when (status /= c_CONNECTION_OK) $
throwLibPQError conn "connect"
F.forM_ csClientEncoding $ \enc -> do
res <- BS.useAsCString (T.encodeUtf8 enc) (c_PQsetClientEncoding conn)
when (res == 1) $
throwLibPQError conn "connect"
c_PQinitTypes conn
registerComposites conn csComposites
Connection <$> newMVar (Just ConnectionData {
cdFrgnPtr = fconn
, cdPtr = conn
, cdStats = initialStats
})
disconnect :: Connection -> IO ()
disconnect (Connection mvconn) = modifyMVar_ mvconn $ \mconn -> do
case mconn of
Just cd -> withForeignPtr (cdFrgnPtr cd) c_PQfinishPtr
Nothing -> E.throwIO (HPQTypesError "disconnect: no connection (shouldn't happen)")
return Nothing