Created
December 6, 2020 06:34
-
-
Save heyrutvik/f3dc526cf2e75b34350efe500355b427 to your computer and use it in GitHub Desktop.
SQL (insert) query builder snippet using shapeless from Scala case classes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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