├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── LICENSE
├── README.md
├── build.sbt
├── project
├── build.properties
└── plugins.sbt
├── renovate.json
├── src
├── main
│ └── scala
│ │ └── com
│ │ └── github
│ │ └── tarao
│ │ └── slickjdbc
│ │ ├── getresult
│ │ └── GetResult.scala
│ │ ├── interpolation
│ │ ├── Interpolation.scala
│ │ ├── MacroTreeBuilder.scala
│ │ ├── Placeholder.scala
│ │ ├── SetParameter.scala
│ │ └── ToPlaceholder.scala
│ │ └── query
│ │ └── Translator.scala
└── test
│ ├── resources
│ └── application.conf
│ └── scala
│ └── com
│ └── github
│ └── tarao
│ └── slickjdbc
│ ├── IntegrationSpec.scala
│ ├── getresult
│ ├── GetResultSpec.scala
│ └── TypeBinderSpec.scala
│ ├── helper
│ ├── TestDB.scala
│ ├── TraitSingletonBehavior.scala
│ └── UnitSpec.scala
│ ├── interpolation
│ ├── InterpolationSpec.scala
│ ├── LiteralSpec.scala
│ └── PlaceholderSpec.scala
│ └── query
│ └── TranslatorSpec.scala
└── version.sbt
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | pull_request:
4 | push:
5 | branches: [ "main" ]
6 | jobs:
7 | test:
8 | strategy:
9 | fail-fast: false
10 | matrix:
11 | include:
12 | - java: 8
13 | - java: 11
14 | - java: 17
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v2
19 | - name: Setup JDK
20 | uses: actions/setup-java@v3
21 | with:
22 | distribution: temurin
23 | java-version: ${{ matrix.java }}
24 | cache: 'sbt'
25 | - name: Build and Test
26 | run: sbt +test
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.log
3 |
4 | # sbt specific
5 | .cache
6 | .history
7 | .lib/
8 | dist/*
9 | target/
10 | lib_managed/
11 | src_managed/
12 | project/boot/
13 | project/plugins/project/
14 |
15 | .bloop
16 | .bsp
17 | .metals
18 | metals.sbt
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 INA Lintaro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | slick-jdbc-extension [![CI][ci-img]][ci] [![Maven Central][maven-img]][maven]
2 | ====================
3 |
4 | An extension to `slick.jdbc`, [Slick][slick]'s plain SQL queries,
5 | including the following features.
6 |
7 | - [More types in SQL interpolation](#setparameter)
8 | - Literal parameters
9 | - Non-empty lists
10 | - Case classes (products)
11 | - [Extensible raw query translator](#translator)
12 | - Embedding caller information as a comment
13 | - Stripping margin
14 | - [Result mapper by column name](#getresult)
15 |
16 | These features can be selectively enabled.
17 |
18 | ## Getting started
19 |
20 | Add dependency in your `build.sbt` as the following.
21 |
22 | ```scala
23 | libraryDependencies ++= Seq(
24 | ...
25 | "com.github.tarao" %% "slick-jdbc-extension" % "0.1.0"
26 | )
27 | ```
28 |
29 | The library is available on [Maven Central][maven]. Currently,
30 | supported Scala versions are 2.12, and 2.13.
31 |
32 | ## Overview
33 |
34 | [Slick][slick] supports plain SQL queries but it lacks some essential
35 | features. For example, there is no way to bind a list parameter to a
36 | prepared statement.
37 |
38 | This extension provides additional features to Slick-style plain SQL
39 | queries. The extension consists of a family of traits and each of
40 | them can be enabled selectively. The easiest way to use the extension
41 | is to define a class with the traits.
42 |
43 | Let's see an example of the usage of the extension. To start with, we
44 | assume to have an abstract class to run a DB query something like
45 | this.
46 |
47 | ```scala
48 | abstract class DBHandler {
49 | import scala.concurrent.{Future, Await}
50 | import scala.concurrent.duration.Duration
51 | import slick.dbio.{DBIOAction, NoStream, Effect}
52 | import slick.backend.DatabaseComponent
53 |
54 | type Database = DatabaseComponent#DatabaseDef
55 |
56 | def run[R](a: DBIOAction[R, NoStream, Nothing])(implicit db: Database): R =
57 | Await.result(db.run(a), Duration.Inf)
58 | }
59 | ```
60 |
61 | Then a repository class to use plain SQL queries with the extension will look like this. (We are not interested in how the concrete DB object is supplied.)
62 |
63 | ```scala
64 | import com.github.tarao.slickjdbc._
65 | import interpolation.{SQLInterpolation, CompoundParameter, TableName}
66 | import getresult.{GetResult, AutoUnwrapOption}
67 |
68 | class SampleRepository extends DBHandler
69 | with SQLInterpolation with CompoundParameter
70 | with GetResult with AutoUnwrapOption {
71 | implicit def db: Database = ???
72 |
73 | ...
74 | }
75 | ```
76 |
77 | `SQLInterpolation`, `CompoundParameter`, `GetResult` and
78 | `AutoUnwrapOption` are feature selectors of the extension.
79 |
80 | For the rest of the example, we use `Entry` model class defined as the
81 | following.
82 |
83 | ```scala
84 | case class Entry(id: Long, url: String)
85 | ```
86 |
87 | To define a mapping from a column name to a field of the model in the
88 | repository class, use `getResult` method defined in `GetResult` trait.
89 |
90 | ```scala
91 | implicit val getEntryResult = getResult { Entry(
92 | column("entry_id"),
93 | column("url")
94 | ) }
95 | ```
96 |
97 | This will map `entry_id` column of a query result to the first field
98 | of `Entry` and `url` column to the second. This is very similar to
99 | Slick's `GetResult.apply`. The type of the column is inferred by the
100 | field type of `Entry.id` (it is `Long` in this case). Note that this
101 | example also uses `AutoUnwrapOption` to convert from `Option[Long]` to
102 | `Long`. The default column type is `Option[_]`ed since the resulting
103 | column value may be `NULL`.
104 |
105 | Let's see an example of SQL interpolation. In addition to Slick's SQL
106 | interpolation, we can specify a structured value directly as a
107 | parameter.
108 |
109 | ```scala
110 | val table = TableName("entry")
111 |
112 | def add(entry: Entry) =
113 | run { sqlu"""
114 | | INSERT INTO ${table} (entry_id, url)
115 | | VALUES ${entry}
116 | """
117 | }
118 | ```
119 |
120 | This actually illustrates three things.
121 |
122 | - You can specify a table name by a value of `TableName`. This value
123 | will be embedded literally in the raw query.
124 | - A value of case class (`entry` in this case) is expanded to
125 | placeholders like `(?, ?)` and the fields of the value are bound
126 | to them.
127 | - Characters before `|` is stripped in the raw query.
128 |
129 | You can also specify a list as a parameter. But in this case, you
130 | must say that it is not empty.
131 |
132 | ```scala
133 | import eu.timepit.refined.api.Refined
134 | import eu.timepit.refined.collection.NonEmpty
135 |
136 | def findAll(entryIds: Seq[Long] Refined NonEmpty): Seq[Entry] = run {
137 | sql"""
138 | | SELECT * FROM ${table}
139 | | WHERE entry_id IN $ids
140 | """.as[Entry]
141 | }
142 | ```
143 |
144 | `$ids` must be refined by `NonEmpty` and it expands to `(?, ?, ...,
145 | ?)`. You can use `refineV` to refine values.
146 |
147 | ```scala
148 | import eu.timepit.refined.collection.NonEmpty
149 | import eu.timepit.refined.refineV
150 |
151 | val idsOrError = refineV[NonEmpty](Seq(101L, 102L, 103L))
152 | val entries = ids match {
153 | case Right(ids) => repository.findAll(ids)
154 | case Left(_) => sys.error("Never happen in this case")
155 | }
156 | ```
157 |
158 | See the documentation of [refined][] for the detail about refined
159 | types.
160 |
161 | If you were using [nonempty][] from older versions, it still works as
162 | expected because `nonempty.NonEmpty` is a kind of refined collection
163 | type.
164 |
165 | ```scala
166 | import com.github.tarao.nonempty.NonEmpty
167 |
168 | def findAll(entryIds: Option[NonEmpty[Long]]): Seq[Entry] = entryIds match {
169 | case Some(ids) => run {
170 | sql"""
171 | | SELECT * FROM ${table}
172 | | WHERE entry_id IN $ids
173 | """.as[Entry]
174 | }
175 | case None => Seq.empty
176 | }
177 | ```
178 |
179 | In this case, you call `findAll` method like the following since there is an implicit conversion from `Seq[Long]` to `Option[NonEmpty[Long]]`.
180 |
181 | ```scala
182 | repository.findAll(Seq(101L, 102L, 103L))
183 | ```
184 |
185 | See the documentation of [nonempty][] for the detail.
186 |
187 | ## Additional types in SQL interpolation
188 |
189 | To use the extended SQL interpolation, you have to first enable it.
190 | To enable the interpolation inside a particular trait or class, add
191 | `SQLInterpolation` trait to the trait or class.
192 |
193 | ```scala
194 | import com.github.tarao.slickjdbc._
195 | class YourRepositoryClass extends interpolation.SQLInterpolation
196 | ```
197 |
198 | If you want to explicitly `import` the feature, then `import` methods
199 | in `SQLInterpolation` object.
200 |
201 | ```scala
202 | import com.github.tarao.slickjdbc._
203 | import interpolation.SQLInterpolation._
204 | ```
205 |
206 | If you are `import`ing a Slick driver API, doing that by `import
207 | slick.driver.SomeDriver.api._` causes a conflict with the extended SQL
208 | interpolation since the API also defines a SQL interpolation of Slick.
209 | In this case, `import` only desired features to avoid the conflict.
210 | For example, if you are using `Database` from the API, import it as
211 | `import slick.driver.SomeDriver.api.Database`.
212 |
213 | ### Literal parameters
214 |
215 | When a value with `Literal` is passed as a parameter of the
216 | interpolation, a string returned by `toString` on the value is
217 | embedded literally in the raw query as if it was embedded by `#${}`.
218 | There are two predefined `Literal`s.
219 |
220 | - `SimpleString`
221 | - takes a string as a parameter of the constructor and embed that string.
222 | - `TableName`
223 | - takes a name as a parameter of the constructor and embed that name.
224 |
225 | Note that a `Literal` value is **not** expanded to a placeholder `?`.
226 | If the value needs to be escaped as a part of a SQL string, then you
227 | should do it by yourself.
228 |
229 | ### Non-empty lists
230 |
231 | This feature requires `interpolation.ComboundParameter` trait or
232 | `import interpolation.ComboundParameter._`.
233 |
234 | When a non-empty list is passed as a parameter of the interpolation,
235 | the value expands to `?`s in the prepared statement, and items in the
236 | value are bound to the statement.
237 |
238 | For example, if you have `val Right(ne) = refineV[NonEmpty](Seq(1, 2,
239 | 3))` and embed it as `sql"... ($ne) ..."`, the raw query will be
240 | `... (?, ?, ?) ...` and `1`, `2`, `3` are bound to the placeholders.
241 | The resulting raw query will be the same if you omit parentheses like
242 | `sql"... $ne ..."`.
243 |
244 | If you pass a non-empty list of tuples, for example pairs, it expands
245 | to `(?, ?), (?, ?), ..., (?, ?)`. This is useful to `INSERT` multiple
246 | values by `INSERT INTO table (col1, col2) VALUES $aListOfPairs`.
247 |
248 | ### Case classes (products)
249 |
250 | This feature requires `interpolation.ComboundParameter` trait or
251 | `import interpolation.ComboundParameter._`.
252 |
253 | When a product especially an instance of a case class is passed as a
254 | parameter of the interpolation, the value expands to `?`s in the
255 | prepared statement, and fields in the value are bound to the
256 | statement.
257 |
258 | For example, if you have `val p = SomeCaseClass(1, "foo")` and embed
259 | it as `sql"... ($p) ..."`, the raw query will be `... (?, ?) ...` and
260 | `1`, `"foo"` are bound to the placeholders. The resulting raw query
261 | will be the same if you omit parentheses like `sql"... $p ..."`.
262 |
263 | If you have another product value in a field of the passed value, it
264 | also be expanded and the placeholders are flattened.
265 |
266 | You can also put a product value in a non-empty list and it has the
267 | same effect as putting tuples **except when the arity of the product
268 | is 1**. When the product arity is exactly 1, the value is treated as
269 | if it is a primitive value. In other words, a value of `Seq[[Single]
270 | Refined NonEmpty` (where `Single` is a `Product` of arity 1) expands
271 | to `(?, ?, ..., ?)` not to `(?), (?), ..., (?)`. In contrast, a value
272 | of `Seq[Tuple1[_]] Refined NonEmpty` expands to `(?), (?), ..., (?)`
273 | not to `(?, ?, ..., ?)`.
274 |
275 | Note that passing `Option[_]` as an interpolation parameter is
276 | statically rejected and passing a product of arity zero (this can be
277 | made by for example `case class Empty()`) causes
278 | `java.sql.SQLException` at runtime.
279 |
280 | ## Raw query translator
281 |
282 | To use this feature, you have to enable the extended SQL
283 | interpolation. Follow the instruction [above](#setparameter) to
284 | enable it.
285 |
286 | The query translator translates a raw query generated by the
287 | interpolation. A translation is simply from string to string and you
288 | can stack multiple translators.
289 |
290 | ### The default translators
291 |
292 | - `MarginStripper`
293 | - Strip characters before `|` from each line of a query string.
294 | This makes a query log a bit more readable.
295 | - `CallerCommenter`
296 | - Record the code position that the interpolation is invoked as a
297 | comment of SQL query. You can trace a line number, a file name
298 | and a method name in a class from an ordinary query log on the
299 | DB server.
300 |
301 | ### Custom translators
302 |
303 | You can use a custom translator stack by defining an implicit value of
304 | `Iterable[query.Translator]`. For example, if you want to add a
305 | translator after the default translators, define an implicit value in
306 | a static scope where you invoke the SQL interpolation.
307 |
308 | ```scala
309 | import com.github.tarao.slickjdbc._
310 |
311 | implicit val translators: Iterable[query.Translator] = Seq(
312 | query.MarginStripper,
313 | query.CallerCommenter,
314 | new query.Translator {
315 | def apply(query: String, context: query.Context) =
316 | ??? /* return a translated string */
317 | }
318 | )
319 | ```
320 |
321 | ## Result mapper by column name
322 |
323 | This feature requires `getresult.GetResult` trait or object.
324 |
325 | With the trait, you can use `getResult` method and can use `column`
326 | method in the block of `getResult` without a receiver like in the
327 | example in [Overview](#overview) section.
328 |
329 | If you prefer the object, you just need to `import` it.
330 |
331 | ```scala
332 | import com.github.tarao.slickjdbc._
333 | import getresult.GetResult
334 | ```
335 |
336 | The usage of `GetResult` is quite similar to that of Slick's
337 | `GetResult`. You will receive an object to extract a result value in
338 | a block passed to `GetResult.apply`.
339 |
340 | ```scala
341 | implicit val getEntryResult = GetResult { r => Entry(
342 | r.column("entry_id"),
343 | r.column("url")
344 | ) }
345 | ```
346 |
347 | It also requires `getresult.AutoUnwrapOption` to extract a column
348 | value of non-`Option[_]` type. This also available both as a trait
349 | and an object.
350 |
351 | You can use this feature without the extended SQL interpolation.
352 |
353 | ### Mapping columns by name
354 |
355 | As you have already seen, a column value can be extracted by `column`
356 | method. If you want to explicitly specify a return type or the return
357 | type somehow cannot be inferred, you can pass a type parameter to
358 | `column` to specify a return type.
359 |
360 | ```scala
361 | implicit val getEntryResult = getResult { Entry(
362 | column[Long]("entry_id"),
363 | column[String]("url")
364 | ) }
365 | ```
366 |
367 | ### Mapping columns by position
368 |
369 | You can also extract the result by the index of a column.
370 |
371 | ```scala
372 | implicit val getEntryResult = getResult { Entry(
373 | column(1),
374 | column(2)
375 | ) }
376 | ```
377 |
378 | If you prefer the way of `<<` method in Slick's `GetResult`, it is
379 | also available in our `GetResult`.
380 |
381 | ```scala
382 | implicit val getEntryResult = getResult { Entry(<<, <<) }
383 | ```
384 |
385 | `<` and `skip` are also available.
386 |
387 | ### `Option[_]` values
388 |
389 | `getresult.AutoUnwrapOption` makes it possible to get non-`Option[_]`
390 | value directly. Note that this may cause `NoSuchElementException`
391 | thrown if the value in the DB record is `NULL`.
392 |
393 | If you want to specify a default value instead of throwing an
394 | exception, then retrieve the result as an `Option[_]` and give a
395 | default value.
396 |
397 | ```scala
398 | implicit val getEntryResult = getResult { Entry(
399 | column[Option[Long]]("entry_id").getOrElse(0),
400 | column[Option[String]]("url").getOrElse("http://example.com/default/page")
401 | ) }
402 | ```
403 |
404 | ### User-defined type binders
405 |
406 | The way of extracting a typed value from a raw query result is
407 | different from that of Slick's `GetResult`. We provide a
408 | `TypeBinder[_]` which is similar to `TypeBinder[_]` in
409 | [ScalikeJDBC][scalikejdbc] to specify extractor methods for both
410 | indexed and named results.
411 |
412 | Let's take `url` field of `Entry` as an example. Assume that you want
413 | to use your custom case class `URL` as a `url` field instead of
414 | `String`.
415 |
416 | ```scala
417 | case class URL(url: String)
418 | case class Entry(id: Long, url: URL)
419 | ```
420 |
421 | Then you need to define a custom `TypeBinder[_]` for `URL`. A good
422 | way to define a custom type binder for a user-defined type is to
423 | extend a type binder for a primitive type.
424 |
425 |
426 | ```scala
427 | implicit def urlBinder(implicit
428 | binder: TypeBinder[Option[String]]
429 | ): TypeBinder[Option[URL]] = binder.map(_.map(URL(_)))
430 | ```
431 |
432 | This binder uses a `TypeBinder[Option[String]]` as a parent binder,
433 | which is defined by default, and convert the string retrieved by the
434 | parent to a `URL`. (The first `map` is to convert retrieved value,
435 | and the second one is to take care of the `Option[_]` type.)
436 |
437 | Note that you should define a `TypeBinder[_]` for `Option[URL]` not
438 | for `URL` since unwrapping `Option[_]` will be done by general
439 | unwrapping conversion by `AutoUnwrapOption`.
440 |
441 | With this binder, you can define a result mapper for `Entry`.
442 |
443 | ```scala
444 | implicit val getEntryResult = getResult { Entry(
445 | column("entry_id"),
446 | column("url")
447 | ) }
448 | ```
449 |
450 | Notice that there is no difference in the code compared to the one
451 | defined before for `case class Entry(id: Long, url: String)` but the
452 | (inferred) return type of `column("url")` is now `URL`.
453 |
454 | ## License
455 |
456 | - Copyright (C) INA Lintaro
457 | - MIT License
458 |
459 | [slick]: http://slick.typesafe.com/
460 | [refined]: https://github.com/fthomas/refined
461 | [nonempty]: https://github.com/tarao/nonempty-scala
462 | [scalikejdbc]: http://scalikejdbc.org/
463 |
464 | [ci]: https://github.com/tarao/slick-jdbc-extension-scala/actions/workflows/ci.yaml
465 | [ci-img]: https://github.com/tarao/slick-jdbc-extension-scala/actions/workflows/ci.yaml/badge.svg
466 | [maven]: https://maven-badges.herokuapp.com/maven-central/com.github.tarao/slick-jdbc-extension_2.13
467 | [maven-img]: https://maven-badges.herokuapp.com/maven-central/com.github.tarao/slick-jdbc-extension_2.13/badge.svg
468 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | import sbt._
2 | import Keys._
3 |
4 | lazy val slickjdbcextension = (project in file(".")).
5 | settings(
6 | name := "slick-jdbc-extension",
7 | organization := "com.github.tarao",
8 | scalaVersion := "2.13.12",
9 | crossScalaVersions := Seq("2.12.18", "2.13.12"),
10 |
11 | // Depenency
12 | libraryDependencies ++= Seq(
13 | "org.scala-lang" % "scala-reflect" % scalaVersion.value,
14 | "com.typesafe.slick" %% "slick" % "3.5.0-M4",
15 | "eu.timepit" %% "refined" % "0.11.0",
16 | "org.scalatest" %% "scalatest" % "3.2.17" % "test",
17 | "org.scalamock" %% "scalamock" % "5.0.0" % "test",
18 | "com.h2database" % "h2" % "1.4.200" % "test"
19 | ),
20 |
21 | // Compilation
22 | scalacOptions ++= Seq(
23 | "-unchecked",
24 | "-deprecation",
25 | "-feature"
26 | ),
27 |
28 | // Documentation
29 | Compile / doc / scalacOptions ++= Nil :::
30 | "-groups" ::
31 | "-sourcepath" ::
32 | baseDirectory.value.getAbsolutePath ::
33 | "-doc-source-url" ::
34 | "https://github.com/tarao/slick-jdbc-extension-scala/tree/master€{FILE_PATH}.scala" ::
35 | Nil,
36 |
37 | // Publishing
38 | releasePublishArtifactsAction := PgpKeys.publishSigned.value,
39 | releaseCrossBuild := true,
40 | publishMavenStyle := true,
41 | publishTo := {
42 | val nexus = "https://oss.sonatype.org/"
43 | if (isSnapshot.value)
44 | Some("snapshots" at nexus + "content/repositories/snapshots")
45 | else
46 | Some("releases" at nexus + "service/local/staging/deploy/maven2")
47 | },
48 | Test / publishArtifact := false,
49 | pomIncludeRepository := { _ => false },
50 | pomExtra := (
51 | https://github.com/tarao/slick-jdbc-extension-scala
52 |
53 |
54 | MIT License
55 | http://www.opensource.org/licenses/mit-license.php
56 | repo
57 |
58 |
59 |
60 | git@github.com:tarao/slick-jdbc-extension-scala.git
61 | scm:git:git@github.com:tarao/slick-jdbc-extension-scala.git
62 |
63 |
64 |
65 | tarao
66 | INA Lintaro
67 | https://github.com/tarao/
68 |
69 | )
70 | )
71 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.9.6
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
2 |
3 | addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0")
4 |
5 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21")
6 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/tarao/slickjdbc/getresult/GetResult.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package getresult
4 |
5 | import scala.util.DynamicVariable
6 | import scala.annotation.implicitNotFound
7 | import slick.jdbc.{GetResult => GR, PositionedResult}
8 | import java.sql.{ResultSet}
9 |
10 | trait GetResult {
11 | private val positionedResult = new DynamicVariable[PositionedResult](null)
12 | private[getresult] def positionedResultValue =
13 | Option(positionedResult.value) getOrElse {
14 | throw new RuntimeException("Column access must be in getResult method")
15 | }
16 |
17 | def getResult[T](block: => T): GR[T] =
18 | GR[T] { r => positionedResult.withValue(r) { block } }
19 |
20 | def column[T](index: Int)(implicit
21 | check: CheckGetter[T],
22 | binder: TypeBinder[T]
23 | ): T = binder(positionedResultValue.rs, index)
24 | def column[T](field: String)(implicit
25 | check: CheckGetter[T],
26 | binder: TypeBinder[T]
27 | ): T = binder(positionedResultValue.rs, field)
28 | def skip = { positionedResultValue.skip; this }
29 | def <<[T](implicit
30 | check: CheckGetter[T],
31 | binder: TypeBinder[T]
32 | ): T = column[T](positionedResultValue.skip.currentPos)
33 | def <[T](implicit
34 | check: CheckGetter[Option[T]],
35 | binder: TypeBinder[Option[T]]
36 | ): Option[T] = <<[Option[T]]
37 | }
38 | object GetResult {
39 | def apply[T](block: GetResult => T): GR[T] =
40 | GR[T] { r => block(new GetResult {
41 | override def positionedResultValue = r
42 | }) }
43 | }
44 |
45 | // $COVERAGE-OFF$
46 |
47 | @implicitNotFound(msg = "No conversion rule for type ${T}\n" +
48 | "[NOTE] You need an implicit of getresult.TypeBinder[${T}] to convert the result.")
49 | sealed trait CheckGetter[+T]
50 | object CheckGetter {
51 | implicit def valid[T](implicit binder: TypeBinder[T]): CheckGetter[T] =
52 | new CheckGetter[T] {}
53 | }
54 |
55 | // $COVERAGE-ON$
56 |
57 | trait TypeBinder[+T] {
58 | def apply(rs: ResultSet, index: Int): T
59 | def apply(rs: ResultSet, field: String): T
60 | def map[S](f: T => S): TypeBinder[S] = new TypeBinder[S] {
61 | def apply(rs: ResultSet, index: Int): S =
62 | f(TypeBinder.this.apply(rs, index))
63 | def apply(rs: ResultSet, field: String): S =
64 | f(TypeBinder.this.apply(rs, field))
65 | }
66 | }
67 |
68 | object TypeBinder {
69 | type Get[X, R] = (ResultSet, X) => R
70 | def apply[T](byIndex: Get[Int, T])(byField: Get[String, T]): TypeBinder[T] =
71 | new TypeBinder[T] {
72 | def apply(rs: ResultSet, index: Int): T = byIndex(rs, index)
73 | def apply(rs: ResultSet, field: String): T = byField(rs, field)
74 | }
75 |
76 | val any: TypeBinder[Any] =
77 | TypeBinder[Any](_ getObject _)(_ getObject _)
78 |
79 | implicit val javaBoolean: TypeBinder[Option[java.lang.Boolean]] = any.map {
80 | case b if b == null => None
81 | case b: java.lang.Boolean => Some(b)
82 | case s: String => Some({
83 | try s.toInt != 0
84 | catch { case e: NumberFormatException => !s.isEmpty }
85 | }.asInstanceOf[java.lang.Boolean])
86 | case n: Number => Some((n.intValue != 0).asInstanceOf[java.lang.Boolean])
87 | case v => Some((v != 0).asInstanceOf[java.lang.Boolean])
88 | }
89 | implicit val scalaBoolean: TypeBinder[Option[Boolean]] =
90 | javaBoolean.map(_.map(_.asInstanceOf[Boolean]))
91 |
92 | def javaNumber[T](valueOf: String => T): TypeBinder[Option[T]] =
93 | any.map {
94 | case v if v == null => None
95 | case v => try {
96 | Some(valueOf(v.toString))
97 | } catch { case e: NumberFormatException => None }
98 | }
99 | def javaFixedNumber[T](
100 | to: Number => T,
101 | valueOf: String => T
102 | ): TypeBinder[Option[T]] = any.map {
103 | case v if v == null => None
104 | case v: Number => Some(to(v))
105 | case v => try {
106 | Some(valueOf(v.toString))
107 | } catch { case e: NumberFormatException => None }
108 | }
109 |
110 | implicit val javaByte: TypeBinder[Option[java.lang.Byte]] =
111 | javaFixedNumber({ n => n.byteValue }, { s => java.lang.Byte.valueOf(s) })
112 | implicit val scalaByte: TypeBinder[Option[Byte]] =
113 | javaByte.map(_.map(_.asInstanceOf[Byte]))
114 | implicit val javaShort: TypeBinder[Option[java.lang.Short]] =
115 | javaFixedNumber({ n => n.shortValue }, { s => java.lang.Short.valueOf(s) })
116 | implicit val scalaShort: TypeBinder[Option[Short]] =
117 | javaShort.map(_.map(_.asInstanceOf[Short]))
118 | implicit val javaInt: TypeBinder[Option[java.lang.Integer]] =
119 | javaFixedNumber({ n => n.intValue }, { s => java.lang.Integer.valueOf(s) })
120 | implicit val scalaInt: TypeBinder[Option[Int]] =
121 | javaInt.map(_.map(_.asInstanceOf[Int]))
122 | implicit val javaLong: TypeBinder[Option[java.lang.Long]] =
123 | javaFixedNumber({ n => n.longValue }, { s => java.lang.Long.valueOf(s) })
124 | implicit val scalaLong: TypeBinder[Option[Long]] =
125 | javaLong.map(_.map(_.asInstanceOf[Long]))
126 | implicit val javaDouble: TypeBinder[Option[java.lang.Double]] =
127 | javaNumber { s => java.lang.Double.valueOf(s) }
128 | implicit val scalaDouble: TypeBinder[Option[Double]] =
129 | javaDouble.map(_.map(_.asInstanceOf[Double]))
130 | implicit val javaFloat: TypeBinder[Option[java.lang.Float]] =
131 | javaNumber { s => java.lang.Float.valueOf(s) }
132 | implicit val scalaFloat: TypeBinder[Option[Float]] =
133 | javaFloat.map(_.map(_.asInstanceOf[Float]))
134 |
135 | implicit val javaBigDecimal: TypeBinder[Option[java.math.BigDecimal]] =
136 | TypeBinder(_ getBigDecimal _)(_ getBigDecimal _).map(Option(_))
137 | implicit val scalaBigDecimal: TypeBinder[Option[BigDecimal]] =
138 | javaBigDecimal.map(_.map(BigDecimal(_)))
139 |
140 | implicit val string: TypeBinder[Option[String]] =
141 | TypeBinder(_ getString _)(_ getString _).map(Option(_))
142 | implicit val bytes: TypeBinder[Option[Array[Byte]]] =
143 | TypeBinder(_ getBytes _)(_ getBytes _).map(Option(_))
144 | implicit val characterStream: TypeBinder[Option[java.io.Reader]] =
145 | TypeBinder(_ getCharacterStream _)(_ getCharacterStream _).map(Option(_))
146 | implicit val binaryStream: TypeBinder[Option[java.io.InputStream]] =
147 | TypeBinder(_ getBinaryStream _)(_ getBinaryStream _).map(Option(_))
148 |
149 | implicit val blob: TypeBinder[Option[java.sql.Blob]] =
150 | TypeBinder(_ getBlob _)(_ getBlob _).map(Option(_))
151 | implicit val clob: TypeBinder[Option[java.sql.Clob]] =
152 | TypeBinder(_ getClob _)(_ getClob _).map(Option(_))
153 | implicit val nClob: TypeBinder[Option[java.sql.NClob]] =
154 | TypeBinder(_ getNClob _)(_ getNClob _).map(Option(_))
155 | implicit val array: TypeBinder[Option[java.sql.Array]] =
156 | TypeBinder(_ getArray _)(_ getArray _).map(Option(_))
157 |
158 | implicit val url: TypeBinder[Option[java.net.URL]] =
159 | TypeBinder(_ getURL _)(_ getURL _).map(Option(_))
160 | implicit val date: TypeBinder[Option[java.sql.Date]] =
161 | TypeBinder(_ getDate _)(_ getDate _).map(Option(_))
162 | implicit val time: TypeBinder[Option[java.sql.Time]] =
163 | TypeBinder(_ getTime _)(_ getTime _).map(Option(_))
164 | implicit val timestamp: TypeBinder[Option[java.sql.Timestamp]] =
165 | TypeBinder(_ getTimestamp _)(_ getTimestamp _).map(Option(_))
166 | implicit val sqlxml: TypeBinder[Option[java.sql.SQLXML]] =
167 | TypeBinder(_ getSQLXML _)(_ getSQLXML _).map(Option(_))
168 | implicit val ref: TypeBinder[Option[java.sql.Ref]] =
169 | TypeBinder(_ getRef _)(_ getRef _).map(Option(_))
170 | implicit val rowId: TypeBinder[Option[java.sql.RowId]] =
171 | TypeBinder(_ getRowId _)(_ getRowId _).map(Option(_))
172 | }
173 |
174 | trait AutoUnwrapOption {
175 | implicit def some[T](implicit
176 | check: IsNotOption[T], // We do this to enable a diagnostic type
177 | // error by CheckGetter. Otherwise an
178 | // implicit expansion of an unknown type
179 | // fails on divergence.
180 | option: TypeBinder[Option[T]]
181 | ): TypeBinder[T] = option.map(_.get) // throws
182 | }
183 | object AutoUnwrapOption extends AutoUnwrapOption
184 |
185 | // $COVERAGE-OFF$
186 |
187 | sealed trait IsNotOption[+T]
188 | object IsNotOption {
189 | implicit def some[T]: IsNotOption[T] = new IsNotOption[T] {}
190 | implicit def ambig1[T]: IsNotOption[Option[T]] = sys.error("unexpected")
191 | implicit def ambig2[T]: IsNotOption[Option[T]] = sys.error("unexpected")
192 | }
193 |
194 | // $COVERAGE-ON$
195 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/tarao/slickjdbc/interpolation/Interpolation.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package interpolation
4 |
5 | import java.sql.PreparedStatement
6 | import scala.language.implicitConversions
7 | import slick.SlickException
8 | import slick.jdbc.{
9 | GetResult,
10 | PositionedParameters,
11 | PositionedResult,
12 | SQLInterpolation => SlickInterpolation,
13 | SetParameter,
14 | StatementInvoker,
15 | StreamingInvokerAction,
16 | TypedParameter
17 | }
18 | import slick.sql.{SqlAction, SqlStreamingAction}
19 | import slick.dbio.{Effect, NoStream}
20 |
21 | trait SQLInterpolation {
22 | implicit def interpolation(s: StringContext): SQLInterpolationImpl = SQLInterpolationImpl(s)
23 | }
24 | object SQLInterpolation extends SQLInterpolation
25 |
26 | case class SQLInterpolationImpl(s: StringContext) extends AnyVal {
27 | import scala.language.experimental.macros
28 |
29 | def sql(params: Any*): SQLActionBuilder =
30 | macro MacroTreeBuilder.sqlImpl
31 |
32 | def sqlu(params: Any*): SqlAction[Int, NoStream, Effect] =
33 | macro MacroTreeBuilder.sqluImpl
34 | }
35 |
36 | trait Literal
37 | class SimpleString(value: String) extends Literal {
38 | override def toString = value
39 | }
40 | case class TableName(name: String) extends SimpleString(name)
41 |
42 | object GetUpdateValue extends GetResult[Int] {
43 | def apply(pr: PositionedResult) =
44 | throw new SlickException("Update statements should not return a ResultSet")
45 | }
46 |
47 | case class SQLActionBuilder(strings: Seq[String], params: Seq[TypedParameter[_]]) {
48 | def as[R](implicit
49 | getResult: GetResult[R],
50 | translators: Iterable[query.Translator]
51 | ): SqlStreamingAction[Vector[R], R, Effect] = {
52 | val (sql, unitPConv) =
53 | SlickInterpolation.parse(strings, params.asInstanceOf[Seq[TypedParameter[Any]]])
54 | val translatedStatements = List(query.Translator.translate(sql))
55 | new StreamingInvokerAction[Vector[R], R, Effect] {
56 | def statements = translatedStatements
57 | protected[this] def createInvoker(statements: Iterable[String]) = new StatementInvoker[R] {
58 | val getStatement = statements.head
59 | protected def setParam(st: PreparedStatement) = unitPConv((), new PositionedParameters(st))
60 | protected def extractValue(rs: PositionedResult): R = getResult(rs)
61 | }
62 | protected[this] def createBuilder = Vector.newBuilder[R]
63 | }
64 | }
65 |
66 | def asUpdate(implicit
67 | translators: Iterable[query.Translator]
68 | ) = as[Int](GetUpdateValue, translators).head
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/tarao/slickjdbc/interpolation/MacroTreeBuilder.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package interpolation
4 |
5 | import interpolation.{Literal => LiteralParameter}
6 | import scala.reflect.macros.blackbox.Context
7 |
8 | private[interpolation] class MacroTreeBuilder(val c: Context) {
9 | import c.universe._
10 | import scala.collection.mutable.ListBuffer
11 | import slick.sql.SqlAction
12 | import slick.dbio.{NoStream, Effect}
13 |
14 | def abort(msg: String) = c.abort(c.enclosingPosition, msg)
15 |
16 | // Retrieve string parts of interpolation from caller context
17 | lazy val rawQueryParts: List[String] = {
18 | // match SQLInterpolationImpl(StringContext(strArg: _*)).sql(params: _*)
19 | val Apply(Select(Apply(_, List(Apply(_, strArg))), _), params) =
20 | c.macroApplication
21 | strArg map {
22 | case Literal(Constant(x: String)) => x
23 | case _ => abort("The interpolation must be a string literal")
24 | }
25 | }
26 |
27 | private val NS = q"com.github.tarao.slickjdbc"
28 | private val interpolation = q"$NS.interpolation"
29 | private lazy val ListRejected =
30 | tq"""$interpolation.${TypeName("ListRejected")}"""
31 | private lazy val OptionRejected =
32 | tq"""$interpolation.${TypeName("OptionRejected")}"""
33 | private lazy val EitherRejected =
34 | tq"""$interpolation.${TypeName("EitherRejected")}"""
35 | private lazy val ValidParameter =
36 | tq"""$interpolation.${TypeName("ValidParameter")}"""
37 | private lazy val ValidProduct =
38 | tq"""$interpolation.${TypeName("ValidProduct")}"""
39 | private lazy val ValidRefinedNonEmpty =
40 | tq"""$interpolation.${TypeName("ValidRefinedNonEmpty")}"""
41 | private def ensure(required: Type, base: Tree = ValidParameter) =
42 | q"implicitly[$base[$required]]"
43 | private val ToPlaceholder =
44 | tq"""$interpolation.${TypeName("ToPlaceholder")}"""
45 | private def toPlaceholder(target: Type, base: Tree = ToPlaceholder) =
46 | q"implicitly[$base[$target]]"
47 |
48 | def invokeInterpolation(param: c.Expr[Any]*): Tree = {
49 | val stats = new ListBuffer[Tree]
50 |
51 | // Additional features of SQL interpolation by preprocessing
52 | // string parts and arguments. The interpolation translates to
53 | // another (expanded) interpolation call of
54 | // `ActionBasedSQLInterpolation`.
55 | //
56 | // [Embedding literals]
57 | //
58 | // A parameter of type `Literal` is embedded as a literal string
59 | // by using "#" expansion.
60 | //
61 | // val literal = new Literal { def toString = "string" }
62 | // sql"some text with a ${literal} value"
63 | // ~> SQLInterpolationImpl(
64 | // StringContext("some text with a ", " value")
65 | // ).sql(literal)
66 | // ~> ActionBasedSQLInterpolation(
67 | // StringContext("some text with a #", " value")
68 | // ).sql(literal)
69 | // => SQLActionBuilder(Seq("some text with a string value"), ...)
70 | //
71 | // [Embedding non-empty lists]
72 | //
73 | // A parameter of type `NonEmpty[Any]` is embedded with repeated
74 | // "?"s. "?"s (except the last one) are inserted as a literal
75 | // string parameter not as a literal string part of
76 | // `StringContext` since the number of elements is not known at
77 | // compile time.
78 | //
79 | // val list = NonEmpty(1, 2, 3)
80 | // sql"some text with a ${list} value"
81 | // ~> SQLInterpolationImpl(
82 | // StringContext("some text with a (#", "", ")#", " value")
83 | // ).sql(new Placeholders(list), list, "")
84 | // ~> ActionBasedSQLInterpolation(
85 | // StringContext("some text with a (#", "", ")#", " value")
86 | // ).sql(new Placeholders(list), list, "")
87 | // => ActionBasedSQLInterpolation(
88 | // StringContext("some text with a (#", "", ")#", " value")
89 | // ).sql("?, ?, ", list, "")
90 | // => SQLActionBuilder(Seq("some text with a (?, ?, ?) value"), ...)
91 | //
92 | // Note that the third "?" is inserted by a conversion of argument
93 | // `list`.
94 | val queryParts = new ListBuffer[Tree]
95 | val params = new ListBuffer[c.Expr[Any]]
96 | def pushLiteral(literal: String) = {
97 | params.append(c.Expr(q""" ${""} """))
98 | queryParts.append(q""" ${literal + "#"} """)
99 | }
100 | def pushLiteralValue(expr: c.Expr[Any]) = {
101 | params.append(c.Expr(q"""slick.jdbc.TypedParameter.typedParameter($expr)(new ${interpolation}.SetLiteral)"""))
102 | }
103 | def pushValue(expr: c.Expr[Any]) = {
104 | params.append(c.Expr(q"""slick.jdbc.TypedParameter.typedParameter($expr)"""))
105 | }
106 | def mayCompleteParen(param: c.Expr[Any], s: String)(block: => Unit) = {
107 | if (!s.matches("""(?s).*\(\s*""")) {
108 | pushLiteralValue(c.Expr(q""" ${toPlaceholder(param.actualType)}.open """))
109 | queryParts.append(q""" ${"#"} """)
110 | block
111 | pushLiteralValue(c.Expr(q""" ${toPlaceholder(param.actualType)}.close """))
112 | queryParts.append(q""" ${"#"} """)
113 | } else block
114 | }
115 | param.toList.iterator.zip(rawQueryParts.iterator).foreach { zipped =>
116 | val (param, s, literal) = zipped match { case (param, s) => {
117 | val literal = s.reverseIterator.takeWhile(_ == '#').length % 2 == 1
118 | if (param.actualType <:< typeOf[LiteralParameter])
119 | (param, s + { if (literal) "" else "#" }, true)
120 | else (param, s, literal)
121 | } }
122 | if (!literal) {
123 | pushLiteral(s)
124 |
125 | mayCompleteParen(param, s) {
126 | // for "?, ?, ?, ..." except the last one
127 | pushLiteralValue(c.Expr(q"""
128 | ${toPlaceholder(param.actualType)}
129 | .apply(${param})
130 | .toTopLevelString
131 | """))
132 | queryParts.append(q""" ${"#"} """)
133 |
134 | // for the last "?" (inserted by ActionBasedSQLInterpolation)
135 | pushValue(param)
136 | queryParts.append(q""" ${""} """)
137 | }
138 | } else {
139 | pushLiteralValue(param)
140 | queryParts.append(q"$s")
141 | }
142 |
143 | if (!literal) {
144 | // Insert parameter type checker for a fine type error message.
145 |
146 | // The order is significant since there can be a type matches
147 | // with multiple conditions for example an
148 | // Option[NonEmpty[Any]] is also a Product.
149 |
150 | stats.append(ensure(param.actualType, ValidRefinedNonEmpty))
151 | stats.append(ensure(param.actualType, ListRejected))
152 |
153 | param.actualType.foreach { t =>
154 | if (t <:< typeOf[Any]) {
155 | stats.append(ensure(t, OptionRejected))
156 | stats.append(ensure(t, EitherRejected))
157 | }
158 | }
159 |
160 | stats.append(ensure(param.actualType, ValidProduct))
161 | stats.append(ensure(param.actualType, ValidParameter))
162 | }
163 | }
164 | queryParts.append(q"${rawQueryParts.last}")
165 |
166 | stats.append(q"""new $interpolation.SQLActionBuilder(Seq(..$queryParts), Seq(..$params))""")
167 | q"{ ..$stats }"
168 |
169 | }
170 |
171 | def sqlImpl(params: c.Expr[Any]*): c.Expr[SQLActionBuilder] =
172 | c.Expr(invokeInterpolation(params: _*))
173 |
174 | def sqluImpl(params: c.Expr[Any]*): c.Expr[SqlAction[Int, NoStream, Effect]] =
175 | c.Expr(q""" ${invokeInterpolation(params: _*)}.asUpdate """)
176 | }
177 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/tarao/slickjdbc/interpolation/Placeholder.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package interpolation
4 |
5 | class Placeholder extends Literal {
6 | import Placeholder.{stripParen, dropLast}
7 | def toSeq: Seq[Placeholder] = Seq(this)
8 | def toTopLevelString: String = dropLast(stripParen(toString))
9 | override def toString: String = "?"
10 | }
11 | object Placeholder {
12 | private def stripParen(str: String) = str.stripPrefix("(").stripSuffix(")")
13 | private def dropLast(str: String) = str.stripSuffix("?")
14 |
15 | def apply(): Placeholder = new Placeholder
16 | def repeat(n: Int): Seq[Placeholder] = (1 to n).map(_ => apply())
17 |
18 | class Nested(children: Placeholder*) extends Placeholder {
19 | override def toSeq: Seq[Placeholder] = children
20 | override def toString: String =
21 | s"""(${stripParen(children.map(_.toString).mkString(", "))})"""
22 | }
23 | object Nested {
24 | def apply(children: Placeholder*): Nested = new Nested(children: _*)
25 | def apply(n: Int): Nested = new Nested(Placeholder.repeat(n): _*)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/tarao/slickjdbc/interpolation/SetParameter.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package interpolation
4 |
5 | import eu.timepit.refined.api.RefType
6 | import eu.timepit.refined.collection.NonEmpty
7 | import scala.annotation.implicitNotFound
8 | import scala.language.higherKinds
9 | import scala.language.implicitConversions
10 | import slick.jdbc.{SetParameter => SP, PositionedParameters}
11 |
12 | trait ListParameter {
13 | @inline implicit def createSetNonEmptyList[A, L[X] <: Iterable[X], F[_, _]](implicit
14 | c: SP[A],
15 | rt: RefType[F]
16 | ): SP[F[L[A], NonEmpty]] =
17 | new SetNonEmptyList[A, L, F, F[L[A], NonEmpty]](c, rt)
18 |
19 | @inline implicit def nonEmptyListToPlaceholder[A, L[X] <: Iterable[X], F[_, _]](implicit
20 | p: ToPlaceholder[A],
21 | rt: RefType[F]
22 | ): ToPlaceholder[F[L[A], NonEmpty]] =
23 | new ToPlaceholder.FromNonEmptyList[A, L, F, F[L[A], NonEmpty]](p, rt)
24 | }
25 | object ListParameter extends ListParameter
26 |
27 | trait ProductParameter {
28 | @inline implicit def createSetProduct[T](implicit
29 | check1: T <:< Product,
30 | check2: IsNotTuple[T]
31 | ): SP[T] = new SetProduct[T]
32 |
33 | @inline implicit def productToPlaceholder[T](implicit
34 | check1: T <:< Product,
35 | check2: IsNotTuple[T]
36 | ): ToPlaceholder[T] = new ToPlaceholder.FromProduct[T]
37 | }
38 | object ProductParameter extends ProductParameter
39 |
40 | trait CompoundParameter extends ListParameter with ProductParameter
41 | object CompoundParameter extends CompoundParameter
42 |
43 | /** SetParameter for non-empty list types. */
44 | class SetNonEmptyList[A, L[X] <: Iterable[X], F[_, _], -T <: F[L[A], NonEmpty]](val c: SP[A], rt: RefType[F]) extends SP[T] {
45 | def apply(param: T, pp: PositionedParameters): Unit = {
46 | rt.unwrap(param).foreach(item => c.asInstanceOf[SP[Any]](item, pp))
47 | }
48 | }
49 |
50 | /** SetParameter for product types especially for case classes. */
51 | class SetProduct[-T](implicit product: T <:< Product) extends SP[T] {
52 | def apply(prod: T, pp: PositionedParameters): Unit =
53 | for (v <- product(prod).productIterator) v match {
54 | case p: Product => new SetProduct[Product].apply(p, pp)
55 | case v => SP.SetSimpleProduct(Tuple1(v), pp)
56 | }
57 | }
58 |
59 | class SetLiteral[-T] extends SP[T] {
60 | def apply(lit: T, pp: PositionedParameters): Unit =
61 | SP.SetString(lit.toString(), pp)
62 | }
63 |
64 | // $COVERAGE-OFF$
65 |
66 | @implicitNotFound(msg = "Unsupported parameter type: ${T}.\n" +
67 | "[NOTE] You need an implicit of slick.jdbc.SetParameter[${T}] to pass a value of the type.")
68 | sealed trait ValidParameter[-T]
69 | object ValidParameter {
70 | implicit def valid[T](implicit c: SP[T]): ValidParameter[T] =
71 | new ValidParameter[T] {}
72 | }
73 |
74 | @implicitNotFound(msg = "A product ${T} is passed.\n" +
75 | "[NOTE] Use interpolation.CompoundParameter trait to enable passing a product.")
76 | sealed trait ValidProduct[-T]
77 | object ValidProduct {
78 | implicit def valid1[T](implicit
79 | c: SP[T],
80 | product: T <:< Product
81 | ): ValidProduct[T] = new ValidProduct[T] {}
82 |
83 | implicit def valid2[T](implicit check: IsNotProduct[T]): ValidProduct[T] =
84 | new ValidProduct[T] {}
85 | }
86 |
87 | @implicitNotFound(msg = "A non-empty list is passed.\n" +
88 | "[NOTE] Use interpolation.CompoundParameter trait to enable passing a non-empty list.")
89 | sealed trait ValidRefinedNonEmpty[-T]
90 | object ValidRefinedNonEmpty {
91 | implicit def valid1[A, L[X] <: Iterable[X], F[_, _]](implicit
92 | c: SP[F[L[A], NonEmpty]],
93 | rt: RefType[F]
94 | ): ValidRefinedNonEmpty[F[L[A], NonEmpty]] =
95 | new ValidRefinedNonEmpty[F[L[A], NonEmpty]] {}
96 |
97 | implicit def valid2[T](implicit
98 | check: IsNotRefinedNonEmpty[T]
99 | ): ValidRefinedNonEmpty[T] = new ValidRefinedNonEmpty[T] {}
100 | }
101 |
102 | @implicitNotFound(msg = "Illegal parameter type: ${T}.\n" +
103 | "[NOTE] A list is not allowed since it may be empty and breaks the query.\n" +
104 | "[NOTE] Pass a ${T} Refind NonEmpty if you know that it is not empty.")
105 | sealed trait ListRejected[-T]
106 | object ListRejected {
107 | implicit def valid[T](implicit check: IsNotList[T]): ListRejected[T] =
108 | new ListRejected[T] {}
109 | }
110 |
111 | @implicitNotFound(msg = "Illegal parameter type: ${T}\n" +
112 | "[NOTE] An option is not allowed since it may be none and breaks the query.\n" +
113 | "[NOTE] Break it into Some(_) or None to confirm that it has a value.")
114 | sealed trait OptionRejected[-T]
115 | object OptionRejected {
116 | implicit def valid[T](implicit check: IsNotOption[T]): OptionRejected[T] =
117 | new OptionRejected[T] {}
118 | }
119 |
120 | @implicitNotFound(msg = "Illegal parameter type: ${T}\n" +
121 | "[NOTE] Break it into Left(_) or Right(_) to confirm that it can be embedded into the query.")
122 | sealed trait EitherRejected[-T]
123 | object EitherRejected {
124 | implicit def valid[T](implicit check: IsNotEither[T]): EitherRejected[T] =
125 | new EitherRejected[T] {}
126 | }
127 |
128 | sealed trait IsNotProduct[-T]
129 | object IsNotProduct {
130 | implicit def valid[T]: IsNotProduct[T] = new IsNotProduct[T] {}
131 | implicit def ambig1: IsNotProduct[Product] = sys.error("unexpected")
132 | implicit def ambig2: IsNotProduct[Product] = sys.error("unexpected")
133 | }
134 |
135 | sealed trait IsNotOption[-T]
136 | object IsNotOption {
137 | implicit def valid[T]: IsNotOption[T] = new IsNotOption[T] {}
138 | implicit def ambig1[S]: IsNotOption[Option[S]] = sys.error("unexpected")
139 | implicit def ambig2[S]: IsNotOption[Option[S]] = sys.error("unexpected")
140 | }
141 |
142 | sealed trait IsNotTuple[-T]
143 | object IsNotTuple {
144 | implicit def valid[T]: IsNotTuple[T] = new IsNotTuple[T] {}
145 | implicit def ambig1[S](implicit tp: IsTuple[S]): IsNotTuple[S] =
146 | sys.error("unexpected")
147 | implicit def ambig2[S](implicit tp: IsTuple[S]): IsNotTuple[S] =
148 | sys.error("unexpected")
149 | }
150 |
151 | sealed trait IsNotEither[-T]
152 | object IsNotEither {
153 | implicit def valid[T]: IsNotEither[T] = new IsNotEither[T] {}
154 | implicit def ambig1[S](implicit either: S <:< Either[_, _]): IsNotEither[S] =
155 | sys.error("unexpected")
156 | implicit def ambig2[S](implicit either: S <:< Either[_, _]): IsNotEither[S] =
157 | sys.error("unexpected")
158 | }
159 |
160 | sealed trait IsNotList[-T]
161 | object IsNotList {
162 | implicit def valid[T]: IsNotList[T] = new IsNotList[T] {}
163 | implicit def ambig1[S]: IsNotList[Iterable[S]] = sys.error("unexpected")
164 | implicit def ambig2[S]: IsNotList[Iterable[S]] = sys.error("unexpected")
165 | }
166 |
167 | sealed trait IsNotRefinedNonEmpty[-T]
168 | object IsNotRefinedNonEmpty {
169 | implicit def valid[T]: IsNotRefinedNonEmpty[T] =
170 | new IsNotRefinedNonEmpty[T] {}
171 |
172 | implicit def ambig1[A, L[X] <: Iterable[X], F[_, _]](implicit
173 | rt: RefType[F]
174 | ): IsNotRefinedNonEmpty[F[L[A], NonEmpty]] = sys.error("unexpected")
175 | implicit def ambig2[A, L[X] <: Iterable[X], F[_, _]](implicit
176 | rt: RefType[F]
177 | ): IsNotRefinedNonEmpty[F[L[A], NonEmpty]] = sys.error("unexpected")
178 | }
179 |
180 | sealed trait IsTuple[-T]
181 | object IsTuple {
182 | implicit def tuple2[T1, T2]: IsTuple[(T1, T2)] =
183 | new IsTuple[(T1, T2)] {}
184 | implicit def tuple3[T1, T2, T3]: IsTuple[(T1, T2, T3)] =
185 | new IsTuple[(T1, T2, T3)] {}
186 | implicit def tuple4[T1, T2, T3, T4]: IsTuple[(T1, T2, T3, T4)] =
187 | new IsTuple[(T1, T2, T3, T4)] {}
188 | implicit def tuple5[T1, T2, T3, T4, T5]: IsTuple[(T1, T2, T3, T4, T5)] =
189 | new IsTuple[(T1, T2, T3, T4, T5)] {}
190 | implicit def tuple6[T1, T2, T3, T4, T5, T6]: IsTuple[(T1, T2, T3, T4, T5, T6)] =
191 | new IsTuple[(T1, T2, T3, T4, T5, T6)] {}
192 | implicit def tuple7[T1, T2, T3, T4, T5, T6, T7]: IsTuple[(T1, T2, T3, T4, T5, T6, T7)] =
193 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7)] {}
194 | implicit def tuple8[T1, T2, T3, T4, T5, T6, T7, T8]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8)] =
195 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8)] {}
196 | implicit def tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9)] =
197 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9)] {}
198 | implicit def tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)] =
199 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)] {}
200 | implicit def tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)] =
201 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)] {}
202 | implicit def tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)] =
203 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)] {}
204 | implicit def tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13)] =
205 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13)] {}
206 | implicit def tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14)] =
207 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14)] {}
208 | implicit def tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)] =
209 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)] {}
210 | implicit def tuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16)] =
211 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16)] {}
212 | implicit def tuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17)] =
213 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17)] {}
214 | implicit def tuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18)] =
215 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18)] {}
216 | implicit def tuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19)] =
217 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19)] {}
218 | implicit def tuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20)] =
219 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20)] {}
220 | implicit def tuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21)] =
221 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21)] {}
222 | implicit def tuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]: IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22)] =
223 | new IsTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22)] {}
224 | }
225 |
226 | // $COVERAGE-ON$
227 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/tarao/slickjdbc/interpolation/ToPlaceholder.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package interpolation
4 |
5 | import eu.timepit.refined.api.RefType
6 | import eu.timepit.refined.collection.NonEmpty
7 | import scala.language.higherKinds
8 |
9 | trait ToPlaceholder[-T] {
10 | def apply(value: T): Placeholder
11 | def open: String = ""
12 | def close: String = ""
13 | }
14 | object ToPlaceholder {
15 | trait Compound[-T] extends ToPlaceholder[T] {
16 | override def open: String = "("
17 | override def close: String = ")"
18 | }
19 |
20 | class FromProduct[-T](implicit product: T <:< Product)
21 | extends Compound[T] {
22 | def apply(value: T): Placeholder = {
23 | def rec(v: Any): Placeholder = v match {
24 | case s: Tuple1[_] =>
25 | Placeholder.Nested(1)
26 | case p: Product if p.productArity <= 0 =>
27 | throw new java.sql.SQLException("No value to bind for " + p)
28 | case p: Product if p.productArity == 1 =>
29 | p.productIterator.map(rec _).toSeq.head
30 | case p: Product =>
31 | Placeholder.Nested(p.productIterator.flatMap(rec(_).toSeq).toSeq: _*)
32 | case _ => Placeholder()
33 | }
34 | rec(value)
35 | }
36 | }
37 |
38 | class FromNonEmptyList[A, L[X] <: Iterable[X], F[_, _], -T <: F[L[A], NonEmpty]](
39 | p: ToPlaceholder[A],
40 | rt: RefType[F]
41 | ) extends Compound[T] {
42 | def apply(value: T): Placeholder =
43 | Placeholder.Nested(rt.unwrap(value).map(p.apply _).toSeq: _*)
44 | }
45 |
46 | class FromTuple[-T <: Product](children: ToPlaceholder[_]*)
47 | extends Compound[T] {
48 | def apply(value: T): Placeholder = Placeholder.Nested(
49 | children.iterator.zip(value.productIterator).map { pair =>
50 | pair._1.asInstanceOf[ToPlaceholder[Any]].apply(pair._2)
51 | }.toSeq: _*
52 | )
53 | }
54 |
55 | @inline implicit def anyToPlaceholder[T]: ToPlaceholder[T] =
56 | new ToPlaceholder[T] {
57 | def apply(value: T): Placeholder = Placeholder()
58 | }
59 |
60 | @inline implicit def tuple1ToPlaceholder[T1](implicit
61 | p1: ToPlaceholder[T1]
62 | ): ToPlaceholder[Tuple1[T1]] =
63 | new FromTuple[Tuple1[T1]](p1)
64 |
65 | @inline implicit def tuple2ToPlaceholder[T1, T2](implicit
66 | p1: ToPlaceholder[T1],
67 | p2: ToPlaceholder[T2]
68 | ): ToPlaceholder[(T1, T2)] =
69 | new FromTuple[(T1, T2)](p1, p2)
70 |
71 | @inline implicit def tuple3ToPlaceholder[T1, T2, T3, T5](implicit
72 | p1: ToPlaceholder[T1],
73 | p2: ToPlaceholder[T2],
74 | p3: ToPlaceholder[T3]
75 | ): ToPlaceholder[(T1, T2, T3)] =
76 | new FromTuple[(T1, T2, T3)](p1, p2, p3)
77 |
78 | @inline implicit def tuple4ToPlaceholder[T1, T2, T3, T4](implicit
79 | p1: ToPlaceholder[T1],
80 | p2: ToPlaceholder[T2],
81 | p3: ToPlaceholder[T3],
82 | p4: ToPlaceholder[T4]
83 | ): ToPlaceholder[(T1, T2, T3, T4)] =
84 | new FromTuple[(T1, T2, T3, T4)](p1, p2, p3, p4)
85 |
86 | @inline implicit def tuple5ToPlaceholder[T1, T2, T3, T4, T5](implicit
87 | p1: ToPlaceholder[T1],
88 | p2: ToPlaceholder[T2],
89 | p3: ToPlaceholder[T3],
90 | p4: ToPlaceholder[T4],
91 | p5: ToPlaceholder[T5]
92 | ): ToPlaceholder[(T1, T2, T3, T4, T5)] =
93 | new FromTuple[(T1, T2, T3, T4, T5)](p1, p2, p3, p4, p5)
94 |
95 | @inline implicit def tuple6ToPlaceholder[T1, T2, T3, T4, T5, T6](implicit
96 | p1: ToPlaceholder[T1],
97 | p2: ToPlaceholder[T2],
98 | p3: ToPlaceholder[T3],
99 | p4: ToPlaceholder[T4],
100 | p5: ToPlaceholder[T5],
101 | p6: ToPlaceholder[T6]
102 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6)] =
103 | new FromTuple[(T1, T2, T3, T4, T5, T6)](p1, p2, p3, p4, p5, p6)
104 |
105 | @inline implicit def tuple7ToPlaceholder[T1, T2, T3, T4, T5, T6, T7](implicit
106 | p1: ToPlaceholder[T1],
107 | p2: ToPlaceholder[T2],
108 | p3: ToPlaceholder[T3],
109 | p4: ToPlaceholder[T4],
110 | p5: ToPlaceholder[T5],
111 | p6: ToPlaceholder[T6],
112 | p7: ToPlaceholder[T7]
113 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7)] =
114 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7)](p1, p2, p3, p4, p5, p6, p7)
115 |
116 | @inline implicit def tuple8ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8](implicit
117 | p1: ToPlaceholder[T1],
118 | p2: ToPlaceholder[T2],
119 | p3: ToPlaceholder[T3],
120 | p4: ToPlaceholder[T4],
121 | p5: ToPlaceholder[T5],
122 | p6: ToPlaceholder[T6],
123 | p7: ToPlaceholder[T7],
124 | p8: ToPlaceholder[T8]
125 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8)] =
126 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8)](p1, p2, p3, p4, p5, p6, p7, p8)
127 |
128 | @inline implicit def tuple9ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9](implicit
129 | p1: ToPlaceholder[T1],
130 | p2: ToPlaceholder[T2],
131 | p3: ToPlaceholder[T3],
132 | p4: ToPlaceholder[T4],
133 | p5: ToPlaceholder[T5],
134 | p6: ToPlaceholder[T6],
135 | p7: ToPlaceholder[T7],
136 | p8: ToPlaceholder[T8],
137 | p9: ToPlaceholder[T9]
138 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9)] =
139 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9)](p1, p2, p3, p4, p5, p6, p7, p8, p9)
140 |
141 | @inline implicit def tuple10ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](implicit
142 | p1: ToPlaceholder[T1],
143 | p2: ToPlaceholder[T2],
144 | p3: ToPlaceholder[T3],
145 | p4: ToPlaceholder[T4],
146 | p5: ToPlaceholder[T5],
147 | p6: ToPlaceholder[T6],
148 | p7: ToPlaceholder[T7],
149 | p8: ToPlaceholder[T8],
150 | p9: ToPlaceholder[T9],
151 | p10: ToPlaceholder[T10]
152 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)] =
153 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10)
154 |
155 | @inline implicit def tuple11ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](implicit
156 | p1: ToPlaceholder[T1],
157 | p2: ToPlaceholder[T2],
158 | p3: ToPlaceholder[T3],
159 | p4: ToPlaceholder[T4],
160 | p5: ToPlaceholder[T5],
161 | p6: ToPlaceholder[T6],
162 | p7: ToPlaceholder[T7],
163 | p8: ToPlaceholder[T8],
164 | p9: ToPlaceholder[T9],
165 | p10: ToPlaceholder[T10],
166 | p11: ToPlaceholder[T11]
167 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)] =
168 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
169 |
170 | @inline implicit def tuple12ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](implicit
171 | p1: ToPlaceholder[T1],
172 | p2: ToPlaceholder[T2],
173 | p3: ToPlaceholder[T3],
174 | p4: ToPlaceholder[T4],
175 | p5: ToPlaceholder[T5],
176 | p6: ToPlaceholder[T6],
177 | p7: ToPlaceholder[T7],
178 | p8: ToPlaceholder[T8],
179 | p9: ToPlaceholder[T9],
180 | p10: ToPlaceholder[T10],
181 | p11: ToPlaceholder[T11],
182 | p12: ToPlaceholder[T12]
183 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)] =
184 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12)
185 |
186 | @inline implicit def tuple13ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](implicit
187 | p1: ToPlaceholder[T1],
188 | p2: ToPlaceholder[T2],
189 | p3: ToPlaceholder[T3],
190 | p4: ToPlaceholder[T4],
191 | p5: ToPlaceholder[T5],
192 | p6: ToPlaceholder[T6],
193 | p7: ToPlaceholder[T7],
194 | p8: ToPlaceholder[T8],
195 | p9: ToPlaceholder[T9],
196 | p10: ToPlaceholder[T10],
197 | p11: ToPlaceholder[T11],
198 | p12: ToPlaceholder[T12],
199 | p13: ToPlaceholder[T13]
200 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13)] =
201 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13)
202 |
203 | @inline implicit def tuple14ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](implicit
204 | p1: ToPlaceholder[T1],
205 | p2: ToPlaceholder[T2],
206 | p3: ToPlaceholder[T3],
207 | p4: ToPlaceholder[T4],
208 | p5: ToPlaceholder[T5],
209 | p6: ToPlaceholder[T6],
210 | p7: ToPlaceholder[T7],
211 | p8: ToPlaceholder[T8],
212 | p9: ToPlaceholder[T9],
213 | p10: ToPlaceholder[T10],
214 | p11: ToPlaceholder[T11],
215 | p12: ToPlaceholder[T12],
216 | p13: ToPlaceholder[T13],
217 | p14: ToPlaceholder[T14]
218 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14)] =
219 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14)
220 |
221 | @inline implicit def tuple15ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](implicit
222 | p1: ToPlaceholder[T1],
223 | p2: ToPlaceholder[T2],
224 | p3: ToPlaceholder[T3],
225 | p4: ToPlaceholder[T4],
226 | p5: ToPlaceholder[T5],
227 | p6: ToPlaceholder[T6],
228 | p7: ToPlaceholder[T7],
229 | p8: ToPlaceholder[T8],
230 | p9: ToPlaceholder[T9],
231 | p10: ToPlaceholder[T10],
232 | p11: ToPlaceholder[T11],
233 | p12: ToPlaceholder[T12],
234 | p13: ToPlaceholder[T13],
235 | p14: ToPlaceholder[T14],
236 | p15: ToPlaceholder[T15]
237 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)] =
238 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15)
239 |
240 | @inline implicit def tuple16ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](implicit
241 | p1: ToPlaceholder[T1],
242 | p2: ToPlaceholder[T2],
243 | p3: ToPlaceholder[T3],
244 | p4: ToPlaceholder[T4],
245 | p5: ToPlaceholder[T5],
246 | p6: ToPlaceholder[T6],
247 | p7: ToPlaceholder[T7],
248 | p8: ToPlaceholder[T8],
249 | p9: ToPlaceholder[T9],
250 | p10: ToPlaceholder[T10],
251 | p11: ToPlaceholder[T11],
252 | p12: ToPlaceholder[T12],
253 | p13: ToPlaceholder[T13],
254 | p14: ToPlaceholder[T14],
255 | p15: ToPlaceholder[T15],
256 | p16: ToPlaceholder[T16]
257 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16)] =
258 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16)
259 |
260 | @inline implicit def tuple17ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](implicit
261 | p1: ToPlaceholder[T1],
262 | p2: ToPlaceholder[T2],
263 | p3: ToPlaceholder[T3],
264 | p4: ToPlaceholder[T4],
265 | p5: ToPlaceholder[T5],
266 | p6: ToPlaceholder[T6],
267 | p7: ToPlaceholder[T7],
268 | p8: ToPlaceholder[T8],
269 | p9: ToPlaceholder[T9],
270 | p10: ToPlaceholder[T10],
271 | p11: ToPlaceholder[T11],
272 | p12: ToPlaceholder[T12],
273 | p13: ToPlaceholder[T13],
274 | p14: ToPlaceholder[T14],
275 | p15: ToPlaceholder[T15],
276 | p16: ToPlaceholder[T16],
277 | p17: ToPlaceholder[T17]
278 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17)] =
279 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17)
280 |
281 | @inline implicit def tuple18ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](implicit
282 | p1: ToPlaceholder[T1],
283 | p2: ToPlaceholder[T2],
284 | p3: ToPlaceholder[T3],
285 | p4: ToPlaceholder[T4],
286 | p5: ToPlaceholder[T5],
287 | p6: ToPlaceholder[T6],
288 | p7: ToPlaceholder[T7],
289 | p8: ToPlaceholder[T8],
290 | p9: ToPlaceholder[T9],
291 | p10: ToPlaceholder[T10],
292 | p11: ToPlaceholder[T11],
293 | p12: ToPlaceholder[T12],
294 | p13: ToPlaceholder[T13],
295 | p14: ToPlaceholder[T14],
296 | p15: ToPlaceholder[T15],
297 | p16: ToPlaceholder[T16],
298 | p17: ToPlaceholder[T17],
299 | p18: ToPlaceholder[T18]
300 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18)] =
301 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18)
302 |
303 | @inline implicit def tuple19ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](implicit
304 | p1: ToPlaceholder[T1],
305 | p2: ToPlaceholder[T2],
306 | p3: ToPlaceholder[T3],
307 | p4: ToPlaceholder[T4],
308 | p5: ToPlaceholder[T5],
309 | p6: ToPlaceholder[T6],
310 | p7: ToPlaceholder[T7],
311 | p8: ToPlaceholder[T8],
312 | p9: ToPlaceholder[T9],
313 | p10: ToPlaceholder[T10],
314 | p11: ToPlaceholder[T11],
315 | p12: ToPlaceholder[T12],
316 | p13: ToPlaceholder[T13],
317 | p14: ToPlaceholder[T14],
318 | p15: ToPlaceholder[T15],
319 | p16: ToPlaceholder[T16],
320 | p17: ToPlaceholder[T17],
321 | p18: ToPlaceholder[T18],
322 | p19: ToPlaceholder[T19]
323 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19)] =
324 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19)
325 |
326 | @inline implicit def tuple20ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](implicit
327 | p1: ToPlaceholder[T1],
328 | p2: ToPlaceholder[T2],
329 | p3: ToPlaceholder[T3],
330 | p4: ToPlaceholder[T4],
331 | p5: ToPlaceholder[T5],
332 | p6: ToPlaceholder[T6],
333 | p7: ToPlaceholder[T7],
334 | p8: ToPlaceholder[T8],
335 | p9: ToPlaceholder[T9],
336 | p10: ToPlaceholder[T10],
337 | p11: ToPlaceholder[T11],
338 | p12: ToPlaceholder[T12],
339 | p13: ToPlaceholder[T13],
340 | p14: ToPlaceholder[T14],
341 | p15: ToPlaceholder[T15],
342 | p16: ToPlaceholder[T16],
343 | p17: ToPlaceholder[T17],
344 | p18: ToPlaceholder[T18],
345 | p19: ToPlaceholder[T19],
346 | p20: ToPlaceholder[T20]
347 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20)] =
348 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20)
349 |
350 | @inline implicit def tuple21ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](implicit
351 | p1: ToPlaceholder[T1],
352 | p2: ToPlaceholder[T2],
353 | p3: ToPlaceholder[T3],
354 | p4: ToPlaceholder[T4],
355 | p5: ToPlaceholder[T5],
356 | p6: ToPlaceholder[T6],
357 | p7: ToPlaceholder[T7],
358 | p8: ToPlaceholder[T8],
359 | p9: ToPlaceholder[T9],
360 | p10: ToPlaceholder[T10],
361 | p11: ToPlaceholder[T11],
362 | p12: ToPlaceholder[T12],
363 | p13: ToPlaceholder[T13],
364 | p14: ToPlaceholder[T14],
365 | p15: ToPlaceholder[T15],
366 | p16: ToPlaceholder[T16],
367 | p17: ToPlaceholder[T17],
368 | p18: ToPlaceholder[T18],
369 | p19: ToPlaceholder[T19],
370 | p20: ToPlaceholder[T20],
371 | p21: ToPlaceholder[T21]
372 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21)] =
373 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21)
374 |
375 | @inline implicit def tuple22ToPlaceholder[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](implicit
376 | p1: ToPlaceholder[T1],
377 | p2: ToPlaceholder[T2],
378 | p3: ToPlaceholder[T3],
379 | p4: ToPlaceholder[T4],
380 | p5: ToPlaceholder[T5],
381 | p6: ToPlaceholder[T6],
382 | p7: ToPlaceholder[T7],
383 | p8: ToPlaceholder[T8],
384 | p9: ToPlaceholder[T9],
385 | p10: ToPlaceholder[T10],
386 | p11: ToPlaceholder[T11],
387 | p12: ToPlaceholder[T12],
388 | p13: ToPlaceholder[T13],
389 | p14: ToPlaceholder[T14],
390 | p15: ToPlaceholder[T15],
391 | p16: ToPlaceholder[T16],
392 | p17: ToPlaceholder[T17],
393 | p18: ToPlaceholder[T18],
394 | p19: ToPlaceholder[T19],
395 | p20: ToPlaceholder[T20],
396 | p21: ToPlaceholder[T21],
397 | p22: ToPlaceholder[T22]
398 | ): ToPlaceholder[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22)] =
399 | new FromTuple[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22)](p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22)
400 | }
401 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/tarao/slickjdbc/query/Translator.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package query
4 |
5 | import slick.jdbc.SQLActionBuilder
6 |
7 | trait Context {
8 | import java.lang.StackTraceElement
9 |
10 | def caller = Thread.currentThread.getStackTrace.reverse.takeWhile { trace =>
11 | trace.getClassName != getClass.getName
12 | }.lastOption getOrElse new StackTraceElement("Unknown", "method", null, -1)
13 | }
14 |
15 | trait Translator {
16 | def apply(query: String, context: Context): String
17 | }
18 |
19 | object MarginStripper extends Translator {
20 | def apply(query: String, context: Context) = query.stripMargin
21 | }
22 |
23 | object CallerCommenter extends Translator {
24 | def apply(query: String, context: Context) =
25 | new SQLComment(context.caller).embedTo(query)
26 | }
27 |
28 | case class SQLComment(comment: Any) {
29 | import scala.util.matching.Regex
30 |
31 | def escaped = comment.toString.replace("*/", """*\\/""")
32 |
33 | def embedTo(query: String) =
34 | query.replaceFirst(" ", Regex.quoteReplacement(s" /* ${escaped} */ "))
35 | }
36 |
37 | object Translator extends Context {
38 | implicit val default: Iterable[Translator] =
39 | Seq(MarginStripper, CallerCommenter)
40 | def translate(query: String)(implicit
41 | translators: Iterable[Translator]
42 | ) = translators.foldLeft(query) { (q, translate) => translate(q, this) }
43 | def translateBuilder(builder: SQLActionBuilder)(implicit
44 | translators: Iterable[Translator]
45 | ): SQLActionBuilder = {
46 | val query = builder.strings.mkString
47 | SQLActionBuilder(Seq(translate(query)(translators)), builder.params)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/resources/application.conf:
--------------------------------------------------------------------------------
1 | h2memtest = {
2 | url = "jdbc:h2:mem:test;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1"
3 | driver = org.h2.Driver
4 | connectionPool = disabled
5 | keepAliveConnection = true
6 | }
7 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/tarao/slickjdbc/IntegrationSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 |
4 | import eu.timepit.refined.api.Refined
5 | import eu.timepit.refined.auto.autoUnwrap
6 | import eu.timepit.refined.collection.NonEmpty
7 | import eu.timepit.refined.refineV
8 | import helper.{UnitSpec, TestDB, Repository}
9 | import interpolation.{SQLInterpolation, CompoundParameter, TableName}
10 | import getresult.{GetResult, AutoUnwrapOption, TypeBinder}
11 | import slick.jdbc.{SetParameter => SP, PositionedParameters}
12 | import slick.jdbc
13 |
14 | case class URL(url: String)
15 | case class Entry(id: Long, url: URL)
16 |
17 | class MyURL(val url: String)
18 |
19 | /** A sample repository trait */
20 | trait EntryRepository extends Repository
21 | with SQLInterpolation with CompoundParameter
22 | with GetResult with AutoUnwrapOption {
23 |
24 | implicit object SetMyURL extends SP[MyURL] {
25 | def apply(v: MyURL, pp: PositionedParameters): Unit = {
26 | pp.setString(v.url)
27 | }
28 | }
29 |
30 | implicit val getEntryResult: jdbc.GetResult[Entry] = getResult { Entry(
31 | column("entry_id"),
32 | column("url")
33 | ) }
34 | implicit val getTupleResult: jdbc.GetResult[(Long, String)] = getResult {
35 | ( column[Long]("entry_id"), column[String]("url") )
36 | }
37 | implicit def urlBinder(implicit
38 | binder: TypeBinder[Option[String]]
39 | ): TypeBinder[Option[URL]] = binder.map(_.map(URL(_)))
40 |
41 | val table = TableName("entry")
42 |
43 | def add1(entry: Entry) =
44 | db.run { sqlu"""
45 | | INSERT INTO ${table} (entry_id, url)
46 | | VALUES (${entry.id}, ${entry.url})
47 | """ }
48 |
49 | def add2(entry: Entry) =
50 | db.run { sqlu"""
51 | | INSERT INTO ${table} (entry_id, url)
52 | | VALUES ${(entry.id, entry.url)}
53 | """ }
54 |
55 | def add1(entries: Seq[Entry] Refined NonEmpty) =
56 | db.run { sqlu"""
57 | | INSERT INTO ${table} (entry_id, url)
58 | | VALUES $entries
59 | """ }
60 |
61 | def add2(entries: Seq[(Long, URL)] Refined NonEmpty) =
62 | db.run { sqlu"""
63 | | INSERT INTO ${table} (entry_id, url)
64 | | VALUES $entries
65 | """ }
66 |
67 | def add3(entries: Seq[(Long, MyURL)] Refined NonEmpty) =
68 | db.run { sqlu"""
69 | | INSERT INTO ${table} (entry_id, url)
70 | | VALUES $entries
71 | """ }
72 |
73 | def find(entryId: Long): Option[Entry] =
74 | db.run { sql"""
75 | | SELECT * FROM ${table}
76 | | WHERE entry_id = $entryId
77 | """.as[Entry] }.headOption
78 |
79 | def findAsTuple(entryId: Long): Option[(Long, String)] =
80 | db.run { sql"""
81 | | SELECT * FROM ${table}
82 | | WHERE entry_id = $entryId
83 | """.as[(Long, String)] }.headOption
84 |
85 | def find1(entryIds: Seq[Long] Refined NonEmpty) =
86 | db.run { sql"""
87 | | SELECT * FROM ${table}
88 | | WHERE entry_id IN ($entryIds)
89 | | ORDER BY entry_id ASC
90 | """.as[Entry] }
91 |
92 | def findByUrls1(urls: Seq[URL] Refined NonEmpty) =
93 | db.run { sql"""
94 | | SELECT * FROM ${table}
95 | | WHERE url IN ($urls)
96 | | ORDER BY entry_id ASC
97 | """.as[Entry] }
98 | }
99 |
100 | class IntegrationSpec extends UnitSpec with TestDB with EntryRepository {
101 | def freshEntry = {
102 | val id = helper.FreshId()
103 | Entry(id, URL("http://example.com/" + id))
104 | }
105 |
106 | describe("SELECTing a record") {
107 | it("should succeed") {
108 | val entry = freshEntry
109 | add1(entry)
110 | val result = find(entry.id)
111 | result shouldBe Some(entry)
112 | }
113 |
114 | it("should fail if nothing matched") {
115 | find(helper.FreshId.max) shouldBe empty
116 | }
117 | }
118 |
119 | describe("SELECTing a record as a tuple") {
120 | it("should succeed") {
121 | val entry = freshEntry
122 | add1(entry)
123 | val result = findAsTuple(entry.id)
124 | result shouldBe Some( (entry.id, entry.url.url) )
125 | }
126 |
127 | it("should fail if nothing matched") {
128 | findAsTuple(helper.FreshId.max) shouldBe empty
129 | }
130 | }
131 |
132 | describe("SELECTing multiple entries") {
133 | it("should succeed") {
134 | val entries1 = Iterator.continually{ freshEntry }.take(10).toSeq
135 | for (e <- entries1) add1(e)
136 |
137 | val entryIds = scala.util.Random.shuffle(entries1.map(_.id)).toSeq
138 | val Right(entryIds1) = refineV[NonEmpty](entryIds)
139 | val result1 = find1(entryIds1)
140 | result1 should be (entries1)
141 | }
142 |
143 | it("should return an empty list if nothing matched") {
144 | val entryIds = Iterator.continually{ helper.FreshId()+0L }.take(10).toSeq
145 | val Right(entryIds1) =
146 | refineV[NonEmpty](scala.util.Random.shuffle(entryIds).toSeq)
147 | val result = find1(entryIds1)
148 | result shouldBe Seq.empty
149 | }
150 | }
151 |
152 | describe("INSERTing multiple entries") {
153 | it("should succeed") {
154 | locally {
155 | val entries = Iterator.continually{ freshEntry }.take(10).toSeq
156 | val Right(entries1) = refineV[NonEmpty](entries)
157 | add1(entries1)
158 |
159 | val Right(entryIds) = refineV[NonEmpty](entries.map(_.id).toSeq)
160 | val result = find1(entryIds)
161 | result should be (entries)
162 | }
163 |
164 | locally {
165 | val entries = Iterator.continually{ freshEntry }.take(10).toSeq
166 | val Right(entries1) =
167 | refineV[NonEmpty](entries.map{ e => (e.id, e.url) })
168 | add2(entries1)
169 |
170 | val Right(urls) = refineV[NonEmpty](entries.map(_.url).toSeq)
171 | val result = findByUrls1(urls)
172 | result should be (entries)
173 | }
174 |
175 | locally {
176 | val entries = Iterator.continually{ freshEntry }.take(10).toSeq
177 | val Right(entries1) =
178 | refineV[NonEmpty](entries.map{ e => (e.id, new MyURL(e.url.url)) })
179 | add3(entries1)
180 |
181 | val Right(urls) = refineV[NonEmpty](entries.map(_.url).toSeq)
182 | val result = findByUrls1(urls)
183 | result should be (entries)
184 | }
185 | }
186 | }
187 | }
188 |
189 | /** A sample repository trait */
190 | trait IdsRepository extends Repository
191 | with SQLInterpolation with CompoundParameter
192 | with GetResult with AutoUnwrapOption {
193 |
194 | def add1(ids: Seq[Tuple1[Long]] Refined NonEmpty) =
195 | db.run { sqlu"""
196 | | INSERT INTO ids (id)
197 | | VALUES ($ids)
198 | """ }
199 |
200 | def addBadly1(ids: Seq[Long] Refined NonEmpty) =
201 | db.run { sqlu"""
202 | | INSERT INTO ids (id)
203 | | VALUES ($ids)
204 | """ }
205 |
206 | def find1(ids: Seq[Long] Refined NonEmpty) =
207 | db.run { sql"""
208 | | SELECT * FROM ids
209 | | WHERE id IN $ids
210 | | ORDER BY id ASC
211 | """.as[Long] }
212 | }
213 |
214 | class SingleTupleSpec extends UnitSpec with TestDB with IdsRepository {
215 | def freshId = helper.FreshId().asInstanceOf[Long]
216 |
217 | describe("SELECTing multiple records INSERTed by single tuples") {
218 | it("should succeed") {
219 | val ids = Iterator.continually{ freshId }.take(10).toSeq
220 | val tuples = ids.map(Tuple1(_))
221 | val Right(tuples1) = refineV[NonEmpty](tuples)
222 | add1(tuples1)
223 |
224 | val Right(ids1) =
225 | refineV[NonEmpty](scala.util.Random.shuffle(ids).toSeq)
226 | val result = find1(ids1)
227 | result should be (ids)
228 | }
229 | }
230 |
231 | describe("INSERTing single columns by a list") {
232 | it("should fail") {
233 | val ids = Iterator.continually{ freshId }.take(10).toSeq
234 | val Right(ids1) = refineV[NonEmpty](ids)
235 | a [java.sql.SQLException] should be thrownBy addBadly1(ids1)
236 | }
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/tarao/slickjdbc/getresult/GetResultSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package getresult
4 |
5 | import scala.reflect.Manifest
6 | import helper.{UnitSpec, TraitSingletonBehavior}
7 | import org.scalamock.scalatest.MockFactory
8 | import java.sql.ResultSet
9 | import slick.jdbc.{GetResult => GR, PositionedResult}
10 |
11 | case class OptionedEntry(id: Option[Long], url: Option[String])
12 | case class SimpleEntry(id: Long, url: String)
13 |
14 | case class URL(url: String)
15 | object URL {
16 | implicit def getUrl(implicit str: TypeBinder[String]): TypeBinder[URL] =
17 | str.map(URL(_))
18 | }
19 | case class UrlEntry(id: Long, url: URL)
20 |
21 | trait GetResultBehavior { self: UnitSpec =>
22 | def positionedResult(rs: ResultSet) =
23 | new PositionedResult(rs) { def close = {} }
24 |
25 | def mappingResult[R : Manifest](rs: ResultSet, expected: R)(implicit
26 | rconv: GR[R]
27 | ) = {
28 | val result = rconv(positionedResult(rs).restart)
29 | result shouldBe a [R]
30 | result should be (expected)
31 | }
32 | }
33 |
34 | class GetResultSpec extends UnitSpec
35 | with GetResultBehavior with MockFactory {
36 | describe("Explicit usage of GetResult") {
37 | it("should resolve a result by named column") {
38 | implicit val getEntryResult = GetResult { r => OptionedEntry(
39 | r.column("entry_id"),
40 | r.column("url")
41 | ) }
42 |
43 | val id = 1234L
44 | val url = "http://github.com/tarao/"
45 |
46 | val rs = mock[ResultSet]
47 | (rs.getObject(_: String)).expects("entry_id").returning(
48 | java.lang.Long.valueOf(id)
49 | )
50 | (rs.getString(_: String)).expects("url").returning(url)
51 |
52 | it should behave like mappingResult(rs, OptionedEntry(
53 | Option(id),
54 | Option(url))
55 | )
56 | }
57 |
58 | it("should resolve a result by named column with options unwrapped") {
59 | import AutoUnwrapOption._
60 |
61 | implicit val getEntryResult = GetResult { r => SimpleEntry(
62 | r.column("entry_id"),
63 | r.column("url")
64 | ) }
65 |
66 | val id = 1234L
67 | val url = "http://github.com/tarao/"
68 |
69 | val rs = mock[ResultSet]
70 | (rs.getObject(_: String)).expects("entry_id").returning(
71 | java.lang.Long.valueOf(id)
72 | )
73 | (rs.getString(_: String)).expects("url").returning(url)
74 |
75 | it should behave like mappingResult(rs, SimpleEntry(id, url))
76 | }
77 |
78 | it("should resolve a result by named column with a custom type") {
79 | import AutoUnwrapOption._
80 |
81 | implicit val getEntryResult = GetResult { r => UrlEntry(
82 | r.column("entry_id"),
83 | r.column("url")
84 | ) }
85 |
86 | val id = 1234L
87 | val url = "http://github.com/tarao/"
88 |
89 | val rs = mock[ResultSet]
90 | (rs.getObject(_: String)).expects("entry_id").returning(
91 | java.lang.Long.valueOf(id)
92 | )
93 | (rs.getString(_: String)).expects("url").returning(url)
94 |
95 | it should behave like mappingResult(rs, UrlEntry(id, URL(url)))
96 | }
97 |
98 | it("should resolve a result by positioned column") {
99 | implicit val getEntryResult = GetResult { r => OptionedEntry(
100 | r.<<,
101 | r.<
102 | ) }
103 |
104 | val id = 1234L
105 | val url = "http://github.com/tarao/"
106 |
107 | val rs = mock[ResultSet]
108 | (rs.getObject(_: Int)).expects(1).returning(
109 | java.lang.Long.valueOf(id)
110 | )
111 | (rs.getString(_: Int)).expects(2).returning(url)
112 |
113 | it should behave like mappingResult(rs, OptionedEntry(
114 | Option(id),
115 | Option(url))
116 | )
117 | }
118 |
119 | it("should resolve a result by positioned column with options unwrapped") {
120 | import AutoUnwrapOption._
121 |
122 | implicit val getEntryResult = GetResult { r => SimpleEntry(
123 | r.<<,
124 | r.skip.<<
125 | ) }
126 |
127 | val id = 1234L
128 | val url = "http://github.com/tarao/"
129 |
130 | val rs = mock[ResultSet]
131 | (rs.getObject(_: Int)).expects(1).returning(
132 | java.lang.Long.valueOf(id)
133 | )
134 | (rs.getString(_: Int)).expects(3).returning(url)
135 |
136 | it should behave like mappingResult(rs, SimpleEntry(id, url))
137 | }
138 |
139 | it("should resolve a result by positioned column with a custom type") {
140 | import AutoUnwrapOption._
141 |
142 | implicit val getEntryResult = GetResult { r => UrlEntry(
143 | r.skip.<<,
144 | r.<<
145 | ) }
146 |
147 | val id = 1234L
148 | val url = "http://github.com/tarao/"
149 |
150 | val rs = mock[ResultSet]
151 | (rs.getObject(_: Int)).expects(2).returning(
152 | java.lang.Long.valueOf(id)
153 | )
154 | (rs.getString(_: Int)).expects(3).returning(url)
155 |
156 | it should behave like mappingResult(rs, UrlEntry(id, URL(url)))
157 | }
158 | }
159 | }
160 |
161 | class GetResultTraitSpec extends UnitSpec
162 | with GetResult
163 | with GetResultBehavior with MockFactory {
164 | describe("Implicit usage of GetResult") {
165 | it("should resolve a result by named column") {
166 | implicit val getEntryResult = getResult { OptionedEntry(
167 | column("entry_id"),
168 | column("url")
169 | ) }
170 |
171 | val id = 1234L
172 | val url = "http://github.com/tarao/"
173 |
174 | val rs = mock[ResultSet]
175 | (rs.getObject(_: String)).expects("entry_id").returning(
176 | java.lang.Long.valueOf(id)
177 | )
178 | (rs.getString(_: String)).expects("url").returning(url)
179 |
180 | it should behave like mappingResult(rs, OptionedEntry(
181 | Option(id),
182 | Option(url))
183 | )
184 | }
185 |
186 | it("should resolve a result by named column with options unwrapped") {
187 | import AutoUnwrapOption._
188 |
189 | implicit val getEntryResult = getResult { SimpleEntry(
190 | column("entry_id"),
191 | column("url")
192 | ) }
193 |
194 | val id = 1234L
195 | val url = "http://github.com/tarao/"
196 |
197 | val rs = mock[ResultSet]
198 | (rs.getObject(_: String)).expects("entry_id").returning(
199 | java.lang.Long.valueOf(id)
200 | )
201 | (rs.getString(_: String)).expects("url").returning(url)
202 |
203 | it should behave like mappingResult(rs, SimpleEntry(id, url))
204 | }
205 |
206 | it("should resolve a result by named column with a custom type") {
207 | import AutoUnwrapOption._
208 |
209 | implicit val getEntryResult = getResult { UrlEntry(
210 | column("entry_id"),
211 | column("url")
212 | ) }
213 |
214 | val id = 1234L
215 | val url = "http://github.com/tarao/"
216 |
217 | val rs = mock[ResultSet]
218 | (rs.getObject(_: String)).expects("entry_id").returning(
219 | java.lang.Long.valueOf(id)
220 | )
221 | (rs.getString(_: String)).expects("url").returning(url)
222 |
223 | it should behave like mappingResult(rs, UrlEntry(id, URL(url)))
224 | }
225 |
226 | it("should resolve a result by positioned column") {
227 | implicit val getEntryResult = getResult { OptionedEntry(
228 | <<,
229 | <
230 | ) }
231 |
232 | val id = 1234L
233 | val url = "http://github.com/tarao/"
234 |
235 | val rs = mock[ResultSet]
236 | (rs.getObject(_: Int)).expects(1).returning(
237 | java.lang.Long.valueOf(id)
238 | )
239 | (rs.getString(_: Int)).expects(2).returning(url)
240 |
241 | it should behave like mappingResult(rs, OptionedEntry(
242 | Option(id),
243 | Option(url))
244 | )
245 | }
246 |
247 | it("should resolve a result by positioned column with options unwrapped") {
248 | import AutoUnwrapOption._
249 |
250 | implicit val getEntryResult = getResult { SimpleEntry(
251 | <<,
252 | skip.<<
253 | ) }
254 |
255 | val id = 1234L
256 | val url = "http://github.com/tarao/"
257 |
258 | val rs = mock[ResultSet]
259 | (rs.getObject(_: Int)).expects(1).returning(
260 | java.lang.Long.valueOf(id)
261 | )
262 | (rs.getString(_: Int)).expects(3).returning(url)
263 |
264 | it should behave like mappingResult(rs, SimpleEntry(id, url))
265 | }
266 |
267 | it("should resolve a result by positioned column with a custom type") {
268 | import AutoUnwrapOption._
269 |
270 | implicit val getEntryResult = getResult { UrlEntry(
271 | skip.<<,
272 | <<
273 | ) }
274 |
275 | val id = 1234L
276 | val url = "http://github.com/tarao/"
277 |
278 | val rs = mock[ResultSet]
279 | (rs.getObject(_: Int)).expects(2).returning(
280 | java.lang.Long.valueOf(id)
281 | )
282 | (rs.getString(_: Int)).expects(3).returning(url)
283 |
284 | it should behave like mappingResult(rs, UrlEntry(id, URL(url)))
285 | }
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/tarao/slickjdbc/getresult/TypeBinderSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package getresult
4 |
5 | import scala.language.implicitConversions
6 | import helper.{UnitSpec, TraitSingletonBehavior}
7 | import org.scalamock.scalatest.MockFactory
8 | import java.sql.ResultSet
9 | import java.io.{
10 | InputStream,
11 | ByteArrayInputStream,
12 | InputStreamReader,
13 | BufferedReader,
14 | Reader
15 | }
16 |
17 | class TypeBinderSpec extends UnitSpec with MockFactory {
18 | def column[T](rs: ResultSet, index: Int, expected: T)(implicit
19 | binder: TypeBinder[T]
20 | ) = { binder.apply(rs, index) should be (expected) }
21 | def column[T](rs: ResultSet, field: String, expected: T)(implicit
22 | binder: TypeBinder[T]
23 | ) = { binder.apply(rs, field) should be (expected) }
24 | def throwingFromColumn[T](rs: ResultSet, index: Int)(implicit
25 | binder: TypeBinder[T]
26 | ) = { a [NoSuchElementException] should be thrownBy binder.apply(rs, index) }
27 | def throwingFromColumn[T](rs: ResultSet, field: String)(implicit
28 | binder: TypeBinder[T]
29 | ) = { a [NoSuchElementException] should be thrownBy binder.apply(rs, field) }
30 |
31 | describe("TypeBinder[String]") {
32 | it("should be able to get a String value") {
33 | val rs = mock[ResultSet]
34 | (rs.getString(_: Int)).expects(1).twice().returning("foo bar")
35 | (rs.getString(_: String)).expects("column1").twice().returning("foo bar")
36 |
37 | it should behave like column(rs, 1, Option("foo bar"))
38 | it should behave like column(rs, "column1", Option("foo bar"))
39 |
40 | assertTypeError("""
41 | it should behave like column(rs, 1, "foo bar")
42 | """)
43 | assertTypeError("""
44 | it should behave like column(rs, "column1", "foo bar")
45 | """)
46 |
47 | import AutoUnwrapOption._
48 |
49 | it should behave like column(rs, 1, "foo bar")
50 | it should behave like column(rs, "column1", "foo bar")
51 | }
52 |
53 | it("should not be able to get a String from null") {
54 | val rs = mock[ResultSet]
55 | (rs.getString(_: Int)).expects(0).twice().returning(null)
56 | (rs.getString(_: String)).expects("null").twice().returning(null)
57 |
58 | it should behave like column[Option[String]](rs, 0, None)
59 | it should behave like column[Option[String]](rs, "null", None)
60 |
61 | import AutoUnwrapOption._
62 |
63 | it should behave like throwingFromColumn[String](rs, 0)
64 | it should behave like throwingFromColumn[String](rs, "null")
65 | }
66 | }
67 |
68 | describe("TypeBinder[BigDecimal]") {
69 | it("should be able to get a BigDecimal value") {
70 | val rs = mock[ResultSet]
71 | (rs.getBigDecimal(_: Int)).expects(1).repeat(2).returning(
72 | new java.math.BigDecimal("1234567")
73 | )
74 | (rs.getBigDecimal(_: String)).expects("column1").repeat(2).returning(
75 | new java.math.BigDecimal("1234567")
76 | )
77 | (rs.getBigDecimal(_: Int)).expects(2).repeat(2).returning(
78 | new java.math.BigDecimal("12345678901234567")
79 | )
80 | (rs.getBigDecimal(_: String)).expects("column2").repeat(2).returning(
81 | new java.math.BigDecimal("12345678901234567")
82 | )
83 |
84 | it should behave like column(rs, 1, Option(BigDecimal("1234567")))
85 | it should behave like column(rs, "column1", Option(BigDecimal("1234567")))
86 |
87 | it should behave like
88 | column(rs, 2, Option(BigDecimal("12345678901234567")))
89 | it should behave like
90 | column(rs, "column2", Option(BigDecimal("12345678901234567")))
91 |
92 | assertTypeError("""
93 | it should behave like column(rs, 1, BigDecimal("1234567"))
94 | """)
95 | assertTypeError("""
96 | it should behave like column(rs, "column1", BigDecimal("1234567"))
97 | """)
98 |
99 | import AutoUnwrapOption._
100 |
101 | it should behave like column(rs, 1, BigDecimal("1234567"))
102 | it should behave like column(rs, "column1", BigDecimal("1234567"))
103 |
104 | it should behave like
105 | column(rs, 2, BigDecimal("12345678901234567"))
106 | it should behave like
107 | column(rs, "column2", BigDecimal("12345678901234567"))
108 | }
109 |
110 | it("should not be able to get a BigDecimal from null") {
111 | val rs = mock[ResultSet]
112 | (rs.getBigDecimal(_: Int)).expects(0).repeat(2).returning(null)
113 | (rs.getBigDecimal(_: String)).expects("null").repeat(2).returning(null)
114 |
115 | it should behave like column[Option[BigDecimal]](rs, 0, None)
116 | it should behave like column[Option[BigDecimal]](rs, "null", None)
117 |
118 | import AutoUnwrapOption._
119 |
120 | it should behave like throwingFromColumn[BigDecimal](rs, 0)
121 | it should behave like throwingFromColumn[BigDecimal](rs, "null")
122 | }
123 | }
124 |
125 | describe("TypeBinder[Boolean]") {
126 | it("should be able to get a Boolean value") {
127 | val rs = mock[ResultSet]
128 | (rs.getObject(_: Int)).expects(1).repeat(2).returning(
129 | java.lang.Boolean.TRUE
130 | )
131 | (rs.getObject(_: String)).expects("column1").repeat(2).returning(
132 | java.lang.Boolean.TRUE
133 | )
134 | (rs.getObject(_: Int)).expects(2).repeat(2).returning(
135 | java.lang.Boolean.FALSE
136 | )
137 | (rs.getObject(_: String)).expects("column2").repeat(2).returning(
138 | java.lang.Boolean.FALSE
139 | )
140 | (rs.getObject(_: Int)).expects(3).repeat(2).returning(
141 | java.math.BigDecimal.ONE
142 | )
143 | (rs.getObject(_: String)).expects("column3").repeat(2).returning(
144 | java.math.BigDecimal.ONE
145 | )
146 | (rs.getObject(_: Int)).expects(4).repeat(2).returning(
147 | java.math.BigDecimal.ZERO
148 | )
149 | (rs.getObject(_: String)).expects("column4").repeat(2).returning(
150 | java.math.BigDecimal.ZERO
151 | )
152 | (rs.getObject(_: Int)).expects(5).repeat(2).returning(
153 | java.lang.Integer.valueOf(1)
154 | )
155 | (rs.getObject(_: String)).expects("column5").repeat(2).returning(
156 | java.lang.Integer.valueOf(1)
157 | )
158 | (rs.getObject(_: Int)).expects(6).repeat(2).returning(
159 | java.lang.Integer.valueOf(0)
160 | )
161 | (rs.getObject(_: String)).expects("column6").repeat(2).returning(
162 | java.lang.Integer.valueOf(0)
163 | )
164 | (rs.getObject(_: Int)).expects(7).repeat(2).returning(
165 | java.lang.Float.valueOf(1.0f)
166 | )
167 | (rs.getObject(_: String)).expects("column7").repeat(2).returning(
168 | java.lang.Float.valueOf(1.0f)
169 | )
170 | (rs.getObject(_: Int)).expects(8).repeat(2).returning(
171 | java.lang.Float.valueOf(0.0f)
172 | )
173 | (rs.getObject(_: String)).expects("column8").repeat(2).returning(
174 | java.lang.Float.valueOf(0.0f)
175 | )
176 | (rs.getObject(_: Int)).expects(9).repeat(2).returning(
177 | new java.lang.String("1")
178 | )
179 | (rs.getObject(_: String)).expects("column9").repeat(2).returning(
180 | new java.lang.String("1")
181 | )
182 | (rs.getObject(_: Int)).expects(10).repeat(2).returning(
183 | new java.lang.String("0")
184 | )
185 | (rs.getObject(_: String)).expects("column10").repeat(2).returning(
186 | new java.lang.String("0")
187 | )
188 | (rs.getObject(_: Int)).expects(11).repeat(2).returning(
189 | new java.lang.String("hoge")
190 | )
191 | (rs.getObject(_: String)).expects("column11").repeat(2).returning(
192 | new java.lang.String("hoge")
193 | )
194 | (rs.getObject(_: Int)).expects(12).repeat(2).returning(
195 | new java.lang.String("")
196 | )
197 | (rs.getObject(_: String)).expects("column12").repeat(2).returning(
198 | new java.lang.String("")
199 | )
200 |
201 | val N = 12
202 |
203 | for (i <- 1 to N) {
204 | val b = i % 2 != 0
205 | it should behave like column(rs, i, Option(b))
206 | it should behave like column(rs, "column"+i, Option(b))
207 | }
208 |
209 | assertTypeError("""
210 | it should behave like column(rs, 1, true)
211 | """)
212 | assertTypeError("""
213 | it should behave like column(rs, "column1", true)
214 | """)
215 |
216 | import AutoUnwrapOption._
217 |
218 | for (i <- 1 to N) {
219 | val b = i % 2 != 0
220 | it should behave like column(rs, i, b)
221 | it should behave like column(rs, "column"+i, b)
222 | }
223 | }
224 |
225 | it("should not be able to get a Boolean value from null") {
226 | val rs = mock[ResultSet]
227 | (rs.getObject(_: Int)).expects(0).repeat(2).returning(null)
228 | (rs.getObject(_: String)).expects("null").repeat(2).returning(null)
229 |
230 | it should behave like column[Option[Boolean]](rs, 0, None)
231 | it should behave like column[Option[Boolean]](rs, "null", None)
232 |
233 | import AutoUnwrapOption._
234 |
235 | it should behave like throwingFromColumn[Boolean](rs, 0)
236 | it should behave like throwingFromColumn[Boolean](rs, "null")
237 | }
238 | }
239 |
240 | describe("TypeBinder[Byte]") {
241 | it("should be able to get a Byte value") {
242 | val rs = mock[ResultSet]
243 | (rs.getObject(_: Int)).expects(1).repeat(2).returning(
244 | java.lang.Byte.valueOf(12.toByte)
245 | )
246 | (rs.getObject(_: String)).expects("column1").repeat(2).returning(
247 | java.lang.Byte.valueOf(12.toByte)
248 | )
249 | (rs.getObject(_: Int)).expects(2).repeat(2).returning(
250 | java.lang.Byte.valueOf(224.toByte)
251 | )
252 | (rs.getObject(_: String)).expects("column2").repeat(2).returning(
253 | java.lang.Byte.valueOf(224.toByte)
254 | )
255 | (rs.getObject(_: Int)).expects(3).repeat(2).returning(
256 | java.lang.Integer.valueOf(1281)
257 | )
258 | (rs.getObject(_: String)).expects("column3").repeat(2).returning(
259 | java.lang.Integer.valueOf(1281)
260 | )
261 | (rs.getObject(_: Int)).expects(4).repeat(2).returning(
262 | java.lang.Integer.valueOf(-1281)
263 | )
264 | (rs.getObject(_: String)).expects("column4").repeat(2).returning(
265 | java.lang.Integer.valueOf(-1281)
266 | )
267 | (rs.getObject(_: Int)).expects(5).repeat(2).returning("12")
268 | (rs.getObject(_: String)).expects("column5").repeat(2).returning("12")
269 | (rs.getObject(_: Int)).expects(6).repeat(2).returning("-3")
270 | (rs.getObject(_: String)).expects("column6").repeat(2).returning("-3")
271 | (rs.getObject(_: Int)).expects(7).repeat(2).returning("010")
272 | (rs.getObject(_: String)).expects("column7").repeat(2).returning("010")
273 |
274 | it should behave like column(rs, 1, Option(12.toByte))
275 | it should behave like column(rs, "column1", Option(12.toByte))
276 | it should behave like column(rs, 2, Option(224.toByte))
277 | it should behave like column(rs, "column2", Option(224.toByte))
278 | it should behave like column(rs, 3, Option(1.toByte))
279 | it should behave like column(rs, "column3", Option(1.toByte))
280 | it should behave like column(rs, 4, Option(255.toByte))
281 | it should behave like column(rs, "column4", Option(255.toByte))
282 | it should behave like column(rs, 5, Option(12.toByte))
283 | it should behave like column(rs, "column5", Option(12.toByte))
284 | it should behave like column(rs, 6, Option(-3.toByte))
285 | it should behave like column(rs, "column6", Option(-3.toByte))
286 | it should behave like column(rs, 7, Option(10.toByte))
287 | it should behave like column(rs, "column7", Option(10.toByte))
288 |
289 | assertTypeError("""
290 | it should behave like column(rs, 1, 12.toByte)
291 | """)
292 | assertTypeError("""
293 | it should behave like column(rs, "column1", 12.toByte)
294 | """)
295 |
296 | import AutoUnwrapOption._
297 |
298 | it should behave like column(rs, 1, 12.toByte)
299 | it should behave like column(rs, "column1", 12.toByte)
300 | it should behave like column(rs, 2, 224.toByte)
301 | it should behave like column(rs, "column2", 224.toByte)
302 | it should behave like column(rs, 3, 1.toByte)
303 | it should behave like column(rs, "column3", 1.toByte)
304 | it should behave like column(rs, 4, 255.toByte)
305 | it should behave like column(rs, "column4", 255.toByte)
306 | it should behave like column(rs, 5, 12.toByte)
307 | it should behave like column(rs, "column5", 12.toByte)
308 | it should behave like column(rs, 6, -3.toByte)
309 | it should behave like column(rs, "column6", -3.toByte)
310 | it should behave like column(rs, 7, 10.toByte)
311 | it should behave like column(rs, "column7", 10.toByte)
312 | }
313 |
314 | it("should not be able to get a Byte value from an invalid rep.") {
315 | val rs = mock[ResultSet]
316 | (rs.getObject(_: Int)).expects(1).repeat(2).returning("")
317 | (rs.getObject(_: String)).expects("column1").repeat(2).returning("")
318 | (rs.getObject(_: Int)).expects(2).repeat(2).returning("1281")
319 | (rs.getObject(_: String)).expects("column2").repeat(2).returning("1281")
320 | (rs.getObject(_: Int)).expects(3).repeat(2).returning("foo")
321 | (rs.getObject(_: String)).expects("column3").repeat(2).returning("foo")
322 |
323 | val N = 3
324 |
325 | for (i <- 1 to N) {
326 | it should behave like column[Option[Byte]](rs, i, None)
327 | it should behave like column[Option[Byte]](rs, "column"+i, None)
328 | }
329 |
330 | import AutoUnwrapOption._
331 |
332 | for (i <- 1 to N) {
333 | it should behave like throwingFromColumn[Byte](rs, i)
334 | it should behave like throwingFromColumn[Byte](rs, "column"+i)
335 | }
336 | }
337 |
338 | it("should not be able to get a Byte from null") {
339 | val rs = mock[ResultSet]
340 | (rs.getObject(_: Int)).expects(0).repeat(2).returning(null)
341 | (rs.getObject(_: String)).expects("null").repeat(2).returning(null)
342 |
343 | it should behave like column[Option[Byte]](rs, 0, None)
344 | it should behave like column[Option[Byte]](rs, "null", None)
345 |
346 | import AutoUnwrapOption._
347 |
348 | it should behave like throwingFromColumn[Byte](rs, 0)
349 | it should behave like throwingFromColumn[Byte](rs, "null")
350 | }
351 | }
352 |
353 | describe("TypeBinder[Short]") {
354 | it("should be able to get a Short value") {
355 | val rs = mock[ResultSet]
356 | (rs.getObject(_: Int)).expects(1).repeat(2).returning(
357 | java.lang.Short.valueOf(12.toShort)
358 | )
359 | (rs.getObject(_: String)).expects("column1").repeat(2).returning(
360 | java.lang.Short.valueOf(12.toShort)
361 | )
362 | (rs.getObject(_: Int)).expects(2).repeat(2).returning(
363 | java.lang.Short.valueOf(38000.toShort)
364 | )
365 | (rs.getObject(_: String)).expects("column2").repeat(2).returning(
366 | java.lang.Short.valueOf(38000.toShort)
367 | )
368 | (rs.getObject(_: Int)).expects(3).repeat(2).returning(
369 | java.lang.Integer.valueOf(129780)
370 | )
371 | (rs.getObject(_: String)).expects("column3").repeat(2).returning(
372 | java.lang.Integer.valueOf(129780)
373 | )
374 | (rs.getObject(_: Int)).expects(4).repeat(2).returning(
375 | java.lang.Integer.valueOf(-129781)
376 | )
377 | (rs.getObject(_: String)).expects("column4").repeat(2).returning(
378 | java.lang.Integer.valueOf(-129781)
379 | )
380 | (rs.getObject(_: Int)).expects(5).repeat(2).returning("12")
381 | (rs.getObject(_: String)).expects("column5").repeat(2).returning("12")
382 | (rs.getObject(_: Int)).expects(6).repeat(2).returning("-3")
383 | (rs.getObject(_: String)).expects("column6").repeat(2).returning("-3")
384 | (rs.getObject(_: Int)).expects(7).repeat(2).returning("010")
385 | (rs.getObject(_: String)).expects("column7").repeat(2).returning("010")
386 |
387 | it should behave like column(rs, 1, Option(12.toShort))
388 | it should behave like column(rs, "column1", Option(12.toShort))
389 | it should behave like column(rs, 2, Option(38000.toShort))
390 | it should behave like column(rs, "column2", Option(38000.toShort))
391 | it should behave like column(rs, 3, Option(64244.toShort))
392 | it should behave like column(rs, "column3", Option(64244.toShort))
393 | it should behave like column(rs, 4, Option(1291.toShort))
394 | it should behave like column(rs, "column4", Option(1291.toShort))
395 | it should behave like column(rs, 5, Option(12.toShort))
396 | it should behave like column(rs, "column5", Option(12.toShort))
397 | it should behave like column(rs, 6, Option(-3.toShort))
398 | it should behave like column(rs, "column6", Option(-3.toShort))
399 | it should behave like column(rs, 7, Option(10.toShort))
400 | it should behave like column(rs, "column7", Option(10.toShort))
401 |
402 | assertTypeError("""
403 | it should behave like column(rs, 1, 12.toShort)
404 | """)
405 | assertTypeError("""
406 | it should behave like column(rs, "column1", 12.toShort)
407 | """)
408 |
409 | import AutoUnwrapOption._
410 |
411 | it should behave like column(rs, 1, 12.toShort)
412 | it should behave like column(rs, "column1", 12.toShort)
413 | it should behave like column(rs, 2, 38000.toShort)
414 | it should behave like column(rs, "column2", 38000.toShort)
415 | it should behave like column(rs, 3, 64244.toShort)
416 | it should behave like column(rs, "column3", 64244.toShort)
417 | it should behave like column(rs, 4, 1291.toShort)
418 | it should behave like column(rs, "column4", 1291.toShort)
419 | it should behave like column(rs, 5, 12.toShort)
420 | it should behave like column(rs, "column5", 12.toShort)
421 | it should behave like column(rs, 6, -3.toShort)
422 | it should behave like column(rs, "column6", -3.toShort)
423 | it should behave like column(rs, 7, 10.toShort)
424 | it should behave like column(rs, "column7", 10.toShort)
425 | }
426 |
427 | it("should not be able to get a Short value from an invalid rep.") {
428 | val rs = mock[ResultSet]
429 | (rs.getObject(_: Int)).expects(1).repeat(2).returning("")
430 | (rs.getObject(_: String)).expects("column1").repeat(2).returning("")
431 | (rs.getObject(_: Int)).expects(2).repeat(2).returning("38000")
432 | (rs.getObject(_: String)).expects("column2").repeat(2).returning("38000")
433 | (rs.getObject(_: Int)).expects(3).repeat(2).returning("foo")
434 | (rs.getObject(_: String)).expects("column3").repeat(2).returning("foo")
435 |
436 | val N = 3
437 |
438 | for (i <- 1 to N) {
439 | it should behave like column[Option[Short]](rs, i, None)
440 | it should behave like column[Option[Short]](rs, "column"+i, None)
441 | }
442 |
443 | import AutoUnwrapOption._
444 |
445 | for (i <- 1 to N) {
446 | it should behave like throwingFromColumn[Short](rs, i)
447 | it should behave like throwingFromColumn[Short](rs, "column"+i)
448 | }
449 | }
450 |
451 | it("should not be able to get a Short from null") {
452 | val rs = mock[ResultSet]
453 | (rs.getObject(_: Int)).expects(0).repeat(2).returning(null)
454 | (rs.getObject(_: String)).expects("null").repeat(2).returning(null)
455 |
456 | it should behave like column[Option[Short]](rs, 0, None)
457 | it should behave like column[Option[Short]](rs, "null", None)
458 |
459 | import AutoUnwrapOption._
460 |
461 | it should behave like throwingFromColumn[Short](rs, 0)
462 | it should behave like throwingFromColumn[Short](rs, "null")
463 | }
464 | }
465 |
466 | describe("TypeBinder[Int]") {
467 | it("should be able to get a Int value") {
468 | val rs = mock[ResultSet]
469 | (rs.getObject(_: Int)).expects(1).repeat(2).returning(
470 | java.lang.Integer.valueOf(12)
471 | )
472 | (rs.getObject(_: String)).expects("column1").repeat(2).returning(
473 | java.lang.Integer.valueOf(12)
474 | )
475 | (rs.getObject(_: Int)).expects(2).repeat(2).returning(
476 | java.lang.Integer.valueOf(3000000000L.toInt)
477 | )
478 | (rs.getObject(_: String)).expects("column2").repeat(2).returning(
479 | java.lang.Integer.valueOf(3000000000L.toInt)
480 | )
481 | (rs.getObject(_: Int)).expects(3).repeat(2).returning("12")
482 | (rs.getObject(_: String)).expects("column3").repeat(2).returning("12")
483 | (rs.getObject(_: Int)).expects(4).repeat(2).returning("-3")
484 | (rs.getObject(_: String)).expects("column4").repeat(2).returning("-3")
485 | (rs.getObject(_: Int)).expects(5).repeat(2).returning("010")
486 | (rs.getObject(_: String)).expects("column5").repeat(2).returning("010")
487 |
488 | it should behave like column(rs, 1, Option(12))
489 | it should behave like column(rs, "column1", Option(12))
490 | it should behave like column(rs, 2, Option(3000000000L.toInt))
491 | it should behave like column(rs, "column2", Option(3000000000L.toInt))
492 | it should behave like column(rs, 3, Option(12))
493 | it should behave like column(rs, "column3", Option(12))
494 | it should behave like column(rs, 4, Option(-3))
495 | it should behave like column(rs, "column4", Option(-3))
496 | it should behave like column(rs, 5, Option(10))
497 | it should behave like column(rs, "column5", Option(10))
498 |
499 | assertTypeError("""
500 | it should behave like column(rs, 1, 12)
501 | """)
502 | assertTypeError("""
503 | it should behave like column(rs, "column1", 12)
504 | """)
505 |
506 | import AutoUnwrapOption._
507 |
508 | it should behave like column(rs, 1, 12)
509 | it should behave like column(rs, "column1", 12)
510 | it should behave like column(rs, 2, 3000000000L.toInt)
511 | it should behave like column(rs, "column2", 3000000000L.toInt)
512 | it should behave like column(rs, 3, 12)
513 | it should behave like column(rs, "column3", 12)
514 | it should behave like column(rs, 4, -3)
515 | it should behave like column(rs, "column4", -3)
516 | it should behave like column(rs, 5, 10)
517 | it should behave like column(rs, "column5", 10)
518 | }
519 |
520 | it("should not be able to get a Int value from an invalid rep.") {
521 | val rs = mock[ResultSet]
522 | (rs.getObject(_: Int)).expects(1).repeat(2).returning("")
523 | (rs.getObject(_: String)).expects("column1").repeat(2).returning("")
524 | (rs.getObject(_: Int)).expects(2).repeat(2).returning("6000000000")
525 | (rs.getObject(_: String)).expects("column2").repeat(2).returning("6000000000")
526 | (rs.getObject(_: Int)).expects(3).repeat(2).returning("foo")
527 | (rs.getObject(_: String)).expects("column3").repeat(2).returning("foo")
528 |
529 | val N = 3
530 |
531 | for (i <- 1 to N) {
532 | it should behave like column[Option[Int]](rs, i, None)
533 | it should behave like column[Option[Int]](rs, "column"+i, None)
534 | }
535 |
536 | import AutoUnwrapOption._
537 |
538 | for (i <- 1 to N) {
539 | it should behave like throwingFromColumn[Int](rs, i)
540 | it should behave like throwingFromColumn[Int](rs, "column"+i)
541 | }
542 | }
543 |
544 | it("should not be able to get a Int from null") {
545 | val rs = mock[ResultSet]
546 | (rs.getObject(_: Int)).expects(0).repeat(2).returning(null)
547 | (rs.getObject(_: String)).expects("null").repeat(2).returning(null)
548 |
549 | it should behave like column[Option[Int]](rs, 0, None)
550 | it should behave like column[Option[Int]](rs, "null", None)
551 |
552 | import AutoUnwrapOption._
553 |
554 | it should behave like throwingFromColumn[Int](rs, 0)
555 | it should behave like throwingFromColumn[Int](rs, "null")
556 | }
557 | }
558 |
559 | describe("TypeBinder[Long]") {
560 | it("should be able to get a Long value") {
561 | val rs = mock[ResultSet]
562 | (rs.getObject(_: Int)).expects(1).repeat(2).returning(
563 | java.lang.Long.valueOf(12)
564 | )
565 | (rs.getObject(_: String)).expects("column1").repeat(2).returning(
566 | java.lang.Long.valueOf(12)
567 | )
568 | (rs.getObject(_: Int)).expects(2).repeat(2).returning(
569 | java.lang.Long.valueOf(6000000000L)
570 | )
571 | (rs.getObject(_: String)).expects("column2").repeat(2).returning(
572 | java.lang.Long.valueOf(6000000000L)
573 | )
574 | (rs.getObject(_: Int)).expects(3).repeat(2).returning(
575 | new java.math.BigInteger("1"+"0"*19)
576 | )
577 | (rs.getObject(_: String)).expects("column3").repeat(2).returning(
578 | new java.math.BigInteger("1"+"0"*19)
579 | )
580 | (rs.getObject(_: Int)).expects(4).repeat(2).returning("12")
581 | (rs.getObject(_: String)).expects("column4").repeat(2).returning("12")
582 | (rs.getObject(_: Int)).expects(5).repeat(2).returning("-3")
583 | (rs.getObject(_: String)).expects("column5").repeat(2).returning("-3")
584 | (rs.getObject(_: Int)).expects(6).repeat(2).returning("010")
585 | (rs.getObject(_: String)).expects("column6").repeat(2).returning("010")
586 |
587 | it should behave like column(rs, 1, Option(12))
588 | it should behave like column(rs, "column1", Option(12))
589 | it should behave like column(rs, 2, Option(6000000000L))
590 | it should behave like column(rs, "column2", Option(6000000000L))
591 | it should behave like column(rs, 3, Option(BigInt("1"+"0"*19).longValue))
592 | it should behave like column(rs, "column3", Option(BigInt("1"+"0"*19).longValue))
593 | it should behave like column(rs, 4, Option(12))
594 | it should behave like column(rs, "column4", Option(12))
595 | it should behave like column(rs, 5, Option(-3))
596 | it should behave like column(rs, "column5", Option(-3))
597 | it should behave like column(rs, 6, Option(10))
598 | it should behave like column(rs, "column6", Option(10))
599 |
600 | assertTypeError("""
601 | it should behave like column(rs, 1, 12)
602 | """)
603 | assertTypeError("""
604 | it should behave like column(rs, "column1", 12)
605 | """)
606 |
607 | import AutoUnwrapOption._
608 |
609 | it should behave like column(rs, 1, 12)
610 | it should behave like column(rs, "column1", 12)
611 | it should behave like column(rs, 2, 6000000000L)
612 | it should behave like column(rs, "column2", 6000000000L)
613 | it should behave like column(rs, 3, BigInt("1"+"0"*19).longValue)
614 | it should behave like column(rs, "column3", BigInt("1"+"0"*19).longValue)
615 | it should behave like column(rs, 4, 12)
616 | it should behave like column(rs, "column4", 12)
617 | it should behave like column(rs, 5, -3)
618 | it should behave like column(rs, "column5", -3)
619 | it should behave like column(rs, 6, 10)
620 | it should behave like column(rs, "column6", 10)
621 | }
622 |
623 | it("should not be able to get a Long value from an invalid rep.") {
624 | val rs = mock[ResultSet]
625 | (rs.getObject(_: Int)).expects(1).repeat(2).returning("")
626 | (rs.getObject(_: String)).expects("column1").repeat(2).returning("")
627 | (rs.getObject(_: Int)).expects(2).repeat(2).returning("1"+"0"*19)
628 | (rs.getObject(_: String)).expects("column2").repeat(2).returning("1"+"0"*19)
629 | (rs.getObject(_: Int)).expects(3).repeat(2).returning("foo")
630 | (rs.getObject(_: String)).expects("column3").repeat(2).returning("foo")
631 |
632 | val N = 3
633 |
634 | for (i <- 1 to N) {
635 | it should behave like column[Option[Long]](rs, i, None)
636 | it should behave like column[Option[Long]](rs, "column"+i, None)
637 | }
638 |
639 | import AutoUnwrapOption._
640 |
641 | for (i <- 1 to N) {
642 | it should behave like throwingFromColumn[Long](rs, i)
643 | it should behave like throwingFromColumn[Long](rs, "column"+i)
644 | }
645 | }
646 |
647 | it("should not be able to get a Long from null") {
648 | val rs = mock[ResultSet]
649 | (rs.getObject(_: Int)).expects(0).repeat(2).returning(null)
650 | (rs.getObject(_: String)).expects("null").repeat(2).returning(null)
651 |
652 | it should behave like column[Option[Long]](rs, 0, None)
653 | it should behave like column[Option[Long]](rs, "null", None)
654 |
655 | import AutoUnwrapOption._
656 |
657 | it should behave like throwingFromColumn[Long](rs, 0)
658 | it should behave like throwingFromColumn[Long](rs, "null")
659 | }
660 | }
661 |
662 | describe("TypeBinder[Float]") {
663 | it("should be able to get a Float value") {
664 | val rs = mock[ResultSet]
665 | (rs.getObject(_: Int)).expects(1).repeat(2).returning(
666 | java.lang.Float.valueOf(1.2f)
667 | )
668 | (rs.getObject(_: String)).expects("column1").repeat(2).returning(
669 | java.lang.Float.valueOf(1.2f)
670 | )
671 | (rs.getObject(_: Int)).expects(2).repeat(2).returning("1.2")
672 | (rs.getObject(_: String)).expects("column2").repeat(2).returning("1.2")
673 | (rs.getObject(_: Int)).expects(3).repeat(2).returning("-3.5")
674 | (rs.getObject(_: String)).expects("column3").repeat(2).returning("-3.5")
675 | (rs.getObject(_: Int)).expects(4).repeat(2).returning("010.8")
676 | (rs.getObject(_: String)).expects("column4").repeat(2).returning("010.8")
677 | (rs.getObject(_: Int)).expects(5).repeat(2).returning("30")
678 | (rs.getObject(_: String)).expects("column5").repeat(2).returning("30")
679 | (rs.getObject(_: Int)).expects(6).repeat(2).returning("1"+"0"*20)
680 | (rs.getObject(_: String)).expects("column6").repeat(2).returning("1"+"0"*20)
681 |
682 | it should behave like column(rs, 1, Option(1.2f))
683 | it should behave like column(rs, "column1", Option(1.2f))
684 | it should behave like column(rs, 2, Option(1.2f))
685 | it should behave like column(rs, "column2", Option(1.2f))
686 | it should behave like column(rs, 3, Option(-3.5f))
687 | it should behave like column(rs, "column3", Option(-3.5f))
688 | it should behave like column(rs, 4, Option(10.8f))
689 | it should behave like column(rs, "column4", Option(10.8f))
690 | it should behave like column(rs, 5, Option(30.0f))
691 | it should behave like column(rs, "column5", Option(30.0f))
692 | it should behave like column(rs, 6, Option(1.0e20f))
693 | it should behave like column(rs, "column6", Option(1.0e20f))
694 |
695 | assertTypeError("""
696 | it should behave like column(rs, 1, 1.2f)
697 | """)
698 | assertTypeError("""
699 | it should behave like column(rs, "column1", 1.2f)
700 | """)
701 |
702 | import AutoUnwrapOption._
703 |
704 | it should behave like column(rs, 1, 1.2f)
705 | it should behave like column(rs, "column1", 1.2f)
706 | it should behave like column(rs, 2, 1.2f)
707 | it should behave like column(rs, "column2", 1.2f)
708 | it should behave like column(rs, 3, -3.5f)
709 | it should behave like column(rs, "column3", -3.5f)
710 | it should behave like column(rs, 4, 10.8f)
711 | it should behave like column(rs, "column4", 10.8f)
712 | it should behave like column(rs, 5, 30.0f)
713 | it should behave like column(rs, "column5", 30.0f)
714 | it should behave like column(rs, 6, 1.0e20f)
715 | it should behave like column(rs, "column6", 1.0e20f)
716 | }
717 |
718 | it("should not be able to get a Float value from an invalid rep.") {
719 | val rs = mock[ResultSet]
720 | (rs.getObject(_: Int)).expects(1).repeat(2).returning("")
721 | (rs.getObject(_: String)).expects("column1").repeat(2).returning("")
722 | (rs.getObject(_: Int)).expects(2).repeat(2).returning("foo")
723 | (rs.getObject(_: String)).expects("column2").repeat(2).returning("foo")
724 |
725 | val N = 2
726 |
727 | for (i <- 1 to N) {
728 | it should behave like column[Option[Float]](rs, i, None)
729 | it should behave like column[Option[Float]](rs, "column"+i, None)
730 | }
731 |
732 | import AutoUnwrapOption._
733 |
734 | for (i <- 1 to N) {
735 | it should behave like throwingFromColumn[Float](rs, i)
736 | it should behave like throwingFromColumn[Float](rs, "column"+i)
737 | }
738 | }
739 |
740 | it("should not be able to get a Float from null") {
741 | val rs = mock[ResultSet]
742 | (rs.getObject(_: Int)).expects(0).repeat(2).returning(null)
743 | (rs.getObject(_: String)).expects("null").repeat(2).returning(null)
744 |
745 | it should behave like column[Option[Float]](rs, 0, None)
746 | it should behave like column[Option[Float]](rs, "null", None)
747 |
748 | import AutoUnwrapOption._
749 |
750 | it should behave like throwingFromColumn[Float](rs, 0)
751 | it should behave like throwingFromColumn[Float](rs, "null")
752 | }
753 | }
754 |
755 | describe("TypeBinder[Double]") {
756 | it("should be able to get a Double value") {
757 | val rs = mock[ResultSet]
758 | (rs.getObject(_: Int)).expects(1).repeat(2).returning(
759 | java.lang.Double.valueOf(1.2)
760 | )
761 | (rs.getObject(_: String)).expects("column1").repeat(2).returning(
762 | java.lang.Double.valueOf(1.2)
763 | )
764 | (rs.getObject(_: Int)).expects(2).repeat(2).returning("1.2")
765 | (rs.getObject(_: String)).expects("column2").repeat(2).returning("1.2")
766 | (rs.getObject(_: Int)).expects(3).repeat(2).returning("-3.5")
767 | (rs.getObject(_: String)).expects("column3").repeat(2).returning("-3.5")
768 | (rs.getObject(_: Int)).expects(4).repeat(2).returning("010.8")
769 | (rs.getObject(_: String)).expects("column4").repeat(2).returning("010.8")
770 | (rs.getObject(_: Int)).expects(5).repeat(2).returning("30")
771 | (rs.getObject(_: String)).expects("column5").repeat(2).returning("30")
772 | (rs.getObject(_: Int)).expects(6).repeat(2).returning("1"+"0"*20)
773 | (rs.getObject(_: String)).expects("column6").repeat(2).returning("1"+"0"*20)
774 |
775 | it should behave like column(rs, 1, Option(1.2))
776 | it should behave like column(rs, "column1", Option(1.2))
777 | it should behave like column(rs, 2, Option(1.2f))
778 | it should behave like column(rs, "column2", Option(1.2))
779 | it should behave like column(rs, 3, Option(-3.5f))
780 | it should behave like column(rs, "column3", Option(-3.5))
781 | it should behave like column(rs, 4, Option(10.8f))
782 | it should behave like column(rs, "column4", Option(10.8))
783 | it should behave like column(rs, 5, Option(30.0f))
784 | it should behave like column(rs, "column5", Option(30.0))
785 | it should behave like column(rs, 6, Option(1.0e20f))
786 | it should behave like column(rs, "column6", Option(1.0e20))
787 |
788 | assertTypeError("""
789 | it should behave like column(rs, 1, 1.2f)
790 | """)
791 | assertTypeError("""
792 | it should behave like column(rs, "column1", 1.2f)
793 | """)
794 |
795 | import AutoUnwrapOption._
796 |
797 | it should behave like column(rs, 1, 1.2)
798 | it should behave like column(rs, "column1", 1.2)
799 | it should behave like column(rs, 2, 1.2)
800 | it should behave like column(rs, "column2", 1.2)
801 | it should behave like column(rs, 3, -3.5)
802 | it should behave like column(rs, "column3", -3.5)
803 | it should behave like column(rs, 4, 10.8)
804 | it should behave like column(rs, "column4", 10.8)
805 | it should behave like column(rs, 5, 30.0)
806 | it should behave like column(rs, "column5", 30.0)
807 | it should behave like column(rs, 6, 1.0e20)
808 | it should behave like column(rs, "column6", 1.0e20)
809 | }
810 |
811 | it("should not be able to get a Double value from an invalid rep.") {
812 | val rs = mock[ResultSet]
813 | (rs.getObject(_: Int)).expects(1).repeat(2).returning("")
814 | (rs.getObject(_: String)).expects("column1").repeat(2).returning("")
815 | (rs.getObject(_: Int)).expects(2).repeat(2).returning("foo")
816 | (rs.getObject(_: String)).expects("column2").repeat(2).returning("foo")
817 |
818 | val N = 2
819 |
820 | for (i <- 1 to N) {
821 | it should behave like column[Option[Double]](rs, i, None)
822 | it should behave like column[Option[Double]](rs, "column"+i, None)
823 | }
824 |
825 | import AutoUnwrapOption._
826 |
827 | for (i <- 1 to N) {
828 | it should behave like throwingFromColumn[Double](rs, i)
829 | it should behave like throwingFromColumn[Double](rs, "column"+i)
830 | }
831 | }
832 |
833 | it("should not be able to get a Double from null") {
834 | val rs = mock[ResultSet]
835 | (rs.getObject(_: Int)).expects(0).repeat(2).returning(null)
836 | (rs.getObject(_: String)).expects("null").repeat(2).returning(null)
837 |
838 | it should behave like column[Option[Double]](rs, 0, None)
839 | it should behave like column[Option[Double]](rs, "null", None)
840 |
841 | import AutoUnwrapOption._
842 |
843 | it should behave like throwingFromColumn[Double](rs, 0)
844 | it should behave like throwingFromColumn[Double](rs, "null")
845 | }
846 | }
847 |
848 | describe("TypeBinder[java.net.URL]") {
849 | import java.net.URL
850 |
851 | it("should be able to get a URL value") {
852 | val rs = mock[ResultSet]
853 | (rs.getURL(_: Int)).expects(1).repeat(2).returning(
854 | new URL("http://github.com/tarao/")
855 | )
856 | (rs.getURL(_: String)).expects("column1").repeat(2).returning(
857 | new URL("http://github.com/tarao/")
858 | )
859 |
860 | it should behave like
861 | column(rs, 1, Option(new URL("http://github.com/tarao/")))
862 | it should behave like
863 | column(rs, "column1", Option(new URL("http://github.com/tarao/")))
864 |
865 | assertTypeError("""
866 | it should behave like
867 | column(rs, 1, new URL("http://github.com/tarao/"))
868 | """)
869 | assertTypeError("""
870 | it should behave like
871 | column(rs, "column1", new URL("http://github.com/tarao/"))
872 | """)
873 |
874 | import AutoUnwrapOption._
875 |
876 | it should behave like
877 | column(rs, 1, new URL("http://github.com/tarao/"))
878 | it should behave like
879 | column(rs, "column1", new URL("http://github.com/tarao/"))
880 | }
881 |
882 | it("should not be able to get a URL from null") {
883 | val rs = mock[ResultSet]
884 | (rs.getURL(_: Int)).expects(0).repeat(2).returning(null)
885 | (rs.getURL(_: String)).expects("null").repeat(2).returning(null)
886 |
887 | it should behave like column[Option[URL]](rs, 0, None)
888 | it should behave like column[Option[URL]](rs, "null", None)
889 |
890 | import AutoUnwrapOption._
891 |
892 | it should behave like throwingFromColumn[URL](rs, 0)
893 | it should behave like throwingFromColumn[URL](rs, "null")
894 | }
895 | }
896 |
897 | describe("TypeBinder[java.sql.Date]") {
898 | import java.sql.Date
899 |
900 | it("should be able to get a Date value") {
901 | val today = new Date(System.currentTimeMillis)
902 |
903 | val rs = mock[ResultSet]
904 | (rs.getDate(_: Int)).expects(1).repeat(2).returning(today)
905 | (rs.getDate(_: String)).expects("column1").repeat(2).returning(today)
906 |
907 | it should behave like column(rs, 1, Option(today))
908 | it should behave like column(rs, "column1", Option(today))
909 |
910 | assertTypeError("""
911 | it should behave like column(rs, 1, today)
912 | """)
913 | assertTypeError("""
914 | it should behave like column(rs, "column1", today)
915 | """)
916 |
917 | import AutoUnwrapOption._
918 |
919 | it should behave like column(rs, 1, today)
920 | it should behave like column(rs, "column1", today)
921 | }
922 |
923 | it("should not be able to get a Date from null") {
924 | val rs = mock[ResultSet]
925 | (rs.getDate(_: Int)).expects(0).repeat(2).returning(null)
926 | (rs.getDate(_: String)).expects("null").repeat(2).returning(null)
927 |
928 | it should behave like column[Option[Date]](rs, 0, None)
929 | it should behave like column[Option[Date]](rs, "null", None)
930 |
931 | import AutoUnwrapOption._
932 |
933 | it should behave like throwingFromColumn[Date](rs, 0)
934 | it should behave like throwingFromColumn[Date](rs, "null")
935 | }
936 | }
937 |
938 | describe("TypeBinder[java.sql.Time]") {
939 | import java.sql.Time
940 |
941 | it("should be able to get a Time value") {
942 | val now = new Time(System.currentTimeMillis)
943 |
944 | val rs = mock[ResultSet]
945 | (rs.getTime(_: Int)).expects(1).repeat(2).returning(now)
946 | (rs.getTime(_: String)).expects("column1").repeat(2).returning(now)
947 |
948 | it should behave like column(rs, 1, Option(now))
949 | it should behave like column(rs, "column1", Option(now))
950 |
951 | assertTypeError("""
952 | it should behave like column(rs, 1, now)
953 | """)
954 | assertTypeError("""
955 | it should behave like column(rs, "column1", now)
956 | """)
957 |
958 | import AutoUnwrapOption._
959 |
960 | it should behave like column(rs, 1, now)
961 | it should behave like column(rs, "column1", now)
962 | }
963 |
964 | it("should not be able to get a Time from null") {
965 | val rs = mock[ResultSet]
966 | (rs.getTime(_: Int)).expects(0).repeat(2).returning(null)
967 | (rs.getTime(_: String)).expects("null").repeat(2).returning(null)
968 |
969 | it should behave like column[Option[Time]](rs, 0, None)
970 | it should behave like column[Option[Time]](rs, "null", None)
971 |
972 | import AutoUnwrapOption._
973 |
974 | it should behave like throwingFromColumn[Time](rs, 0)
975 | it should behave like throwingFromColumn[Time](rs, "null")
976 | }
977 | }
978 |
979 | describe("TypeBinder[java.sql.Timestamp]") {
980 | import java.sql.Timestamp
981 |
982 | it("should be able to get a Timestamp value") {
983 | val now = new Timestamp(System.currentTimeMillis)
984 |
985 | val rs = mock[ResultSet]
986 | (rs.getTimestamp(_: Int)).expects(1).repeat(2).returning(now)
987 | (rs.getTimestamp(_: String)).expects("column1").repeat(2).returning(now)
988 |
989 | it should behave like column(rs, 1, Option(now))
990 | it should behave like column(rs, "column1", Option(now))
991 |
992 | assertTypeError("""
993 | it should behave like column(rs, 1, now)
994 | """)
995 | assertTypeError("""
996 | it should behave like column(rs, "column1", now)
997 | """)
998 |
999 | import AutoUnwrapOption._
1000 |
1001 | it should behave like column(rs, 1, now)
1002 | it should behave like column(rs, "column1", now)
1003 | }
1004 |
1005 | it("should not be able to get a Timestamp from null") {
1006 | val rs = mock[ResultSet]
1007 | (rs.getTimestamp(_: Int)).expects(0).repeat(2).returning(null)
1008 | (rs.getTimestamp(_: String)).expects("null").repeat(2).returning(null)
1009 |
1010 | it should behave like column[Option[Timestamp]](rs, 0, None)
1011 | it should behave like column[Option[Timestamp]](rs, "null", None)
1012 |
1013 | import AutoUnwrapOption._
1014 |
1015 | it should behave like throwingFromColumn[Timestamp](rs, 0)
1016 | it should behave like throwingFromColumn[Timestamp](rs, "null")
1017 | }
1018 | }
1019 |
1020 | describe("TypeBinder[Array[Byte]]") {
1021 | it("should be able to get an Array[Byte] value") {
1022 | val rs = mock[ResultSet]
1023 | (rs.getBytes(_: Int)).expects(1).repeat(2).returning(
1024 | Array[Byte](1, 2, 3)
1025 | )
1026 | (rs.getBytes(_: String)).expects("column1").repeat(2).returning(
1027 | Array[Byte](1, 2, 3)
1028 | )
1029 | (rs.getBytes(_: Int)).expects(2).repeat(2).returning(
1030 | Array[Byte]()
1031 | )
1032 | (rs.getBytes(_: String)).expects("column2").repeat(2).returning(
1033 | Array[Byte]()
1034 | )
1035 |
1036 | implicitly[TypeBinder[Option[Array[Byte]]]].apply(rs, 1).get should
1037 | be (Array[Byte](1, 2, 3))
1038 | implicitly[TypeBinder[Option[Array[Byte]]]].apply(rs, "column1").get should
1039 | be (Array[Byte](1, 2, 3))
1040 | implicitly[TypeBinder[Option[Array[Byte]]]].apply(rs, 2).get should
1041 | be (Array[Byte]())
1042 | implicitly[TypeBinder[Option[Array[Byte]]]].apply(rs, "column2").get should
1043 | be (Array[Byte]())
1044 |
1045 | assertTypeError("""
1046 | it should behave like column(rs, 1, Array[Byte](1, 2, 3))
1047 | """)
1048 | assertTypeError("""
1049 | it should behave like column(rs, "column1", Array[Byte](1, 2, 3))
1050 | """)
1051 |
1052 | import AutoUnwrapOption._
1053 |
1054 | it should behave like column(rs, 1, Array[Byte](1, 2, 3))
1055 | it should behave like column(rs, "column1", Array[Byte](1, 2, 3))
1056 | it should behave like column(rs, 2, Array[Byte]())
1057 | it should behave like column(rs, "column2", Array[Byte]())
1058 | }
1059 |
1060 | it("should not be able to get an Array[Byte] from null") {
1061 | val rs = mock[ResultSet]
1062 | (rs.getBytes(_: Int)).expects(0).repeat(2).returning(null)
1063 | (rs.getBytes(_: String)).expects("null").repeat(2).returning(null)
1064 |
1065 | it should behave like column[Option[Array[Byte]]](rs, 0, None)
1066 | it should behave like column[Option[Array[Byte]]](rs, "null", None)
1067 |
1068 | import AutoUnwrapOption._
1069 |
1070 | it should behave like throwingFromColumn[Array[Byte]](rs, 0)
1071 | it should behave like throwingFromColumn[Array[Byte]](rs, "null")
1072 | }
1073 | }
1074 |
1075 | class Reader2Seq(val r: Reader) {
1076 | def toSeq = {
1077 | val reader = new BufferedReader(r)
1078 | Iterator.continually{reader.read}.takeWhile(_ >= 0).toSeq
1079 | }
1080 | }
1081 | implicit def reader2Seq(r: Reader): Reader2Seq = new Reader2Seq(r)
1082 |
1083 | class InputStream2Seq(val is: InputStream) {
1084 | def toSeq = {
1085 | val reader = new BufferedReader(new InputStreamReader(is))
1086 | Iterator.continually{reader.read}.takeWhile(_ >= 0).toSeq
1087 | }
1088 | }
1089 | implicit def inputStream2Seq(is: InputStream): Reader2Seq =
1090 | new Reader2Seq(new InputStreamReader(is))
1091 |
1092 | describe("TypeBinder[java.io.InputStream]") {
1093 | it("should be able to get an InputStream value") {
1094 | val rs = mock[ResultSet]
1095 | (rs.getBinaryStream(_: Int)).expects(1).returning(
1096 | new ByteArrayInputStream(Array[Byte](1, 2, 3))
1097 | )
1098 | (rs.getBinaryStream(_: String)).expects("column1").returning(
1099 | new ByteArrayInputStream(Array[Byte](1, 2, 3))
1100 | )
1101 | (rs.getBinaryStream(_: Int)).expects(2).returning(
1102 | new ByteArrayInputStream(Array[Byte](4, 5, 6))
1103 | )
1104 | (rs.getBinaryStream(_: String)).expects("column2").returning(
1105 | new ByteArrayInputStream(Array[Byte](4, 5, 6))
1106 | )
1107 |
1108 | implicitly[TypeBinder[Option[InputStream]]].apply(rs, 1)
1109 | .get.toSeq should be (Seq(1, 2, 3))
1110 | implicitly[TypeBinder[Option[InputStream]]].apply(rs, "column1")
1111 | .get.toSeq should be (Seq(1, 2, 3))
1112 |
1113 | assertTypeError("""
1114 | implicitly[TypeBinder[InputStream]].apply(rs, 2)
1115 | .toSeq should be (Seq(4, 5, 6))
1116 | """)
1117 | assertTypeError("""
1118 | implicitly[TypeBinder[InputStream]].apply(rs, "column2")
1119 | .toSeq should be (Seq(4, 5, 6))
1120 | """)
1121 |
1122 | import AutoUnwrapOption._
1123 |
1124 | implicitly[TypeBinder[InputStream]].apply(rs, 2)
1125 | .toSeq should be (Seq(4, 5, 6))
1126 | implicitly[TypeBinder[InputStream]].apply(rs, "column2")
1127 | .toSeq should be (Seq(4, 5, 6))
1128 | }
1129 |
1130 | it("should not be be able to get an InputStream from null") {
1131 | val rs = mock[ResultSet]
1132 | (rs.getBinaryStream(_: Int)).expects(0).repeat(2).returning(null)
1133 | (rs.getBinaryStream(_: String)).expects("null").repeat(2).returning(null)
1134 |
1135 | it should behave like column[Option[InputStream]](rs, 0, None)
1136 | it should behave like column[Option[InputStream]](rs, "null", None)
1137 |
1138 | import AutoUnwrapOption._
1139 |
1140 | it should behave like throwingFromColumn[InputStream](rs, 0)
1141 | it should behave like throwingFromColumn[InputStream](rs, "null")
1142 | }
1143 | }
1144 |
1145 | describe("TypeBinder[java.io.Reader]") {
1146 | it("should be able to get an InputStream value") {
1147 | val rs = mock[ResultSet]
1148 | (rs.getCharacterStream(_: Int)).expects(1).returning(
1149 | new InputStreamReader(new ByteArrayInputStream("foo bar".getBytes))
1150 | )
1151 | (rs.getCharacterStream(_: String)).expects("column1").returning(
1152 | new InputStreamReader(new ByteArrayInputStream("foo bar".getBytes))
1153 | )
1154 | (rs.getCharacterStream(_: Int)).expects(2).returning(
1155 | new InputStreamReader(new ByteArrayInputStream("foo bar".getBytes))
1156 | )
1157 | (rs.getCharacterStream(_: String)).expects("column2").returning(
1158 | new InputStreamReader(new ByteArrayInputStream("foo bar".getBytes))
1159 | )
1160 |
1161 | implicitly[TypeBinder[Option[Reader]]].apply(rs, 1)
1162 | .get.toSeq.map(_.asInstanceOf[Char]).mkString should be ("foo bar")
1163 | equal (Seq(1, 2, 3))
1164 | implicitly[TypeBinder[Option[Reader]]].apply(rs, "column1")
1165 | .get.toSeq.map(_.asInstanceOf[Char]).mkString should be ("foo bar")
1166 |
1167 | assertTypeError("""
1168 | implicitly[TypeBinder[Reader]].apply(rs, 2)
1169 | .toSeq.map(_.asInstanceOf[Char]).mkString should be ("foo bar")
1170 | """)
1171 | assertTypeError("""
1172 | implicitly[TypeBinder[Reader]].apply(rs, "column2")
1173 | .toSeq.map(_.asInstanceOf[Char]).mkString should be ("foo bar")
1174 | """)
1175 |
1176 | import AutoUnwrapOption._
1177 |
1178 | implicitly[TypeBinder[Reader]].apply(rs, 2)
1179 | .toSeq.map(_.asInstanceOf[Char]).mkString should be ("foo bar")
1180 | implicitly[TypeBinder[Reader]].apply(rs, "column2")
1181 | .toSeq.map(_.asInstanceOf[Char]).mkString should be ("foo bar")
1182 | }
1183 |
1184 | it("should not be be able to get an Reader from null") {
1185 | val rs = mock[ResultSet]
1186 | (rs.getCharacterStream(_: Int)).expects(0).repeat(2).returning(null)
1187 | (rs.getCharacterStream(_: String)).expects("null").repeat(2).returning(null)
1188 |
1189 | it should behave like column[Option[Reader]](rs, 0, None)
1190 | it should behave like column[Option[Reader]](rs, "null", None)
1191 |
1192 | import AutoUnwrapOption._
1193 |
1194 | it should behave like throwingFromColumn[Reader](rs, 0)
1195 | it should behave like throwingFromColumn[Reader](rs, "null")
1196 | }
1197 | }
1198 | }
1199 |
1200 | class AutoUnwrapOptionSpec extends UnitSpec
1201 | with TraitSingletonBehavior {
1202 | describe("object AutoUnwrapOption") {
1203 | it ("shuold inherit the trait") {
1204 | it should behave like exportingTheTraitMethods
1205 | [AutoUnwrapOption](AutoUnwrapOption)
1206 | }
1207 | }
1208 | }
1209 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/tarao/slickjdbc/helper/TestDB.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package helper
4 |
5 | import scala.language.implicitConversions
6 | import scala.concurrent.duration.Duration
7 | import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}
8 | import org.scalatest.funspec.AnyFunSpec
9 | import slick.jdbc.H2Profile.api.Database
10 |
11 | case class Timeout(duration: Duration)
12 | object Timeout {
13 | implicit val forever: Timeout = Timeout(Duration.Inf)
14 | }
15 |
16 | class DBRunner(val db: Database) {
17 | import scala.concurrent.{Future, Await}
18 | import slick.jdbc.H2Profile.api.Database
19 | import slick.dbio.{DBIOAction, NoStream, Effect}
20 |
21 | def run[R](a: DBIOAction[R, NoStream, Nothing])(implicit
22 | timeout: Timeout
23 | ): R = Await.result(db.run(a), timeout.duration)
24 |
25 | def close = db.close
26 | }
27 |
28 | object FreshId {
29 | var id = 0
30 | def apply() = { id = max; id }
31 | def max = { id + 1 }
32 | }
33 |
34 | trait Repository {
35 | def db: DBRunner
36 | }
37 |
38 | trait TestDB extends BeforeAndAfterAll with BeforeAndAfterEach {
39 | self: AnyFunSpec =>
40 |
41 | lazy val config = {
42 | import com.typesafe.config.{ConfigFactory, ConfigValueFactory => V}
43 | import slick.jdbc.JdbcDataSource
44 |
45 | // Rewrite database name to thread local one so that writing from
46 | // multiple test threads run parallel won't conflict each other.
47 | val c = ConfigFactory.load.getConfig("h2memtest")
48 | val name = "test" + Thread.currentThread.getId
49 | val url = c.getString("url").replaceFirst("""\btest\b""", name)
50 | c.withValue("url", V.fromAnyRef(url))
51 | }
52 |
53 | lazy val db = new DBRunner(Database.forConfig("", config))
54 |
55 | override def beforeAll() = {
56 | import slick.jdbc.H2Profile.api._
57 |
58 | db.run { sqlu"""
59 | CREATE TABLE IF NOT EXISTS entry (
60 | entry_id BIGINT NOT NULL PRIMARY KEY,
61 | url VARCHAR(2048) NOT NULL UNIQUE
62 | )
63 | """ }
64 |
65 | db.run { sqlu"""
66 | CREATE TABLE IF NOT EXISTS ids (
67 | id BIGINT NOT NULL PRIMARY KEY
68 | )
69 | """ }
70 |
71 | super.beforeAll()
72 | }
73 |
74 | override def afterAll() = {
75 | db.close
76 | super.afterAll()
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/tarao/slickjdbc/helper/TraitSingletonBehavior.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package helper
4 |
5 | trait TraitSingletonBehavior { self: UnitSpec =>
6 | import scala.reflect.Manifest
7 | import java.lang.Class
8 |
9 | def signatures[T](clazz: Class[T]): Set[String] =
10 | clazz.getDeclaredMethods.map { x =>
11 | x.getReturnType.toString + " " + x.getName +
12 | "(" + x.getParameterTypes.mkString(", ") + ")"
13 | }.toSet
14 |
15 | /**
16 | Check a singleton object to export methods in a trait. The object
17 | should implement the trait and have exactly the same methods as
18 | the trait. This ensures that importing the methods by `with
19 | TheTrait` and by `import TheSingleton._` have the the same effect.
20 | */
21 | def exportingTheTraitMethods[T : Manifest](singleton: Any) = {
22 | singleton shouldBe a [T]
23 | val parent = implicitly[Manifest[T]].runtimeClass.asInstanceOf[Class[T]]
24 | signatures(singleton.getClass) subsetOf (signatures(parent)) shouldBe true
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/tarao/slickjdbc/helper/UnitSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package helper
4 |
5 | import org.scalatest.Inside
6 | import org.scalatest.Inspectors
7 | import org.scalatest.OptionValues
8 | import org.scalatest.funspec.AnyFunSpec
9 | import org.scalatest.matchers
10 |
11 | abstract class UnitSpec extends AnyFunSpec
12 | with matchers.should.Matchers with OptionValues with Inside with Inspectors
13 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/tarao/slickjdbc/interpolation/InterpolationSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package interpolation
4 |
5 | import helper.{UnitSpec, TraitSingletonBehavior}
6 | import slick.jdbc.TypedParameter
7 | import slick.sql.SqlAction
8 | import slick.dbio.{NoStream, Effect}
9 |
10 | case class EmptyTuple()
11 | case class Single[T](value: T)
12 | case class Double[S, T](left: S, right: T)
13 | case class Triple[S, T, U](left: S, middle: T, right: U)
14 |
15 | class InterpolationSpec extends UnitSpec
16 | with TraitSingletonBehavior {
17 | implicit class EitherOps[A, B](e: Either[A, B]) { // Scala 2.11 compatibility
18 | def toOption: Option[B] = e match {
19 | case Right(x) => Some(x)
20 | case Left(_) => None
21 | }
22 | }
23 |
24 | def canonicalQuery(query: String) =
25 | query.replaceAll("[\r\n\t ]+", " ").trim
26 |
27 | def parsedQuery(sql: SQLActionBuilder): String =
28 | slick.jdbc.SQLInterpolation.parse(sql.strings, sql.params.asInstanceOf[Seq[TypedParameter[Any]]])._1
29 |
30 | def theExactQuery(sql: => SQLActionBuilder)(query: String) = {
31 | parsedQuery(sql) should equal (query)
32 | }
33 |
34 | def anIdenticalQuery(sql: => SQLActionBuilder)(query: String) = {
35 | canonicalQuery(parsedQuery(sql)) should equal (canonicalQuery(query))
36 | }
37 |
38 | def theExactStatement(sql: => SqlAction[Int, NoStream, Effect])
39 | (statement: String) = {
40 | val result = sql
41 | result.statements.mkString should equal (statement)
42 | }
43 |
44 | def anIdenticalStatement(sql: => SqlAction[Int, NoStream, Effect])
45 | (statement: String) = {
46 | val expected = canonicalQuery(statement)
47 | canonicalQuery(sql.statements.mkString) should equal(expected)
48 | }
49 |
50 | describe("object SQLInterpolation") {
51 | it("should inherit the trait") {
52 | it should behave like exportingTheTraitMethods
53 | [SQLInterpolation](SQLInterpolation)
54 | }
55 | }
56 |
57 | describe("SQLInterpolation.sql") {
58 | it("should not be callable without enabling it") {
59 | assertTypeError(""" sql"test" """)
60 | }
61 |
62 | import SQLInterpolation._
63 | implicit val translators: Iterable[query.Translator] = Seq.empty
64 |
65 | it("should return an action builder with a specified string") {
66 | it should behave like theExactQuery{ sql"test" }("test")
67 | it should behave like anIdenticalQuery {
68 | sql"""
69 | SELECT * FROM entry
70 | LIMIT 1
71 | """
72 | }("SELECT * FROM entry LIMIT 1")
73 | }
74 |
75 | it("should embed simple values") {
76 | val id = 1234
77 | val url = "http://github.com/tarao/"
78 |
79 | it should behave like anIdenticalQuery {
80 | sql"""
81 | SELECT * FROM entry
82 | WHERE entry_id = $id AND url = $url
83 | """
84 | }("SELECT * FROM entry WHERE entry_id = ? AND url = ?")
85 | }
86 |
87 | it("should embed a literal string with #${}") {
88 | val tableName = "entry"
89 |
90 | it should behave like anIdenticalQuery {
91 | sql"""
92 | SELECT * FROM #${tableName}
93 | LIMIT 1
94 | """
95 | }("SELECT * FROM entry LIMIT 1")
96 | }
97 |
98 | it("should embed a literal value") {
99 | val tableName = TableName("entry")
100 |
101 | it should behave like anIdenticalQuery {
102 | sql"""
103 | SELECT * FROM ${tableName}
104 | LIMIT 1
105 | """
106 | }("SELECT * FROM entry LIMIT 1")
107 | }
108 |
109 | it("should embed a custom literal value") {
110 | val tableName = new Literal { override def toString = "entry" }
111 |
112 | it should behave like anIdenticalQuery {
113 | sql"""
114 | SELECT * FROM ${tableName}
115 | LIMIT 1
116 | """
117 | }("SELECT * FROM entry LIMIT 1")
118 | }
119 |
120 | it("should not embed an undefined value") {
121 | assertTypeError(""" sql"SELECT * FROM $undefined" """)
122 | }
123 |
124 | it("should not embed a refined non-empty list") {
125 | import eu.timepit.refined.collection.NonEmpty
126 | import eu.timepit.refined.refineV
127 |
128 | val Right(entryIds) = refineV[NonEmpty](Seq(1, 2, 3, 4))
129 |
130 | assertTypeError("""
131 | sql"SELECT * FROM entry WHERE entry_id IN ($entryIds)"
132 | """)
133 | }
134 |
135 | it("should embed a refined non-empty list if it is explicitly enabled") {
136 | import eu.timepit.refined.collection.NonEmpty
137 | import eu.timepit.refined.refineV
138 |
139 | val Right(entryIds) = refineV[NonEmpty](Seq(1, 2, 3, 4))
140 | val Right(entryIds1) = refineV[NonEmpty](Seq(5))
141 |
142 | import CompoundParameter._
143 |
144 | it should behave like anIdenticalQuery {
145 | sql"SELECT * FROM entry WHERE entry_id IN ($entryIds)"
146 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?, ?)")
147 |
148 | it should behave like anIdenticalQuery {
149 | sql"SELECT * FROM entry WHERE entry_id IN $entryIds"
150 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?, ?)")
151 |
152 | it should behave like anIdenticalQuery {
153 | sql"SELECT * FROM entry WHERE entry_id IN (${entryIds1})"
154 | }("SELECT * FROM entry WHERE entry_id IN (?)")
155 |
156 | it should behave like anIdenticalQuery {
157 | sql"SELECT * FROM entry WHERE entry_id IN ${entryIds1}"
158 | }("SELECT * FROM entry WHERE entry_id IN (?)")
159 | }
160 |
161 | it("should embed a refined non-empty set if it is explicitly enabled") {
162 | import eu.timepit.refined.collection.NonEmpty
163 | import eu.timepit.refined.refineV
164 |
165 | val Right(entryIds) = refineV[NonEmpty](Set(1, 2, 3, 4))
166 |
167 | import CompoundParameter._
168 |
169 | it should behave like anIdenticalQuery {
170 | sql"SELECT * FROM entry WHERE entry_id IN ($entryIds)"
171 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?, ?)")
172 |
173 | it should behave like anIdenticalQuery {
174 | sql"SELECT * FROM entry WHERE entry_id IN $entryIds"
175 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?, ?)")
176 | }
177 |
178 | it("should not embed an option non-empty list") {
179 | import eu.timepit.refined.collection.NonEmpty
180 | import eu.timepit.refined.refineV
181 |
182 | import CompoundParameter._
183 |
184 | val entryIds = refineV[NonEmpty](Seq(1, 2, 3, 4)).toOption
185 |
186 | assertTypeError("""
187 | sql"SELECT * FROM entry WHERE entry_id IN ($entryIds)"
188 | """)
189 |
190 | it should behave like anIdenticalQuery {
191 | sql"SELECT * FROM entry WHERE entry_id IN (${entryIds.get})"
192 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?, ?)")
193 | }
194 |
195 | it("should not embed a refined option non-empty list") {
196 | import CompoundParameter._
197 | import eu.timepit.refined.collection.NonEmpty
198 | import eu.timepit.refined.refineV
199 |
200 | val entryIds = refineV[NonEmpty](Seq(1, 2, 3, 4)).toOption
201 |
202 | assertTypeError("""
203 | sql"SELECT * FROM entry WHERE entry_id IN ($entryIds)"
204 | """)
205 | }
206 |
207 | it("should not embed a maybe-empty list") {
208 | val entryIds = Seq(1, 2, 3, 4)
209 |
210 | assertTypeError("""
211 | sql"SELECT * FROM entry WHERE entry_id IN ($entryIds)"
212 | """)
213 |
214 | import CompoundParameter._
215 |
216 | assertTypeError("""
217 | sql"SELECT * FROM entry WHERE entry_id IN ($entryIds)"
218 | """)
219 | }
220 |
221 | it("should embed a tuple") {
222 | val entryIds = (1, 2, 3, 4)
223 |
224 | it should behave like anIdenticalQuery {
225 | sql"SELECT * FROM entry WHERE entry_id IN ($entryIds)"
226 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?, ?)")
227 |
228 | it should behave like anIdenticalQuery {
229 | sql"SELECT * FROM entry WHERE entry_id IN $entryIds"
230 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?, ?)")
231 | }
232 |
233 | it("should fail to embed a nullary product") {
234 | import CompoundParameter._
235 |
236 | val empty = EmptyTuple()
237 | a [java.sql.SQLException] should be thrownBy
238 | sql"SELECT * FROM entry WHERE param = $empty"
239 | }
240 |
241 | it("should not embed a product or a signle tuple") {
242 | val tuple1 = Tuple1(5)
243 | val single = Single(5)
244 | val double = Double(1, 2)
245 | val triple = Triple(1, 2, 3)
246 |
247 | assertTypeError("""
248 | sql"SELECT * FROM entry WHERE entry_id IN ($tuple1)"
249 | """)
250 |
251 | assertTypeError("""
252 | sql"SELECT * FROM entry WHERE entry_id IN ($single)"
253 | """)
254 |
255 | assertTypeError("""
256 | sql"SELECT * FROM entry WHERE entry_id IN ($double)"
257 | """)
258 |
259 | assertTypeError("""
260 | sql"SELECT * FROM entry WHERE entry_id IN ($triple)"
261 | """)
262 | }
263 |
264 | it("should embed a product or a signle tuple if they are explictly enabled") {
265 | val entryIds = (1, 2, 3, 4)
266 | val tuple1 = Tuple1(5)
267 | val single = Single(5)
268 | val double = Double(1, 2)
269 | val triple = Triple(1, 2, 3)
270 |
271 | import CompoundParameter._
272 |
273 | // check if it doesn't break the normal tuple behavior
274 | it should behave like anIdenticalQuery {
275 | sql"SELECT * FROM entry WHERE entry_id IN ($entryIds)"
276 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?, ?)")
277 |
278 | it should behave like anIdenticalQuery {
279 | sql"SELECT * FROM entry WHERE entry_id IN $entryIds"
280 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?, ?)")
281 |
282 | it should behave like anIdenticalQuery {
283 | sql"SELECT * FROM entry WHERE entry_id IN ($tuple1)"
284 | }("SELECT * FROM entry WHERE entry_id IN (?)")
285 | it should behave like anIdenticalQuery {
286 | sql"SELECT * FROM entry WHERE entry_id IN $tuple1"
287 | }("SELECT * FROM entry WHERE entry_id IN (?)")
288 |
289 | it should behave like anIdenticalQuery {
290 | sql"SELECT * FROM entry WHERE entry_id IN ($single)"
291 | }("SELECT * FROM entry WHERE entry_id IN (?)")
292 | it should behave like anIdenticalQuery {
293 | sql"SELECT * FROM entry WHERE entry_id IN $single"
294 | }("SELECT * FROM entry WHERE entry_id IN (?)")
295 |
296 | it should behave like anIdenticalQuery {
297 | sql"SELECT * FROM entry WHERE entry_id IN ($double)"
298 | }("SELECT * FROM entry WHERE entry_id IN (?, ?)")
299 | it should behave like anIdenticalQuery {
300 | sql"SELECT * FROM entry WHERE entry_id IN $double"
301 | }("SELECT * FROM entry WHERE entry_id IN (?, ?)")
302 |
303 | it should behave like anIdenticalQuery {
304 | sql"SELECT * FROM entry WHERE entry_id IN ($triple)"
305 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?)")
306 | it should behave like anIdenticalQuery {
307 | sql"SELECT * FROM entry WHERE entry_id IN $triple"
308 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?)")
309 | }
310 |
311 | it("should not embed a refined non-empty list of product") {
312 | import eu.timepit.refined.collection.NonEmpty
313 | import eu.timepit.refined.refineV
314 |
315 | val Right(params) = refineV[NonEmpty](Seq(
316 | Single("http://example.com/1"),
317 | Single("http://example.com/2"),
318 | Single("http://example.com/3")
319 | ))
320 |
321 | assertTypeError("""
322 | sql"SELECT * FROM entry WHERE entry_id IN ($params)"
323 | """)
324 | }
325 |
326 | it("should embed a refined non-empty list of product if it is explicitly enabled") {
327 | import eu.timepit.refined.collection.NonEmpty
328 | import eu.timepit.refined.refineV
329 |
330 | // This works as if Single[Int] is an alias of Int
331 | val Right(params) = refineV[NonEmpty](Seq(
332 | Single(1),
333 | Single(2),
334 | Single(3)
335 | ))
336 |
337 | import CompoundParameter._
338 |
339 | it should behave like anIdenticalQuery {
340 | sql"SELECT * FROM entry WHERE entry_id IN ($params)"
341 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?)")
342 |
343 | it should behave like anIdenticalQuery {
344 | sql"SELECT * FROM entry WHERE entry_id IN $params"
345 | }("SELECT * FROM entry WHERE entry_id IN (?, ?, ?)")
346 | }
347 |
348 | it("should not embed a refined option value") {
349 | import eu.timepit.refined.collection.NonEmpty
350 | import eu.timepit.refined.refineV
351 |
352 | val param = Option(3)
353 | val Right(params) = refineV[NonEmpty](Seq(Option(3)))
354 | val tuple = (Option(3), 2, 1)
355 |
356 | assertTypeError("""
357 | sql"SELECT * FROM entry WHERE param = $param"
358 | """)
359 |
360 | assertTypeError("""
361 | sql"SELECT * FROM entry WHERE params IN ($params)"
362 | """)
363 |
364 | assertTypeError("""
365 | sql"SELECT * FROM entry WHERE params IN ($tuple)"
366 | """)
367 |
368 | import CompoundParameter._
369 |
370 | assertTypeError("""
371 | sql"SELECT * FROM entry WHERE param = $param"
372 | """)
373 |
374 | assertTypeError("""
375 | sql"SELECT * FROM entry WHERE params IN ($params)"
376 | """)
377 |
378 | assertTypeError("""
379 | sql"SELECT * FROM entry WHERE params IN ($tuple)"
380 | """)
381 | }
382 |
383 | it("should not embed a user-defined class value") {
384 | class Foo
385 | val param = new Foo
386 |
387 | import CompoundParameter._
388 |
389 | assertTypeError("""
390 | sql"SELECT * FROM entry WHERE param = $param"
391 | """)
392 | }
393 |
394 | it("should embed a user-defined class value with custom SetParameter") {
395 | import slick.jdbc.{SetParameter => SP, PositionedParameters}
396 | class Foo { override def toString = "foo" }
397 | implicit object SetFoo extends SP[Foo] {
398 | def apply(v: Foo, pp: PositionedParameters) = {
399 | pp.setString(v.toString)
400 | }
401 | }
402 |
403 | val param = new Foo
404 |
405 | it should behave like anIdenticalQuery {
406 | sql"SELECT * FROM entry WHERE param = $param"
407 | }("SELECT * FROM entry WHERE param = ?")
408 | }
409 | }
410 |
411 | describe("SQLInterpolation.sql with query translation") {
412 | import SQLInterpolation._
413 |
414 | it("should provide a default translation") {
415 | sql"test" // just compiles
416 | }
417 |
418 | it("should provide a custom translation") {
419 | implicit val translators: Iterable[query.Translator] = Seq(
420 | new query.Translator {
421 | def apply(q: String, context: query.Context) = q + " translated"
422 | }
423 | )
424 | it should behave like theExactStatement{ sqlu"test" }("test translated")
425 | }
426 |
427 | it("should provide a custom ordered translation") {
428 | implicit val translators: Iterable[query.Translator] = Seq(
429 | new query.Translator {
430 | def apply(q: String, context: query.Context) = q + " translated"
431 | },
432 | new query.Translator {
433 | def apply(q: String, context: query.Context) = q + " more"
434 | }
435 | )
436 | it should behave like theExactStatement{ sqlu"test" }("test translated more")
437 | }
438 | }
439 |
440 | describe("SQLInterpolation.sqlu") {
441 | it("should not be callable without enabling it") {
442 | assertTypeError(""" sqlu"test" """)
443 | }
444 |
445 | import SQLInterpolation._
446 | implicit val translators: Iterable[query.Translator] = Seq.empty
447 |
448 | it("should return an action with a specified string") {
449 | it should behave like theExactStatement{ sqlu"test" }("test")
450 | it should behave like anIdenticalStatement {
451 | sqlu"""
452 | INSERT INTO entry (entry_id)
453 | VALUES (1)
454 | """
455 | }("INSERT INTO entry (entry_id) VALUES (1)")
456 | }
457 |
458 | it("should embed simple values") {
459 | val id = 1234
460 | val url = "http://github.com/tarao/"
461 |
462 | it should behave like anIdenticalStatement {
463 | sqlu"""
464 | INSERT INTO entry (entry_id, url)
465 | VALUES ($id, $url)
466 | """
467 | }("INSERT INTO entry (entry_id, url) VALUES (?, ?)")
468 | }
469 |
470 | it("should embed a literal string with #${}") {
471 | val tableName = "entry"
472 |
473 | it should behave like anIdenticalStatement {
474 | sqlu"""
475 | INSERT INTO #${tableName} (entry_id)
476 | VALUES (1)
477 | """
478 | }("INSERT INTO entry (entry_id) VALUES (1)")
479 | }
480 |
481 | it("should embed a literal value") {
482 | val tableName = TableName("entry")
483 |
484 | it should behave like anIdenticalStatement {
485 | sqlu"""
486 | INSERT INTO ${tableName} (entry_id)
487 | VALUES (1)
488 | """
489 | }("INSERT INTO entry (entry_id) VALUES (1)")
490 | }
491 |
492 | it("should embed a custom literal value") {
493 | val tableName = new Literal { override def toString = "entry" }
494 |
495 | it should behave like anIdenticalStatement {
496 | sqlu"""
497 | INSERT INTO ${tableName} (entry_id)
498 | VALUES (1)
499 | """
500 | }("INSERT INTO entry (entry_id) VALUES (1)")
501 | }
502 |
503 | it("should not embed an undefined value") {
504 | assertTypeError("""
505 | sqlu"INSERT INTO $undefined (entry_id) VALUES (1)"
506 | """)
507 | }
508 |
509 | it("should not embed a refined non-empty list") {
510 | import eu.timepit.refined.collection.NonEmpty
511 | import eu.timepit.refined.refineV
512 |
513 | val Right(entryIds) = refineV[NonEmpty](Seq(1, 2, 3, 4))
514 |
515 | assertTypeError("""
516 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($entryIds)"
517 | """)
518 | }
519 |
520 | it("should embed a refined non-empty list if it is explicitly enabled") {
521 | import eu.timepit.refined.collection.NonEmpty
522 | import eu.timepit.refined.refineV
523 |
524 | import CompoundParameter._
525 |
526 | val Right(entryIds) = refineV[NonEmpty](Seq(1, 2, 3, 4))
527 | val Right(entryIds1) = refineV[NonEmpty](Seq(5))
528 |
529 | it should behave like anIdenticalStatement {
530 | sqlu"""
531 | UPDATE entry
532 | SET flag = 1
533 | WHERE entry_id IN ($entryIds)
534 | """
535 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?, ?, ?)")
536 |
537 | it should behave like anIdenticalStatement {
538 | sqlu"""
539 | UPDATE entry
540 | SET flag = 1
541 | WHERE entry_id IN $entryIds
542 | """
543 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?, ?, ?)")
544 |
545 | it should behave like anIdenticalStatement {
546 | sqlu"""
547 | UPDATE entry
548 | SET flag = 1
549 | WHERE entry_id IN (${entryIds1})
550 | """
551 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?)")
552 |
553 | it should behave like anIdenticalStatement {
554 | sqlu"""
555 | UPDATE entry
556 | SET flag = 1
557 | WHERE entry_id IN ${entryIds1}
558 | """
559 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?)")
560 | }
561 |
562 | it("should not embed an option refined non-empty list") {
563 | import eu.timepit.refined.collection.NonEmpty
564 | import eu.timepit.refined.refineV
565 |
566 | import CompoundParameter._
567 |
568 | val entryIds = refineV[NonEmpty](Seq(1, 2, 3, 4)).toOption
569 |
570 | assertTypeError("""
571 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($entryIds)"
572 | """)
573 |
574 | it should behave like anIdenticalStatement {
575 | sqlu"""
576 | UPDATE entry
577 | SET flag = 1
578 | WHERE entry_id IN (${entryIds.get})
579 | """
580 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?, ?, ?)")
581 | }
582 |
583 | it("should not embed an either refined non-empty list") {
584 | import eu.timepit.refined.collection.NonEmpty
585 | import eu.timepit.refined.refineV
586 |
587 | import CompoundParameter._
588 |
589 | val entryIds = refineV[NonEmpty](Seq(1, 2, 3, 4))
590 |
591 | assertTypeError("""
592 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($entryIds)"
593 | """)
594 | }
595 |
596 | it("should not embed a maybe-empty list") {
597 | val entryIds = Seq(1, 2, 3, 4)
598 |
599 | assertTypeError("""
600 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($entryIds)"
601 | """)
602 |
603 | import CompoundParameter._
604 |
605 | assertTypeError("""
606 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($entryIds)"
607 | """)
608 | }
609 |
610 | it("should embed a tuple") {
611 | val entryIds = (1, 2, 3, 4)
612 |
613 | it should behave like anIdenticalStatement {
614 | sqlu"""
615 | UPDATE entry
616 | SET flag = 1
617 | WHERE entry_id IN ($entryIds)
618 | """
619 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?, ?, ?)")
620 |
621 | it should behave like anIdenticalStatement {
622 | sqlu"""
623 | UPDATE entry
624 | SET flag = 1
625 | WHERE entry_id IN $entryIds
626 | """
627 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?, ?, ?)")
628 | }
629 |
630 | it("should fail to embed a nullary product") {
631 | import CompoundParameter._
632 |
633 | val empty = EmptyTuple()
634 | a [java.sql.SQLException] should be thrownBy
635 | sqlu"UPDATE entry SET flag = 1 WHERE param = $empty"
636 | }
637 |
638 | it("should not embed a product or a signle tuple") {
639 | val tuple1 = Tuple1(5)
640 | val single = Single(5)
641 | val double = Double(1, 2)
642 | val triple = Triple(1, 2, 3)
643 |
644 | assertTypeError("""
645 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($tuple1)"
646 | """)
647 |
648 | assertTypeError("""
649 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($single)"
650 | """)
651 |
652 | assertTypeError("""
653 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($double)"
654 | """)
655 |
656 | assertTypeError("""
657 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($triple)"
658 | """)
659 | }
660 |
661 | it("should embed a product or a signle tuple if they are explictly enabled") {
662 | val entryIds = (1, 2, 3, 4)
663 | val tuple1 = Tuple1(5)
664 | val single = Single(5)
665 | val double = Double(1, 2)
666 | val triple = Triple(1, 2, 3)
667 |
668 | import CompoundParameter._
669 |
670 | // check if it doesn't break the normal tuple behavior
671 | it should behave like anIdenticalStatement {
672 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($entryIds)"
673 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?, ?, ?)")
674 |
675 | it should behave like anIdenticalStatement {
676 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN $entryIds"
677 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?, ?, ?)")
678 |
679 | it should behave like anIdenticalStatement {
680 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($tuple1)"
681 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?)")
682 | it should behave like anIdenticalStatement {
683 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN $tuple1"
684 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?)")
685 |
686 | it should behave like anIdenticalStatement {
687 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($single)"
688 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?)")
689 | it should behave like anIdenticalStatement {
690 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN $single"
691 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?)")
692 |
693 | it should behave like anIdenticalStatement {
694 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($double)"
695 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?)")
696 | it should behave like anIdenticalStatement {
697 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN $double"
698 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?)")
699 |
700 | it should behave like anIdenticalStatement {
701 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($triple)"
702 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?, ?)")
703 | it should behave like anIdenticalStatement {
704 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN $triple"
705 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?, ?)")
706 | }
707 |
708 | it("should not embed a refined non-empty list of product") {
709 | import eu.timepit.refined.collection.NonEmpty
710 | import eu.timepit.refined.refineV
711 |
712 | val Right(params1) = refineV[NonEmpty](Seq(
713 | (1, "http://example.com/1"),
714 | (2, "http://example.com/2"),
715 | (3, "http://example.com/3")
716 | ))
717 |
718 | val Right(params2) = refineV[NonEmpty](Seq(
719 | Tuple1("http://example.com/1"),
720 | Tuple1("http://example.com/2"),
721 | Tuple1("http://example.com/3")
722 | ))
723 |
724 | val Right(params3) = refineV[NonEmpty](Seq(
725 | Single("http://example.com/1"),
726 | Single("http://example.com/2"),
727 | Single("http://example.com/3")
728 | ))
729 |
730 | val Right(params4) = refineV[NonEmpty](Seq(
731 | Double(1, "http://example.com/1"),
732 | Double(2, "http://example.com/2"),
733 | Double(3, "http://example.com/3")
734 | ))
735 |
736 | assertTypeError("""
737 | sqlu"INSERT INTO entry (entry_id, url) VALUES ($params1)"
738 | """)
739 |
740 | assertTypeError("""
741 | sqlu"INSERT INTO entry (url) VALUES ($params2)"
742 | """)
743 |
744 | assertTypeError("""
745 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($params3)"
746 | """)
747 |
748 | assertTypeError("""
749 | sqlu"INSERT INTO entry (entry_id, url) VALUES ($params4)"
750 | """)
751 | }
752 |
753 | it("should embed a refined non-empty list of product if it is explicitly enabled") {
754 | import eu.timepit.refined.collection.NonEmpty
755 | import eu.timepit.refined.refineV
756 |
757 | val Right(params1) = refineV[NonEmpty](Seq(
758 | (1, "http://example.com/1"),
759 | (2, "http://example.com/2"),
760 | (3, "http://example.com/3")
761 | ))
762 |
763 | val Right(params2) = refineV[NonEmpty](Seq(
764 | Tuple1("http://example.com/1"),
765 | Tuple1("http://example.com/2"),
766 | Tuple1("http://example.com/3")
767 | ))
768 |
769 | // This works as if Single[String] is an alias of String
770 | val Right(params3) = refineV[NonEmpty](Seq(
771 | Single("http://example.com/1"),
772 | Single("http://example.com/2"),
773 | Single("http://example.com/3")
774 | ))
775 |
776 | val Right(params4) = refineV[NonEmpty](Seq(
777 | Double(1, "http://example.com/1"),
778 | Double(2, "http://example.com/2"),
779 | Double(3, "http://example.com/3")
780 | ))
781 |
782 | import CompoundParameter._
783 |
784 | it should behave like anIdenticalStatement {
785 | sqlu"INSERT INTO entry (entry_id, url) VALUES ($params1)"
786 | }("INSERT INTO entry (entry_id, url) VALUES (?, ?), (?, ?), (?, ?)")
787 |
788 | it should behave like anIdenticalStatement {
789 | sqlu"INSERT INTO entry (entry_id, url) VALUES $params1"
790 | }("INSERT INTO entry (entry_id, url) VALUES (?, ?), (?, ?), (?, ?)")
791 |
792 | it should behave like anIdenticalStatement {
793 | sqlu"INSERT INTO entry (url) VALUES ($params2)"
794 | }("INSERT INTO entry (url) VALUES (?), (?), (?)")
795 |
796 | it should behave like anIdenticalStatement {
797 | sqlu"INSERT INTO entry (url) VALUES $params2"
798 | }("INSERT INTO entry (url) VALUES (?), (?), (?)")
799 |
800 | it should behave like anIdenticalStatement {
801 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN ($params3)"
802 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?, ?)")
803 |
804 | it should behave like anIdenticalStatement {
805 | sqlu"UPDATE entry SET flag = 1 WHERE entry_id IN $params3"
806 | }("UPDATE entry SET flag = 1 WHERE entry_id IN (?, ?, ?)")
807 |
808 | it should behave like anIdenticalStatement {
809 | sqlu"INSERT INTO entry (entry_id, url) VALUES ($params4)"
810 | }("INSERT INTO entry (entry_id, url) VALUES (?, ?), (?, ?), (?, ?)")
811 |
812 | it should behave like anIdenticalStatement {
813 | sqlu"INSERT INTO entry (entry_id, url) VALUES $params4"
814 | }("INSERT INTO entry (entry_id, url) VALUES (?, ?), (?, ?), (?, ?)")
815 | }
816 |
817 | it("should not embed a refined option value") {
818 | import eu.timepit.refined.collection.NonEmpty
819 | import eu.timepit.refined.refineV
820 |
821 | val param = Option(3)
822 | val Right(params) = refineV[NonEmpty](Seq(Option(3)))
823 | val tuple = (Option(3), 2, 1)
824 |
825 | assertTypeError("""
826 | sqlu"UPDATE entry SET flag = 1 WHERE param = $param"
827 | """)
828 |
829 | assertTypeError("""
830 | sqlu"UPDATE entry SET flag = 1 WHERE params IN ($params)"
831 | """)
832 |
833 | assertTypeError("""
834 | sqlu"UPDATE entry SET flag = 1 WHERE params IN ($tuple)"
835 | """)
836 |
837 | import CompoundParameter._
838 |
839 | assertTypeError("""
840 | sqlu"UPDATE entry SET flag = 1 WHERE param = $param"
841 | """)
842 |
843 | assertTypeError("""
844 | sqlu"UPDATE entry SET flag = 1 WHERE params IN ($params)"
845 | """)
846 |
847 | assertTypeError("""
848 | sqlu"UPDATE entry SET flag = 1 WHERE params IN ($tuple)"
849 | """)
850 | }
851 |
852 | it("should not embed a user-defined class value") {
853 | class Foo
854 | val param = new Foo
855 |
856 | import CompoundParameter._
857 |
858 | assertTypeError("""
859 | sqlu"UPDATE entry SET flag = 1 WHERE param = $param"
860 | """
861 | )
862 | }
863 |
864 | it("should embed a user-defined class value with custom SetParameter") {
865 | import slick.jdbc.{SetParameter => SP, PositionedParameters}
866 | class Foo { override def toString = "foo" }
867 | implicit object SetFoo extends SP[Foo] {
868 | def apply(v: Foo, pp: PositionedParameters) = {
869 | pp.setString(v.toString)
870 | }
871 | }
872 |
873 | val param = new Foo
874 |
875 | it should behave like anIdenticalStatement {
876 | sqlu"UPDATE entry SET flag = 1 WHERE param = $param"
877 | }("UPDATE entry SET flag = 1 WHERE param = ?")
878 | }
879 | }
880 |
881 | describe("SQLInterpolation.sqlu with query translation") {
882 | import SQLInterpolation._
883 |
884 | it("should provide a default translation") {
885 | sqlu"test" // just compiles
886 | }
887 |
888 | it("should provide a custom translation") {
889 | implicit val translators: Iterable[query.Translator] = Seq(
890 | new query.Translator {
891 | def apply(q: String, context: query.Context) = q + " translated"
892 | }
893 | )
894 | it should behave like theExactStatement{ sqlu"test" }("test translated")
895 | }
896 |
897 | it("should provide a custom ordered translation") {
898 | implicit val translators: Iterable[query.Translator] = Seq(
899 | new query.Translator {
900 | def apply(q: String, context: query.Context) = q + " translated"
901 | },
902 | new query.Translator {
903 | def apply(q: String, context: query.Context) = q + " more"
904 | }
905 | )
906 | it should behave like theExactStatement{
907 | sqlu"test"
908 | }("test translated more")
909 | }
910 | }
911 | }
912 |
913 | class CompoundParameterSpec extends UnitSpec
914 | with TraitSingletonBehavior {
915 | describe("object ListParameter") {
916 | it("should inherit the trait") {
917 | it should behave like exportingTheTraitMethods
918 | [ListParameter](ListParameter)
919 | }
920 | }
921 |
922 | describe("object ProductParameter") {
923 | it("should inherit the trait") {
924 | it should behave like exportingTheTraitMethods
925 | [ProductParameter](ProductParameter)
926 | }
927 | }
928 |
929 | describe("object CompoundParameter") {
930 | it("should inherit the trait") {
931 | val compoundParameter = new CompoundParameter {}
932 | compoundParameter shouldBe a[ListParameter]
933 | compoundParameter shouldBe a[ProductParameter]
934 |
935 | CompoundParameter shouldBe a[ListParameter]
936 | CompoundParameter shouldBe a[ProductParameter]
937 | }
938 | }
939 | }
940 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/tarao/slickjdbc/interpolation/LiteralSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package interpolation
4 |
5 | import helper.UnitSpec
6 |
7 | class LiteralSpec extends UnitSpec {
8 | describe("SimpleString") {
9 | it("should be a Literal which prints to specified string") {
10 | val simple = new SimpleString("foo")
11 | simple shouldBe a [Literal]
12 | simple.toString should equal ("foo")
13 | }
14 | }
15 |
16 | describe("TableName") {
17 | it("should be a Literal which prints to specified name") {
18 | val table = new TableName("bar")
19 | table shouldBe a [Literal]
20 | table.toString should equal ("bar")
21 | }
22 |
23 | it("should provide a constructor method") {
24 | val table = TableName("baz")
25 | table shouldBe a [Literal]
26 | table.toString should equal ("baz")
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/tarao/slickjdbc/interpolation/PlaceholderSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package interpolation
4 |
5 | import helper.UnitSpec
6 |
7 | class PlaceholderSpec extends UnitSpec {
8 | import PlaceholderSpec._
9 |
10 | def toPlaceholder[T](value: T)(implicit p: ToPlaceholder[T]): Placeholder =
11 | p(value)
12 |
13 | describe("Placeholder of simple types") {
14 | it("should be a Literal") {
15 | val p1 = toPlaceholder(1)
16 | p1 shouldBe a [Literal]
17 |
18 | val p2 = toPlaceholder("foo")
19 | p2 shouldBe a [Literal]
20 |
21 | val p3 = toPlaceholder((1, "foo"))
22 | p3 shouldBe a [Literal]
23 | }
24 |
25 | describe(".toTopLevelString") {
26 | it("should print an empty string for a simple type") {
27 | val p1 = toPlaceholder(1)
28 | p1.toTopLevelString should equal ("")
29 | val p2 = toPlaceholder("foo")
30 | p2.toTopLevelString should equal ("")
31 | val p3 = toPlaceholder(Foo(1, "foo"))
32 | p3.toTopLevelString should equal ("")
33 | val p4 = toPlaceholder(new Bar(1, "foo"))
34 | p4.toTopLevelString should equal ("")
35 | }
36 |
37 | it("should print '?'s x (tuple size - 1)") {
38 | locally {
39 | val p1 = toPlaceholder((1, "foo", 3))
40 | p1.toTopLevelString should equal ("?, ?, ")
41 | val p2 = toPlaceholder((1, "foo", 3, "bar", 5))
42 | p2.toTopLevelString should equal ("?, ?, ?, ?, ")
43 | val p3 = toPlaceholder(Tuple1(1))
44 | p3.toTopLevelString should equal ("")
45 | }
46 |
47 | locally {
48 | val p1 = toPlaceholder(Tuple1(1))
49 | p1.toTopLevelString should equal ("")
50 | val p2 = toPlaceholder((1, 2))
51 | p2.toTopLevelString should equal ("?, ")
52 | val p3 = toPlaceholder((1, 2, 3))
53 | p3.toTopLevelString should equal ("?, ?, ")
54 | val p4 = toPlaceholder((1, 2, 3, 4))
55 | p4.toTopLevelString should equal ("?, ?, ?, ")
56 | val p5 = toPlaceholder((1, 2, 3, 4, 5))
57 | p5.toTopLevelString should equal ("?, ?, ?, ?, ")
58 | val p6 = toPlaceholder((1, 2, 3, 4, 5, 6))
59 | p6.toTopLevelString should equal ("?, ?, ?, ?, ?, ")
60 | val p7 = toPlaceholder((1, 2, 3, 4, 5, 6, 7))
61 | p7.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ")
62 | val p8 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8))
63 | p8.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ")
64 | val p9 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9))
65 | p9.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ")
66 | val p10 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
67 | p10.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ")
68 | val p11 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11))
69 | p11.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
70 | val p12 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
71 | p12.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
72 | val p13 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13))
73 | p13.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
74 | val p14 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14))
75 | p14.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
76 | val p15 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15))
77 | p15.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
78 | val p16 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))
79 | p16.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
80 | val p17 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17))
81 | p17.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
82 | val p18 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18))
83 | p18.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
84 | val p19 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19))
85 | p19.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
86 | val p20 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20))
87 | p20.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
88 | val p21 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21))
89 | p21.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
90 | val p22 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22))
91 | p22.toTopLevelString should equal ("?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ")
92 | }
93 | }
94 |
95 | it("should print nested '?'s for nested tuples") {
96 | val p1 = toPlaceholder((("foo", 1), ("bar", 2)))
97 | p1.toTopLevelString should equal ("?, ?), (?, ")
98 | }
99 |
100 | it("should not look into nested products") {
101 | val p1 = toPlaceholder(Baz("simple", 1))
102 | p1.toTopLevelString should equal ("")
103 | val p2 = toPlaceholder(Some(1))
104 | p2.toTopLevelString should equal ("")
105 | val p3 = toPlaceholder(Some(Baz("single", 1)))
106 | p3.toTopLevelString should equal ("")
107 | val p4 = toPlaceholder(Pair(Baz("nested", 1), Baz("nested", 2)))
108 | p4.toTopLevelString should equal ("")
109 | }
110 |
111 | it("should be customizable") {
112 | class FromPair[T1, T2](p1: ToPlaceholder[T1], p2: ToPlaceholder[T2])
113 | extends ToPlaceholder[Pair[T1, T2]] {
114 | def apply(value: Pair[T1, T2]): Placeholder = Placeholder.Nested(
115 | p1(value.a),
116 | p2(value.b)
117 | )
118 | }
119 | implicit def pairToPlaceholder[T1, T2](implicit
120 | p1: ToPlaceholder[T1],
121 | p2: ToPlaceholder[T2]
122 | ): ToPlaceholder[Pair[T1, T2]] = new FromPair[T1, T2](p1, p2)
123 |
124 | val p1 = toPlaceholder(Pair(Baz("nested", 1), Baz("nested", 2)))
125 | p1.toTopLevelString should equal ("?, ")
126 | val p2 = toPlaceholder(Pair((1, 2), (3, 4)))
127 | p2.toTopLevelString should equal ("?, ?), (?, ")
128 | }
129 | }
130 |
131 | describe(".toString") {
132 | it("should print a single '?' for a simple type") {
133 | val p1 = toPlaceholder(1)
134 | p1.toString should equal ("?")
135 | val p2 = toPlaceholder("foo")
136 | p2.toString should equal ("?")
137 | val p3 = toPlaceholder(Foo(1, "foo"))
138 | p3.toString should equal ("?")
139 | val p4 = toPlaceholder(new Bar(1, "foo"))
140 | p4.toString should equal ("?")
141 | }
142 |
143 | it("should print '?'s x (tuple size) with parenthesis") {
144 | locally {
145 | val p1 = toPlaceholder((1, "foo", 3))
146 | p1.toString should equal ("(?, ?, ?)")
147 | val p2 = toPlaceholder((1, "foo", 3, "bar", 5))
148 | p2.toString should equal ("(?, ?, ?, ?, ?)")
149 | val p3 = toPlaceholder(Tuple1(1))
150 | p3.toString should equal ("(?)")
151 | }
152 |
153 | locally {
154 | val p1 = toPlaceholder(Tuple1(1))
155 | p1.toString should equal ("(?)")
156 | val p2 = toPlaceholder((1, 2))
157 | p2.toString should equal ("(?, ?)")
158 | val p3 = toPlaceholder((1, 2, 3))
159 | p3.toString should equal ("(?, ?, ?)")
160 | val p4 = toPlaceholder((1, 2, 3, 4))
161 | p4.toString should equal ("(?, ?, ?, ?)")
162 | val p5 = toPlaceholder((1, 2, 3, 4, 5))
163 | p5.toString should equal ("(?, ?, ?, ?, ?)")
164 | val p6 = toPlaceholder((1, 2, 3, 4, 5, 6))
165 | p6.toString should equal ("(?, ?, ?, ?, ?, ?)")
166 | val p7 = toPlaceholder((1, 2, 3, 4, 5, 6, 7))
167 | p7.toString should equal ("(?, ?, ?, ?, ?, ?, ?)")
168 | val p8 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8))
169 | p8.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?)")
170 | val p9 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9))
171 | p9.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?)")
172 | val p10 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
173 | p10.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
174 | val p11 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11))
175 | p11.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
176 | val p12 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
177 | p12.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
178 | val p13 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13))
179 | p13.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
180 | val p14 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14))
181 | p14.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
182 | val p15 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15))
183 | p15.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
184 | val p16 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))
185 | p16.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
186 | val p17 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17))
187 | p17.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
188 | val p18 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18))
189 | p18.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
190 | val p19 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19))
191 | p19.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
192 | val p20 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20))
193 | p20.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
194 | val p21 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21))
195 | p21.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
196 | val p22 = toPlaceholder((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22))
197 | p22.toString should equal ("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
198 | }
199 | }
200 |
201 | it("should print nested '?'s for nested tuples") {
202 | val p1 = toPlaceholder((("foo", 1), ("bar", 2)))
203 | p1.toString should equal ("(?, ?), (?, ?)")
204 | }
205 |
206 | it("should not look into nested products") {
207 | val p1 = toPlaceholder(Baz("simple", 1))
208 | p1.toString should equal ("?")
209 | val p2 = toPlaceholder(Some(1))
210 | p2.toString should equal ("?")
211 | val p3 = toPlaceholder(Some(Baz("single", 1)))
212 | p3.toString should equal ("?")
213 | val p4 = toPlaceholder(Pair(Baz("nested", 1), Baz("nested", 2)))
214 | p4.toString should equal ("?")
215 | }
216 |
217 | it("should be customizable") {
218 | class FromPair[T1, T2](p1: ToPlaceholder[T1], p2: ToPlaceholder[T2])
219 | extends ToPlaceholder[Pair[T1, T2]] {
220 | def apply(value: Pair[T1, T2]): Placeholder = Placeholder.Nested(
221 | p1(value.a),
222 | p2(value.b)
223 | )
224 | }
225 | implicit def pairToPlaceholder[T1, T2](implicit
226 | p1: ToPlaceholder[T1],
227 | p2: ToPlaceholder[T2]
228 | ): ToPlaceholder[Pair[T1, T2]] = new FromPair[T1, T2](p1, p2)
229 |
230 | val p1 = toPlaceholder(Pair(Baz("nested", 1), Baz("nested", 2)))
231 | p1.toString should equal ("(?, ?)")
232 | val p2 = toPlaceholder(Pair((1, 2), (3, 4)))
233 | p2.toString should equal ("(?, ?), (?, ?)")
234 | }
235 | }
236 | }
237 |
238 | describe("Placeholder of compound parameters") {
239 | import eu.timepit.refined.collection.NonEmpty
240 | import eu.timepit.refined.refineV
241 | import CompoundParameter._
242 |
243 | it("should be a Literal") {
244 | val p1 = toPlaceholder(Foo(1, "foo"))
245 | p1 shouldBe a [Literal]
246 |
247 | val Right(nel) = refineV[NonEmpty](Seq(1, 2, 3))
248 | val p2 = toPlaceholder(nel)
249 | p2 shouldBe a [Literal]
250 | }
251 |
252 | it("should not be instantiated by a nullary product") {
253 | a[java.sql.SQLException] should be thrownBy toPlaceholder(None)
254 | }
255 |
256 | describe(".toTopLevelString") {
257 | it("should print '?'s x (list size - 1)") {
258 | val Right(nel1) = refineV[NonEmpty](Seq(1, 2, 3))
259 | val Right(nel2) = refineV[NonEmpty](Seq(1, 2, 3, 4, 5))
260 | val Right(nel3) = refineV[NonEmpty](Seq(1))
261 |
262 | val p1 = toPlaceholder(nel1)
263 | p1.toTopLevelString should equal ("?, ?, ")
264 | val p2 = toPlaceholder(nel2)
265 | p2.toTopLevelString should equal ("?, ?, ?, ?, ")
266 | val p3 = toPlaceholder(nel3)
267 | p3.toTopLevelString should equal ("")
268 | }
269 |
270 | it("should print '?'s x (product arity - 1)") {
271 | val p1 = toPlaceholder(Foo(1, "foo"))
272 | p1.toTopLevelString should equal ("?, ")
273 | }
274 |
275 | it("should print nested '?'s for a nested structure") {
276 | val p1 = toPlaceholder(((1, "foo"), (2, "bar"), (3, "baz")))
277 | p1.toTopLevelString should equal ("?, ?), (?, ?), (?, ")
278 |
279 | val Right(nel1) = refineV[NonEmpty](Seq(1, 2))
280 | val Right(nel2) = refineV[NonEmpty](Seq(3))
281 | val Right(nel3) = refineV[NonEmpty](Seq(4, 5))
282 | val Right(nel4) = refineV[NonEmpty](Seq(nel1, nel2, nel3))
283 | val Right(nel5) = refineV[NonEmpty](Seq(
284 | (1, "foo"),
285 | (2, "bar"),
286 | (3, "baz")
287 | ))
288 |
289 | val p2 = toPlaceholder(nel4)
290 | p2.toTopLevelString should equal ("?, ?), (?), (?, ")
291 | val p3 = toPlaceholder(nel5)
292 | p3.toTopLevelString should equal ("?, ?), (?, ?), (?, ")
293 | }
294 |
295 | it("should look into the structure of a nested product") {
296 | val p1 = toPlaceholder(Baz("simple", 1))
297 | p1.toTopLevelString should equal ("?, ")
298 | val p2 = toPlaceholder(Some(1))
299 | p2.toTopLevelString should equal ("")
300 | val p3 = toPlaceholder(Some(Baz("single", 1)))
301 | p3.toTopLevelString should equal ("?, ")
302 | val p4 = toPlaceholder(Pair(Baz("nested", 1), Baz("nested", 2)))
303 | p4.toTopLevelString should equal ("?, ?, ?, ")
304 | val p5 = toPlaceholder(Baz("nested", (1, Pair(2, 3))))
305 | p5.toTopLevelString should equal ("?, ?, ?, ")
306 |
307 | val Right(nel) = refineV[NonEmpty](Seq(1, 2))
308 |
309 | val p6 = toPlaceholder(Baz("a list in product is not expanded", nel))
310 | p6.toTopLevelString should equal ("?, ")
311 | }
312 |
313 | it("should preserve simple type conversions") {
314 | locally {
315 | val p1 = toPlaceholder(1)
316 | p1.toTopLevelString should equal ("")
317 | val p2 = toPlaceholder("foo")
318 | p2.toTopLevelString should equal ("")
319 | val p3 = toPlaceholder(new Bar(1, "foo"))
320 | p3.toTopLevelString should equal ("")
321 | }
322 |
323 | locally {
324 | val p1 = toPlaceholder((1, "foo", 3))
325 | p1.toTopLevelString should equal ("?, ?, ")
326 | val p2 = toPlaceholder((1, "foo", 3, "bar", 5))
327 | p2.toTopLevelString should equal ("?, ?, ?, ?, ")
328 | val p3 = toPlaceholder(Tuple1(1))
329 | p3.toTopLevelString should equal ("")
330 | }
331 |
332 | locally {
333 | val p1 = toPlaceholder((("foo", 1), ("bar", 2)))
334 | p1.toTopLevelString should equal ("?, ?), (?, ")
335 | }
336 | }
337 |
338 | it("should be customizable") {
339 | class FromPair[T1, T2] extends ToPlaceholder[Pair[T1, T2]] {
340 | def apply(value: Pair[T1, T2]): Placeholder = Placeholder()
341 | }
342 | implicit def pairToPlaceholder[T1, T2]: ToPlaceholder[Pair[T1, T2]] =
343 | new FromPair[T1, T2]
344 |
345 | val p1 = toPlaceholder(Pair(Baz("nested", 1), Baz("nested", 2)))
346 | p1.toTopLevelString should equal ("")
347 | val p2 = toPlaceholder(Pair((1, 2), (3, 4)))
348 | p2.toTopLevelString should equal ("")
349 | }
350 | }
351 |
352 | describe(".toString") {
353 | it("should print '?'s x (list size) with parenthesis") {
354 | val Right(nel1) = refineV[NonEmpty](Seq(1, 2, 3))
355 | val Right(nel2) = refineV[NonEmpty](Seq(1, 2, 3, 4, 5))
356 | val Right(nel3) = refineV[NonEmpty](Seq(1))
357 |
358 | val p1 = toPlaceholder(nel1)
359 | p1.toString should equal ("(?, ?, ?)")
360 | val p2 = toPlaceholder(nel2)
361 | p2.toString should equal ("(?, ?, ?, ?, ?)")
362 | val p3 = toPlaceholder(nel3)
363 | p3.toString should equal ("(?)")
364 | }
365 |
366 | it("should print '?'s x (product arity - 1)") {
367 | val p1 = toPlaceholder(Foo(1, "foo"))
368 | p1.toString should equal ("(?, ?)")
369 | }
370 |
371 | it("should print nested '?'s for a nested structure") {
372 | val Right(nel1) = refineV[NonEmpty](Seq(1, 2))
373 | val Right(nel2) = refineV[NonEmpty](Seq(3))
374 | val Right(nel3) = refineV[NonEmpty](Seq(4, 5))
375 | val Right(nel4) = refineV[NonEmpty](Seq(nel1, nel2, nel3))
376 | val Right(nel5) = refineV[NonEmpty](Seq(
377 | (1, "foo"),
378 | (2, "bar"),
379 | (3, "baz")
380 | ))
381 |
382 | val p1 = toPlaceholder(nel4)
383 | p1.toString should equal ("(?, ?), (?), (?, ?)")
384 | val p2 = toPlaceholder(nel5)
385 | p2.toString should equal ("(?, ?), (?, ?), (?, ?)")
386 | val p3 = toPlaceholder(((1, "foo"), (2, "bar"), (3, "baz")))
387 | p3.toString should equal ("(?, ?), (?, ?), (?, ?)")
388 | }
389 |
390 | it("should look into the structure of a nested product") {
391 | val p1 = toPlaceholder(Baz("simple", 1))
392 | p1.toString should equal ("(?, ?)")
393 | val p2 = toPlaceholder(Some(1))
394 | p2.toString should equal ("?")
395 | val p3 = toPlaceholder(Some(Baz("single", 1)))
396 | p3.toString should equal ("(?, ?)")
397 | val p4 = toPlaceholder(Pair(Baz("nested", 1), Baz("nested", 2)))
398 | p4.toString should equal ("(?, ?, ?, ?)")
399 | val p5 = toPlaceholder(Baz("nested", (1, Pair(2, 3))))
400 | p5.toString should equal ("(?, ?, ?, ?)")
401 |
402 | val Right(nel) = refineV[NonEmpty](Seq(1, 2))
403 | val p7 = toPlaceholder(Baz("a list in product is not expanded", nel))
404 | p7.toString should equal ("(?, ?)")
405 | }
406 |
407 | it("should preserve simple type conversions") {
408 | locally {
409 | val p1 = toPlaceholder(1)
410 | p1.toString should equal ("?")
411 | val p2 = toPlaceholder("foo")
412 | p2.toString should equal ("?")
413 | val p3 = toPlaceholder(new Bar(1, "foo"))
414 | p3.toString should equal ("?")
415 | }
416 |
417 | locally {
418 | val p1 = toPlaceholder((1, "foo", 3))
419 | p1.toString should equal ("(?, ?, ?)")
420 | val p2 = toPlaceholder((1, "foo", 3, "bar", 5))
421 | p2.toString should equal ("(?, ?, ?, ?, ?)")
422 | val p3 = toPlaceholder(Tuple1(1))
423 | p3.toString should equal ("(?)")
424 | }
425 |
426 | locally {
427 | val p1 = toPlaceholder((("foo", 1), ("bar", 2)))
428 | p1.toString should equal ("(?, ?), (?, ?)")
429 | }
430 | }
431 |
432 | it("should be customizable") {
433 | class FromPair[T1, T2] extends ToPlaceholder[Pair[T1, T2]] {
434 | def apply(value: Pair[T1, T2]): Placeholder = Placeholder()
435 | }
436 | implicit def pairToPlaceholder[T1, T2]: ToPlaceholder[Pair[T1, T2]] =
437 | new FromPair[T1, T2]
438 |
439 | val p1 = toPlaceholder(Pair(Baz("nested", 1), Baz("nested", 2)))
440 | p1.toString should equal ("?")
441 | val p2 = toPlaceholder(Pair((1, 2), (3, 4)))
442 | p2.toString should equal ("?")
443 | }
444 | }
445 | }
446 | }
447 | object PlaceholderSpec {
448 | case class Foo(a: Int, b: String)
449 | class Bar(a: Int, b: String)
450 |
451 | case class Baz[T](name: String, value: T)
452 | case class Pair[S, T](a: S, b: T)
453 | }
454 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/tarao/slickjdbc/query/TranslatorSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.tarao
2 | package slickjdbc
3 | package query
4 |
5 | import helper.UnitSpec
6 |
7 | class ContextSpec extends UnitSpec {
8 | describe("Context.caller") {
9 | it("should return a caller") {
10 | val context = new Context {}
11 | context.caller.toString should fullyMatch regex (
12 | "com[.]github[.]tarao[.]slickjdbc[.]query[.]ContextSpec.*[(]TranslatorSpec[.]scala:[0-9]+[)]"
13 | )
14 | }
15 | }
16 | }
17 |
18 | class SQLCommentSpec extends UnitSpec {
19 | describe("SQLComment") {
20 | it("should do nothing for a query without whitespace") {
21 | SQLComment("comment").embedTo("query") should equal ("query")
22 | }
23 |
24 | it("should insert a comment") {
25 | SQLComment("comment").embedTo("some query") should equal (
26 | "some /* comment */ query"
27 | )
28 | }
29 |
30 | it("should escape a comment") {
31 | SQLComment("/* comment */").embedTo("some query") should equal (
32 | """some /* /* comment *\\/ */ query"""
33 | )
34 | }
35 |
36 | it("should insert any comment that can be a string") {
37 | val obj = new { override def toString = "comment" }
38 | SQLComment(obj).embedTo("some query") should equal (
39 | """some /* comment */ query"""
40 | )
41 | }
42 | }
43 | }
44 |
45 | class CallerCommenterSpec extends UnitSpec {
46 | describe("CallerCommenter") {
47 | it("should embed a caller information as a comment") {
48 | implicit val translators : Iterable[Translator] = Seq(CallerCommenter)
49 | val query = Translator.translate("SELECT * FROM entry LIMIT 1")
50 | val caller = "com[.]github[.]tarao[.]slickjdbc[.]query[.]CallerCommenterSpec.*[(]TranslatorSpec[.]scala:[0-9]+[)]"
51 | query should fullyMatch regex (
52 | s"SELECT /[*] $caller [*]/ [*] FROM entry LIMIT 1"
53 | )
54 | }
55 | }
56 | }
57 |
58 | class MarginStripperSpec extends UnitSpec {
59 | describe("MarginStripper") {
60 | it("should strip margin by '|'") {
61 | implicit val translators : Iterable[Translator] = Seq(MarginStripper)
62 | val query = Translator.translate("""
63 | | SELECT * FROM entry
64 | | LIMIT 1""")
65 | query should equal ("\n SELECT * FROM entry\n LIMIT 1")
66 | }
67 | }
68 | }
69 |
70 | class TranslatorSpec extends UnitSpec {
71 | describe("Translator") {
72 | it("should a Context") {
73 | Translator shouldBe a [Context]
74 | }
75 |
76 | it("should pass itself as a context") {
77 | Translator.translate("")(Seq(
78 | new Translator {
79 | def apply(query: String, context: Context) = {
80 | context should equal (Translator)
81 | query
82 | }
83 | }
84 | ))
85 | }
86 |
87 | it("should do nothing for empty translators") {
88 | Translator.translate("foo")(Seq.empty) should equal ("foo")
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/version.sbt:
--------------------------------------------------------------------------------
1 | ThisBuild / version := "0.2.0-SNAPSHOT"
2 |
--------------------------------------------------------------------------------