In the Slick examples there are a few examples of joining where one of the resulting columns can be nulls, as it can be the case when doing left, right, or outer joins. For
UPDATE: This will be solved and simply work in Slick 3.0 coming end of 2014, no need anymore for the following workaround
This is a limitation of Slick at the moment. You have to call .? on every column individually. You can however place a function called ?
in the table class that does this in a central place and thereby get .? on complete rows. This play-slick example code contains a generic solution involving some generated code. We also have a PR that adds auto-generation of a ? method lined up.
In the long run we will support a variant of outer joins in Slick where Slick is fully aware of the types involved and you do not need to specify .? anywhere. For now we have to live with workarounds involving code generation.
In addition to the answer above: In case you have a class which extends Table and your * projection looks something like this:
def * = (col1, col2, col3)
than your ? function would look like:
def ? = (col1.?, col2.?, col3.?)
If you have defined such a function you can write:
for {
(x,y) <- x leftJoin y on (...)
} yield (x, y.?)
Not the cleanest solution (uses scalaz 7.0.6 and shapeless 2.0.1), but this works for now (Slick 2.0.1):
Using the ?
projection above, it's possible to create a Slick projection that converts the tuple of Option
values => Option[TupleN]
=> Option[YourClass]
.
option
ProjectionNote: sequence
is used to convert a tuple of Option
values to Option[TupleN]
. Code for sequence
is defined at the bottom of this answer.
Add to Suppliers
. Assumes Supplier
is a case class.
import scalaz._, Scalaz._
import SequenceTupleOption._
def option = (id.?, name.?, street.?) <> (optionApply, optionUnapply)
def optionApply(t: (Option[Int], Option[String], Option[String])): Option[Comment] = {
sequence(t).map(Supplier.tupled)
}
def optionUnapply(oc: Option[Supplier]): Option[(Option[Int], Option[String], Option[String])] = None
option
Projectionval explicitLeftOuterJoin = for {
(c, s) <- Coffees leftJoin Suppliers on (_.supID === _.id)
} yield (c, s.option)
sequence
, Converts Tuple of Option
Values to Option[TupleN]
This is the hard part that Travis Brown wrote. sequence
converts from a tuple of Option
values to a Option[TupleN]
(using scalaz and shapeless).
import scalaz._, Scalaz._
import shapeless._, ops.hlist.{ RightFolder, Tupler }
object SequenceTupleOption {
object applicativeFolder extends Poly2 {
implicit def caseApplicative[A, B <: HList, F[_]](implicit
app: Applicative[F]
) = at[F[A], F[B]] {
(a, b) => app.ap(a)(app.map(b)(bb => (_: A) :: bb))
}
}
def sequence[T, EL <: HList, L <: HList, OL <: HList, OT](t: T)(implicit
gen: Generic.Aux[T, EL],
eq: EL =:= L,
folder: RightFolder.Aux[L, Option[HNil], applicativeFolder.type, Option[OL]],
tupler: Tupler.Aux[OL, OT]
): Option[OT] =
eq(gen.to(t)).foldRight(some(HNil: HNil))(applicativeFolder).map(tupler(_))
}
Usage for sequence
:
import scalaz._, Scalaz._
import SequenceTupleOption._
case class Person(id: Int, name: String, age: Int)
val t = (Option(1), Option("Bob"), Option(40))
val person: Option[Person] = sequence(t).map(Person.tupled) // Some(Person(1,Bob,40))
High-level overview of what sequence
does (not proper types):
Option
to an shapeless HList[Option[_]]
.sequence
over the HList[Option[_]]
to a Option[HList[_]]
HList
back to a tuple.In Slick 3.1.1, the correct answer would be simply (as mentioned in some comments):
for {
(c, s) <- coffees joinLeft suppliers on (_.supID === _.id)
} yield (c, s)