Search code examples
scalaslickscala-cats

Scala Slick - Identify which DBIO is failing in DBIO.sequence


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 => ...
  ...
}

Solution

  • 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 => ...
      ...
    }