Skip to content

Commit

Permalink
param typeclass, in-clause support
Browse files Browse the repository at this point in the history
  • Loading branch information
tpolecat committed Sep 7, 2015
1 parent c8a273c commit 80d08d2
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 5 deletions.
58 changes: 54 additions & 4 deletions core/src/main/scala/doobie/syntax/string.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,60 @@ import doobie.syntax.process._

import doobie.hi._

import scalaz.Monad
import scalaz.syntax.monad._
import scalaz._, Scalaz._
import scalaz.stream.Process

import shapeless._

/** Module defining the `sql` string interpolator. */
object string {

/**
* Typeclass for an `Atom` or singleton `NonEmptyList` of some atomic type.
*/
sealed trait Param[A] {
val composite: Composite[A]
val placeholders: List[Int]
}

object Param {

implicit def fromAtom[A](implicit ev: Atom[A]): Param[A] =
new Param[A] {
val composite = Composite.fromAtom(ev)
val placeholders = List(1)
}

implicit val ParamHNil: Param[HNil] =
new Param[HNil] {
val composite = Composite.typeClass.emptyProduct
val placeholders = Nil
}

implicit def ParamHList[H, T <: HList](implicit ph: Param[H], pt: Param[T]) =
new Param[H :: T] {
val composite = Composite.typeClass.product[H,T](ph.composite, pt.composite)
val placeholders = ph.placeholders ++ pt.placeholders
}

def many[A](t: NonEmptyList[A])(implicit ev: Atom[A]): Param[t.type] =
new Param[t.type] {
val composite = new Composite[t.type] {
val length = t.size
val set = (n: Int, in: t.type) =>
t.foldLeft((n, ().point[PreparedStatementIO])) { case ((n, psio), a) =>
(n + 1, psio *> ev.set(n, a))
} ._2
val meta = List.fill(length)(ev.meta)
val unsafeGet = (_: java.sql.ResultSet, _: Int) => fail
val update = (_: Int, _: t.type) => fail
def fail = sys.error("singleton `IN` composite does not support get or update")
}
val placeholders = List(t.size)
}

}

/**
* String interpolator for SQL literals. An expression of the form `sql".. $a ... $b ..."` with
* interpolated values of type `A` and `B` (which must have `[[doobie.util.atom.Atom Atom]]`
Expand All @@ -29,15 +74,20 @@ object string {
Thread.currentThread.getStackTrace.lift(3)
}

def placeholders(n: Int): String =
List.fill(n)("?").mkString(", ")

/**
* Arity-abstracted method accepting a sequence of values along with `[[doobie.util.atom.Atom Atom]]`
* witnesses, yielding a `[[Builder]]``[...]` parameterized over the product of the types of the
* passed arguments. This method uses the `ProductArgs` macro from Shapeless and has no
* meaningful internal structure.
*/
object sql extends ProductArgs {
def applyProduct[A: Composite](a: A): Builder[A] =
new Builder(a, sc.parts.mkString("?"), stackFrame)
def applyProduct[A <: HList](a: A)(implicit ev: Param[A]) = {
val sql = sc.parts.toList.fzipWith(ev.placeholders.map(placeholders) ++ List(""))(_ + _).suml
new Builder(a, sql, stackFrame)(ev.composite)
}
}

}
Expand Down
2 changes: 1 addition & 1 deletion doc/src/main/tut/10-Custom-Mappings.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Composite[PersonId].length

However if we try to use this type for a *single* column value (i.e., as a query parameter, which requires an `Atom` instance), it doesn't compile.

```tut:nofail
```tut:fail
sql"select * from person where id = $pid"
```

Expand Down

0 comments on commit 80d08d2

Please sign in to comment.