rfc-psql-0.0.0.1: src/RFC/Psql.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module RFC.Psql
( module Database.PostgreSQL.Typed
, module Database.PostgreSQL.Typed.Array
, module Database.PostgreSQL.Typed.Query
, module Database.PostgreSQL.Typed.Types
, module Database.PostgreSQL.Typed.TH
, module RFC.Psql
, module Network
) where
import Control.Monad.Trans.Class ( MonadTrans (..) )
import Control.Monad.Trans.Reader ( ask )
import qualified Data.ByteString.Char8 as C8
import Data.Pool
import Database.PostgreSQL.Typed
import Database.PostgreSQL.Typed.Array
import Database.PostgreSQL.Typed.Query
import Database.PostgreSQL.Typed.TH
import Database.PostgreSQL.Typed.Types
import Network ( PortID (PortNumber) )
import qualified RFC.Data.UUID as UUID
import qualified RFC.Env as Env
import RFC.Prelude hiding ( ask )
type PGConnectionPool = Pool PGConnection
type ConnectionPool = PGConnectionPool
class HasPsql m where
withPsqlConnection :: (PGConnection -> IO a) -> m a
instance {-# OVERLAPPABLE #-} (MonadTrans t, Monad m, HasPsql m) => HasPsql (t m) where
withPsqlConnection :: (PGConnection -> IO a) -> (t m) a
withPsqlConnection = lift . withPsqlConnection
instance {-# OVERLAPS #-} (MonadIO m) => HasPsql (ReaderT PGConnectionPool m) where
withPsqlConnection :: (PGConnection -> IO a) -> ReaderT PGConnectionPool m a
withPsqlConnection action = do
pool <- ask
liftIO $ withResource pool action
{-# INLINE withPsqlConnection #-}
{-# SPECIALIZE instance HasPsql (ReaderT PGConnectionPool IO) #-}
instance {-# OVERLAPS #-} (MonadIO m) => HasPsql (ReaderT PGConnection m) where
withPsqlConnection :: (PGConnection -> IO a) -> ReaderT PGConnection m a
withPsqlConnection action = do
conn <- ask
liftIO $ action conn
{-# INLINE withPsqlConnection #-}
{-# SPECIALIZE instance HasPsql (ReaderT PGConnection IO) #-}
liftHasPsql :: (PGConnection -> IO a) -> ReaderT PGConnection IO a
liftHasPsql = ReaderT
{-# INLINE liftHasPsql #-}
withPsqlTransaction :: (HasPsql psql) => (ReaderT PGConnection IO a) -> psql a
withPsqlTransaction action = withPsqlConnection $ \conn ->
pgTransaction conn $ runReaderT action conn
{-# INLINABLE withPsqlTransaction #-}
instance {-# OVERLAPPING #-} Env.DefConfig PGDatabase where
defConfig = defaultPGDatabase
{-# INLINE defConfig #-}
instance Env.FromEnv PGDatabase where
fromEnv = PGDatabase <$> Env.env "PSQL_HOST"
<*> Env.env "PSQL_PORT"
<*> Env.env "PSQL_DATABASE"
<*> Env.env "PSQL_USERNAME"
<*> Env.env "PSQL_PASSWORD"
<*> pure []
<*> pure Env.isDevelopment
<*> pure (
if Env.isDevelopment then
print . PGError
else
const $ return ()
)
{-# INLINE fromEnv #-}
defaultConnectInfo :: (MonadIO m, MonadFail m) => m PGDatabase
defaultConnectInfo = do
result <- liftIO Env.decodeEnv
case result of
Left err -> fail $ "Could not retrieve psql connection info: " <> err
Right connInfo -> return connInfo
{-# INLINE defaultConnectInfo #-}
createConnectionPool :: (MonadIO m) => PGDatabase -> m PGConnectionPool
createConnectionPool connInfo = liftIO $
createPool connect close 1 10 100
where
connect = pgConnect connInfo
close = pgDisconnect
{-# INLINE createConnectionPool #-}
query :: (HasPsql m, PGQuery q a) => q -> m [a]
query q = withPsqlConnection $ \conn -> liftIO $ pgQuery conn q
{-# INLINE query #-}
query1 :: (MonadIO m, HasPsql m, PGQuery q a) => q -> m (Maybe a)
query1 qry = safeHead <$> query qry
{-# INLINE query1 #-}
query1Else :: (MonadIO m, HasPsql m, PGQuery q a, Exception e) => q -> e -> m (Maybe a)
query1Else qry e = do
result <- query1 qry
case result of
(Just _) -> return result
Nothing -> throwIO e
{-# INLINE query1Else #-}
execute :: (HasPsql m, PGQuery q ()) => q -> m Int
execute q = withPsqlConnection $ \conn -> liftIO $ pgExecute conn q
{-# INLINE execute #-}
execute_ :: (MonadIO m, HasPsql m, PGQuery q ()) => q -> m ()
execute_ = void . execute
{-# INLINE execute_ #-}
instance {-# OVERLAPPING #-} PGType "style" where
type PGVal "style" = StrictText
pgBinaryColumn _ _ = True
instance {-# OVERLAPPING #-} PGStringType "style"
instance {-# OVERLAPPING #-} PGColumn "uuid[]" [UUID] where
pgDecode _ sbs =
let (sbsList::[StrictByteString]) = C8.split (pgArrayDelim pgTypePxy) sbs in
let strList = catMaybes $ fromUTF8 <$> sbsList in
let uuidList = catMaybes $ UUID.fromString <$> strList in
uuidList
where
pgTypePxy = PGTypeProxy :: PGTypeID "uuid[]"