packages feed

effectful-postgresql-0.2.0.0: src/Effectful/PostgreSQL/Effect.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE TemplateHaskell #-}

module Effectful.PostgreSQL.Effect
  ( -- * Effect
    PostgreSQL (..)

    -- ** Interpreters
  , runPostgreSQL
#if OTEL
  , runPostgreSQLOT
#endif

    -- * Lifted versions of functions from Database.PostgreSQL.Simple

    -- ** Queries that return results
  , query
  , query_
  , queryWith
  , queryWith_

    -- ** Statements that do not return results
  , execute
  , execute_
  , executeMany

    -- ** Transaction handling
  , withTransaction
  , withTransactionLevel
  , PSQL.IsolationLevel (..)
  , withTransactionMode
  , PSQL.TransactionMode (..)
  , PSQL.ReadWriteMode (..)
  , withTransactionModeRetry
  , withTransactionModeRetry'
  , withTransactionSerializable
  , withTransactionSerialisable
  , withSavepoint
  , begin
  , commit
  , rollback

    -- ** Queries that stream results
  , fold
  , foldWithOptions
  , fold_
  , foldWithOptions_
  , forEach
  , forEach_
  , returning
  , foldWith
  , foldWithOptionsAndParser
  , foldWith_
  , foldWithOptionsAndParser_
  , forEachWith
  , forEachWith_
  , returningWith
  )
where

import qualified Control.Exception as E
import Data.Int (Int64)
import qualified Database.PostgreSQL.Simple as PSQL
import qualified Database.PostgreSQL.Simple.Transaction as PSQL
import qualified Database.PostgreSQL.Simple.FromRow as PSQL
import Effectful
import Effectful.Dispatch.Dynamic
import Effectful.PostgreSQL.Connection
import Effectful.TH
#if OTEL
import qualified "hs-opentelemetry-instrumentation-postgresql-simple" OpenTelemetry.Instrumentation.PostgresqlSimple as OT
#endif

-- | Dynamic effect representing all the Postgres operations we want to perform.
data PostgreSQL :: Effect where
  -- | Lifted 'PSQL.query'.
  Query :: (PSQL.ToRow q, PSQL.FromRow r) => PSQL.Query -> q -> PostgreSQL m [r]
  -- | Lifted 'PSQL.queryWith'.
  QueryWith :: (PSQL.ToRow q) => PSQL.RowParser r -> PSQL.Query -> q -> PostgreSQL m [r]
  -- | Lifted 'PSQL.query_'.
  Query_ :: (PSQL.FromRow r) => PSQL.Query -> PostgreSQL m [r]
  -- | Lifted 'PSQL.queryWith_'.
  QueryWith_ :: PSQL.RowParser r -> PSQL.Query -> PostgreSQL m [r]
  --

  -- | Lifted 'PSQL.execute'.
  Execute :: (PSQL.ToRow q) => PSQL.Query -> q -> PostgreSQL m Int64
  -- | Lifted 'PSQL.execute_'.
  Execute_ :: PSQL.Query -> PostgreSQL m Int64
  -- | Lifted 'PSQL.executeMany'.
  ExecuteMany :: (PSQL.ToRow q) => PSQL.Query -> [q] -> PostgreSQL m Int64
  --

  -- | Lifted 'PSQL.withTransaction'.
  WithTransaction :: m a -> PostgreSQL m a
  -- | Lifted 'PSQL.withTransactionLevel'.
  WithTransactionLevel :: PSQL.IsolationLevel -> m a -> PostgreSQL m a
  -- | Lifted 'PSQL.withTransactionMode'.
  WithTransactionMode :: PSQL.TransactionMode -> m a -> PostgreSQL m a
  -- | Lifted 'PSQL.withTransactionModeRetry'.
  WithTransactionModeRetry :: PSQL.TransactionMode -> (PSQL.SqlError -> Bool) -> m a -> PostgreSQL m a
  -- | Lifted 'PSQL.withTransactionModeRetry''.
  WithTransactionModeRetry' :: E.Exception e => PSQL.TransactionMode -> (e -> Bool) -> m a -> PostgreSQL m a
  -- | Lifted 'PSQL.withTransactionSerializable'.
  WithTransactionSerializable :: m a -> PostgreSQL m a
  -- | Lifted 'PSQL.withSavepoint'.
  WithSavepoint :: m a -> PostgreSQL m a
  -- | Lifted 'PSQL.begin'.
  Begin :: PostgreSQL m ()
  -- | Lifted 'PSQL.commit'.
  Commit :: PostgreSQL m ()
  -- | Lifted 'PSQL.rollback'.
  Rollback :: PostgreSQL m ()
  --

  -- | Lifted 'PSQL.fold'.
  Fold ::
    (PSQL.FromRow row, PSQL.ToRow params) =>
    PSQL.Query ->
    params ->
    a ->
    (a -> row -> m a) ->
    PostgreSQL m a
  -- | Lifted 'PSQL.fold_'.
  Fold_ ::
    (PSQL.FromRow row) =>
    PSQL.Query ->
    a ->
    (a -> row -> m a) ->
    PostgreSQL m a
  -- | Lifted 'PSQL.foldWithOptions'.
  FoldWithOptions ::
    (PSQL.FromRow row, PSQL.ToRow params) =>
    PSQL.FoldOptions ->
    PSQL.Query ->
    params ->
    a ->
    (a -> row -> m a) ->
    PostgreSQL m a
  -- | Lifted 'PSQL.foldWithOptions_'.
  FoldWithOptions_ ::
    (PSQL.FromRow row) =>
    PSQL.FoldOptions ->
    PSQL.Query ->
    a ->
    (a -> row -> m a) ->
    PostgreSQL m a
  -- | Lifted 'PSQL.forEach'.
  ForEach ::
    (PSQL.FromRow r, PSQL.ToRow q) =>
    PSQL.Query ->
    q ->
    (r -> m ()) ->
    PostgreSQL m ()
  -- | Lifted 'PSQL.forEach_'.
  ForEach_ ::
    (PSQL.FromRow r) =>
    PSQL.Query ->
    (r -> m ()) ->
    PostgreSQL m ()
  -- | Lifted 'PSQL.returning'.
  Returning :: (PSQL.ToRow q, PSQL.FromRow r) => PSQL.Query -> [q] -> PostgreSQL m [r]
  -- | Lifted 'PSQL.foldWith'.
  FoldWith ::
    (PSQL.ToRow params) =>
    PSQL.RowParser row ->
    PSQL.Query ->
    params ->
    a ->
    (a -> row -> m a) ->
    PostgreSQL m a
  -- | Lifted 'PSQL.foldWithOptionsAndParser'.
  FoldWithOptionsAndParser ::
    (PSQL.ToRow params) =>
    PSQL.FoldOptions ->
    PSQL.RowParser row ->
    PSQL.Query ->
    params ->
    a ->
    (a -> row -> m a) ->
    PostgreSQL m a
  -- | Lifted 'PSQL.foldWith_'.
  FoldWith_ ::
    () =>
    PSQL.RowParser row ->
    PSQL.Query ->
    a ->
    (a -> row -> m a) ->
    PostgreSQL m a
  -- | Lifted 'PSQL.foldWithOptionsAndParser_'.
  FoldWithOptionsAndParser_ ::
    () =>
    PSQL.FoldOptions ->
    PSQL.RowParser row ->
    PSQL.Query ->
    a ->
    (a -> row -> m a) ->
    PostgreSQL m a
  -- | Lifted 'PSQL.forEachWith'.
  ForEachWith ::
    (PSQL.ToRow q) =>
    PSQL.RowParser r ->
    PSQL.Query ->
    q ->
    (r -> m ()) ->
    PostgreSQL m ()
  -- | Lifted 'PSQL.forEachWith_'.
  ForEachWith_ ::
    () =>
    PSQL.RowParser r ->
    PSQL.Query ->
    (r -> m ()) ->
    PostgreSQL m ()
  -- | Lifted 'PSQL.returningWith'.
  ReturningWith :: (PSQL.ToRow q) => PSQL.RowParser r -> PSQL.Query -> [q] -> PostgreSQL m [r]

makeEffect ''PostgreSQL

-- | British alias of 'withTransactionSerializable'.
withTransactionSerialisable ::
  (HasCallStack, PostgreSQL :> es) => Eff es a -> Eff es a
withTransactionSerialisable = withTransactionSerializable
{-# INLINE withTransactionSerialisable #-}

localUnliftWithConn ::
  (HasCallStack, WithConnection :> es, IOE :> es) =>
  LocalEnv localEs es ->
  (PSQL.Connection -> (forall b. Eff localEs b -> IO b) -> IO a) ->
  Eff es a
localUnliftWithConn env f =
  withConnection $ \conn ->
    localSeqUnliftIO env $ \unlift ->
      liftIO $ f conn unlift
{-# INLINE localUnliftWithConn #-}

(...) :: (a -> b) -> (t1 -> t2 -> a) -> t1 -> t2 -> b
unlift ... f = \a' row -> unlift $ f a' row

{- | Obvious interepreter for 'PostgreSQL'. Just gets a 'PSQL.Connection' from 'WithConnection' and calls the
corresponding function from "Database.PostgreSQL.Simple".
-}
runPostgreSQL :: forall es a. (HasCallStack, WithConnection :> es, IOE :> es) => Eff (PostgreSQL : es) a -> Eff es a
runPostgreSQL = interpret $ \env -> \case
  Query q row ->
    withConnection $ \conn -> liftIO (PSQL.query conn q row)
  QueryWith parser q row ->
    withConnection $ \conn -> liftIO (PSQL.queryWith parser conn q row)
  Query_ row ->
    withConnection $ \conn -> liftIO (PSQL.query_ conn row)
  QueryWith_ parser row ->
    withConnection $ \conn -> liftIO (PSQL.queryWith_ parser conn row)
  Execute q row -> withConnection $ \conn -> liftIO (PSQL.execute conn q row)
  Execute_ q -> withConnection $ \conn -> liftIO (PSQL.execute_ conn q)
  ExecuteMany q row -> withConnection $ \conn -> liftIO (PSQL.executeMany conn q row)
  WithTransaction f -> localUnliftWithConn env $ \conn unlift -> PSQL.withTransaction conn (unlift f)
  WithTransactionLevel level f -> localUnliftWithConn env $ \conn unlift -> PSQL.withTransactionLevel level conn (unlift f)
  WithTransactionMode mode f -> localUnliftWithConn env $ \conn unlift -> PSQL.withTransactionMode mode conn (unlift f)
  WithTransactionModeRetry mode shouldRetry f -> localUnliftWithConn env $ \conn unlift -> PSQL.withTransactionModeRetry mode shouldRetry conn (unlift f)
  WithTransactionModeRetry' mode shouldRetry f -> localUnliftWithConn env $ \conn unlift -> PSQL.withTransactionModeRetry' mode shouldRetry conn (unlift f)
  WithTransactionSerializable f -> localUnliftWithConn env $ \conn unlift -> PSQL.withTransactionSerializable conn (unlift f)
  WithSavepoint f -> localUnliftWithConn env $ \conn unlift -> PSQL.withSavepoint conn (unlift f)
  Begin -> withConnection $ liftIO . PSQL.begin
  Commit -> withConnection $ liftIO . PSQL.commit
  Rollback -> withConnection $ liftIO . PSQL.rollback
  Fold q params a f ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.fold conn q params a (unlift ... f)
  Fold_ q a f ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.fold_ conn q a (unlift ... f)
  FoldWithOptions opts q params a f ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.foldWithOptions opts conn q params a (unlift ... f)
  FoldWithOptions_ opts q a f ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.foldWithOptions_ opts conn q a (unlift ... f)
  ForEach q row forR ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.forEach conn q row (unlift . forR)
  ForEach_ q forR ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.forEach_ conn q (unlift . forR)
  Returning q rows -> withConnection $ \conn -> liftIO $ PSQL.returning conn q rows
  FoldWith parser q params a f ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.foldWith parser conn q params a (unlift ... f)
  FoldWithOptionsAndParser opts parser q params a f ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.foldWithOptionsAndParser opts parser conn q params a (unlift ... f)
  FoldWith_ parser q a f ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.foldWith_ parser conn q a (unlift ... f)
  FoldWithOptionsAndParser_ opts parser q a f ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.foldWithOptionsAndParser_ opts parser conn q a (unlift ... f)
  ForEachWith parser q row forR ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.forEachWith parser conn q row (unlift . forR)
  ForEachWith_ parser q forR ->
    localUnliftWithConn env $ \conn unlift ->
      PSQL.forEachWith_ parser conn q (unlift . forR)
  ReturningWith parser q rows -> withConnection $ \conn -> liftIO $ PSQL.returningWith parser conn q rows

#if OTEL
{- | An interpreter for the 'PostgreSQL' effect that runs database operations using OpenTelemetry instrumentation.

Basically the same as 'runPostgreSQL' except it uses the functions from
[OpenTelemetry.Instrumentation.PostgresqlSimple](https://hackage-content.haskell.org/package/hs-opentelemetry-instrumentation-postgresql-simple/docs/OpenTelemetry-Instrumentation-PostgresqlSimple.html).

Note that the @enable-opentel@ cabal flag must be set to enable this functionality.
-}
runPostgreSQLOT :: forall es a. (HasCallStack, WithConnection :> es, IOE :> es) => Eff (PostgreSQL : es) a -> Eff es a
runPostgreSQLOT = interpret $ \env -> \case
  Query q row ->
    withConnection $ \conn -> OT.query conn q row
  QueryWith parser q row ->
    withConnection $ \conn -> OT.queryWith parser conn q row
  Query_ row ->
    withConnection $ \conn -> OT.query_ conn row
  QueryWith_ parser row ->
    withConnection $ \conn -> OT.queryWith_ parser conn row
  Execute q row -> withConnection $ \conn -> OT.execute conn q row
  Execute_ q -> withConnection $ \conn -> OT.execute_ conn q
  ExecuteMany q row -> withConnection $ \conn -> OT.executeMany conn q row
  WithTransaction f -> localUnliftWithConn env $ \conn unlift -> OT.withTransaction conn (unlift f)
  WithTransactionLevel level f -> localUnliftWithConn env $ \conn unlift -> PSQL.withTransactionLevel level conn (unlift f)
  WithTransactionMode mode f -> localUnliftWithConn env $ \conn unlift -> PSQL.withTransactionMode mode conn (unlift f)
  WithTransactionModeRetry mode shouldRetry f -> localUnliftWithConn env $ \conn unlift -> PSQL.withTransactionModeRetry mode shouldRetry conn (unlift f)
  WithTransactionModeRetry' mode shouldRetry f -> localUnliftWithConn env $ \conn unlift -> PSQL.withTransactionModeRetry' mode shouldRetry conn (unlift f)
  WithTransactionSerializable f -> localUnliftWithConn env $ \conn unlift -> PSQL.withTransactionSerializable conn (unlift f)
  WithSavepoint f -> localUnliftWithConn env $ \conn unlift -> OT.withSavepoint conn (unlift f)
  Begin -> withConnection $ liftIO . OT.begin
  Commit -> withConnection $ liftIO . OT.commit
  Rollback -> withConnection $ liftIO . OT.rollback
  Fold q params a f ->
    localUnliftWithConn env $ \conn unlift ->
      OT.fold conn q params a (unlift ... f)
  Fold_ q a f ->
    localUnliftWithConn env $ \conn unlift ->
      OT.fold_ conn q a (unlift ... f)
  FoldWithOptions opts q params a f ->
    localUnliftWithConn env $ \conn unlift ->
      OT.foldWithOptions opts conn q params a (unlift ... f)
  FoldWithOptions_ opts q a f ->
    localUnliftWithConn env $ \conn unlift ->
      OT.foldWithOptions_ opts conn q a (unlift ... f)
  ForEach q row forR ->
    localUnliftWithConn env $ \conn unlift ->
      OT.forEachWith PSQL.fromRow conn q row (unlift . forR)
  ForEach_ q forR ->
    localUnliftWithConn env $ \conn unlift ->
      OT.forEach_ conn q (unlift . forR)
  Returning q rows -> withConnection $ \conn -> OT.returning conn q rows
  FoldWith parser q params a f ->
    localUnliftWithConn env $ \conn unlift ->
      OT.foldWith parser conn q params a (unlift ... f)
  FoldWithOptionsAndParser opts parser q params a f ->
    localUnliftWithConn env $ \conn unlift ->
      OT.foldWithOptionsAndParser opts parser conn q params a (unlift ... f)
  FoldWith_ parser q a f ->
    localUnliftWithConn env $ \conn unlift ->
      OT.foldWith_ parser conn q a (unlift ... f)
  FoldWithOptionsAndParser_ opts parser q a f ->
    localUnliftWithConn env $ \conn unlift ->
      OT.foldWithOptionsAndParser_ opts parser conn q a (unlift ... f)
  ForEachWith parser q row forR ->
    localUnliftWithConn env $ \conn unlift ->
      OT.forEachWith parser conn q row (unlift . forR)
  ForEachWith_ parser q forR ->
    localUnliftWithConn env $ \conn unlift ->
      OT.forEachWith_ parser conn q (unlift . forR)
  ReturningWith parser q rows -> withConnection $ \conn -> OT.returningWith parser conn q rows
#endif