kaizen-solutions / virgil

A purely functional Cassandra client built on top of the Datastax Java Driver supporting a variety of effect systems like ZIO & Cats-Effect supporting both Scala 2 & 3
Mozilla Public License 2.0
39 stars 9 forks source link

Implement CQL.unbatched #16

Open calvinlfer opened 2 years ago

calvinlfer commented 2 years ago

Let's say you have a set of mutations in a single batch but you decide that you no longer want them in a batch:

val batchMutation = update1 + insert1 + update2 
val individualMutations: NonEmptyChunk[CQL[MutationResult]] = CQL.unbatched { batchMutation }

Currently, we track each individual mutation in a NonEmptyChunk but if we wanted to provide the user with each of the individual mutations in an API like so:

val batchMutation = update1 + insert1 + update2 
val u1 :*: i1 :*: u2 :*: Finish = CQL.unbatched { batchMutation }

Then we would need to use an HList and keep track of each insertion

calvinlfer commented 2 years ago

Here's a possible implementation that provides an API which tracks each individual element of the Mutation:

package io.kaizensolutions.virgil.internal

import io.kaizensolutions.virgil.{CQL, MutationResult}
import zio.NonEmptyChunk

import scala.annotation.tailrec

sealed trait MutationSet {
  type Concat[In <: MutationSet] <: MutationSet
  def :*:[In <: CQL[MutationResult]](in: In): MutationSet
  def ++[That <: MutationSet](that: That): Concat[That]
}
object MutationSet {
  type :*:[Head <: CQL[MutationResult], Tail <: MutationSet] = Cons[Head, Tail]

  type Finish = Finish.type
  case object Finish extends MutationSet { self =>
    override type Concat[In <: MutationSet] = In

    override def :*:[In <: CQL[MutationResult]](in: In): In :*: Finish = Cons(in, self)
    override def ++[That <: MutationSet](that: That): Concat[That]     = that
  }

  final case class Cons[
    Head <: CQL[MutationResult],
    Tail <: MutationSet
  ](head: Head, tail: Tail)
      extends MutationSet { self =>
    override type Concat[In <: MutationSet] = Head :*: tail.Concat[In]

    override def :*:[In <: CQL[MutationResult]](in: In): In :*: Head :*: Tail = Cons(in, self)
    override def ++[That <: MutationSet](that: That): Concat[That]            = Cons(head, tail ++ that)

    def toNonEmptyChunk: NonEmptyChunk[CQL[MutationResult]] = {
      @tailrec
      def go(curr: MutationSet, acc: NonEmptyChunk[CQL[MutationResult]]): NonEmptyChunk[CQL[MutationResult]] =
        curr match {
          case Cons(head, tail) => go(tail, acc :+ head)
          case Finish           => acc
        }

      go(self.tail, NonEmptyChunk.single(head))
    }
  }

  def :*:[Head <: CQL[MutationResult], Tail <: MutationSet](head: Head, tail: Tail): Cons[Head, Tail] =
    Cons(head, tail)

  object :*: {
    def unapply[Head <: CQL[MutationResult], Tail <: MutationSet](in: Cons[Head, Tail]): Option[(Head, Tail)] =
      Some((in.head, in.tail))
  }
}

object Example extends App {
  import MutationSet._
  import io.kaizensolutions.virgil.cql._

  val mutations =
    cql"INSERT INTO users (id, name) VALUES (1, 'John')".mutation :*:
      cql"INSERT INTO users (id, name) VALUES (2, 'Jane')".mutation :*:
      cql"INSERT INTO users (id, name) VALUES (3, 'Jack)".mutation :*:
      Finish

  case class NewBatch[M <: MutationSet](mutations: M) {
    def unbatch: M = mutations
  }
  val batch                             = NewBatch(mutations)
  val john :*: jane :*: jack :*: Finish = batch.unbatch
}

Note that if you tried to add an extra element (for example: val john :*: jane :*: jack :*: jake :*: Finish = batch.unbatch) then it would fail at compile time.

image

You can also concatenate batches together and it will keep track of individual elements:

val john1 :*: jane1 :*: jack1 :*: john2 :*: jane2 :*: jack2 :*: Finish = batch.unbatch ++ batch.unbatch

This provides a richer API to the user so they can access their individual mutations or simply call toNonEmptyChunk if they don't want to individually address each element

calvinlfer commented 2 years ago

Some additional work would also need to be done to CQL so we can distinguish a batch mutation from a single one