I'm executing transactionally 3 different operations on DB in the following way
// firstDBIO, secondDBIOA, thirdDBIO: DBIOAction[Unit]
F.delay {
val unitOfWork = DBIO.sequence(
List(
firstDBIO,
secondDBIO,
thirdDBIO,
),
)
db.run(unitOfWork.transactionally)
}.futureLift.void.map(_.asRight[ImportError]).recover {
case ex: SQLException => Left(ImportError.UnexpectedError)
}
This works correctly but, when the transaction fails, in the recover
I can't make logic based on which of the DBIO
caused the error (I don't want to rely on the SQLException
).
I would like to be able to do something like
.recover {
case ex: ImportError.CauseFirst => ...
case ex: ImportError.CauseSecond => ...
case ex: ImportError.CauseThird => ...
...
}
I found that this solution is exactly what I was looking for
dbio.asTry.flatMap {
case Success(v) => DBIO.successful(v)
case Failure(e) => DBIO.failed(ImportError.CauseFirst)
}
It preserves the success/failure outcome triggering the abortion of the atomic transaction as requested. In the end, it's just mapping the error contained in the failure, indeed it's quite useful to implement a function to do that
implicit class DBIOOps[A](dbio: DBIO[A]) {
def mapFailure(f: Throwable => E with Throwable) = dbio.asTry.flatMap {
case Success(v) => DBIO.successful(v)
case Failure(e) => DBIO.failed(f(e))
}
}
F.delay {
val unitOfWork = DBIO.sequence(
List(
firstDBIO.mapFailure(_ => ImportError.CauseFirst),
secondDBIO.mapFailure(_ => ImportError.CauseSecond),
thirdDBIO.mapFailure(_ => ImportError.CauseThird),
...
),
)
db.run(unitOfWork.transactionally)
}.futureLift.void.map(_.asRight[ImportError]).recover {
case ex: ImportError.CauseFirst => ...
case ex: ImportError.CauseSecond => ...
case ex: ImportError.CauseThird => ...
...
}