module Hasql.Engine.Contexts.Session where

import Data.HashMap.Strict qualified as HashMap
import Data.HashSet qualified as HashSet
import Hasql.Codecs.RequestingOid qualified as RequestingOid
import Hasql.Codecs.Vocab.OidCache qualified as OidCache
import Hasql.Codecs.Vocab.QualifiedTypeName qualified as Vocab.QualifiedTypeName
import Hasql.Comms.Roundtrip qualified as Comms.Roundtrip
import Hasql.Engine.Contexts.Pipeline qualified as Pipeline
import Hasql.Engine.Errors qualified as Errors
import Hasql.Engine.PqProcedures.SelectTypeInfo qualified as PqProcedures.SelectTypeInfo
import Hasql.Engine.Statement qualified as Statement
import Hasql.Engine.Structures.ConnectionState qualified as ConnectionState
import Hasql.Engine.Structures.StatementCache qualified as StatementCache
import Hasql.Platform.Prelude
import Hasql.Pq qualified as Pq

-- |
-- A sequence of operations to be executed in the context of a single database connection with exclusive access to it.
--
-- Construct sessions using helpers in this module such as
-- 'statement', 'pipeline' and 'script', or use 'onLibpqConnection' for a low-level
-- escape hatch.
--
-- To actually execute a 'Session' use 'Hasql.Connection.use', which manages
-- concurrent access to the shared connection state and returns either a
-- 'Errors.SessionError' or the result:
--
-- > result <- Hasql.Connection.use connection mySession
--
-- Note: while most session errors are returned as values, user code executed
-- inside a session may still throw exceptions; in that case the driver will
-- reset the connection to a clean state.
newtype Session a
  = Session (ConnectionState.ConnectionState -> IO (Either Errors.SessionError a, ConnectionState.ConnectionState))
  deriving
    ((forall a b. (a -> b) -> Session a -> Session b)
-> (forall a b. a -> Session b -> Session a) -> Functor Session
forall a b. a -> Session b -> Session a
forall a b. (a -> b) -> Session a -> Session b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
$cfmap :: forall a b. (a -> b) -> Session a -> Session b
fmap :: forall a b. (a -> b) -> Session a -> Session b
$c<$ :: forall a b. a -> Session b -> Session a
<$ :: forall a b. a -> Session b -> Session a
Functor, Functor Session
Functor Session =>
(forall a. a -> Session a)
-> (forall a b. Session (a -> b) -> Session a -> Session b)
-> (forall a b c.
    (a -> b -> c) -> Session a -> Session b -> Session c)
-> (forall a b. Session a -> Session b -> Session b)
-> (forall a b. Session a -> Session b -> Session a)
-> Applicative Session
forall a. a -> Session a
forall a b. Session a -> Session b -> Session a
forall a b. Session a -> Session b -> Session b
forall a b. Session (a -> b) -> Session a -> Session b
forall a b c. (a -> b -> c) -> Session a -> Session b -> Session c
forall (f :: * -> *).
Functor f =>
(forall a. a -> f a)
-> (forall a b. f (a -> b) -> f a -> f b)
-> (forall a b c. (a -> b -> c) -> f a -> f b -> f c)
-> (forall a b. f a -> f b -> f b)
-> (forall a b. f a -> f b -> f a)
-> Applicative f
$cpure :: forall a. a -> Session a
pure :: forall a. a -> Session a
$c<*> :: forall a b. Session (a -> b) -> Session a -> Session b
<*> :: forall a b. Session (a -> b) -> Session a -> Session b
$cliftA2 :: forall a b c. (a -> b -> c) -> Session a -> Session b -> Session c
liftA2 :: forall a b c. (a -> b -> c) -> Session a -> Session b -> Session c
$c*> :: forall a b. Session a -> Session b -> Session b
*> :: forall a b. Session a -> Session b -> Session b
$c<* :: forall a b. Session a -> Session b -> Session a
<* :: forall a b. Session a -> Session b -> Session a
Applicative, Applicative Session
Applicative Session =>
(forall a b. Session a -> (a -> Session b) -> Session b)
-> (forall a b. Session a -> Session b -> Session b)
-> (forall a. a -> Session a)
-> Monad Session
forall a. a -> Session a
forall a b. Session a -> Session b -> Session b
forall a b. Session a -> (a -> Session b) -> Session b
forall (m :: * -> *).
Applicative m =>
(forall a b. m a -> (a -> m b) -> m b)
-> (forall a b. m a -> m b -> m b)
-> (forall a. a -> m a)
-> Monad m
$c>>= :: forall a b. Session a -> (a -> Session b) -> Session b
>>= :: forall a b. Session a -> (a -> Session b) -> Session b
$c>> :: forall a b. Session a -> Session b -> Session b
>> :: forall a b. Session a -> Session b -> Session b
$creturn :: forall a. a -> Session a
return :: forall a. a -> Session a
Monad, MonadError Errors.SessionError, Monad Session
Monad Session => (forall a. IO a -> Session a) -> MonadIO Session
forall a. IO a -> Session a
forall (m :: * -> *).
Monad m =>
(forall a. IO a -> m a) -> MonadIO m
$cliftIO :: forall a. IO a -> Session a
liftIO :: forall a. IO a -> Session a
MonadIO)
    via (ExceptT Errors.SessionError (StateT ConnectionState.ConnectionState IO))

run :: Session a -> ConnectionState.ConnectionState -> IO (Either Errors.SessionError a, ConnectionState.ConnectionState)
run :: forall a.
Session a
-> ConnectionState -> IO (Either SessionError a, ConnectionState)
run (Session ConnectionState -> IO (Either SessionError a, ConnectionState)
session) ConnectionState
connectionState = ConnectionState -> IO (Either SessionError a, ConnectionState)
session ConnectionState
connectionState

-- |
-- Possibly a multi-statement query,
-- which however cannot be parameterized or prepared,
-- nor can any results of it be collected.
script :: ByteString -> Session ()
script :: ByteString -> Session ()
script ByteString
sql =
  (ConnectionState -> IO (Either SessionError (), ConnectionState))
-> Session ()
forall a.
(ConnectionState -> IO (Either SessionError a, ConnectionState))
-> Session a
Session \ConnectionState
connectionState -> do
    let connection :: Connection
connection = ConnectionState -> Connection
ConnectionState.connection ConnectionState
connectionState
    Either (Error (Maybe ByteString)) ()
result <- Roundtrip (Maybe ByteString) ()
-> Connection -> IO (Either (Error (Maybe ByteString)) ())
forall context a.
Roundtrip context a -> Connection -> IO (Either (Error context) a)
Comms.Roundtrip.toSerialIO (Maybe ByteString -> ByteString -> Roundtrip (Maybe ByteString) ()
forall context. context -> ByteString -> Roundtrip context ()
Comms.Roundtrip.script (ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
sql) ByteString
sql) Connection
connection
    case Either (Error (Maybe ByteString)) ()
result of
      Left Error (Maybe ByteString)
err -> case Error (Maybe ByteString)
err of
        Comms.Roundtrip.ClientError Maybe ByteString
_ Maybe ByteString
details -> do
          (Either SessionError (), ConnectionState)
-> IO (Either SessionError (), ConnectionState)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
            ( SessionError -> Either SessionError ()
forall a b. a -> Either a b
Left (Text -> SessionError
Errors.ConnectionSessionError (Text -> (ByteString -> Text) -> Maybe ByteString -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" ByteString -> Text
decodeUtf8Lenient Maybe ByteString
details)),
              ConnectionState
connectionState
            )
        Comms.Roundtrip.ServerError Error (Maybe ByteString)
recvError ->
          (Either SessionError (), ConnectionState)
-> IO (Either SessionError (), ConnectionState)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
            ( SessionError -> Either SessionError ()
forall a b. a -> Either a b
Left (ByteString -> Error (Maybe ByteString) -> SessionError
Errors.fromRecvErrorInScript ByteString
sql Error (Maybe ByteString)
recvError),
              ConnectionState
connectionState
            )
      Right () ->
        (Either SessionError (), ConnectionState)
-> IO (Either SessionError (), ConnectionState)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
          ( () -> Either SessionError ()
forall a b. b -> Either a b
Right (),
            ConnectionState
connectionState
          )

-- |
-- Execute a single statement by providing parameters to it,
-- running it directly in serial mode.
--
-- Each execution is a dedicated network roundtrip. The first execution of a
-- preparable statement costs an extra roundtrip (a separate @PARSE@), after
-- which steady-state execution is a single roundtrip.
--
-- To batch multiple statements into fewer roundtrips, use 'pipeline' instead.
statement ::
  Statement.Statement params result ->
  params ->
  Session result
statement :: forall params result.
Statement params result -> params -> Session result
statement Statement params result
stmt params
params =
  (ConnectionState
 -> IO (Either SessionError result, ConnectionState))
-> Session result
forall a.
(ConnectionState -> IO (Either SessionError a, ConnectionState))
-> Session a
Session \ConnectionState
connectionState -> do
    let usePreparedStatements :: Bool
usePreparedStatements = ConnectionState -> Bool
ConnectionState.preparedStatements ConnectionState
connectionState
        statementCache :: StatementCache
statementCache = ConnectionState -> StatementCache
ConnectionState.statementCache ConnectionState
connectionState
        oidCache :: OidCache
oidCache = ConnectionState -> OidCache
ConnectionState.oidCache ConnectionState
connectionState
        connection :: Connection
connection = ConnectionState -> Connection
ConnectionState.connection ConnectionState
connectionState
        sql :: ByteString
sql = Statement params result -> ByteString
forall params result. Statement params result -> ByteString
Statement.sql Statement params result
stmt
        missingTypes :: HashSet QualifiedTypeName
missingTypes = HashSet QualifiedTypeName -> OidCache -> HashSet QualifiedTypeName
OidCache.selectUnknownNames (Statement params result -> HashSet QualifiedTypeName
forall params result.
Statement params result -> HashSet QualifiedTypeName
Statement.unknownTypes Statement params result
stmt) OidCache
oidCache
    Either SessionError OidCache
resolvedOidCache <-
      if HashSet QualifiedTypeName -> Bool
forall a. HashSet a -> Bool
HashSet.null HashSet QualifiedTypeName
missingTypes
        then Either SessionError OidCache -> IO (Either SessionError OidCache)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (OidCache -> Either SessionError OidCache
forall a b. b -> Either a b
Right OidCache
oidCache)
        else do
          Either SessionError SelectTypeInfoResult
oidCacheUpdates <-
            Connection
-> SelectTypeInfo -> IO (Either SessionError SelectTypeInfoResult)
PqProcedures.SelectTypeInfo.run Connection
connection (HashSet QualifiedTypeName -> SelectTypeInfo
PqProcedures.SelectTypeInfo.SelectTypeInfo HashSet QualifiedTypeName
missingTypes)
          pure $ case Either SessionError SelectTypeInfoResult
oidCacheUpdates of
            Left SessionError
err -> SessionError -> Either SessionError OidCache
forall a b. a -> Either a b
Left SessionError
err
            Right SelectTypeInfoResult
oidCacheUpdates ->
              let foundTypes :: HashSet QualifiedTypeName
foundTypes = SelectTypeInfoResult -> HashSet QualifiedTypeName
forall k a. HashMap k a -> HashSet k
HashMap.keysSet SelectTypeInfoResult
oidCacheUpdates
                  notFoundTypes :: HashSet QualifiedTypeName
notFoundTypes = HashSet QualifiedTypeName
-> HashSet QualifiedTypeName -> HashSet QualifiedTypeName
forall a. Hashable a => HashSet a -> HashSet a -> HashSet a
HashSet.difference HashSet QualifiedTypeName
missingTypes HashSet QualifiedTypeName
foundTypes
               in if Bool -> Bool
not (HashSet QualifiedTypeName -> Bool
forall a. HashSet a -> Bool
HashSet.null HashSet QualifiedTypeName
notFoundTypes)
                    then SessionError -> Either SessionError OidCache
forall a b. a -> Either a b
Left (HashSet (Maybe Text, Text) -> SessionError
Errors.MissingTypesSessionError ((QualifiedTypeName -> (Maybe Text, Text))
-> HashSet QualifiedTypeName -> HashSet (Maybe Text, Text)
forall b a. Hashable b => (a -> b) -> HashSet a -> HashSet b
HashSet.map QualifiedTypeName -> (Maybe Text, Text)
Vocab.QualifiedTypeName.toNameTuple HashSet QualifiedTypeName
notFoundTypes))
                    else OidCache -> Either SessionError OidCache
forall a b. b -> Either a b
Right (OidCache
oidCache OidCache -> OidCache -> OidCache
forall a. Semigroup a => a -> a -> a
<> SelectTypeInfoResult -> OidCache
OidCache.fromHashMap SelectTypeInfoResult
oidCacheUpdates)
    case Either SessionError OidCache
resolvedOidCache of
      Left SessionError
err -> (Either SessionError result, ConnectionState)
-> IO (Either SessionError result, ConnectionState)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SessionError -> Either SessionError result
forall a b. a -> Either a b
Left SessionError
err, ConnectionState
connectionState)
      Right OidCache
newOidCache -> do
        let decoder' :: ResultDecoder result
decoder' = RequestingOid (ResultDecoder result)
-> OidCache -> ResultDecoder result
forall a. RequestingOid a -> OidCache -> a
RequestingOid.toBase (Statement params result -> RequestingOid (ResultDecoder result)
forall params result.
Statement params result -> RequestingOid (ResultDecoder result)
Statement.decoder Statement params result
stmt) OidCache
newOidCache
            prepared :: Bool
prepared = Bool
usePreparedStatements Bool -> Bool -> Bool
&& Statement params result -> Bool
forall params result. Statement params result -> Bool
Statement.isPrepared Statement params result
stmt
            -- Single-statement context for error reporting:
            -- total statements 1, index 0.
            context :: Maybe (Int, Int, ByteString, [Text], Bool)
context = (Int, Int, ByteString, [Text], Bool)
-> Maybe (Int, Int, ByteString, [Text], Bool)
forall a. a -> Maybe a
Just (Int
1, Int
0, ByteString
sql, Statement params result -> params -> [Text]
forall params result. Statement params result -> params -> [Text]
Statement.printer Statement params result
stmt params
params, Bool
prepared)
            mapError :: Error (Maybe (Int, Int, ByteString, [Text], Bool)) -> SessionError
mapError = \case
              Comms.Roundtrip.ClientError Maybe (Int, Int, ByteString, [Text], Bool)
_ Maybe ByteString
details ->
                Text -> SessionError
Errors.ConnectionSessionError (Text -> (ByteString -> Text) -> Maybe ByteString -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" ByteString -> Text
decodeUtf8Lenient Maybe ByteString
details)
              Comms.Roundtrip.ServerError Error (Maybe (Int, Int, ByteString, [Text], Bool))
recvError ->
                Error (Maybe (Int, Int, ByteString, [Text], Bool)) -> SessionError
Errors.fromRecvError Error (Maybe (Int, Int, ByteString, [Text], Bool))
recvError
            withState :: (Either
   (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result,
 StatementCache)
-> (Either SessionError result, ConnectionState)
withState (Either (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result
result, StatementCache
newStatementCache) =
              ( (Error (Maybe (Int, Int, ByteString, [Text], Bool))
 -> SessionError)
-> Either
     (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result
-> Either SessionError result
forall a b c. (a -> b) -> Either a c -> Either b c
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first Error (Maybe (Int, Int, ByteString, [Text], Bool)) -> SessionError
mapError Either (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result
result,
                ConnectionState
connectionState
                  { ConnectionState.oidCache = newOidCache,
                    ConnectionState.statementCache = newStatementCache
                  }
              )
        ((Either
    (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result,
  StatementCache)
 -> (Either SessionError result, ConnectionState))
-> IO
     (Either
        (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result,
      StatementCache)
-> IO (Either SessionError result, ConnectionState)
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Either
   (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result,
 StatementCache)
-> (Either SessionError result, ConnectionState)
withState
          (IO
   (Either
      (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result,
    StatementCache)
 -> IO (Either SessionError result, ConnectionState))
-> IO
     (Either
        (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result,
      StatementCache)
-> IO (Either SessionError result, ConnectionState)
forall a b. (a -> b) -> a -> b
$ if Bool
prepared
            then do
              let ([Word32]
oidList, [Maybe (ByteString, Bool)]
valueAndFormatList) =
                    Statement params result
-> OidCache -> params -> ([Word32], [Maybe (ByteString, Bool)])
forall params result.
Statement params result
-> OidCache -> params -> ([Word32], [Maybe (ByteString, Bool)])
Statement.compilePreparedStatementData Statement params result
stmt OidCache
newOidCache params
params
                  pqOidList :: [Oid]
pqOidList = (Word32 -> Oid) -> [Word32] -> [Oid]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (CUInt -> Oid
Pq.Oid (CUInt -> Oid) -> (Word32 -> CUInt) -> Word32 -> Oid
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Word32 -> CUInt
forall a b. (Integral a, Num b) => a -> b
fromIntegral) [Word32]
oidList
                  encodedParams :: [Maybe (ByteString, Format)]
encodedParams =
                    [Maybe (ByteString, Bool)]
valueAndFormatList
                      [Maybe (ByteString, Bool)]
-> ([Maybe (ByteString, Bool)] -> [Maybe (ByteString, Format)])
-> [Maybe (ByteString, Format)]
forall a b. a -> (a -> b) -> b
& (Maybe (ByteString, Bool) -> Maybe (ByteString, Format))
-> [Maybe (ByteString, Bool)] -> [Maybe (ByteString, Format)]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (((ByteString, Bool) -> (ByteString, Format))
-> Maybe (ByteString, Bool) -> Maybe (ByteString, Format)
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(ByteString
bytes, Bool
format) -> (ByteString
bytes, Format -> Format -> Bool -> Format
forall a. a -> a -> Bool -> a
bool Format
Pq.Binary Format
Pq.Text Bool
format)))
                  execute :: ByteString
-> IO
     (Either
        (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result)
execute ByteString
remoteKey =
                    Roundtrip (Maybe (Int, Int, ByteString, [Text], Bool)) result
-> Connection
-> IO
     (Either
        (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result)
forall context a.
Roundtrip context a -> Connection -> IO (Either (Error context) a)
Comms.Roundtrip.toSerialIO
                      (Maybe (Int, Int, ByteString, [Text], Bool)
-> ByteString
-> [Maybe (ByteString, Format)]
-> Format
-> ResultDecoder result
-> Roundtrip (Maybe (Int, Int, ByteString, [Text], Bool)) result
forall context a.
context
-> ByteString
-> [Maybe (ByteString, Format)]
-> Format
-> ResultDecoder a
-> Roundtrip context a
Comms.Roundtrip.queryPrepared Maybe (Int, Int, ByteString, [Text], Bool)
context ByteString
remoteKey [Maybe (ByteString, Format)]
encodedParams Format
Pq.Binary ResultDecoder result
decoder')
                      Connection
connection
              case ByteString -> [Oid] -> StatementCache -> Maybe ByteString
StatementCache.lookup ByteString
sql [Oid]
pqOidList StatementCache
statementCache of
                Just ByteString
remoteKey -> do
                  Either (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result
result <- ByteString
-> IO
     (Either
        (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result)
execute ByteString
remoteKey
                  pure (Either (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result
result, StatementCache
statementCache)
                Maybe ByteString
Nothing -> do
                  let (ByteString
remoteKey, StatementCache
newStatementCache) = ByteString
-> [Oid] -> StatementCache -> (ByteString, StatementCache)
StatementCache.insert ByteString
sql [Oid]
pqOidList StatementCache
statementCache
                  -- In non-pipeline mode PARSE and EXECUTE cannot be sent
                  -- back-to-back, so prepare in a dedicated roundtrip first.
                  Either (Error (Maybe (Int, Int, ByteString, [Text], Bool))) ()
prepareResult <-
                    Roundtrip (Maybe (Int, Int, ByteString, [Text], Bool)) ()
-> Connection
-> IO
     (Either (Error (Maybe (Int, Int, ByteString, [Text], Bool))) ())
forall context a.
Roundtrip context a -> Connection -> IO (Either (Error context) a)
Comms.Roundtrip.toSerialIO
                      (Maybe (Int, Int, ByteString, [Text], Bool)
-> ByteString
-> ByteString
-> [Oid]
-> Roundtrip (Maybe (Int, Int, ByteString, [Text], Bool)) ()
forall context.
context
-> ByteString -> ByteString -> [Oid] -> Roundtrip context ()
Comms.Roundtrip.prepare Maybe (Int, Int, ByteString, [Text], Bool)
context ByteString
remoteKey ByteString
sql [Oid]
pqOidList)
                      Connection
connection
                  case Either (Error (Maybe (Int, Int, ByteString, [Text], Bool))) ()
prepareResult of
                    -- PARSE failed: the statement is not on the server, so
                    -- keep the old cache (no entry committed).
                    Left Error (Maybe (Int, Int, ByteString, [Text], Bool))
err -> (Either
   (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result,
 StatementCache)
-> IO
     (Either
        (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result,
      StatementCache)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Error (Maybe (Int, Int, ByteString, [Text], Bool))
-> Either
     (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result
forall a b. a -> Either a b
Left Error (Maybe (Int, Int, ByteString, [Text], Bool))
err, StatementCache
statementCache)
                    Right () -> do
                      -- PARSE succeeded, so the statement is on the server
                      -- under remoteKey regardless of whether EXECUTE then
                      -- fails. Commit the cache so a later use hits it instead
                      -- of re-issuing PARSE for an already-existing name.
                      Either (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result
result <- ByteString
-> IO
     (Either
        (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result)
execute ByteString
remoteKey
                      pure (Either (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result
result, StatementCache
newStatementCache)
            else do
              let encodedParams :: [Maybe (Oid, ByteString, Format)]
encodedParams =
                    Statement params result
-> OidCache -> params -> [Maybe (Word32, ByteString, Bool)]
forall params result.
Statement params result
-> OidCache -> params -> [Maybe (Word32, ByteString, Bool)]
Statement.compileUnpreparedStatementData Statement params result
stmt OidCache
newOidCache params
params
                      [Maybe (Word32, ByteString, Bool)]
-> ([Maybe (Word32, ByteString, Bool)]
    -> [Maybe (Oid, ByteString, Format)])
-> [Maybe (Oid, ByteString, Format)]
forall a b. a -> (a -> b) -> b
& (Maybe (Word32, ByteString, Bool)
 -> Maybe (Oid, ByteString, Format))
-> [Maybe (Word32, ByteString, Bool)]
-> [Maybe (Oid, ByteString, Format)]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (((Word32, ByteString, Bool) -> (Oid, ByteString, Format))
-> Maybe (Word32, ByteString, Bool)
-> Maybe (Oid, ByteString, Format)
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(Word32
oid, ByteString
bytes, Bool
format) -> (CUInt -> Oid
Pq.Oid (Word32 -> CUInt
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word32
oid), ByteString
bytes, Format -> Format -> Bool -> Format
forall a. a -> a -> Bool -> a
bool Format
Pq.Binary Format
Pq.Text Bool
format)))
              Either (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result
result <-
                Roundtrip (Maybe (Int, Int, ByteString, [Text], Bool)) result
-> Connection
-> IO
     (Either
        (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result)
forall context a.
Roundtrip context a -> Connection -> IO (Either (Error context) a)
Comms.Roundtrip.toSerialIO
                  (Maybe (Int, Int, ByteString, [Text], Bool)
-> ByteString
-> [Maybe (Oid, ByteString, Format)]
-> Format
-> ResultDecoder result
-> Roundtrip (Maybe (Int, Int, ByteString, [Text], Bool)) result
forall context a.
context
-> ByteString
-> [Maybe (Oid, ByteString, Format)]
-> Format
-> ResultDecoder a
-> Roundtrip context a
Comms.Roundtrip.queryParams Maybe (Int, Int, ByteString, [Text], Bool)
context ByteString
sql [Maybe (Oid, ByteString, Format)]
encodedParams Format
Pq.Binary ResultDecoder result
decoder')
                  Connection
connection
              pure (Either (Error (Maybe (Int, Int, ByteString, [Text], Bool))) result
result, StatementCache
statementCache)

-- |
-- Execute a pipeline.
pipeline :: Pipeline.Pipeline result -> Session result
pipeline :: forall result. Pipeline result -> Session result
pipeline Pipeline result
pipeline = (ConnectionState
 -> IO (Either SessionError result, ConnectionState))
-> Session result
forall a.
(ConnectionState -> IO (Either SessionError a, ConnectionState))
-> Session a
Session \ConnectionState
connectionState -> do
  let usePreparedStatements :: Bool
usePreparedStatements = ConnectionState -> Bool
ConnectionState.preparedStatements ConnectionState
connectionState
      statementCache :: StatementCache
statementCache = ConnectionState -> StatementCache
ConnectionState.statementCache ConnectionState
connectionState
      oidCache :: OidCache
oidCache = ConnectionState -> OidCache
ConnectionState.oidCache ConnectionState
connectionState
      pqConnection :: Connection
pqConnection = ConnectionState -> Connection
ConnectionState.connection ConnectionState
connectionState
   in do
        (Either SessionError result
result, OidCache
newOidCache, StatementCache
newStatementCache) <- Pipeline result
-> Bool
-> Connection
-> OidCache
-> StatementCache
-> IO (Either SessionError result, OidCache, StatementCache)
forall a.
Pipeline a
-> Bool
-> Connection
-> OidCache
-> StatementCache
-> IO (Either SessionError a, OidCache, StatementCache)
Pipeline.run Pipeline result
pipeline Bool
usePreparedStatements Connection
pqConnection OidCache
oidCache StatementCache
statementCache
        let newConnectionState :: ConnectionState
newConnectionState =
              ConnectionState
connectionState
                { ConnectionState.oidCache = newOidCache,
                  ConnectionState.statementCache = newStatementCache
                }

        (Either SessionError result, ConnectionState)
-> IO (Either SessionError result, ConnectionState)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either SessionError result
result, ConnectionState
newConnectionState)

-- |
-- Execute an operation on the raw libpq connection possibly producing an error and updating the connection.
-- This is a low-level escape hatch for custom integrations.
--
-- You can supply a new connection in the result to replace it in the running Hasql connection.
-- The responsibility to close the old libpq connection is on you.
-- Otherwise, just return the same connection you've received.
--
-- Producing a 'Left' value will cause the session to fail with the given error.
-- Regardless of success or failure, the connection will be replaced with the one you return.
--
-- Throwing exceptions is okay. It will lead to the connection getting reset.
onLibpqConnection ::
  (Pq.Connection -> IO (Either Errors.SessionError a, Pq.Connection)) ->
  Session a
onLibpqConnection :: forall a.
(Connection -> IO (Either SessionError a, Connection)) -> Session a
onLibpqConnection Connection -> IO (Either SessionError a, Connection)
f = (ConnectionState -> IO (Either SessionError a, ConnectionState))
-> Session a
forall a.
(ConnectionState -> IO (Either SessionError a, ConnectionState))
-> Session a
Session \ConnectionState
connectionState -> do
  let pqConnection :: Connection
pqConnection = ConnectionState -> Connection
ConnectionState.connection ConnectionState
connectionState
  (Either SessionError a
result, Connection
newConnection) <- Connection -> IO (Either SessionError a, Connection)
f Connection
pqConnection
  let newState :: ConnectionState
newState = Connection -> ConnectionState -> ConnectionState
ConnectionState.setConnection Connection
newConnection ConnectionState
connectionState
  (Either SessionError a, ConnectionState)
-> IO (Either SessionError a, ConnectionState)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either SessionError a
result, ConnectionState
newState)