Slick left/right/outer joins with Option

后端 未结 4 2195
一向
一向 2020-12-25 14:38

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

相关标签:
4条回答
  • 2020-12-25 15:29

    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.

    0 讨论(0)
  • 2020-12-25 15:29

    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.?)
    
    0 讨论(0)
  • 2020-12-25 15:33

    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.
    0 讨论(0)
  • 2020-12-25 15:35

    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)
    
    0 讨论(0)
提交回复
热议问题