Skip to content

Commit

Permalink
Add docs about DBIO composition.
Browse files Browse the repository at this point in the history
This also explains the difference between the strict and eager
combinators and the more flexible ones like `flatMap`. Fixes #1090.
  • Loading branch information
szeiger committed Apr 7, 2015
1 parent e9ab330 commit 0cab15e
Showing 1 changed file with 72 additions and 2 deletions.
74 changes: 72 additions & 2 deletions slick/src/sphinx/dbio.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,83 @@ convenience method ``mapResult`` is provided for this purpose:
single: transaction
.. _transactions:

Composing Database I/O Actions
------------------------------

``DBIOAction``\ s describe sequences of individual actions to execute in strictly sequential order on
one database session (at least conceptually), therefore the most commonly used combinators deal with
sequencing. Since a ``DBIOAction`` eventually results in a ``Success`` or ``Failure``, its combinators,
just like the ones on ``Future``, have to distinguish between successful and failed executions. Unless
specifically noted, all combinators only apply to successful actions. Any failure will abort the
sequence of execution and result in a failed ``Future`` or *Reactive Stream*.

Sequential Execution
____________________

The simplest combinator is :api:`DBIO.seq <slick.dbio.DBIO$@seq[E<:Effect](DBIOAction[_,NoStream,E]*):DBIOAction[Unit,NoStream,E]>`
which takes a varargs list of actions to run in sequence, discarding their return value. If you
need the return value, you can use :api:`andThen <slick.dbio.DBIOAction@andThen[R2,S2<:NoStream,E2<:Effect](DBIOAction[R2,S2,E2]):DBIOAction[R2,S2,EwithE2]>`
to combine two actions and keep the result of the second one. If you need both return values of two
actions, there is the :api:`zip <slick.dbio.DBIOAction@zip[R2,E2<:Effect](DBIOAction[R2,NoStream,E2]):DBIOAction[(R,R2),NoStream,EwithE2]>`
combinator. For getting all result values from a sequence of actions (of compatible types), use
:api:`DBIO.sequence <slick.dbio.DBIO$@sequence[R,M[+_]<:TraversableOnce[_],E<:Effect](M[DBIOAction[R,NoStream,E]])(CanBuildFrom[M[DBIOAction[R,NoStream,E]],R,M[R]]):DBIOAction[M[R],NoStream,E]>`.
All these combinators work with pre-existing ``DBIOAction``\ s which are composed eagerly.

If an action depends on a previous action in the sequence, you have to compute it on the fly with
:api:`flatMap <slick.dbio.DBIOAction@flatMap[R2,S2<:NoStream,E2<:Effect]((R)⇒DBIOAction[R2,S2,E2])(ExecutionContext):DBIOAction[R2,S2,EwithE2]>`
or :api:`map <slick.dbio.DBIOAction@map[R2]((R)⇒R2)(ExecutionContext):DBIOAction[R2,NoStream,E]>`.
These two methods plus :api:`filter <slick.dbio.DBIOAction@filter((R)⇒Boolean)(ExecutionContext):DBIOAction[R,NoStream,E]>`
enable the use of *for comprehensions* for action sequencing. Since they take function
arguments, they also require an implicit ``ExecutionContext`` on which to run the function. This
way Slick ensures that no non-database code is run on the database thread pool.

.. note::
You should prefer the less flexible methods without an ``ExecutionContext`` where possible. The
resulting actions can be executed more efficiently.

Similar to :api:`DBIO.sequence <slick.dbio.DBIO$@sequence[R,M[+_]<:TraversableOnce[_],E<:Effect](M[DBIOAction[R,NoStream,E]])(CanBuildFrom[M[DBIOAction[R,NoStream,E]],R,M[R]]):DBIOAction[M[R],NoStream,E]>`
for upfront composition, there is :api:`DBIO.fold <slick.dbio.DBIO$@fold[T,E<:Effect](Seq[DBIOAction[T,NoStream,E]],T)((T,T)⇒T)(ExecutionContext):DBIOAction[T,NoStream,E]>`
for working with sequences of actions and composing them based on the previous result.

Error Handling
______________

You can use :api:`andFinally <slick.dbio.DBIOAction@andFinally[E2<:Effect](DBIOAction[_,NoStream,E2]):DBIOAction[R,S,EwithE2]>`
to perform a cleanup action, no matter whether the previous action succeeded or failed. This is similar to using
``try ... finally ...`` in imperative Scala code. A more flexible version of
:api:`andFinally <slick.dbio.DBIOAction@andFinally[E2<:Effect](DBIOAction[_,NoStream,E2]):DBIOAction[R,S,EwithE2]>`
is :api:`cleanUp <slick.dbio.DBIOAction@cleanUp[E2<:Effect]((Option[Throwable])⇒DBIOAction[_,NoStream,E2],Boolean)(ExecutionContext):DBIOAction[R,S,EwithE2]>`.
It lets you transform the failure and decide how to fail the resulting action if both the original
one and the cleanup failed.

.. note::
For even more flexible error handling use :api:`asTry <slick.dbio.DBIOAction@asTry:DBIOAction[Try[R],NoStream,E]>`
and :api:`failed <slick.dbio.DBIOAction@failed:DBIOAction[Throwable,NoStream,E]>`. Unlike with
:api:`andFinally <slick.dbio.DBIOAction@andFinally[E2<:Effect](DBIOAction[_,NoStream,E2]):DBIOAction[R,S,EwithE2]>`
and :api:`cleanUp <slick.dbio.DBIOAction@cleanUp[E2<:Effect]((Option[Throwable])⇒DBIOAction[_,NoStream,E2],Boolean)(ExecutionContext):DBIOAction[R,S,EwithE2]>`
the resulting actions cannot be used for streaming.

Primitives
__________

You can convert a ``Future`` into an action with :api:`DBIO.from <slick.dbio.DBIO$@from[R](Future[R]):DBIOAction[R,NoStream,Effect]>`.
This allows the result of the ``Future`` to be used in an action sequence. A pre-existing value or
failure can be converted with :api:`DBIO.successful <slick.dbio.DBIO$@successful[R](R):DBIOAction[R,NoStream,Effect]>`
and :api:`DBIO.failed <slick.dbio.DBIO$@failed(Throwable):DBIOAction[Nothing,NoStream,Effect]>`, respectively.

Debugging
_________

The :api:`named <slick.dbio.DBIOAction@named(String):DBIOAction[R,S,E]>` combinator names an
action. This name can be seen in debug logs if you enable the
``slick.backend.DatabaseComponent.action`` logger.

Transactions and Pinned Sessions
________________________________

When executing a ``DBIOAction`` which is composed of several smaller actions, Slick acquires sessions from
the connection pool and releases them again as needed so that a session is not kept in use
unnecessarily while waiting for the result from a non-database computation (e.g. the function
passed to
unnecessarily while waiting for the result from a non-database computation (e.g. the function passed to
:api:`flatMap <slick.dbio.DBIOAction@flatMap[R2,S2<:NoStream,E2<:Effect]((R)⇒DBIOAction[R2,S2,E2])(ExecutionContext):DBIOAction[R2,S2,EwithE2]>`
that determines the next Action to run). All :api:`DBIOAction combinators <slick.dbio.DBIOAction>`
which combine two database actions without any non-database computations in between (e.g.
Expand Down

0 comments on commit 0cab15e

Please sign in to comment.