packages feed

persistent-sql-lifted-0.4.2.0: library/Database/Persist/Sql/Lifted/Savepoint.hs

module Database.Persist.Sql.Lifted.Savepoint
  ( rollbackWhen
  ) where

import Control.Applicative (pure)
import Control.Monad (replicateM)
import Control.Monad.IO.Class (MonadIO)
import Data.Bool (Bool)
import Data.Char (Char)
import Data.Function (($))
import Data.Functor ((<$>))
import Data.Semigroup ((<>))
import Data.Text (Text)
import Data.Text qualified as T
import Data.Vector (Vector)
import Data.Vector qualified as V
import Database.Persist.Sql.Lifted.MonadSqlBackend (MonadSqlBackend)
import Database.Persist.Sql.Lifted.Persistent (rawExecute)
import GHC.Stack (HasCallStack)
import System.Random (randomRIO)
import Prelude ((-))

--  | Create a new transaction @SAVEPOINT@, returning its name
newSavepoint :: (HasCallStack, MonadSqlBackend m) => m Text
newSavepoint = do
  r <- replicateM 8 randomCharacter
  let savepoint = "savepoint_" <> T.pack r
  rawExecute ("SAVEPOINT " <> savepoint) []
  pure savepoint

randomCharacter :: MonadIO m => m Char
randomCharacter = (characterSet V.!) <$> randomRIO (0, V.length characterSet - 1)

characterSet :: Vector Char
characterSet = V.fromList $ ['a' .. 'z'] <> ['1' .. '9']

--  | Release a @SAVEPOINT@
releaseSavepoint :: (HasCallStack, MonadSqlBackend m) => Text -> m ()
releaseSavepoint name = rawExecute ("RELEASE SAVEPOINT " <> name) []

--  | Rollback to a @SAVEPOINT@
rollbackToSavepoint
  :: (HasCallStack, MonadSqlBackend m) => Text -> m ()
rollbackToSavepoint name = rawExecute ("ROLLBACK TO SAVEPOINT " <> name) []

-- | Runs a SQL action with SAVEPOINT, rolling back when specified
rollbackWhen
  :: (HasCallStack, MonadSqlBackend m)
  => (a -> Bool)
  -- ^ When to ROLLBACK based on the result of the action
  -> m a
  -- ^ The action to be run
  -> m a
rollbackWhen shouldRollback act = do
  savepoint <- newSavepoint
  a <- act
  if shouldRollback a
    then rollbackToSavepoint savepoint
    else releaseSavepoint savepoint
  pure a