-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from scala-exercises/paco-parameterized-querie…
…s-section Adds Parameterized queries section
- Loading branch information
Showing
6 changed files
with
202 additions
and
5 deletions.
There are no files selected for viewing
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
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
136 changes: 136 additions & 0 deletions
136
src/main/scala/doobie/ParameterizedQueriesSection.scala
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
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)) | ||
} | ||
} |
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
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] | ||
} | ||
} |
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
38 changes: 38 additions & 0 deletions
38
src/test/scala/doobie/ParameterizedQueriesSectionSpec.scala
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
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 | ||
) | ||
) | ||
} | ||
} |