Skip to content

Commit 60b8931

Browse files
committed
Merge pull request #4 from typesafehub/wip/2.0-improvements
Some improvements to Hello Slick, still targeting 2.0
2 parents f1b313b + 2c7acbc commit 60b8931

6 files changed

Lines changed: 135 additions & 29 deletions

File tree

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ libraryDependencies ++= List(
1010
"com.typesafe.slick" %% "slick" % "2.0.0",
1111
"org.slf4j" % "slf4j-nop" % "1.6.4",
1212
"com.h2database" % "h2" % "1.3.170",
13-
"org.scalatest" %% "scalatest" % "2.0"
13+
"org.scalatest" %% "scalatest" % "2.0" % "test"
1414
)

src/main/scala/CaseClassMapping.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ object CaseClassMapping extends App {
55
// the base query for the Users table
66
val users = TableQuery[Users]
77

8-
Database.forURL("jdbc:h2:mem:hello", driver = "org.h2.Driver").withSession { implicit session =>
8+
val db = Database.forURL("jdbc:h2:mem:hello", driver = "org.h2.Driver")
9+
db.withSession { implicit session =>
910

1011
// create the schema
1112
users.ddl.create
@@ -27,6 +28,7 @@ class Users(tag: Tag) extends Table[User](tag, "USERS") {
2728
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
2829
// The name can't be null
2930
def name = column[String]("NAME", O.NotNull)
30-
// the * projection (e.g. select * ...) auto-transform the tupled column values to / from a User
31+
// the * projection (e.g. select * ...) auto-transforms the tupled
32+
// column values to / from a User
3133
def * = (name, id.?) <> (User.tupled, User.unapply)
32-
}
34+
}

src/main/scala/HelloSlick.scala

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@ object HelloSlick extends App {
1010
val coffees: TableQuery[Coffees] = TableQuery[Coffees]
1111

1212
// Create a connection (called a "session") to an in-memory H2 database
13-
Database.forURL("jdbc:h2:mem:hello", driver = "org.h2.Driver").withSession { implicit session =>
13+
val db = Database.forURL("jdbc:h2:mem:hello", driver = "org.h2.Driver")
14+
db.withSession { implicit session =>
1415

15-
// Create the schema by combining the DDLs for the Suppliers and Coffees tables using the query interfaces
16+
// Create the schema by combining the DDLs for the Suppliers and Coffees
17+
// tables using the query interfaces
1618
(suppliers.ddl ++ coffees.ddl).create
1719

1820

1921
/* Create / Insert */
2022

2123
// Insert some suppliers
2224
suppliers += (101, "Acme, Inc.", "99 Market Street", "Groundsville", "CA", "95199")
23-
suppliers += (49, "Superior Coffee", "1 Party Place", "Mendocino", "CA", "95460")
25+
suppliers += ( 49, "Superior Coffee", "1 Party Place", "Mendocino", "CA", "95460")
2426
suppliers += (150, "The High Ground", "100 Coffee Lane", "Meadows", "CA", "93966")
2527

2628
// Insert some coffees (using JDBC's batch insert feature)
@@ -32,10 +34,13 @@ object HelloSlick extends App {
3234
("French_Roast_Decaf", 49, 9.99, 0, 0)
3335
)
3436

35-
val allSuppliers: List[(Int, String, String, String, String, String)] = suppliers.list
37+
val allSuppliers: List[(Int, String, String, String, String, String)] =
38+
suppliers.list
3639

3740
// Print the number of rows inserted
38-
coffeesInsertResult foreach (numRows => println(s"Inserted $numRows rows into the Coffees table"))
41+
coffeesInsertResult foreach { numRows =>
42+
println(s"Inserted $numRows rows into the Coffees table")
43+
}
3944

4045

4146
/* Read / Query / Select */
@@ -52,7 +57,8 @@ object HelloSlick extends App {
5257
/* Filtering / Where */
5358

5459
// Construct a query where the price of Coffees is > 9.0
55-
val filterQuery: Query[Coffees, (String, Int, Double, Int, Int)] = coffees.filter(_.price > 9.0)
60+
val filterQuery: Query[Coffees, (String, Int, Double, Int, Int)] =
61+
coffees.filter(_.price > 9.0)
5662

5763
println("Generated SQL for filter query:\n" + filterQuery.selectStatement)
5864

@@ -77,7 +83,8 @@ object HelloSlick extends App {
7783
/* Delete */
7884

7985
// Construct a delete query that deletes coffees with a price less than 8.0
80-
val deleteQuery: Query[Coffees,(String, Int, Double, Int, Int)] = coffees.filter(_.price < 8.0)
86+
val deleteQuery: Query[Coffees,(String, Int, Double, Int, Int)] =
87+
coffees.filter(_.price < 8.0)
8188

8289
// Print the SQL for the Coffees delete query
8390
println("Generated SQL for Coffees delete:\n" + deleteQuery.deleteStatement)
@@ -93,27 +100,32 @@ object HelloSlick extends App {
93100
// Construct a new coffees query that just selects the name
94101
val justNameQuery: Query[Column[String], String] = coffees.map(_.name)
95102

96-
println("Generated SQL for query returning just the name:\n" + justNameQuery.selectStatement)
103+
println("Generated SQL for query returning just the name:\n" +
104+
justNameQuery.selectStatement)
97105

98106
// Execute the query
99107
println(justNameQuery.list)
100108

101109

102110
/* Sorting / Order By */
103111

104-
val sortByPriceQuery: Query[Coffees, (String, Int, Double, Int, Int)] = coffees.sortBy(_.price)
112+
val sortByPriceQuery: Query[Coffees, (String, Int, Double, Int, Int)] =
113+
coffees.sortBy(_.price)
105114

106-
println("Generated SQL for query sorted by price:\n" + sortByPriceQuery.selectStatement)
115+
println("Generated SQL for query sorted by price:\n" +
116+
sortByPriceQuery.selectStatement)
107117

108118
// Execute the query
109119
println(sortByPriceQuery.list)
110120

111121

112122
/* Query Composition */
113123

114-
val composedQuery: Query[Column[String], String] = coffees.sortBy(_.name).take(3).filter(_.price > 9.0).map(_.name)
124+
val composedQuery: Query[Column[String], String] =
125+
coffees.sortBy(_.name).take(3).filter(_.price > 9.0).map(_.name)
115126

116-
println("Generated SQL for composed query:\n" + composedQuery.selectStatement)
127+
println("Generated SQL for composed query:\n" +
128+
composedQuery.selectStatement)
117129

118130
// Execute the composed query
119131
println(composedQuery.list)
@@ -161,4 +173,4 @@ object HelloSlick extends App {
161173
println(plainQuery.list)
162174

163175
}
164-
}
176+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import scala.slick.driver.H2Driver.simple._
2+
3+
// Demonstrates various ways of reading data from an Invoker.
4+
object InvokerMethods extends App {
5+
6+
// A simple dictionary table with keys and values
7+
class Dict(tag: Tag) extends Table[(Int, String)](tag, "INT_DICT") {
8+
def key = column[Int]("KEY", O.PrimaryKey)
9+
def value = column[String]("VALUE")
10+
def * = (key, value)
11+
}
12+
val dict = TableQuery[Dict]
13+
14+
val db = Database.forURL("jdbc:h2:mem:invoker", driver = "org.h2.Driver")
15+
db.withSession { implicit session =>
16+
17+
// Create the dictionary table and insert some data
18+
dict.ddl.create
19+
dict ++= Seq(1 -> "a", 2 -> "b", 3 -> "c", 4 -> "d", 5 -> "e")
20+
21+
// Define a pre-compiled parameterized query for reading all key/value
22+
// pairs up to a given key.
23+
val upTo = Compiled { k: Column[Int] =>
24+
dict.filter(_.key <= k).sortBy(_.key)
25+
}
26+
27+
println("List of k/v pairs up to 3 with .list")
28+
println("- " + upTo(3).list)
29+
30+
println("IndexedSeq of k/v pairs up to 3 with .buildColl")
31+
println("- " + upTo(3).buildColl[IndexedSeq])
32+
33+
println("Set of k/v pairs up to 3 with .buildColl")
34+
println("- " + upTo(3).buildColl[Set])
35+
36+
println("Array of k/v pairs up to 3 with .buildColl")
37+
println("- " + upTo(3).buildColl[Array])
38+
39+
println("All keys in an unboxed Array[Int]")
40+
val allKeys = dict.map(_.key)
41+
println(" " + allKeys.buildColl[Array]())
42+
43+
println("Stream k/v pairs up to 3 via an Iterator")
44+
val it = upTo(3).iterator
45+
try {
46+
it.foreach { case (k, v) => println(s"- $k -> $v") }
47+
} finally {
48+
// Make sure to close the Iterator in case of an error. (It is
49+
// automatically closed when all data has been read.)
50+
it.close
51+
}
52+
53+
println("Only get the first result, failing if there is none")
54+
println("- " + upTo(3).first)
55+
56+
println("Get the first result as an Option, or None")
57+
println("- " + upTo(3).firstOption)
58+
59+
println("Map of k/v pairs up to 3 with .toMap")
60+
println("- " + upTo(3).toMap)
61+
62+
println("Combine the k/v pairs up to 3 with .foldLeft")
63+
println("- " + upTo(3).foldLeft("") { case (z, (k, v)) => s"$z[$k -> $v] " })
64+
}
65+
}

src/main/scala/Tables.scala

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,34 @@ import scala.slick.driver.H2Driver.simple._
22
import scala.slick.lifted.{ProvenShape, ForeignKeyQuery}
33

44
// A Suppliers table with 6 columns: id, name, street, city, state, zip
5-
class Suppliers(tag: Tag) extends Table[(Int, String, String, String, String, String)](tag, "SUPPLIERS") {
6-
def id: Column[Int] = column[Int]("SUP_ID", O.PrimaryKey) // This is the primary key column
5+
class Suppliers(tag: Tag)
6+
extends Table[(Int, String, String, String, String, String)](tag, "SUPPLIERS") {
7+
8+
// This is the primary key column:
9+
def id: Column[Int] = column[Int]("SUP_ID", O.PrimaryKey)
710
def name: Column[String] = column[String]("SUP_NAME")
811
def street: Column[String] = column[String]("STREET")
912
def city: Column[String] = column[String]("CITY")
1013
def state: Column[String] = column[String]("STATE")
1114
def zip: Column[String] = column[String]("ZIP")
1215

1316
// Every table needs a * projection with the same type as the table's type parameter
14-
def * : ProvenShape[(Int, String, String, String, String, String)] = (id, name, street, city, state, zip)
17+
def * : ProvenShape[(Int, String, String, String, String, String)] =
18+
(id, name, street, city, state, zip)
1519
}
1620

1721
// A Coffees table with 5 columns: name, supplier id, price, sales, total
18-
class Coffees(tag: Tag) extends Table[(String, Int, Double, Int, Int)](tag, "COFFEES") {
22+
class Coffees(tag: Tag)
23+
extends Table[(String, Int, Double, Int, Int)](tag, "COFFEES") {
24+
1925
def name: Column[String] = column[String]("COF_NAME", O.PrimaryKey)
2026
def supID: Column[Int] = column[Int]("SUP_ID")
2127
def price: Column[Double] = column[Double]("PRICE")
2228
def sales: Column[Int] = column[Int]("SALES")
2329
def total: Column[Int] = column[Int]("TOTAL")
2430

25-
def * : ProvenShape[(String, Int, Double, Int, Int)] = (name, supID, price, sales, total)
31+
def * : ProvenShape[(String, Int, Double, Int, Int)] =
32+
(name, supID, price, sales, total)
2633

2734
// A reified foreign key relation that can be navigated to create a join
2835
def supplier: ForeignKeyQuery[Suppliers, (Int, String, String, String, String, String)] =

tutorial/index.html

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ <h2>Database Connection</h2>
5454

5555
<p>
5656
Every query that runs against the database needs a database session to run with. The <a href="#code/src/main/scala/HelloSlick.scala" class="shortcut">HelloSlick.scala</a> file sets up a database connection and gets a session:
57-
<pre><code>Database.forURL("jdbc:h2:mem:hello", driver = "org.h2.Driver").withSession { session =&gt;</code></pre>
57+
<pre><code>val db = Database.forURL("jdbc:h2:mem:hello", driver = "org.h2.Driver")
58+
db.withSession { implicit session =&gt; ... }</code></pre>
5859
Note: the <code>session</code> can be implicit to avoid specifying it explicitly with every query.<br/>
5960

6061
The <code>session</code> can now be used in the scope of the provided function to make queries to the database. The session is automatically closed after the function completes.
@@ -69,11 +70,12 @@ <h2>Database Connection</h2>
6970
<h2>Creating the Schema</h2>
7071

7172
<p>
72-
Once a session is available you can use it to perform operations on the database. To create corresponding tables from a mapping you can get the DDL via it's <code>TableQuery</code> and then call the <code>create</code> method, like:
73+
Once a session is available you can use it to perform operations on the database. To create corresponding tables from a mapping you can get the DDL via its <code>TableQuery</code> and then call the <code>create</code> method, like:
7374
<pre><code>suppliers.ddl.create</code></pre>
7475

7576
Multiple DDLs can also be combined together and created, like in <a href="#code/src/main/scala/HelloSlick.scala" class="shortcut">HelloSlick.scala</a>:
7677
<pre><code>(suppliers.ddl ++ coffees.ddl).create</code></pre>
78+
This will create all database entities and links (like foreign key references) in the correct order, even in the presence of cyclic dependencies between tables.
7779
</p>
7880
</div>
7981

@@ -170,10 +172,10 @@ <h2>Computed Values</h2>
170172
</div>
171173

172174
<div>
173-
<h2>Manual SQL / String Interpolation</h2>
175+
<h2>Plain SQL / String Interpolation</h2>
174176

175177
<p>
176-
Sometimes writing manual sql is the easiest and best way to go but we don't want to lose SQL injection protection that Slick includes. SQL String Interpolation provides a nice API for doing this. Start by importing the interpolation API:
178+
Sometimes writing manual SQL is the easiest and best way to go but we don't want to lose SQL injection protection that Slick includes. SQL String Interpolation provides a nice API for doing this. Start by importing the interpolation API:
177179
<pre><code>import scala.slick.jdbc.StaticQuery.interpolation</code></pre>
178180

179181
Then use the <code>sql</code> String Interpolator:
@@ -182,6 +184,8 @@ <h2>Manual SQL / String Interpolation</h2>
182184

183185
This produces a query that can be run using the normal functions like <code>list</code>.
184186
</p>
187+
188+
<p>You can learn more about Slick's <em>Plain SQL</em> queries in the <a href="https://typesafe.com/activator/template/slick-plainsql" target="_blank">Slick Plain SQL Queries</a> template for Activator.</p>
185189
</div>
186190

187191
<div>
@@ -199,15 +203,31 @@ <h2>Auto-Generated Primary Keys</h2>
199203

200204
<p>
201205
The <code>Users</code> table mapping in <a href="#code/src/main/scala/CaseClassMapping.scala" class="shortcut">CaseClassMapping.scala</a> defines an id column which uses an auto-incrementing primary key:
202-
<pre><code>def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)</code></pre>
203-
206+
<pre><code>def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)</code></pre>
207+
208+
</p>
209+
</div>
210+
211+
<div>
212+
<h2>Running Queries</h2>
213+
214+
<p>So far you have seen the <code>Invoker</code> methods <code>.list</code> and <code>.foreach</code> being used to run a collection-valued query. There are several other useful methods which are shown in <a href="#code/src/main/scala/InvokerMethods.scala" class="shortcut">InvokerMethods.scala</a>. They are equally applicable to <em>Lifted Embedding</em> and <em>Plain SQL</em> queries.</p>
215+
216+
<p>Note the use of <code>Compiled</code> in this app. It is used to define a pre-compiled query that can be executed with different parameters without having to recompile the SQL statement each time. This is the prefered way of defining queries in real-world applications. It prevents the (possibly expensive) compilation each time and leads to the same SQL statement (or a small, fixed set of SQL statements) so that the database system can also reuse a previously computed execution plan. As a side-effect, all parameters are automatically turned into bind variables:
217+
218+
<pre><code>val upTo = Compiled { k: Column[Int] =>
219+
ts.filter(_.k <= k).sortBy(_.k)
220+
}</code></pre>
204221
</p>
222+
205223
</div>
206224

207225
<div>
208226
<h2>Next Steps</h2>
209227

210-
Check out the full <a href="http://slick.typesafe.com/doc/2.0.0/" target="_blank">Slick docs</a>.
228+
<p>Check out the full <a href="http://slick.typesafe.com/doc/2.0.1/" target="_blank">Slick manual and API docs</a>.</p>
229+
230+
<p>You can also find more Slick templates, contributed by both, the Slick team and the community, <a href="/home" class="shortcut">here in Activator</a>.</p>
211231
</div>
212232

213233
</body>

0 commit comments

Comments
 (0)