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 =
NoResult !(Maybe ByteString) |
ErroneousResult !ByteString !ByteString !(Maybe ByteString) !(Maybe ByteString) |
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
colInt :: PQ.Column -> Int
colInt (PQ.Col n) = fromIntegral n
rowInt :: PQ.Row -> Int
rowInt (PQ.Row n) = fromIntegral n