module HsDev.Database.SQLite.Transaction (
TransactionType(..),
Retries(..), def, noRetry, retryForever, retryN,
withTransaction, beginTransaction, commitTransaction, rollbackTransaction,
transaction, transaction_,
retry, retry_
) where
import Control.Concurrent
import Control.Monad.Catch
import Control.Monad.IO.Class
import Data.Default
import Database.SQLite.Simple as SQL hiding (withTransaction)
import HsDev.Server.Types (SessionMonad, serverSqlDatabase)
data TransactionType = Deferred | Immediate | Exclusive
deriving (Eq, Ord, Read, Show)
data Retries = Retries {
retriesIntervals :: [Int],
retriesError :: SQLError -> Bool }
instance Default Retries where
def = Retries (replicate 10 100000) $ \e -> sqlError e `elem` [ErrorBusy, ErrorLocked]
noRetry :: Retries
noRetry = Retries [] (const False)
retryForever :: Int -> Retries
retryForever interval = def { retriesIntervals = repeat interval }
retryN :: Int -> Int -> Retries
retryN interval times = def { retriesIntervals = replicate times interval }
withTransaction :: (MonadIO m, MonadMask m) => Connection -> TransactionType -> Retries -> m a -> m a
withTransaction conn t rs act = mask $ \restore -> do
mretry restore (beginTransaction conn t)
(restore act <* mretry restore (commitTransaction conn)) `onException` rollbackTransaction conn
where
mretry restore' fn = mretry' (retriesIntervals rs) where
mretry' [] = fn
mretry' (tm:tms) = catch @_ @SQLError fn $ \e -> if
| retriesError rs e -> do
_ <- restore' $ liftIO $ threadDelay tm
mretry' tms
| otherwise -> throwM e
beginTransaction :: MonadIO m => Connection -> TransactionType -> m ()
beginTransaction conn t = liftIO $ SQL.execute_ conn $ case t of
Deferred -> "begin transaction;"
Immediate -> "begin immediate transaction;"
Exclusive -> "begin exclusive transaction;"
commitTransaction :: MonadIO m => Connection -> m ()
commitTransaction conn = liftIO $ SQL.execute_ conn "commit transaction;"
rollbackTransaction :: MonadIO m => Connection -> m ()
rollbackTransaction conn = liftIO $ SQL.execute_ conn "rollback transaction;"
transaction :: SessionMonad m => TransactionType -> Retries -> m a -> m a
transaction t rs act = do
conn <- serverSqlDatabase
withTransaction conn t rs act
transaction_ :: SessionMonad m => TransactionType -> m a -> m a
transaction_ t = transaction t def
retry :: (MonadIO m, MonadCatch m) => Retries -> m a -> m a
retry rs = retry' (retriesIntervals rs) where
retry' [] fn = fn
retry' (tm:tms) fn = catch @_ @SQLError fn $ \e -> if
| retriesError rs e -> do
liftIO $ threadDelay tm
retry' tms fn
| otherwise -> throwM e
retry_ :: (MonadIO m, MonadCatch m) => m a -> m a
retry_ = retry def