Skip to content

Instantly share code, notes, and snippets.

@heyrutvik
Created December 6, 2020 06:34
Show Gist options
  • Save heyrutvik/f3dc526cf2e75b34350efe500355b427 to your computer and use it in GitHub Desktop.
Save heyrutvik/f3dc526cf2e75b34350efe500355b427 to your computer and use it in GitHub Desktop.
SQL (insert) query builder snippet using shapeless from Scala case classes
package example
import java.util.UUID
import shapeless.labelled.FieldType
import shapeless.ops.hlist.MapFolder
import shapeless.{HList, LabelledGeneric, Poly1, Witness}
sealed trait Table extends Product {
type Id
val id: Id
}
case class Person(id: UUID, name: String, age: Int) extends Table { type Id = UUID }
case class Order(id: Long, person: Person, itemCount: Int, total: Double) extends Table { type Id = Long }
trait QueryBuilder[T] {
def build(t: T): String
}
object QueryBuilder {
def apply[T](implicit ev: QueryBuilder[T]): QueryBuilder[T] = ev
def build[T](t: T)(implicit ev: QueryBuilder[T]): String = ev.build(t)
object TagToMap extends Poly1 {
implicit def all[K <: Symbol, V: ValueMapper](implicit
wit: Witness.Aux[K]
): Case.Aux[FieldType[K, V], Map[String, String]] =
at[FieldType[K, V]] { (s: V) =>
Map(wit.value.name -> ValueMapper[V].mapper(s))
}
}
implicit def all[T <: Product, R <: HList](implicit
lg: LabelledGeneric.Aux[T, R],
folder: MapFolder[R, Map[String, String], TagToMap.type]
): QueryBuilder[T] = (t: T) => {
val tableName = t.productPrefix.toLowerCase
val props = folder.apply(lg.to(t), Map.empty, _ ++ _)
helper(tableName, props)
}
private def helper(tableName: String, props: Map[String, String]): String =
s"INSERT INTO $tableName " +
props.keys.mkString("(", ", ", ") ") +
s"VALUES " +
props.values.mkString("(", ", ", ");")
}
trait ValueMapper[T] {
def mapper(t: T): String
}
object ValueMapper {
def apply[T](implicit ev: ValueMapper[T]): ValueMapper[T] = ev
def mapper[T](t: T)(implicit valueMapper: ValueMapper[T]): String = valueMapper.mapper(t)
class LowerPriority {
implicit def all[T <: Table](implicit ev: ValueMapper[T#Id]): ValueMapper[T] = (t: T) => ev.mapper(t.id)
}
object Implicits extends LowerPriority {
implicit val uuid: ValueMapper[UUID] = (v: UUID) => s"'${v.toString}'"
implicit val long: ValueMapper[Long] = (v: Long) => v.toString
implicit val string: ValueMapper[String] = (v: String) => s"'$v'"
implicit val int: ValueMapper[Int] = (v: Int) => v.toString
implicit val double: ValueMapper[Double] = (v: Double) => v.toString
}
}
object SQLBuilder extends App {
import ValueMapper.Implicits._
val person = Person(UUID.randomUUID(), "John Doe", 51)
val order = Order(1L, person, 10, 562.9)
List(
QueryBuilder[Person].build(
person
), // INSERT INTO person (id, name, age) VALUES ('0cb1f783-1263-4e99-895e-6bde521abcf3', 'John Doe', 51);
QueryBuilder[Order].build(
order
) // INSERT INTO order (id, person, itemCount, total) VALUES (1, '0cb1f783-1263-4e99-895e-6bde521abcf3', 10, 562.9);
).foreach(println)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment