module Hasql.Postgres
(
Postgres,
Connector.Settings(..),
CxError(..),
TxError(..),
Unknown(..),
)
where
import Hasql.Postgres.Prelude
import qualified Database.PostgreSQL.LibPQ as PQ
import qualified Hasql.Backend as Bknd
import qualified Hasql.Postgres.Connector as Connector
import qualified Hasql.Postgres.Statement as Statement
import qualified Hasql.Postgres.PTI as PTI
import qualified Hasql.Postgres.Mapping as Mapping
import qualified Hasql.Postgres.Session.Transaction as Transaction
import qualified Hasql.Postgres.Session.Execution as Execution
import qualified Hasql.Postgres.Session.ResultProcessing as ResultProcessing
import qualified Language.Haskell.TH as TH
import qualified Data.Text.Encoding as Text
import qualified Data.Vector as Vector
import qualified ListT
data Postgres =
Postgres {
connection :: !PQ.Connection,
executionEnv :: !Execution.Env,
transactionEnv :: !Transaction.Env,
mappingEnv :: !Mapping.Environment
}
data CxError =
CantConnect (Maybe ByteString) |
UnsupportedVersion Int
deriving (Show, Eq)
instance Bknd.Cx Postgres where
type CxSettings Postgres =
Connector.Settings
type CxError Postgres =
CxError
acquireCx settings =
runEitherT $ do
c <- EitherT $ fmap (mapLeft connectorErrorMapping) $ Connector.open settings
lift $ do
e <- Execution.newEnv c
Postgres <$> pure c <*> pure e <*> Transaction.newEnv e <*> getIntegerDatetimes c
where
getIntegerDatetimes c =
fmap decodeValue $ PQ.parameterStatus c "integer_datetimes"
where
decodeValue =
\case
Just "on" -> True
_ -> False
connectorErrorMapping =
\case
Connector.BadStatus x -> CantConnect x
Connector.UnsupportedVersion x -> UnsupportedVersion x
releaseCx =
PQ.finish . connection
data TxError =
NoResult !(Maybe ByteString) |
ErroneousResult !ByteString !ByteString !(Maybe ByteString) !(Maybe ByteString) |
UnexpectedResult !Text |
NotInTransaction
deriving (Show, Eq)
instance Bknd.CxTx Postgres where
type TxError Postgres =
TxError
runTx p mode =
runEitherT . runMaybeT . flip runReaderT p . inTransaction mode . interpretTx
type Interpreter a =
ReaderT Postgres (MaybeT (EitherT TxError IO)) a
liftExecution :: Execution.M a -> Interpreter a
liftExecution m =
do
r <- ReaderT $ \p -> liftIO $ Execution.run (executionEnv p) m
either throwResultProcessingError return r
liftTransaction :: Transaction.M a -> Interpreter a
liftTransaction m =
do
r <- ReaderT $ \p -> liftIO $ Transaction.run (transactionEnv p) m
either throwTransactionError return r
where
throwTransactionError =
\case
Transaction.NotInTransaction -> lift $ lift $ left $ NotInTransaction
Transaction.ResultProcessingError a -> throwResultProcessingError a
throwResultProcessingError :: ResultProcessing.Error -> Interpreter a
throwResultProcessingError =
\case
ResultProcessing.NoResult a -> lift $ lift $ left $ NoResult a
ResultProcessing.ErroneousResult a b c d -> lift $ lift $ left $ ErroneousResult a b c d
ResultProcessing.UnexpectedResult a -> lift $ lift $ left $ UnexpectedResult a
ResultProcessing.TransactionConflict -> lift $ mzero
convertStatement :: Bknd.Stmt Postgres -> Interpreter Statement.Statement
convertStatement s =
asks $ \p ->
let
liftParam (StmtParam o f) =
(,) o ((,) <$> f (mappingEnv p) <*> pure PQ.Binary)
in
Statement.Statement
(Statement.UnicodeTemplate (Bknd.stmtTemplate s))
(toList $ fmap liftParam $ Bknd.stmtParams s)
(Bknd.stmtPreparable s)
interpretTx :: Bknd.Tx Postgres a -> Interpreter a
interpretTx =
iterTM $ \case
Bknd.UnitTx stmt next -> do
stmt' <- convertStatement stmt
liftExecution $ Execution.unitResult =<< Execution.statement stmt'
next
Bknd.CountTx stmt next -> do
stmt' <- convertStatement stmt
r <- liftExecution $ Execution.countResult =<< Execution.statement stmt'
next $ r
Bknd.VectorTx stmt next -> do
stmt' <- convertStatement stmt
r <- liftExecution $ Execution.vectorResult =<< Execution.statement stmt'
r' <-
asks $ \p ->
(fmap . fmap) (ResultValue (mappingEnv p)) $ r
next r'
Bknd.StreamTx stmt next -> do
stmt' <- convertStatement stmt
r <- liftTransaction $ Transaction.streamWithCursor stmt'
r' <-
asks $ \p ->
(fmap . fmap) (ResultValue (mappingEnv p)) $
hoist (lift . flip runReaderT p . liftTransaction) $
r
next r'
inTransaction :: Bknd.TxMode -> Interpreter a -> Interpreter a
inTransaction mode m =
do
liftTransaction $ beginTransaction
result <- ReaderT $ \p -> lift $ lift $ runEitherT $ runMaybeT $ flip runReaderT p $ m
case result of
Left e -> do
liftTransaction $ finishTransaction False
lift $ lift $ left $ e
Right Nothing -> do
liftTransaction $ finishTransaction False
mzero
Right (Just r) -> do
liftTransaction $ finishTransaction True
return r
where
(,) beginTransaction finishTransaction =
case mode of
Nothing ->
(,) (return ())
(const (return ()))
Just (isolation, Nothing) ->
(,) (Transaction.beginTransaction (convertIsolation isolation, False))
(Transaction.finishTransaction)
Just (isolation, Just commit) ->
(,) (Transaction.beginTransaction (convertIsolation isolation, True))
(\commit' -> Transaction.finishTransaction (commit && commit'))
where
convertIsolation =
\case
Bknd.Serializable -> Statement.Serializable
Bknd.RepeatableReads -> Statement.RepeatableRead
Bknd.ReadCommitted -> Statement.ReadCommitted
Bknd.ReadUncommitted -> Statement.ReadCommitted
data instance Bknd.ResultValue Postgres =
ResultValue !Mapping.Environment !(Maybe ByteString)
data instance Bknd.StmtParam Postgres =
StmtParam !PQ.Oid !(Mapping.Environment -> Maybe ByteString)
encodeValueUsingMapping :: Mapping.Mapping a => a -> Bknd.StmtParam Postgres
encodeValueUsingMapping x =
StmtParam
(PTI.oidPQ $ Mapping.oid x)
(flip Mapping.encode x)
decodeValueUsingMapping :: Mapping.Mapping a => Bknd.ResultValue Postgres -> Either Text a
decodeValueUsingMapping (ResultValue e x) =
Mapping.decode e x
instance Mapping.Mapping a => Bknd.CxValue Postgres (Maybe a) where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance (Mapping.Mapping a, Mapping.ArrayMapping a) => Bknd.CxValue Postgres [a] where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance (Mapping.Mapping a, Mapping.ArrayMapping a) => Bknd.CxValue Postgres (Vector a) where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Int where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Int8 where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Int16 where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Int32 where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Int64 where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Word where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Word8 where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Word16 where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Word32 where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Word64 where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Float where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Double where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Scientific where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Day where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres TimeOfDay where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres (TimeOfDay, TimeZone) where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres LocalTime where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres UTCTime where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres DiffTime where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Char where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Text where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres LazyText where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres ByteString where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres LazyByteString where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres Bool where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
instance Bknd.CxValue Postgres UUID where
encodeValue = encodeValueUsingMapping
decodeValue = decodeValueUsingMapping
newtype Unknown =
Unknown ByteString
instance Bknd.CxValue Postgres Unknown where
encodeValue (Unknown x) =
StmtParam (PTI.oidPQ (PTI.ptiOID (PTI.unknown))) (const $ Just x)
decodeValue (ResultValue _ x) =
maybe (Left "Decoding a NULL to Unknown") (Right . Unknown) x