hasql-postgres-0.10.0: library/Hasql/Postgres/Session/ResultProcessing.hs
module Hasql.Postgres.Session.ResultProcessing where
import Hasql.Postgres.Prelude
import qualified Database.PostgreSQL.LibPQ as PQ
import qualified Hasql.Postgres.ErrorCode as ErrorCode
import qualified Data.Attoparsec.ByteString.Char8 as Atto
import qualified Data.ByteString as B
import qualified Data.Vector as Vector
import qualified Data.Vector.Mutable as MVector
newtype M r =
M (EitherT Error (ReaderT PQ.Connection IO) r)
deriving (Functor, Applicative, Monad, MonadIO)
data Error =
-- |
-- Received no response from the database.
NoResult !(Maybe ByteString) |
-- |
-- An error reported by the DB. Code, message, details, hint.
--
-- * The SQLSTATE code for the error. The SQLSTATE code identifies the type of error that has occurred; it can be used by front-end applications to perform specific operations (such as error handling) in response to a particular database error. For a list of the possible SQLSTATE codes, see Appendix A. This field is not localizable, and is always present.
-- * The primary human-readable error message (typically one line). Always present.
-- * Detail: an optional secondary error message carrying more detail about the problem. Might run to multiple lines.
-- * Hint: an optional suggestion what to do about the problem. This is intended to differ from detail in that it offers advice (potentially inappropriate) rather than hard facts. Might run to multiple lines.
ErroneousResult !ByteString !ByteString !(Maybe ByteString) !(Maybe ByteString) |
-- |
-- The database returned an unexpected result.
-- Indicates an improper statement or a schema mismatch.
UnexpectedResult !Text |
TransactionConflict
deriving (Show)
run :: PQ.Connection -> M r -> IO (Either Error r)
run e (M m) =
flip runReaderT e $ runEitherT m
just :: Maybe PQ.Result -> M PQ.Result
just =
($ return) $ maybe $ M $ do
m <- lift $ ask >>= liftIO . PQ.errorMessage
left $ NoResult $ m
checkStatus :: (PQ.ExecStatus -> Bool) -> PQ.Result -> M ()
checkStatus g r =
do
s <- liftIO $ PQ.resultStatus r
unless (g s) $ do
case s of
PQ.BadResponse -> failWithErroneousResult
PQ.NonfatalError -> failWithErroneousResult
PQ.FatalError -> failWithErroneousResult
_ -> M $ left $ UnexpectedResult $ "Unexpected result status: " <> (fromString $ show s)
where
failWithErroneousResult =
do
code <-
fmap (fromMaybe ($bug "No code")) $
liftIO $ PQ.resultErrorField r PQ.DiagSqlstate
let transactionConflict = code == ErrorCode.serialization_failure
when transactionConflict $ M $ left $ TransactionConflict
message <-
fmap (fromMaybe ($bug "No message")) $
liftIO $ PQ.resultErrorField r PQ.DiagMessagePrimary
detail <-
liftIO $ PQ.resultErrorField r PQ.DiagMessageDetail
hint <-
liftIO $ PQ.resultErrorField r PQ.DiagMessageHint
M $ left $ ErroneousResult code message detail hint
unit :: PQ.Result -> M ()
unit r =
checkStatus (\case PQ.CommandOk -> True; PQ.TuplesOk -> True; _ -> False) r
count :: PQ.Result -> M Word64
count r =
do checkStatus (\case PQ.CommandOk -> True; _ -> False) r
r' <- liftIO $ PQ.cmdTuples r
maybe (M $ left $ UnexpectedResult $ "No number of affected rows")
(parseWord64)
(mfilter (not . B.null) r')
parseWord64 :: ByteString -> M Word64
parseWord64 b =
either (\m -> M $ left $ UnexpectedResult $ "Couldn't parse Word64: " <> fromString m)
(return)
(Atto.parseOnly (Atto.decimal <* Atto.endOfInput) b)
vector :: PQ.Result -> M (Vector (Vector (Maybe ByteString)))
vector r =
do
checkStatus (\case PQ.TuplesOk -> True; _ -> False) r
liftIO $ do
nr <- PQ.ntuples r
nc <- PQ.nfields r
mvx <- MVector.unsafeNew (rowInt nr)
forM_ [0..pred nr] $ \ir -> do
mvy <- MVector.unsafeNew (colInt nc)
forM_ [0..pred nc] $ \ic -> do
MVector.unsafeWrite mvy (colInt ic) =<< PQ.getvalue r ir ic
vy <- Vector.unsafeFreeze mvy
MVector.unsafeWrite mvx (rowInt ir) vy
Vector.unsafeFreeze mvx
{-# INLINE colInt #-}
colInt :: PQ.Column -> Int
colInt (PQ.Col n) = fromIntegral n
{-# INLINE rowInt #-}
rowInt :: PQ.Row -> Int
rowInt (PQ.Row n) = fromIntegral n