Skip to content

Commit

Permalink
Merge pull request #10 from scala-exercises/paco-parameterized-querie…
Browse files Browse the repository at this point in the history
…s-section

Adds Parameterized queries section
  • Loading branch information
raulraja authored Jul 19, 2016
2 parents d4d5aa1 + f64f95c commit 4f7d7a1
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 5 deletions.
3 changes: 2 additions & 1 deletion src/main/scala/doobie/DoobieLibrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ object DoobieLibrary extends Library {
override def sections: List[Section] = List(
ConnectingToDatabaseSection,
SelectingDataSection,
MultiColumnQueriesSection
MultiColumnQueriesSection,
ParameterizedQueriesSection
)
}
4 changes: 2 additions & 2 deletions src/main/scala/doobie/MultiColumnQueriesSection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ object MultiColumnQueriesSection extends FlatSpec with Matchers with Section {

hlist.head should be(res0)
}

/*
/**
* And with a shapeless record:
*/
Expand All @@ -80,7 +80,7 @@ object MultiColumnQueriesSection extends FlatSpec with Matchers with Section {
record('pop) should be(res0)
}

*/
/**
* And again, mapping rows to a case class.
*
Expand Down
136 changes: 136 additions & 0 deletions src/main/scala/doobie/ParameterizedQueriesSection.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package doobie

import doobie.DoobieUtils._
import doobie.Model._
import doobie.ParameterizedQueryHelpers._
import doobie.imports._
import org.scalaexercises.definitions.Section
import org.scalatest.{FlatSpec, Matchers}

import scalaz.NonEmptyList

/**
* Previously we have worked with static SQL queries where the values used to filter data were
* hard-coded and didn't change.
*
* {{{
* select code, name, population, gnp from country where code = "GBR"
* }}}
*
* In this section, we'll learn how to construct parameterized queries.
*
* We’re still playing with the country table, shown here for reference.
* {{{
* code name population gnp
* "DEU" "Germany" 82164700 2133367.00
* "ESP" "Spain" 39441700 null
* "FRA" "France", 59225700 1424285.00
* "GBR" "United Kingdom" 59623400 1378330.00
* "USA" "United States of America" 278357000 8510700.00
* }}}
*
* @param name parameterized_queries
*/
object ParameterizedQueriesSection extends FlatSpec with Matchers with Section {

/**
* == Adding a Parameter ==
*
* Let’s factor our query into a method and add a parameter that selects only the countries with
* a population larger than some value the user will provide. We insert the minPop argument into
* our SQL statement as $minPop, just as if we were doing string interpolation.
* {{{
* def biggerThan(minPop: Int) =
* sql"""
* select code, name, population, gnp
* from country
* where population > $minPop
* order by population asc
* """.query[Country]
* }}}
*/
def addingAParameter(res0: String, res1: String) = {

val countriesName = biggerThan(75000000)
.list
.transact(xa)
.run
.map(_.name)

countriesName should be(List(res0, res1))
}

/**
* So what’s going on? It looks like we’re just dropping a string literal into our SQL string,
* but actually we’re constructing a proper parameterized PreparedStatement, and the minProp
* value is ultimately set via a call to setInt
*
* '''doobie''' allows you to interpolate values of any type with a `Atom` instance, which
* includes:
* - any JVM type that has a target mapping defined by the JDBC specification,
* - vendor-specific types defined by extension packages,
* - custom column types that you define, and
* - single-member products (case classes, typically) of any of the above.
*
* == Multiple Parameters ==
*
* Multiple parameters work the same way.
* {{{
* def populationIn(range: Range) =
* sql"""
* select code, name, population, gnp
* from country
* where population > ${range.min} and population < ${range.max}
* order by population asc
* """.query[Country]
* }}}
*/
def addingMultipleParameters(res0: String, res1: String, res2: String) = {

val countriesName = populationIn(25000000 to 75000000)
.list
.transact(xa)
.run
.map(_.name)

countriesName should be(List(res0, res1, res2))
}

/**
* == Dealing with IN Clauses ==
*
* A common irritant when dealing with SQL literals is the desire to inline a sequence of
* arguments into an IN clause, but SQL does not support this notion (nor does JDBC do anything
* to assist). So as of version 0.2.3 doobie provides support in the form of some slightly
* inconvenient machinery.
* {{{
* def populationIn(range: Range, codes: NonEmptyList[String]) = {
* implicit val codesParam = Param.many(codes)
* sql"""
* select code, name, population, gnp
* from country
* where population > ${range.min}
* and population < ${range.max}
* and code in (${codes : codes.type})
* """.query[Country]
* }
* }}}
*
* There are a few things to notice here:
* - The `IN` clause must be non-empty, so `codes` is a `NonEmptyList`.
* - We must derive a `Param` instance for the singleton type of `codes`, which we do via
* `Param.many`. This derivation is legal for any `F[A]` given `Foldable1[F]` and `Atom[A]`. You
* can have any number of `IN` arguments but each must have its own derived `Param` instance.
* - When interpolating `codes` we must explicitly ascribe its singleton type `codes.type`.
*/
def dealingWithInClause(res0: String, res1: String) = {

val countriesName = populationIn(25000000 to 75000000, NonEmptyList("ESP","USA","FRA"))
.list
.transact(xa)
.run
.map(_.name)

countriesName should be(List(res0, res1))
}
}
22 changes: 22 additions & 0 deletions src/main/scala/doobie/ParameterizedQueryHelpers.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package doobie

import doobie.Model.Country
import doobie.imports._
import scalaz.NonEmptyList

object ParameterizedQueryHelpers {

def biggerThan(minPop: Int) =
sql"select code, name, population, gnp from country where population > $minPop order by population asc"
.query[Country]

def populationIn(range: Range) =
sql"select code, name, population, gnp from country where population > ${range.min} and population < ${range.max} order by population asc"
.query[Country]

def populationIn(range: Range, codes: NonEmptyList[String]) = {
implicit val codesParam = Param.many(codes)
sql"select code, name, population, gnp from country where population > ${range.min} and population < ${range.max} and code in (${codes: codes.type}) order by population asc"
.query[Country]
}
}
4 changes: 2 additions & 2 deletions src/test/scala/doobie/MultiColumnQueriesSectionSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class MultiColumnQueriesSectionSpec extends Spec with Checkers {
)
)
}

/*
def `select multiple columns using Record` = {
check(
Test.testSuccess(
Expand All @@ -36,7 +36,7 @@ class MultiColumnQueriesSectionSpec extends Spec with Checkers {
)
)
}

*/
def `select multiple columns using case class` = {
check(
Test.testSuccess(
Expand Down
38 changes: 38 additions & 0 deletions src/test/scala/doobie/ParameterizedQueriesSectionSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package doobie

import org.scalacheck.Shapeless._
import org.scalaexercises.Test
import org.scalatest.Spec
import org.scalatest.prop.Checkers
import shapeless.HNil

class ParameterizedQueriesSectionSpec extends Spec with Checkers {


def `adding a parameter` = {
check(
Test.testSuccess(
ParameterizedQueriesSection.addingAParameter _,
"Germany" :: "United States of America" :: HNil
)
)
}

def `adding multiple parameters` = {
check(
Test.testSuccess(
ParameterizedQueriesSection.addingMultipleParameters _,
"Spain" :: "France" :: "United Kingdom" :: HNil
)
)
}

def `dealing with IN clause` = {
check(
Test.testSuccess(
ParameterizedQueriesSection.dealingWithInClause _,
"Spain" :: "France" :: HNil
)
)
}
}

0 comments on commit 4f7d7a1

Please # to comment.