-- | Haskell driver for Neo4j over the BOLT protocol (4.4+).
--
-- This is the main entry point for using bolty. It re-exports everything
-- needed to connect, run queries, manage transactions, and decode results.
--
-- @
-- import qualified Database.Bolty as Bolt
-- import           Data.Default   (def)
--
-- main :: IO ()
-- main = do
--   cfg  <- Bolt.'validateConfig' def{ Bolt.'scheme' = Bolt.'Basic' \"neo4j\" \"password\" }
--   conn <- Bolt.'connect' cfg
--   rows <- Bolt.'runBolt' conn $
--     Bolt.'queryWith' (Bolt.'field' \"n\" Bolt.'node') \"MATCH (n) RETURN n LIMIT 10\" mempty
--   print rows  -- Either DecodeError (Vector Node)
--   Bolt.'close' conn
-- @
module Database.Bolty
  ( -- * Configuration
    Config(..)
  , ValidatedConfig
  , validateConfig
  , Scheme(..)
  , Routing(..)
  , UserAgent(..)
  , Version(..)
    -- * Connection
  , Connection
  , connect
  , close
  , reset
  , ping
  , logon
  , logoff
  , connectionVersion
  , connectionAgent
  , connectionId
  , connectionTelemetryEnabled
  , connectionServerIdleTimeout
    -- * Telemetry
  , TelemetryApi(..)
  , sendTelemetry
    -- * Transactions
  , withTransaction
  , withRetryTransaction
  , withTransaction'
    -- * Retry configuration
  , RetryConfig(..)
  , defaultRetryConfig
    -- * Error helpers
  , isTransient
  , isRoutingError
    -- * Types
  , Bolt(..)
  , Record
  , Error(..)
    -- * Bolt value extractors
  , asNull, asBool, asInt, asFloat, asBytes, asText
  , asList, asDict, asNode, asRelationship, asPath
    -- * Query metadata
  , QueryMeta(..)
    -- * Connection pooling
  , BoltPool
  , PoolConfig(..)
  , defaultPoolConfig
  , ValidationStrategy(..)
  , PoolCounters(..)
  , createPool
  , destroyPool
  , withConnection
  , CheckedOutConnection(..)
  , acquireConnection
  , releaseConnection
  , releaseConnectionOnError
  , poolCounters
    -- * Routing
  , AccessMode(..)
  , RoutingTable(..)
  , RoutingPool
  , RoutingPoolConfig(..)
  , defaultRoutingPoolConfig
  , createRoutingPool
  , destroyRoutingPool
  , withRoutingConnection
  , acquireRoutingConnection
  , withRoutingTransaction
  , invalidateRoutingTable
  , getRoutingTable
    -- * Sessions
  , Session
  , SessionConfig(..)
  , defaultSessionConfig
  , BookmarkManager
  , createSession
  , createRoutingSession
  , readTransaction
  , writeTransaction
  , getLastBookmarks
    -- * Plan / Profile
  , PlanNode(..)
  , ProfileNode(..)
  , queryExplain
  , queryProfile
    -- * Query logging
  , QueryLog(..)
    -- * Notifications
  , Notification(..)
  , Severity(..)
  , Position(..)
    -- * Query statistics
  , QueryStats(..)
    -- * BoltM monad
  , BoltM
  , runBolt
    -- * Queries
  , query
  , queryWith
  , queryResult
  , queryMeta
  , queryMetaWith
  , queryResultMeta
  , execute
    -- * Record decoding
  , DecodeError(..)
  , Decode(..)
  , RowDecoder(..)
  , FromBolt(..)
  , ToBolt(..)
  , Database.Bolty.Decode.bool
  , Database.Bolty.Decode.int
  , int64
  , Database.Bolty.Decode.float
  , Database.Bolty.Decode.text
  , Database.Bolty.Decode.bytes
  , nullable, list, dict
  , Database.Bolty.Decode.uuid
  , utcTime, day, timeOfDay
  , aesonValue
  , nodeProperty, nodePropertyOptional, Database.Bolty.Decode.nodeLabels, Database.Bolty.Decode.nodeProperties
  , Database.Bolty.Decode.node
  , Database.Bolty.Decode.relationship
  , Database.Bolty.Decode.path
  , column, field
    -- * Result sets
  , ResultSet(..)
  , decodeResultSet
  , decodeHead
  , groupByField
    -- * PackStream re-exports
  , Ps(..)
  ) where

import           Database.Bolty.Connection          (queryPWithFieldsIO, queryPMetaIO)
import           Database.Bolty.Logging            (QueryLog(..))
import           Database.Bolty.Plan              (PlanNode(..), ProfileNode(..))
import           Database.Bolty.Notification      (Notification(..), Severity(..), Position(..))
import           Database.Bolty.Stats             (QueryStats(..))
import           Database.Bolty.Decode
import           Database.Bolty.Connection.Config   (validateConfig)
import qualified Database.Bolty.Connection.Pipe     as P
import           Database.Bolty.Connection.Pipe     (connectionVersion, connectionAgent,
                                                    connectionId, connectionTelemetryEnabled,
                                                    connectionServerIdleTimeout)
import           Database.Bolty.Connection.Type
import           Database.Bolty.Message.Request     (Begin(Begin), TelemetryApi(..))
import           Database.Bolty.Message.Response
import           Database.Bolty.Pool
import           Database.Bolty.Record
import           Database.Bolty.ResultSet
import           Database.Bolty.Routing (AccessMode(..), RoutingPool, RoutingPoolConfig(..),
                                        defaultRoutingPoolConfig, createRoutingPool,
                                        destroyRoutingPool, withRoutingConnection,
                                        acquireRoutingConnection,
                                        withRoutingTransaction, invalidateRoutingTable,
                                        getRoutingTable)
import           Database.Bolty.Session (Session, SessionConfig(..), defaultSessionConfig,
                                        BookmarkManager, createSession, createRoutingSession,
                                        readTransaction, writeTransaction, getLastBookmarks)
import           Database.Bolty.Value.Type          (Bolt(..), asNull, asBool, asInt, asFloat
                                                    , asBytes, asText, asList, asDict
                                                    , asNode, asRelationship, asPath)
import           Database.Bolty.Connection.Version  (Version(..))

import           Data.Kind                           (Type)
import           Control.Concurrent                  (threadDelay)
import           Control.Exception                  (SomeException, fromException, onException,
                                                     throwIO, try)
import           Control.Monad                      (void)
import           Control.Monad.Reader               (ReaderT(..))
import           Control.Monad.Trans                (MonadIO(..))
import           Data.Text                          (Text)
import           GHC.Stack                          (HasCallStack)
import qualified Data.HashMap.Lazy                  as H
import qualified Data.Vector                        as V
import           Data.PackStream.Ps                 (Ps(..))


-- | Connect to a Neo4j server using a validated configuration.
connect :: (MonadIO m, HasCallStack) => ValidatedConfig -> m Connection
connect :: forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
ValidatedConfig -> m Connection
connect = ValidatedConfig -> m Connection
forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
ValidatedConfig -> m Connection
P.connect


-- | Close a connection to a Neo4j server.
close :: (MonadIO m, HasCallStack) => Connection -> m ()
close :: forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Connection -> m ()
close = Connection -> m ()
forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Connection -> m ()
P.close


-- | Reset the connection state after an error.
reset :: (MonadIO m, HasCallStack) => Connection -> m ()
reset :: forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Connection -> m ()
reset = Connection -> m ()
forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Connection -> m ()
P.reset


-- | Check if a connection is alive by sending RESET.
-- Returns True if healthy, False otherwise.
ping :: MonadIO m => Connection -> m Bool
ping :: forall (m :: * -> *). MonadIO m => Connection -> m Bool
ping = Connection -> m Bool
forall (m :: * -> *). MonadIO m => Connection -> m Bool
P.ping


-- | Send LOGON with credentials (Bolt 5.1+).
-- Transitions from Authentication to Ready state.
logon :: (MonadIO m, HasCallStack) => Connection -> Scheme -> m ()
logon :: forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Connection -> Scheme -> m ()
logon = Connection -> Scheme -> m ()
forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Connection -> Scheme -> m ()
P.logon


-- | Send LOGOFF (Bolt 5.1+).
-- Transitions from Ready to Authentication state.
logoff :: (MonadIO m, HasCallStack) => Connection -> m ()
logoff :: forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Connection -> m ()
logoff = Connection -> m ()
forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Connection -> m ()
P.logoff


-- | Send a TELEMETRY message if the server supports it (Bolt 5.4+).
-- No-op if the server does not support telemetry.
sendTelemetry :: (MonadIO m, HasCallStack) => Connection -> TelemetryApi -> m ()
sendTelemetry :: forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Connection -> TelemetryApi -> m ()
sendTelemetry = Connection -> TelemetryApi -> m ()
forall (m :: * -> *).
(MonadIO m, HasCallStack) =>
Connection -> TelemetryApi -> m ()
P.sendTelemetry


-- ---------------------------------------------------------------------------
-- BoltM monad
-- ---------------------------------------------------------------------------

-- | A monad for running queries against a Neo4j connection.
-- Use 'runBolt' to execute a 'BoltM' action with a 'Connection'.
type BoltM :: Type -> Type
type role BoltM nominal
newtype BoltM a = BoltM (ReaderT Connection IO a)
  deriving newtype ((forall a b. (a -> b) -> BoltM a -> BoltM b)
-> (forall a b. a -> BoltM b -> BoltM a) -> Functor BoltM
forall a b. a -> BoltM b -> BoltM a
forall a b. (a -> b) -> BoltM a -> BoltM 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) -> BoltM a -> BoltM b
fmap :: forall a b. (a -> b) -> BoltM a -> BoltM b
$c<$ :: forall a b. a -> BoltM b -> BoltM a
<$ :: forall a b. a -> BoltM b -> BoltM a
Functor, Functor BoltM
Functor BoltM =>
(forall a. a -> BoltM a)
-> (forall a b. BoltM (a -> b) -> BoltM a -> BoltM b)
-> (forall a b c. (a -> b -> c) -> BoltM a -> BoltM b -> BoltM c)
-> (forall a b. BoltM a -> BoltM b -> BoltM b)
-> (forall a b. BoltM a -> BoltM b -> BoltM a)
-> Applicative BoltM
forall a. a -> BoltM a
forall a b. BoltM a -> BoltM b -> BoltM a
forall a b. BoltM a -> BoltM b -> BoltM b
forall a b. BoltM (a -> b) -> BoltM a -> BoltM b
forall a b c. (a -> b -> c) -> BoltM a -> BoltM b -> BoltM 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 -> BoltM a
pure :: forall a. a -> BoltM a
$c<*> :: forall a b. BoltM (a -> b) -> BoltM a -> BoltM b
<*> :: forall a b. BoltM (a -> b) -> BoltM a -> BoltM b
$cliftA2 :: forall a b c. (a -> b -> c) -> BoltM a -> BoltM b -> BoltM c
liftA2 :: forall a b c. (a -> b -> c) -> BoltM a -> BoltM b -> BoltM c
$c*> :: forall a b. BoltM a -> BoltM b -> BoltM b
*> :: forall a b. BoltM a -> BoltM b -> BoltM b
$c<* :: forall a b. BoltM a -> BoltM b -> BoltM a
<* :: forall a b. BoltM a -> BoltM b -> BoltM a
Applicative, Applicative BoltM
Applicative BoltM =>
(forall a b. BoltM a -> (a -> BoltM b) -> BoltM b)
-> (forall a b. BoltM a -> BoltM b -> BoltM b)
-> (forall a. a -> BoltM a)
-> Monad BoltM
forall a. a -> BoltM a
forall a b. BoltM a -> BoltM b -> BoltM b
forall a b. BoltM a -> (a -> BoltM b) -> BoltM 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. BoltM a -> (a -> BoltM b) -> BoltM b
>>= :: forall a b. BoltM a -> (a -> BoltM b) -> BoltM b
$c>> :: forall a b. BoltM a -> BoltM b -> BoltM b
>> :: forall a b. BoltM a -> BoltM b -> BoltM b
$creturn :: forall a. a -> BoltM a
return :: forall a. a -> BoltM a
Monad, Monad BoltM
Monad BoltM => (forall a. IO a -> BoltM a) -> MonadIO BoltM
forall a. IO a -> BoltM a
forall (m :: * -> *).
Monad m =>
(forall a. IO a -> m a) -> MonadIO m
$cliftIO :: forall a. IO a -> BoltM a
liftIO :: forall a. IO a -> BoltM a
MonadIO)

-- | Run a 'BoltM' action with a 'Connection'.
runBolt :: Connection -> BoltM a -> IO a
runBolt :: forall a. Connection -> BoltM a -> IO a
runBolt Connection
conn (BoltM ReaderT Connection IO a
m) = ReaderT Connection IO a -> Connection -> IO a
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT ReaderT Connection IO a
m Connection
conn


-- ---------------------------------------------------------------------------
-- Queries (BoltM)
-- ---------------------------------------------------------------------------

-- | Run a Cypher query, decode each record using the 'FromBolt' instance, and
-- return the decoded rows or a 'DecodeError'. Pass 'mempty' for no parameters.
query :: (FromBolt a, HasCallStack) => Text -> H.HashMap Text Ps -> BoltM (Either DecodeError (V.Vector a))
query :: forall a.
(FromBolt a, HasCallStack) =>
Text -> HashMap Text Ps -> BoltM (Either DecodeError (Vector a))
query = RowDecoder a
-> Text -> HashMap Text Ps -> BoltM (Either DecodeError (Vector a))
forall a.
HasCallStack =>
RowDecoder a
-> Text -> HashMap Text Ps -> BoltM (Either DecodeError (Vector a))
queryWith RowDecoder a
forall a. FromBolt a => RowDecoder a
rowDecoder

-- | Like 'query', but with an explicit 'RowDecoder' instead of using 'FromBolt'.
queryWith :: HasCallStack => RowDecoder a -> Text -> H.HashMap Text Ps -> BoltM (Either DecodeError (V.Vector a))
queryWith :: forall a.
HasCallStack =>
RowDecoder a
-> Text -> HashMap Text Ps -> BoltM (Either DecodeError (Vector a))
queryWith RowDecoder a
decoder Text
cypher HashMap Text Ps
params = ReaderT Connection IO (Either DecodeError (Vector a))
-> BoltM (Either DecodeError (Vector a))
forall a. ReaderT Connection IO a -> BoltM a
BoltM (ReaderT Connection IO (Either DecodeError (Vector a))
 -> BoltM (Either DecodeError (Vector a)))
-> ReaderT Connection IO (Either DecodeError (Vector a))
-> BoltM (Either DecodeError (Vector a))
forall a b. (a -> b) -> a -> b
$ (Connection -> IO (Either DecodeError (Vector a)))
-> ReaderT Connection IO (Either DecodeError (Vector a))
forall r (m :: * -> *) a. (r -> m a) -> ReaderT r m a
ReaderT ((Connection -> IO (Either DecodeError (Vector a)))
 -> ReaderT Connection IO (Either DecodeError (Vector a)))
-> (Connection -> IO (Either DecodeError (Vector a)))
-> ReaderT Connection IO (Either DecodeError (Vector a))
forall a b. (a -> b) -> a -> b
$ \Connection
conn -> do
  (cols, recs) <- HasCallStack =>
Connection
-> Text -> HashMap Text Ps -> IO (Vector Text, Vector Record)
Connection
-> Text -> HashMap Text Ps -> IO (Vector Text, Vector Record)
queryPWithFieldsIO Connection
conn Text
cypher HashMap Text Ps
params
  pure $ decodeRows decoder cols recs

-- | Run a Cypher query and return a 'ResultSet' (field names + records).
-- Use 'decodeResultSet' or 'groupByField' for multi-pass decoding.
-- Pass 'mempty' for no parameters.
queryResult :: HasCallStack => Text -> H.HashMap Text Ps -> BoltM ResultSet
queryResult :: HasCallStack => Text -> HashMap Text Ps -> BoltM ResultSet
queryResult Text
cypher HashMap Text Ps
params = ReaderT Connection IO ResultSet -> BoltM ResultSet
forall a. ReaderT Connection IO a -> BoltM a
BoltM (ReaderT Connection IO ResultSet -> BoltM ResultSet)
-> ReaderT Connection IO ResultSet -> BoltM ResultSet
forall a b. (a -> b) -> a -> b
$ (Connection -> IO ResultSet) -> ReaderT Connection IO ResultSet
forall r (m :: * -> *) a. (r -> m a) -> ReaderT r m a
ReaderT ((Connection -> IO ResultSet) -> ReaderT Connection IO ResultSet)
-> (Connection -> IO ResultSet) -> ReaderT Connection IO ResultSet
forall a b. (a -> b) -> a -> b
$ \Connection
conn -> do
  (fs, rs) <- HasCallStack =>
Connection
-> Text -> HashMap Text Ps -> IO (Vector Text, Vector Record)
Connection
-> Text -> HashMap Text Ps -> IO (Vector Text, Vector Record)
queryPWithFieldsIO Connection
conn Text
cypher HashMap Text Ps
params
  pure $ ResultSet fs rs

-- | Run a Cypher query, decode each record using the 'FromBolt' instance, and
-- return the decoded rows paired with server metadata ('QueryMeta').
-- Pass 'mempty' for no parameters.
queryMeta :: (FromBolt a, HasCallStack) => Text -> H.HashMap Text Ps -> BoltM (Either DecodeError (V.Vector a), QueryMeta)
queryMeta :: forall a.
(FromBolt a, HasCallStack) =>
Text
-> HashMap Text Ps
-> BoltM (Either DecodeError (Vector a), QueryMeta)
queryMeta = RowDecoder a
-> Text
-> HashMap Text Ps
-> BoltM (Either DecodeError (Vector a), QueryMeta)
forall a.
HasCallStack =>
RowDecoder a
-> Text
-> HashMap Text Ps
-> BoltM (Either DecodeError (Vector a), QueryMeta)
queryMetaWith RowDecoder a
forall a. FromBolt a => RowDecoder a
rowDecoder

-- | Like 'queryMeta', but with an explicit 'RowDecoder' instead of using 'FromBolt'.
queryMetaWith :: HasCallStack => RowDecoder a -> Text -> H.HashMap Text Ps -> BoltM (Either DecodeError (V.Vector a), QueryMeta)
queryMetaWith :: forall a.
HasCallStack =>
RowDecoder a
-> Text
-> HashMap Text Ps
-> BoltM (Either DecodeError (Vector a), QueryMeta)
queryMetaWith RowDecoder a
decoder Text
cypher HashMap Text Ps
params = ReaderT Connection IO (Either DecodeError (Vector a), QueryMeta)
-> BoltM (Either DecodeError (Vector a), QueryMeta)
forall a. ReaderT Connection IO a -> BoltM a
BoltM (ReaderT Connection IO (Either DecodeError (Vector a), QueryMeta)
 -> BoltM (Either DecodeError (Vector a), QueryMeta))
-> ReaderT Connection IO (Either DecodeError (Vector a), QueryMeta)
-> BoltM (Either DecodeError (Vector a), QueryMeta)
forall a b. (a -> b) -> a -> b
$ (Connection -> IO (Either DecodeError (Vector a), QueryMeta))
-> ReaderT Connection IO (Either DecodeError (Vector a), QueryMeta)
forall r (m :: * -> *) a. (r -> m a) -> ReaderT r m a
ReaderT ((Connection -> IO (Either DecodeError (Vector a), QueryMeta))
 -> ReaderT
      Connection IO (Either DecodeError (Vector a), QueryMeta))
-> (Connection -> IO (Either DecodeError (Vector a), QueryMeta))
-> ReaderT Connection IO (Either DecodeError (Vector a), QueryMeta)
forall a b. (a -> b) -> a -> b
$ \Connection
conn -> do
  (fs, rs, qi) <- HasCallStack =>
Connection
-> Text
-> HashMap Text Ps
-> IO (Vector Text, Vector Record, QueryMeta)
Connection
-> Text
-> HashMap Text Ps
-> IO (Vector Text, Vector Record, QueryMeta)
queryPMetaIO Connection
conn Text
cypher HashMap Text Ps
params
  pure (decodeRows decoder fs rs, qi)

-- | Run a Cypher query and return a 'ResultSet' paired with server metadata
-- ('QueryMeta' — timing, statistics, notifications, plan/profile).
-- Pass 'mempty' for no parameters.
queryResultMeta :: HasCallStack => Text -> H.HashMap Text Ps -> BoltM (ResultSet, QueryMeta)
queryResultMeta :: HasCallStack =>
Text -> HashMap Text Ps -> BoltM (ResultSet, QueryMeta)
queryResultMeta Text
cypher HashMap Text Ps
params = ReaderT Connection IO (ResultSet, QueryMeta)
-> BoltM (ResultSet, QueryMeta)
forall a. ReaderT Connection IO a -> BoltM a
BoltM (ReaderT Connection IO (ResultSet, QueryMeta)
 -> BoltM (ResultSet, QueryMeta))
-> ReaderT Connection IO (ResultSet, QueryMeta)
-> BoltM (ResultSet, QueryMeta)
forall a b. (a -> b) -> a -> b
$ (Connection -> IO (ResultSet, QueryMeta))
-> ReaderT Connection IO (ResultSet, QueryMeta)
forall r (m :: * -> *) a. (r -> m a) -> ReaderT r m a
ReaderT ((Connection -> IO (ResultSet, QueryMeta))
 -> ReaderT Connection IO (ResultSet, QueryMeta))
-> (Connection -> IO (ResultSet, QueryMeta))
-> ReaderT Connection IO (ResultSet, QueryMeta)
forall a b. (a -> b) -> a -> b
$ \Connection
conn -> do
  (fs, rs, qi) <- HasCallStack =>
Connection
-> Text
-> HashMap Text Ps
-> IO (Vector Text, Vector Record, QueryMeta)
Connection
-> Text
-> HashMap Text Ps
-> IO (Vector Text, Vector Record, QueryMeta)
queryPMetaIO Connection
conn Text
cypher HashMap Text Ps
params
  pure (ResultSet fs rs, qi)


-- | Run a Cypher query for side effects only, discarding the result.
-- Pass 'mempty' for no parameters.
execute :: HasCallStack => Text -> H.HashMap Text Ps -> BoltM ()
execute :: HasCallStack => Text -> HashMap Text Ps -> BoltM ()
execute Text
cypher HashMap Text Ps
params = BoltM ResultSet -> BoltM ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (BoltM ResultSet -> BoltM ()) -> BoltM ResultSet -> BoltM ()
forall a b. (a -> b) -> a -> b
$ HasCallStack => Text -> HashMap Text Ps -> BoltM ResultSet
Text -> HashMap Text Ps -> BoltM ResultSet
queryResult Text
cypher HashMap Text Ps
params


-- | Run EXPLAIN on a Cypher query, returning the query plan without executing it.
queryExplain :: HasCallStack => Text -> H.HashMap Text Ps -> BoltM (Maybe PlanNode)
queryExplain :: HasCallStack => Text -> HashMap Text Ps -> BoltM (Maybe PlanNode)
queryExplain Text
cypher HashMap Text Ps
params = do
  (_, meta) <- HasCallStack =>
Text -> HashMap Text Ps -> BoltM (ResultSet, QueryMeta)
Text -> HashMap Text Ps -> BoltM (ResultSet, QueryMeta)
queryResultMeta (Text
"EXPLAIN " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
cypher) HashMap Text Ps
params
  pure (parsedPlan meta)


-- | Run PROFILE on a Cypher query, returning both decoded rows and the profile tree
-- with actual execution statistics (db hits, rows, timing, etc.).
queryProfile :: (FromBolt a, HasCallStack)
             => Text -> H.HashMap Text Ps
             -> BoltM (Either DecodeError (V.Vector a), ProfileNode)
queryProfile :: forall a.
(FromBolt a, HasCallStack) =>
Text
-> HashMap Text Ps
-> BoltM (Either DecodeError (Vector a), ProfileNode)
queryProfile Text
cypher HashMap Text Ps
params = do
  (result, meta) <- Text
-> HashMap Text Ps
-> BoltM (Either DecodeError (Vector a), QueryMeta)
forall a.
(FromBolt a, HasCallStack) =>
Text
-> HashMap Text Ps
-> BoltM (Either DecodeError (Vector a), QueryMeta)
queryMeta (Text
"PROFILE " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
cypher) HashMap Text Ps
params
  case parsedProfile meta of
    Just ProfileNode
prof -> (Either DecodeError (Vector a), ProfileNode)
-> BoltM (Either DecodeError (Vector a), ProfileNode)
forall a. a -> BoltM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either DecodeError (Vector a)
result, ProfileNode
prof)
    Maybe ProfileNode
Nothing   -> String -> BoltM (Either DecodeError (Vector a), ProfileNode)
forall a. HasCallStack => String -> a
error String
"queryProfile: no profile in response"


-- | Run an action inside an explicit transaction. Automatically commits on success
-- and rolls back on failure.
withTransaction :: HasCallStack => Connection -> (Connection -> IO a) -> IO a
withTransaction :: forall a.
HasCallStack =>
Connection -> (Connection -> IO a) -> IO a
withTransaction Connection
conn Connection -> IO a
action = do
  HasCallStack => Connection -> Begin -> IO ()
Connection -> Begin -> IO ()
P.beginTx Connection
conn Begin
defaultBegin
  result <- Connection -> IO a
action Connection
conn IO a -> IO () -> IO a
forall a b. IO a -> IO b -> IO a
`onException` Connection -> IO ()
P.tryRollback Connection
conn
  _ <- P.commitTx conn
  pure result
  where
    defaultBegin :: Begin
defaultBegin = Vector Text
-> Maybe Int64
-> HashMap Text Ps
-> Char
-> Maybe Text
-> Maybe Text
-> Begin
Begin Vector Text
forall a. Vector a
V.empty Maybe Int64
forall a. Maybe a
Nothing HashMap Text Ps
forall k v. HashMap k v
H.empty Char
'w' Maybe Text
forall a. Maybe a
Nothing Maybe Text
forall a. Maybe a
Nothing


-- | Run a transaction with automatic retry on transient failures.
-- Uses exponential backoff between retries.
withRetryTransaction :: HasCallStack
                     => RetryConfig -> Connection -> (Connection -> IO a) -> IO a
withRetryTransaction :: forall a.
HasCallStack =>
RetryConfig -> Connection -> (Connection -> IO a) -> IO a
withRetryTransaction RetryConfig{Int
maxRetries :: Int
maxRetries :: RetryConfig -> Int
maxRetries, Int
initialDelay :: Int
initialDelay :: RetryConfig -> Int
initialDelay, Int
maxDelay :: Int
maxDelay :: RetryConfig -> Int
maxDelay} Connection
conn Connection -> IO a
action =
    Int -> Int -> IO a
go Int
maxRetries Int
initialDelay
  where
    go :: Int -> Int -> IO a
go Int
0 Int
_     = Connection -> (Connection -> IO a) -> IO a
forall a.
HasCallStack =>
Connection -> (Connection -> IO a) -> IO a
withTransaction Connection
conn Connection -> IO a
action
    go Int
n Int
delay = do
      result <- IO a -> IO (Either SomeException a)
forall e a. Exception e => IO a -> IO (Either e a)
try (IO a -> IO (Either SomeException a))
-> IO a -> IO (Either SomeException a)
forall a b. (a -> b) -> a -> b
$ Connection -> (Connection -> IO a) -> IO a
forall a.
HasCallStack =>
Connection -> (Connection -> IO a) -> IO a
withTransaction Connection
conn Connection -> IO a
action
      case result of
        Right a
x -> a -> IO a
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure a
x
        Left (SomeException
e :: SomeException) -> case SomeException -> Maybe Error
forall e. Exception e => SomeException -> Maybe e
fromException SomeException
e :: Maybe Error of
          Just Error
err | Error -> Bool
isTransient Error
err -> do
            Int -> IO ()
threadDelay Int
delay
            Int -> Int -> IO a
go (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) (Int -> Int -> Int
forall a. Ord a => a -> a -> a
min Int
maxDelay (Int
delay Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
2))
          Maybe Error
_ -> SomeException -> IO a
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO SomeException
e


-- | Convenience: acquire a pooled connection, run a retrying transaction, and release.
withTransaction' :: HasCallStack => BoltPool -> (Connection -> IO a) -> IO a
withTransaction' :: forall a. HasCallStack => BoltPool -> (Connection -> IO a) -> IO a
withTransaction' pool :: BoltPool
pool@BoltPool{RetryConfig
bpRetryConfig :: RetryConfig
bpRetryConfig :: BoltPool -> RetryConfig
bpRetryConfig} Connection -> IO a
action =
  BoltPool -> (Connection -> IO a) -> IO a
forall a. HasCallStack => BoltPool -> (Connection -> IO a) -> IO a
withConnection BoltPool
pool ((Connection -> IO a) -> IO a) -> (Connection -> IO a) -> IO a
forall a b. (a -> b) -> a -> b
$ \Connection
conn ->
    RetryConfig -> Connection -> (Connection -> IO a) -> IO a
forall a.
HasCallStack =>
RetryConfig -> Connection -> (Connection -> IO a) -> IO a
withRetryTransaction RetryConfig
bpRetryConfig Connection
conn Connection -> IO a
action