Skip to content

Commit

Permalink
Port to Slick 3.2
Browse files Browse the repository at this point in the history
  • Loading branch information
d6y committed Feb 24, 2017
1 parent f764fb1 commit 3d20df2
Show file tree
Hide file tree
Showing 14 changed files with 80 additions and 424 deletions.
6 changes: 3 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ tutSourceDirectory := sourceDirectory.value / "pages"

tutTargetDirectory := target.value / "pages"

scalaVersion := "2.11.7"
scalaVersion := "2.12.1"

scalacOptions ++= Seq(
"-deprecation",
Expand All @@ -18,8 +18,8 @@ scalacOptions ++= Seq(
)

libraryDependencies ++= Seq(
"com.typesafe.slick" %% "slick" % "3.1.1",
"com.typesafe.slick" %% "slick-hikaricp" % "3.1.1",
"com.typesafe.slick" %% "slick" % "3.2.0",
"com.typesafe.slick" %% "slick-hikaricp" % "3.2.0",
"com.h2database" % "h2" % "1.4.185",
"ch.qos.logback" % "logback-classic" % "1.1.2",
"joda-time" % "joda-time" % "2.6",
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.7")
addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.8")

5 changes: 5 additions & 0 deletions sbt.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash

export JAVA_OPTS="-Xmx3g -XX:+TieredCompilation -XX:ReservedCodeCacheSize=256m -XX:+UseNUMA -XX:+UseParallelGC -XX:+CMSClassUnloadingEnabled"

sbt "$@"
2 changes: 1 addition & 1 deletion src/pages/0-preface.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ This material is aimed at beginner-to-intermediate Scala developers. You need:

* an installed JDK 8 or later, along with a programmer's text editor or IDE.

The material presented focuses on Slick version 3.1. Examples use [H2][link-h2-home] as the relational database.
The material presented focuses on Slick version 3.2. Examples use [H2][link-h2-home] as the relational database.

## How to Contact Us {-}

Expand Down
35 changes: 18 additions & 17 deletions src/pages/1-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,19 @@ Which will give output similar to:

~~~ scala
> console
[info] Compiling 1 Scala source to /Users/jonoabroad/developer/books/essential-slick-code/chapter-01/target/scala-2.11/classes...
[info] Starting scala interpreter...
[info]
import slick.driver.H2Driver.api._
Welcome to Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_112).
Type in expressions for evaluation. Or try :help.

scala> import slick.jdbc.H2Profile.api._
import Example._
import scala.concurrent.duration._
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
db: slick.driver.H2Driver.backend.Database = slick.jdbc.JdbcBackend$DatabaseDef@75028b56
exec: [T](program: slick.driver.H2Driver.api.DBIO[T])T
db: slick.jdbc.H2Profile.backend.Database = slick.jdbc.JdbcBackend$DatabaseDef@ac9a820
exec: [T](program: slick.jdbc.H2Profile.api.DBIO[T])T
res0: Option[Int] = Some(4)
Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala>
~~~

Expand Down Expand Up @@ -162,10 +160,10 @@ name := "essential-slick-chapter-01"

version := "1.0.0"

scalaVersion := "2.11.7"
scalaVersion := "2.12.1"

libraryDependencies ++= Seq(
"com.typesafe.slick" %% "slick" % "3.1.1",
"com.typesafe.slick" %% "slick" % "3.2.0",
"com.h2database" % "h2" % "1.4.185",
"ch.qos.logback" % "logback-classic" % "1.1.2"
)
Expand All @@ -183,13 +181,13 @@ If we were using a separate database like MySQL or PostgreSQL, we would substitu

### Importing Library Code

Database management systems are not created equal. Different systems support different data types, different dialects of SQL, and different querying capabilities. To model these capabilities in a way that can be checked at compile time, Slick provides most of its API via a database-specific *driver*. For example, we access most of the Slick API for H2 via the following `import`:
Database management systems are not created equal. Different systems support different data types, different dialects of SQL, and different querying capabilities. To model these capabilities in a way that can be checked at compile time, Slick provides most of its API via a database-specific *profile*. For example, we access most of the Slick API for H2 via the following `import`:

```tut:silent
import slick.driver.H2Driver.api._
import slick.jdbc.H2Profile.api._
```

Slick makes heavy use of implicit conversions and extension methods, so we generally need to include this import anywhere where we're working with queries or the database. [Chapter 5](#Modelling) looks how you can keep a specific database driver out of your code until necessary.
Slick makes heavy use of implicit conversions and extension methods, so we generally need to include this import anywhere where we're working with queries or the database. [Chapter 5](#Modelling) looks how you can keep a specific database profile out of your code until necessary.

### Defining our Schema

Expand Down Expand Up @@ -600,25 +598,28 @@ messages += Message("Dave","What if I say 'Pretty please'?")
If you don't wait for the future to complete, you'll see just the future itself:

```tut:book
db.run(messages += Message("Dave","What if I say 'Pretty please'?"))
val f = db.run(messages += Message("Dave","What if I say 'Pretty please'?"))
```


```tut:invisible
{
// Post-exercise clean up
// We inserted a new message for Dave twice in the last solution.
// We need to fix this so the next exercise doesn't contain confusing duplicates
// NB: this block is not inside {}s because doing that triggered:
// Could not initialize class $line41.$read$$iw$$iw$$iw$$iw$$iw$$iw$
import scala.concurrent.ExecutionContext.Implicits.global
val ex1cleanup = for {
val ex1cleanup: DBIO[Int] = for {
_ <- messages.filter(_.content === "What if I say 'Pretty please'?").delete
m = Message("Dave","What if I say 'Pretty please'?", 5L)
_ <- messages.forceInsert(m)
count <- messages.filter(_.content === "What if I say 'Pretty please'?").length.result
} yield count
val rowCount = exec(ex1cleanup)
assert(rowCount == 1, s"Wrong number of rows after cleaning up ex1: $rowCount")
}
```
</div>

Expand Down
7 changes: 4 additions & 3 deletions src/pages/2-selecting.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ In [Chapter 6](#joins) we'll look at more complex queries involving joins, aggre
The simplest select query is the `TableQuery` generated from a `Table`. In the following example, `messages` is a `TableQuery` for `MessageTable`:

```tut:silent
import slick.driver.H2Driver.api._
import slick.jdbc.H2Profile.api._
```
```tut:book
final case class Message(
Expand Down Expand Up @@ -48,10 +48,10 @@ Our `TableQuery` is the equivalent of the SQL `select * from message`.
**Query Extension Methods**

Like many of the methods discussed below, the `result` method is actually an extension method applied to `Query` via an implicit conversion.
You'll need to have everything from `H2Driver.api` in scope for this to work:
You'll need to have everything from `H2Profile.api` in scope for this to work:

```tut:silent
import slick.driver.H2Driver.api._
import slick.jdbc.H2Profile.api._
```
</div>

Expand Down Expand Up @@ -681,6 +681,7 @@ We'll see the Slick representation of `GROUP` and `JOIN` in [Chapter 6](#joins).
We introduced some new terminology:

* _unpacked_ type, which is the regular Scala types we work with, such as `String`; and

* _mixed_ type, which is Slick's column representation, such as `Rep[String]`.

We run queries by converting them to actions using the `result` method.
Expand Down
10 changes: 5 additions & 5 deletions src/pages/3-modifying.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
```tut:invisible
import slick.driver.H2Driver.api._
import slick.jdbc.H2Profile.api._
final case class Message(
sender: String,
Expand Down Expand Up @@ -178,11 +178,11 @@ exec(messages returning messages +=
This is a shame, but getting the primary key is often all we need.

<div class="callout callout-info">
**Driver Capabilities**
**Profile Capabilities**

The Slick manual contains a comprehensive table of the [capabilities for each database driver][link-ref-dbs]. The ability to return complete records from an insert query is referenced as the `jdbc.returnInsertOther` capability.
The Slick manual contains a comprehensive table of the [capabilities for each database profile][link-ref-dbs]. The ability to return complete records from an insert query is referenced as the `jdbc.returnInsertOther` capability.

The API documentation for each driver also lists the capabilities that the driver *doesn't* have. For an example, the top of the [H2 Driver Scaladoc][link-ref-h2driver] page points out several of its shortcomings.
The API documentation for each profile also lists the capabilities that the profile *doesn't* have. For an example, the top of the [H2 Profile Scaladoc][link-ref-h2driver] page points out several of its shortcomings.
</div>

If we want to get a complete populated `Message` back from a database without `jdbc.returnInsertOther` support, we retrieve the primary key and manually add it to the inserted record. Slick simplifies this with another method, `into`:
Expand Down Expand Up @@ -520,7 +520,7 @@ For modifying the rows in the database we have seen that:

Auto-incrementing values are inserted by Slick, unless forced. The auto-incremented values can be returned from the insert by using `returning`.

Databases have different capabilities. The limitations of each driver is listed in the driver's Scala Doc page.
Databases have different capabilities. The limitations of each profile is listed in the profile's Scala Doc page.


## Exercises
Expand Down
2 changes: 1 addition & 1 deletion src/pages/4-combining-actions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
```tut:invisible
import slick.driver.H2Driver.api._
import slick.jdbc.H2Profile.api._
final case class Message(
sender: String,
Expand Down
45 changes: 12 additions & 33 deletions src/pages/5-data_modelling.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,40 +21,31 @@ So far, all of our examples have been written in a single Scala file.
This approach doesn't scale to larger application codebases.
In this section we'll explain how to split up application code into modules.

Until now we've also been exclusively using Slick's H2 driver.
Until now we've also been exclusively using Slick's H2 profile.
When writing real applications we often need to be able to
switch drivers in different circumstances.
switch profiles in different circumstances.
For example, we may use PostgreSQL in production and H2 in our unit tests.

An example of this pattern can be found in the [example project][link-example],
folder _chapter-05_, file _structure.scala_.

### Abstracting over Databases

Let's look at how we can write code that works with multiple different database drivers.
Let's look at how we can write code that works with multiple different database profiles.
When we previously wrote...

```tut:book
import slick.driver.H2Driver.api._
import slick.jdbc.H2Profile.api._
```

...we were locking ourselves into H2.
We want to write an import that works with a variety of drivers.
Fortunately, Slick provides a common supertype for the drivers
for the most popular databases---a trait called `JdbcProfile`:
We want to write an import that works with a variety of profiles.
Fortunately, Slick provides a common supertype for profiles---a trait called `JdbcProfile`:

```tut:book
import slick.driver.JdbcProfile
import slick.jdbc.JdbcProfile
```

<div class="callout callout-info">
*Drivers and Profiles*

Slick uses the words "driver" and "profile" interchangeably.
We'll start referring to Slick drivers as "profiles" here
to distinguish them from the JDBC drivers that sit lower in the code.
</div>

We can't import directly from `JdbcProfile` because it isn't a concrete object.
Instead, we have to *inject a dependency* of type `JdbcProfile` into our application
and import from that. The basic pattern we'll use is as follows:
Expand All @@ -81,7 +72,7 @@ trait DatabaseModule {
object Main extends App {
// Instantiate the database module, assigning a concrete profile:
val databaseLayer = new DatabaseModule {
val profile = slick.driver.H2Driver
val profile = slick.jdbc.H2Profile
}
}
```
Expand Down Expand Up @@ -123,7 +114,7 @@ class DatabaseLayer(val profile: JdbcProfile) extends
// Instantiate the modules and inject a profile:
object Main extends App {
val databaseLayer = new DatabaseLayer(slick.driver.H2Driver)
val databaseLayer = new DatabaseLayer(slick.jdbc.H2Profile)
}
```

Expand All @@ -136,7 +127,7 @@ To work with a different database, we inject a different profile
when we instantiate the database code:

```tut:book
val anotherDatabaseLayer = new DatabaseLayer(slick.driver.PostgresDriver)
val anotherDatabaseLayer = new DatabaseLayer(slick.jdbc.PostgresProfile)
```

This basic pattern is reasonable way of structuring your application.
Expand All @@ -159,7 +150,7 @@ object messages extends TableQuery(new MessageTable(_)) {
def messagesFrom(name: String) =
this.filter(_.sender === name)
val numSenders = this.map(_.sender).countDistinct
val numSenders = this.map(_.sender).distinct.length
}
~~~
Expand Down Expand Up @@ -533,7 +524,7 @@ so we've introduced a type alias for `AttrHList`
to avoid as much typing as we can.

```tut:invisible
import slick.driver.H2Driver.api._
import slick.jdbc.H2Profile.api._
import scala.concurrent.{Await,Future}
import scala.concurrent.duration._
Expand Down Expand Up @@ -865,18 +856,6 @@ meaning the schema will include:
ALTER TABLE "user" ADD CONSTRAINT "pk_id" PRIMARY KEY("id")
~~~

<div class="callout callout-info">
**H2 Issue**

As it happens, this specific example [doesn't currently work with H2 and Slick](https://github.com/slick/slick/issues/763).

The `O.AutoInc` marks the column as an H2 "IDENTIY"
column which is, implicitly, a primary key as far as H2 is concerned.

It's fixed in Slick 3.2.
</div>


The `primaryKey` method is more useful for defining *compound* primary keys
that involve two or more columns.

Expand Down
16 changes: 7 additions & 9 deletions src/pages/6-joins.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ To demonstrate joins we will need at least two tables.
We'll start this chapter with `User`...

```tut:book
import slick.driver.H2Driver.api._
import slick.jdbc.H2Profile.api._
import scala.concurrent.ExecutionContext.Implicits.global
case class User(name: String, id: Long = 0L)
Expand Down Expand Up @@ -632,19 +632,19 @@ of your chosen database profile:

```tut:book
// H2 supports zip
slick.driver.H2Driver.capabilities.
slick.jdbc.H2Profile.capabilities.
map(_.toString).
contains("relational.zip")
// SQLite does not support zip
slick.driver.SQLiteDriver.capabilities.
slick.jdbc.SQLiteProfile.capabilities.
map(_.toString).
contains("relational.zip")
```

```tut:invisible
assert(false == slick.driver.SQLiteDriver.capabilities.map(_.toString).contains("relational.zip"), "SQLLite now supports ZIP!")
assert(true == slick.driver.H2Driver.capabilities.map(_.toString).contains("relational.zip"), "H2 no longer supports ZIP?!")
assert(false == slick.jdbc.SQLiteProfile.capabilities.map(_.toString).contains("relational.zip"), "SQLLite now supports ZIP!")
assert(true == slick.jdbc.H2Profile.capabilities.map(_.toString).contains("relational.zip"), "H2 no longer supports ZIP?!")
```


Expand Down Expand Up @@ -712,8 +712,6 @@ Method SQL
--------------- ---------------------------------------------------
`length` `COUNT(1)`

`countDistinct` `COUNT(DISTINCT column)`

`min` `MIN(column)`

`max` `MAX(column)`
Expand All @@ -732,13 +730,13 @@ Using them causes no great surprises, as shown in the following examples:
val numRows: DBIO[Int] = messages.length.result
val numDifferentSenders: DBIO[Int] =
messages.map(_.senderId).countDistinct.result
messages.map(_.senderId).distinct.length.result
val firstSent: DBIO[Option[Long]] =
messages.map(_.id).min.result
```

While `length` and `countDistinct` return an `Int`, the other functions return an `Option`.
While `length` returns an `Int`, the other functions return an `Option`.
This is because there may be no rows returned by the query, meaning the is no minimum, maximum and so on.


Expand Down
8 changes: 4 additions & 4 deletions src/pages/7-plain_sql.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
```tut:invisible
import slick.driver.H2Driver.api._
import slick.jdbc.H2Profile.api._
import scala.concurrent.{Await,Future}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
Expand Down Expand Up @@ -358,7 +358,7 @@ The `@StaticDatabaseConfig` syntax is called an _annotation_. This particular `S

```scala
tsql {
driver = "slick.driver.H2Driver$"
profile = "slick.jdbc.H2Profile$"
db {
connectionPool = disabled
url = "jdbc:h2:mem:chapter06; INIT=
Expand All @@ -369,9 +369,9 @@ tsql {
}
```

Note the `$` in the driver class name is not a typo. The class name is being passed to Java's `Class.forName`, but of course Java doesn't have a singleton as such. The Slick configuration does the right thing to load `$MODULE` when it sees `$`. This interoperability with Java is described in [Chapter 29 of _Programming in Scala_][link-pins-interop].
Note the `$` in the profile class name is not a typo. The class name is being passed to Java's `Class.forName`, but of course Java doesn't have a singleton as such. The Slick configuration does the right thing to load `$MODULE` when it sees `$`. This interoperability with Java is described in [Chapter 29 of _Programming in Scala_][link-pins-interop].

You won't have seen this when we introduced the database configuration in Chapter 1. That's because this `tsql` configuration has a different format, and combines the Slick driver (`slicker.driver.H2Driver$`) and the JDBC driver (`org.h2.Drvier`) in one entry.
You won't have seen this when we introduced the database configuration in Chapter 1. That's because this `tsql` configuration has a different format, and combines the Slick profile (`slick.jdbcr.H2Profile`) and the JDBC driver (`org.h2.Drvier`) in one entry.

A consequence of supplying a `@StaticDatabaseConfig` is that you can define one databases configuration for your application and a different one for the compiler to use. That is, perhaps you are running an application, or test suite, against an in-memory database, but validating the queries at compile time against a full-populated production-like integration database.

Expand Down
Loading

0 comments on commit 3d20df2

Please sign in to comment.