Let's do some exercises where errors will happen and see how to deal with them.
Let's do some exercises where errors will happen and see how to deal with them.
We're going to work with person
table again, where the name
column is marked as being
unique.
CREATE TABLE IF NOT EXISTS person ( id IDENTITY, name VARCHAR NOT NULL UNIQUE, age INT )
Alright, let’s define a way to insert instances.
def insert(n: String, a: Option[Int]): ConnectionIO[Long] = sql"insert into person (name, age) values ($n, $a)" .update .withUniqueGeneratedKeys("id")
The following exercises will try to insert two people with the same name. The second operation will fail with a unique constraint violation. Let's see how we can avoid this error by using several combinators.
A first approach could be to specify the Throwable
that we want to trap by using
attemptSome
combinator.
If we want to trap a specific SqlState
like unique constraint violation
, we'll use the
attemptSomeSqlState
.
If we want to trap a specific SqlState
like unique constraint violation
, we'll use the
attemptSomeSqlState
. We can specify several SqlState
values and indicate what value we'll
return in each case. We can:
SqlState
values provided as constants in the contrib-postgresql add-onSqlState
value by typing val UNIQUE_VIOLATION = SqlState("23505")
Finally we can recover from an error with a new action by using exceptSqlState
.
Finally we can recover from an error with a new action by using exceptSqlState
. In this
case, if the name already exists, we'll insert the person with a different name.
The parameterless execute method has been deprecated and will be removed in a future version of ScalaTest. Please invoke execute with empty parens instead: execute().
The trap method is no longer needed for demos in the REPL, which now abreviates stack traces, and will be removed in a future version of ScalaTest
About Exceptions
doobie allows exceptions to propagate and escape unless they are handled explicitly (exactly as
IO
andTask
work). This means when a doobie action (transformed to some target monad) is executed, exceptions can escape.The Catchable Typeclass and Derived Combinators
All doobie monads have associated instances of the
scalaz.Catchable
typeclass, and the provided interpreter requires all target monads to have an instance as well.Catchable
provides two operations:attempt
convertsM[A]
intoM[Throwable \/ A]
fail
constructs anM[A]
that fails with a providedThrowable
So any doobie program can be lifted into a disjunction simply by adding
.attempt
.From the
.attempt
combinator we derive the following, available as combinators and as syntax:attemptSome
allows you to catch only specifiedThrowable
s.except
recovers with a new action.exceptSome
same, but only for specifiedThrowable
s.onException
executes an action on failure, discarding its result.ensuring
executes an action in all cases, generalizingfinally
.From these we can derive combinators that only pay attention to
SQLException
:attemptSql
is likeattempt
but only trapsSQLException
.attemptSomeSql
traps only specifiedSQLException
s.exceptSql
recovers from a SQLException with a new action.onSqlException
executes an action onSQLException
and discards its result.And finally we have a set of combinators that focus on SQLStates.
attemptSqlState
is likeattemptSql
but yieldsM[SQLState \/ A]
.attemptSomeSqlState
traps only specifiedSQLState
s.exceptSqlState
recovers from aSQLState
with a new action.exceptSomeSqlState
recovers from specifiedSQLState
s with a new action.