packages feed

valiant-mtl-0.1.0.0: src/Valiant/Mtl.hs

-- | MTL-style adapter for valiant.
--
-- All operations work in any monad with @HasPool m@ (which is
-- @MonadReader env m@ where @env@ has a 'Pool') and @MonadIO m@.
-- This allows valiant to compose with arbitrary monad stacks.
--
-- @
-- data AppEnv = AppEnv { appPool :: Pool, appLogger :: Logger }
--
-- instance HasPool (ReaderT AppEnv IO) where
--   getPool = asks appPool
--
-- myApp :: (HasPool m, MonadIO m) => m [User]
-- myApp = do
--   users <- fetchAllMtl listUsers ()
--   count <- fetchScalarMtl countUsers ()
--   pure users
-- @
module Valiant.Mtl
  ( -- * Pool access
    HasPool (..)

    -- * Query operations
  , fetchOneMtl
  , fetchAllMtl
  , fetchScalarMtl
  , fetchOneOrThrowMtl
  , fetchExistsMtl
  , forEachMtl

    -- * Command operations
  , executeMtl
  , executeReturningMtl
  , executeBatchMtl

    -- * Transaction operations
  , withTransactionMtl
  , withTransactionLevelMtl

    -- * Pool management
  , poolStatsMtl
  , withConnectionMtl
  ) where

import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Int (Int64)
import Valiant (Connection, IsolationLevel, Pool, PoolStats, Statement, Transaction)
import Valiant qualified
import PgWire.Pool (poolStats, withResource)

-- | Type class for monads that can provide a 'Pool'.
--
-- Implement this for your application monad:
--
-- @
-- instance HasPool (ReaderT Pool IO) where
--   getPool = ask
--
-- -- Or with a larger environment:
-- instance HasPool (ReaderT AppEnv IO) where
--   getPool = asks appPool
-- @
class Monad m => HasPool m where
  getPool :: m Pool

-- | Fetch zero or one row.
fetchOneMtl :: (HasPool m, MonadIO m) => Statement p r -> p -> m (Maybe r)
fetchOneMtl stmt params = do
  pool <- getPool
  liftIO $ withResource pool $ \conn -> Valiant.fetchOne conn stmt params

-- | Fetch all rows.
fetchAllMtl :: (HasPool m, MonadIO m) => Statement p r -> p -> m [r]
fetchAllMtl stmt params = do
  pool <- getPool
  liftIO $ withResource pool $ \conn -> Valiant.fetchAll conn stmt params

-- | Fetch exactly one scalar value.
fetchScalarMtl :: (HasPool m, MonadIO m) => Statement p r -> p -> m r
fetchScalarMtl stmt params = do
  pool <- getPool
  liftIO $ withResource pool $ \conn -> Valiant.fetchScalar conn stmt params

-- | Fetch one row, throwing if none returned.
fetchOneOrThrowMtl :: (HasPool m, MonadIO m) => Statement p r -> p -> m r
fetchOneOrThrowMtl stmt params = do
  pool <- getPool
  liftIO $ withResource pool $ \conn -> Valiant.fetchOneOrThrow conn stmt params

-- | Check if a query returns any rows.
fetchExistsMtl :: (HasPool m, MonadIO m) => Statement p r -> p -> m Bool
fetchExistsMtl stmt params = do
  pool <- getPool
  liftIO $ withResource pool $ \conn -> Valiant.fetchExists conn stmt params

-- | Execute a callback for each row (streaming, no intermediate list).
forEachMtl :: (HasPool m, MonadIO m) => Statement p r -> p -> (r -> IO ()) -> m ()
forEachMtl stmt params action = do
  pool <- getPool
  liftIO $ withResource pool $ \conn -> Valiant.forEach conn stmt params action

-- | Execute a command. Returns rows affected.
executeMtl :: (HasPool m, MonadIO m) => Statement p () -> p -> m Int64
executeMtl stmt params = do
  pool <- getPool
  liftIO $ withResource pool $ \conn -> Valiant.execute conn stmt params

-- | Execute with RETURNING.
executeReturningMtl :: (HasPool m, MonadIO m) => Statement p r -> p -> m (Int64, [r])
executeReturningMtl stmt params = do
  pool <- getPool
  liftIO $ withResource pool $ \conn -> Valiant.executeReturning conn stmt params

-- | Execute a batch (pipelined). Returns total rows affected.
executeBatchMtl :: (HasPool m, MonadIO m) => Statement p () -> [p] -> m Int64
executeBatchMtl stmt paramsList = do
  pool <- getPool
  liftIO $ withResource pool $ \conn -> Valiant.executeBatch conn stmt paramsList

-- | Run an action in a transaction.
withTransactionMtl :: (HasPool m, MonadIO m) => (Transaction -> IO a) -> m a
withTransactionMtl action = do
  pool <- getPool
  liftIO $ Valiant.withTransaction pool action

-- | Run an action in a transaction with a specific isolation level.
withTransactionLevelMtl :: (HasPool m, MonadIO m) => IsolationLevel -> (Transaction -> IO a) -> m a
withTransactionLevelMtl level action = do
  pool <- getPool
  liftIO $ Valiant.withTransactionLevel level pool action

-- | Get pool statistics.
poolStatsMtl :: (HasPool m, MonadIO m) => m PoolStats
poolStatsMtl = do
  pool <- getPool
  liftIO $ poolStats pool

-- | Run an action with a raw connection.
withConnectionMtl :: (HasPool m, MonadIO m) => (Connection -> IO a) -> m a
withConnectionMtl action = do
  pool <- getPool
  liftIO $ withResource pool action