Catching SQL error on future failure in Slick - scala

The code below uses Slick 3.1.x to read a row from a table, attempting to catch any SQL errors. UserDB is the Slick representation of the table, and User is the related object.
This code does not compile in the failure statement with the following error:
type mismatch; found : Unit required:
scala.concurrent.Future[Option[user.manage.User]]
How to fix this to catch the SQL error?
def read (sk: Int): Future[Option[User]] = {
val users = TableQuery[UserDB]
val action = users.filter(_.sk === sk).result
val future = db.run(action)
future.onSuccess {
case result =>
if (!result.isEmpty)
Some(result(0))
else
None
}
future.onFailure { // <-- compilation error
case e => println (e.getMessage)
None
}
}

You can use asTry method to catch exception ex into a successful result Failure(ex) and the successful value in Success(v). In your case the following should work.
db.run(action.asTry).map{
case Failure(ex) => {
Log.error(s"error : ${ex.getMessage}")
None
}
case Success(x) => x
}
As mentioned in the slick documentation, asTry is used to pipeline the exceptions for recovery handling logic.

You can use the onComplete method of the future.
future.onComplete{
case Success(r) ⇒ ...
case Failure(t) ⇒ log.error("failure in db query " + t.getMessage)
}

Related

How to implement a retry logic in pattern match?

This is my function that used to call another api, I want to do a retry when I got some specific exception(TimeoutException, etc) for 3 times, how can do that? Thanks for help!
determinationService.getTax(getRequest, runID) match {
case Success(getResponse) =>
LOGGER.info("Following response received from determination service:")
LOGGER.info(getTaxResponse.toString)
...
case Failure(exception) =>
LOGGER.error(
"AppName:{} RunID:{} Grpc error when calling Determination service: {}",
Array(LoggingConstant.APP_NAME, runID, exception): _*
)
//do retry here
}
unit test
val determinationServiceMock = mock[DeterminationService]
doReturn(Failure(new TimeOutException())).when(determinationServiceMock).getTax(any(), any())
Assuming a result type of Unit you could do:
#tailrec
def someDef(retry: Int = 0): Unit = {
determinationService.getTax(getRequest, runID) match {
case Success(getResponse) =>
LOGGER.info("Following response received from determination service:")
LOGGER.info(getTaxResponse.toString)
// ...
case Failure(exception) =>
LOGGER.error(
"AppName:{} RunID:{} Grpc error when calling Determination service: {}",
Array(LoggingConstant.APP_NAME, runID, exception): _*
)
exception match {
case _: TimeoutException if retry < 3 => someDef(retry + 1)
case _ => // ...
}
}
}
Note: your actual code might not be tail-recursive, just this snippet is.

Propagate errors through a chain of Scala futures

Considering a sequence of futures each returning Either[Status, Resp].
How would you propagate error status codes through a for comprehension which is using Future and not Either?
The code bellow does not work, since the parsing exception is not caught by .recover of the last future
The use case is Scala Play ActionRefiners which returns Future[Either[Status, TRequest[A]]].
def parseId(id: String):Future[Int] = {
Future.successful(Integer.parseInt(id))
}
def getItem(id: Int)(implicit ec: ExecutionContext): Future[Either[Status, String]] =
Future(Some("dummy res from db " + id)).transformWith {
case Success(opt) => opt match {
case Some(item) => Future.successful(Right(item))
case _ => Future.successful(Left(NotFound))
}
case Failure(_) => Future.successful(Left(InternalServerError))
}
(for {
id <- parseId("bad request")
resp <- getItem(id)
} yield resp).recover {
case _:NumberFormatException => Left(BadRequest)
}
I could move the .recover to parseId, but this makes the for comprehension very ugly - having to treat the Either[Status, id] in the middle
def parseId(id: String):Future[Either[Status, Int]] = {
Future.successful(Right(Integer.parseInt(id))).recover {
case _:NumberFormatException => Left(BadRequest)
}
}
Your exception is not caught because you are not throwing it inside the Future: Future.successful is immediately satisfied with the result of the expression you give it, if it throws an exception, it is executed on the current thread.
Try removing the .successful: Future(id.toInt) will do what you want.
Also, I would recommend to get rid of all the Eithers: these are highly overrated/overused, especially in the context of Future (that already wrap their result into Try anyhow), and just make the code more complicated and less readable without offering much benefit.
case class FailureReason(status: Status)
extends Exception(status.toString)
def notFound() = throw FailureReason(NotFound)
def internalError() = throw FailureReason(InternalError)
def badRequest() = throw FailureReason(BadRequest)
def parseId(id: String):Future[Int] = Future(id.toInt)
def getItem(id: Int): Future[String] = Future(Some("dummy"))
.map { _.getOrElse(notFound) }
.recover { _ => internalError }
// this is the same as your for-comprehension, just looking less ugly imo :)
parseId("foo").flatMap(getItem).recover {
case _: NumberFormatException => badRequest()
}
// if you still want `Either` in the end for some reason:
.map(Right.apply[Status, String])
.recover {
case _: NumberFormatException => Left(BadRequest) // no need for the first recover above if you do this
case FailureReason(status) => Left(status)
}

Handling errors as a Future[Result] in ReactiveMongo 16.6

How can I output mongoDB errors in a Result with ReactiveMongo (16.6)? I've spent virtually the whole day looking through samples but have not been able to achieve this as of yet. The error section of the documentation returns a Future[Unit] rather than a Future[Result]. And every other example/sample that I can find either is outdated or does not do this; example_1, example2
Here is what I would like to do:
def updateById(collName: String, id: BSONObjectID) = authAction.async(parse.json) { implicit request: Request[JsValue] =>
val oWriteJso = request.body.asOpt[JsObject]
lazy val qJso = Json.obj("_id" -> id)
val res = oWriteJso.map(
wJso => mongoRepo.update(collName)(qJso, wJso)().recoverWith {
case WriteResult.Code(11000) => Future.successful(BadRequest("it went bad"))
case _ => Future.successful(BadRequest("also bad"))
}
)
res
}
Of course with the function signature as recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] this code above will return an error as it needs to return a Future[WriteResult]. But how then would I be able to put any error messages, codes, etc (from mongoDB) into a Result?
The documentation indicates how to recover a Future[WriteResult]:
.recover {
case WriteResult.Code(11000) =>
// if the result is defined with the error code 11000 (duplicate error)
println("Match the code 11000")
case WriteResult.Message("Must match this exact message") =>
println("Match the error message")
// ...
}
Thanks to Future combinators (not specific to ReactiveMongo), it can be used whatever is the type of the successful value to be lifted inside the Future.
def foo[T](future: Future[WriteResult], recoveredValue: => T)(success: WriteResult => T): Future[T] = future.map(success).recover {
case WriteResult.Code(11000) =>
// if the result is defined with the error code 11000 (duplicate error)
recoveredValue
case WriteResult.Message("Must match this exact message") =>
recoveredValue
}

How to NOT throw an exception?

I have the following Slick code that given an id returns a customer (if exists). If there's a problem (such as connectivity lost) a Failure clause will throw an exception:
def read (id: Int): Future[Option[Customer]] = {
val db = // ....
val customers = TableQuery[CustomerDB]
val action = customers.filter(_.id === id).result
val future = db.run(action.asTry)
future.map{
case Success(s) =>
if (s.length>0)
Some(s(0))
else
None
case Failure(f) => throw new Exception (f.getMessage)
}
}
Now, my understanding is that instead of using try/catch/finally of exceptions, in Scala one should use Try. In addition, no exceptions should be thrown. But if the exception is not thrown, how to notify the upper layer that a problem occurred?
Future itself does already have Try inside. So, I would say that you need to just flatten (also you code a bit complicated, I simplified):
future.flatMap {
case Success(s) => Future.successful(s.headOption)
case Failure(f) => Future.failed(f)
}
Result Future when in failed state notifies caller that execution failed (with wrapped original exception). Otherwise, successful.
The right way to do report errors is by using Either.
trait Error
case class NotFound(id: Int) extends Error
case class QueryFailed(msg: String) extends Error
def read (id: Int): Future[Either[Error, Customer]] = {
val db = // ....
val customers = TableQuery[CustomerDB]
val action = customers.filter(_.id === id).result
val future = db.run(action.asTry)
future.map{
case Success(s) =>
if (s.length>0)
Right(s(0))
else
Left(NotFound(id))
case Failure(f) => Left(QueryFailed(f.getMessage))
}
}
Ok so, in general you can use Future.successful or Future.failed(msg: String) to "signal" the upper level (aka calling method) you got the value or not.
Better approach
A good approach is however to use .recoverWith{} on a Future in case of failure.
For example:
def getUserFromCloud (userId: String): Future[String] = Future{
cloudProviderApi.getUsername(userId)
}.recoverWith{
Future.failed(s"$userId does not exist.")
}
What about the calling method?
Well you just map the success with and underscode and deal with the error by using recover:
getUserFromCloud("test").map(_ => {
//In case of success
}).recover{
//In case of failure, like return BadRequest.
}
More on recover and recoverWith in case you are interested: Scala recover or recoverWith

How to catch slick postgres exceptions for duplicate key value violations

My table has a unique index on a pair of columns in my postgresql database.
I want to know how I can catch a duplicate key exception when I am inserting:
def save(user: User)(implicit session: Session): User = {
val newId = (users returning users.map(_id) += user
user.copy(id = newId)
}
My logs show this exception:
Execution exception[[PSQLException: ERROR: duplicate key value violates unique constraint "...."
I haven't really used exceptions much in scala either yet also.
Your save method should probably return something different than just User, to indicate the possibility of failure. If the only exception that will be thrown is by unique key, and you really only care about success or failure (and not the type of failure), one way to go would be to return Option[User].
You could use a simple try/catch block, mapping successful saves to Some[User] and PSQLException to None:
def save(user: User)(implicit session: Session): Option[User] = {
try {
val newId = (users returning users.map(_id) += user
Some(user.copy(id = newId))
} catch {
case PSQLException => None
}
}
Personally not the way I'd go, as try/catch isn't really idiomatic Scala, and your error type is discarded. The next option is to use scala.util.Try.
def save(user: User)(implicit session: Session): Try[User] = Try {
val newId = (users returning users.map(_id) += user
user.copy(id = newId)
}
The code here is simpler. If the body of Try is successful, then save will return Success[User], and if not it will return the exception wrapped in Failure. This will allow you to do many things with Try.
You could pattern match:
save(user) match {
case Success(user) => Ok(user)
case Failure(t: PSQLException) if(e.getSQLState == "23505") => InternalServerError("Some sort of unique key violation..")
case Failure(t: PSQLException) => InternalServerError("Some sort of psql error..")
case Failure(_) => InternalServerError("Something else happened.. it was bad..")
}
You could use it like Option:
save(user) map { user =>
Ok(user)
} getOrElse {
InternalServerError("Something terrible happened..")
}
You can compose many together at once, and stop on the first failure:
(for {
u1 <- save(user1)
u2 <- save(user2)
u3 <- save(user3)
} yield {
(u1, u2, u3)
}) match {
case Success((u1, u2, u3)) => Ok(...)
case Failure(...) => ...
}
In Slick 3.x you can use asTry.
I'm using MySQL, but the same code can be used for PostgreSQL, only the exceptions are different.
import scala.util.Try
import scala.util.Success
import scala.util.Failure
db.run(myAction.asTry).map {result =>
result match {
case Success(res) =>
println("success")
// ...
case Failure(e: MySQLIntegrityConstraintViolationException) => {
//code: 1062, status: 23000, e: Duplicate entry 'foo' for key 'name'
println(s"MySQLIntegrityConstraintViolationException, code: ${e.getErrorCode}, sql status: ${e.getSQLState}, message: ${e.getMessage}")
// ...
}
case Failure(e) => {
println(s"Exception in insertOrUpdateListItem, ${e.getMessage}")
// ...
}
}
}
Note: It's also possible to map the action (myAction.asTry.map ...) instead of the future returned by db.run.