-
Notifications
You must be signed in to change notification settings - Fork 24
/
ParameterizedQueriesSection.scala
138 lines (125 loc) · 4.69 KB
/
ParameterizedQueriesSection.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*
* scala-exercises - exercises-doobie
* Copyright (C) 2015-2016 47 Degrees, LLC. <http://www.47deg.com>
*/
package doobie
import doobie.DoobieUtils.CountryTable._
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))
}
}