Slick left/right/outer joins with Option

你说的曾经没有我的故事 提交于 2019-11-30 01:54:43

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.

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].

Add option Projection

Note: 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

Using the option Projection

val explicitLeftOuterJoin = for {
  (c, s) <- Coffees leftJoin Suppliers on (_.supID === _.id)
} yield (c, s.option)

Advanced: 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):

  1. Converts a tuple of Option to an shapeless HList[Option[_]].
  2. sequence over the HList[Option[_]] to a Option[HList[_]]
  3. Convert the HList back to a tuple.

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.?)

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)
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!