Open calvinlfer opened 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.
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
Some additional work would also need to be done to CQL
so we can distinguish a batch mutation from a single one
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:
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:
Then we would need to use an HList and keep track of each insertion