Slick left/right/outer joins with Option

后端 未结 4 2210
一向
一向 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: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.

提交回复
热议问题