{-|

TODO:
We may want to
do one transaction retry in case of the 23505 error, and fail if an identical
error is seen.
-}
module Hasql.Transaction.Requisites.Sessions
where

import Hasql.Transaction.Prelude
import Hasql.Transaction.Requisites.Model
import Hasql.Session
import qualified Hasql.Transaction.Requisites.Statements as Statements


inRetryingTransaction :: Mode -> Level -> Session (a, Condemnation) -> Session a
inRetryingTransaction mode isolation session =
  fix $ \ recur -> catchError normal (onError recur)
  where
    normal =
      do
        statement () (Statements.beginTransaction mode isolation)
        (result, condemnation) <- session
        case condemnation of
          Uncondemned -> statement () Statements.commitTransaction
          Condemned -> statement () Statements.abortTransaction
        return result
    onError continue error =
      do
        statement () Statements.abortTransaction
        case error of
          QueryError _ _ (ResultError (ServerError "40001" _ _ _)) -> continue
          _ -> throwError error

inAlternatingTransaction :: Mode -> Level -> [Session (a, Condemnation)] -> Session a
inAlternatingTransaction mode level sessions =
  let
    loop = \ case
      session : sessionsTail -> tryTransaction mode level session >>= \ case
        Just a -> return a
        Nothing -> loop sessionsTail
      _ -> loop sessions
    in loop sessions

tryTransaction :: Mode -> Level -> Session (a, Condemnation) -> Session (Maybe a)
tryTransaction mode level session = do
  statement () (Statements.beginTransaction mode level)
  catchError
    (do
      (result, condemnation) <- session
      case condemnation of
        Uncondemned -> statement () Statements.commitTransaction
        Condemned -> statement () Statements.abortTransaction
      return (Just result))
    (\ error -> do
      statement () Statements.abortTransaction
      case error of
        QueryError _ _ (ResultError (ServerError "40001" _ _ _)) -> return Nothing
        error -> throwError error)