diff --git a/build.sbt b/build.sbt index 5c19847..7e8ee44 100644 --- a/build.sbt +++ b/build.sbt @@ -2,13 +2,12 @@ name := "hello-slick" version := "1.0" -scalaVersion := "2.10.3" +scalaVersion := "2.11.6" mainClass in Compile := Some("HelloSlick") -libraryDependencies ++= List( - "com.typesafe.slick" %% "slick" % "2.0.2", +libraryDependencies ++= Seq( + "com.typesafe.slick" %% "slick" % "3.1.0", "org.slf4j" % "slf4j-nop" % "1.6.4", - "com.h2database" % "h2" % "1.3.170", - "org.scalatest" %% "scalatest" % "2.0" % "test" + "com.h2database" % "h2" % "1.3.170" ) diff --git a/project/build.properties b/project/build.properties index be6c454..a6e117b 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.5 +sbt.version=0.13.8 diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf new file mode 100644 index 0000000..1601bc7 --- /dev/null +++ b/src/main/resources/application.conf @@ -0,0 +1,6 @@ +h2mem1 = { + url = "jdbc:h2:mem:test1" + driver = org.h2.Driver + connectionPool = disabled + keepAliveConnection = true +} \ No newline at end of file diff --git a/src/main/scala/CaseClassMapping.scala b/src/main/scala/CaseClassMapping.scala deleted file mode 100644 index 288b233..0000000 --- a/src/main/scala/CaseClassMapping.scala +++ /dev/null @@ -1,34 +0,0 @@ -import scala.slick.driver.H2Driver.simple._ - -object CaseClassMapping extends App { - - // the base query for the Users table - val users = TableQuery[Users] - - val db = Database.forURL("jdbc:h2:mem:hello", driver = "org.h2.Driver") - db.withSession { implicit session => - - // create the schema - users.ddl.create - - // insert two User instances - users += User("John Doe") - users += User("Fred Smith") - - // print the users (select * from USERS) - println(users.list) - } - -} - -case class User(name: String, id: Option[Int] = None) - -class Users(tag: Tag) extends Table[User](tag, "USERS") { - // Auto Increment the id primary key column - def id = column[Int]("ID", O.PrimaryKey, O.AutoInc) - // The name can't be null - def name = column[String]("NAME", O.NotNull) - // the * projection (e.g. select * ...) auto-transforms the tupled - // column values to / from a User - def * = (name, id.?) <> (User.tupled, User.unapply) -} diff --git a/src/main/scala/HelloSlick.scala b/src/main/scala/HelloSlick.scala index f09d135..34f53c0 100644 --- a/src/main/scala/HelloSlick.scala +++ b/src/main/scala/HelloSlick.scala @@ -1,4 +1,10 @@ -import scala.slick.driver.H2Driver.simple._ +import slick.driver.H2Driver.api._ +import slick.driver.H2Driver.api.Database +import scala.concurrent.duration._ + + +import scala.concurrent.Await +import scala.concurrent.ExecutionContext.Implicits.global // The main application object HelloSlick extends App { @@ -8,169 +14,42 @@ object HelloSlick extends App { // the query interface for the Coffees table val coffees: TableQuery[Coffees] = TableQuery[Coffees] - + // + // // Create a connection (called a "session") to an in-memory H2 database + // Create a connection (called a "session") to an in-memory H2 database - val db = Database.forURL("jdbc:h2:mem:hello", driver = "org.h2.Driver") - db.withSession { implicit session => + val db = Database.forConfig("h2mem1") - // Create the schema by combining the DDLs for the Suppliers and Coffees - // tables using the query interfaces - (suppliers.ddl ++ coffees.ddl).create + val setup = DBIO.seq( + // Create the tables, including primary and foreign keys + (suppliers.schema ++ coffees.schema).create, - - /* Create / Insert */ - // Insert some suppliers - suppliers += (101, "Acme, Inc.", "99 Market Street", "Groundsville", "CA", "95199") - suppliers += ( 49, "Superior Coffee", "1 Party Place", "Mendocino", "CA", "95460") - suppliers += (150, "The High Ground", "100 Coffee Lane", "Meadows", "CA", "93966") - - // Insert some coffees (using JDBC's batch insert feature) - val coffeesInsertResult: Option[Int] = coffees ++= Seq ( - ("Colombian", 101, 7.99, 0, 0), - ("French_Roast", 49, 8.99, 0, 0), - ("Espresso", 150, 9.99, 0, 0), - ("Colombian_Decaf", 101, 8.99, 0, 0), + suppliers +=(101, "Acme, Inc.", "99 Market Street", "Groundsville", "CA", "95199"), + suppliers +=(49, "Superior Coffee", "1 Party Place", "Mendocino", "CA", "95460"), + suppliers +=(150, "The High Ground", "100 Coffee Lane", "Meadows", "CA", "93966"), + // Equivalent SQL code: + // insert into SUPPLIERS(SUP_ID, SUP_NAME, STREET, CITY, STATE, ZIP) values (?,?,?,?,?,?) + + // Insert some coffees (using JDBC's batch insert feature, if supported by the DB) + coffees ++= Seq( + ("Colombian", 101, 7.99, 0, 0), + ("French_Roast", 49, 8.99, 0, 0), + ("Espresso", 150, 9.99, 0, 0), + ("Colombian_Decaf", 101, 8.99, 0, 0), ("French_Roast_Decaf", 49, 9.99, 0, 0) ) - - val allSuppliers: List[(Int, String, String, String, String, String)] = - suppliers.list - - // Print the number of rows inserted - coffeesInsertResult foreach { numRows => - println(s"Inserted $numRows rows into the Coffees table") - } - - - /* Read / Query / Select */ - - // Print the SQL for the Coffees query - println("Generated SQL for base Coffees query:\n" + coffees.selectStatement) - - // Query the Coffees table using a foreach and print each row - coffees foreach { case (name, supID, price, sales, total) => - println(" " + name + "\t" + supID + "\t" + price + "\t" + sales + "\t" + total) - } - - - /* Filtering / Where */ - - // Construct a query where the price of Coffees is > 9.0 - val filterQuery: Query[Coffees, (String, Int, Double, Int, Int)] = - coffees.filter(_.price > 9.0) - - println("Generated SQL for filter query:\n" + filterQuery.selectStatement) - - // Execute the query - println(filterQuery.list) - - - /* Update */ - - // Construct an update query with the sales column being the one to update - val updateQuery: Query[Column[Int], Int] = coffees.map(_.sales) - - // Print the SQL for the Coffees update query - println("Generated SQL for Coffees update:\n" + updateQuery.updateStatement) - - // Perform the update - val numUpdatedRows = updateQuery.update(1) - - println(s"Updated $numUpdatedRows rows") - - - /* Delete */ - - // Construct a delete query that deletes coffees with a price less than 8.0 - val deleteQuery: Query[Coffees,(String, Int, Double, Int, Int)] = - coffees.filter(_.price < 8.0) - - // Print the SQL for the Coffees delete query - println("Generated SQL for Coffees delete:\n" + deleteQuery.deleteStatement) - - // Perform the delete - val numDeletedRows = deleteQuery.delete - - println(s"Deleted $numDeletedRows rows") - - - /* Selecting Specific Columns */ - - // Construct a new coffees query that just selects the name - val justNameQuery: Query[Column[String], String] = coffees.map(_.name) - - println("Generated SQL for query returning just the name:\n" + - justNameQuery.selectStatement) - - // Execute the query - println(justNameQuery.list) - - - /* Sorting / Order By */ - - val sortByPriceQuery: Query[Coffees, (String, Int, Double, Int, Int)] = - coffees.sortBy(_.price) - - println("Generated SQL for query sorted by price:\n" + - sortByPriceQuery.selectStatement) - - // Execute the query - println(sortByPriceQuery.list) - - - /* Query Composition */ - - val composedQuery: Query[Column[String], String] = - coffees.sortBy(_.name).take(3).filter(_.price > 9.0).map(_.name) - - println("Generated SQL for composed query:\n" + - composedQuery.selectStatement) - - // Execute the composed query - println(composedQuery.list) - - - /* Joins */ - - // Join the tables using the relationship defined in the Coffees table - val joinQuery: Query[(Column[String], Column[String]), (String, String)] = for { - c <- coffees if c.price > 9.0 - s <- c.supplier - } yield (c.name, s.name) - - println("Generated SQL for the join query:\n" + joinQuery.selectStatement) - - // Print the rows which contain the coffee name and the supplier name - println(joinQuery.list) - - - /* Computed Values */ - - // Create a new computed column that calculates the max price - val maxPriceColumn: Column[Option[Double]] = coffees.map(_.price).max - - println("Generated SQL for max price column:\n" + maxPriceColumn.selectStatement) - - // Execute the computed value query - println(maxPriceColumn.run) - - - /* Manual SQL / String Interpolation */ - - // Required import for the sql interpolator - import scala.slick.jdbc.StaticQuery.interpolation - - // A value to insert into the statement - val state = "CA" - - // Construct a SQL statement manually with an interpolated value - val plainQuery = sql"select SUP_NAME from SUPPLIERS where STATE = $state".as[String] - - println("Generated SQL for plain query:\n" + plainQuery.getStatement) - - // Execute the query - println(plainQuery.list) - - } + // Equivalent SQL code: + // insert into COFFEES(COF_NAME, SUP_ID, PRICE, SALES, TOTAL) values (?,?,?,?,?) + ) + + val setupFuture = db.run(setup).map(f => { + println("Coffees:") + db.run(coffees.result).map(_.foreach { + case (name, supID, price, sales, total) => + println(" " + name + "\t" + supID + "\t" + price + "\t" + sales + "\t" + total) + }) + }) + + Await.ready(setupFuture, 5 seconds) } diff --git a/src/main/scala/InvokerMethods.scala b/src/main/scala/InvokerMethods.scala deleted file mode 100644 index 4cbe293..0000000 --- a/src/main/scala/InvokerMethods.scala +++ /dev/null @@ -1,65 +0,0 @@ -import scala.slick.driver.H2Driver.simple._ - -// Demonstrates various ways of reading data from an Invoker. -object InvokerMethods extends App { - - // A simple dictionary table with keys and values - class Dict(tag: Tag) extends Table[(Int, String)](tag, "INT_DICT") { - def key = column[Int]("KEY", O.PrimaryKey) - def value = column[String]("VALUE") - def * = (key, value) - } - val dict = TableQuery[Dict] - - val db = Database.forURL("jdbc:h2:mem:invoker", driver = "org.h2.Driver") - db.withSession { implicit session => - - // Create the dictionary table and insert some data - dict.ddl.create - dict ++= Seq(1 -> "a", 2 -> "b", 3 -> "c", 4 -> "d", 5 -> "e") - - // Define a pre-compiled parameterized query for reading all key/value - // pairs up to a given key. - val upTo = Compiled { k: Column[Int] => - dict.filter(_.key <= k).sortBy(_.key) - } - - println("List of k/v pairs up to 3 with .list") - println("- " + upTo(3).list) - - println("IndexedSeq of k/v pairs up to 3 with .buildColl") - println("- " + upTo(3).buildColl[IndexedSeq]) - - println("Set of k/v pairs up to 3 with .buildColl") - println("- " + upTo(3).buildColl[Set]) - - println("Array of k/v pairs up to 3 with .buildColl") - println("- " + upTo(3).buildColl[Array]) - - println("All keys in an unboxed Array[Int]") - val allKeys = dict.map(_.key) - println(" " + allKeys.buildColl[Array]()) - - println("Stream k/v pairs up to 3 via an Iterator") - val it = upTo(3).iterator - try { - it.foreach { case (k, v) => println(s"- $k -> $v") } - } finally { - // Make sure to close the Iterator in case of an error. (It is - // automatically closed when all data has been read.) - it.close - } - - println("Only get the first result, failing if there is none") - println("- " + upTo(3).first) - - println("Get the first result as an Option, or None") - println("- " + upTo(3).firstOption) - - println("Map of k/v pairs up to 3 with .toMap") - println("- " + upTo(3).toMap) - - println("Combine the k/v pairs up to 3 with .foldLeft") - println("- " + upTo(3).foldLeft("") { case (z, (k, v)) => s"$z[$k -> $v] " }) - } -} diff --git a/src/main/scala/Tables.scala b/src/main/scala/Tables.scala index e05522c..d86e542 100644 --- a/src/main/scala/Tables.scala +++ b/src/main/scala/Tables.scala @@ -1,17 +1,18 @@ -import scala.slick.driver.H2Driver.simple._ -import scala.slick.lifted.{ProvenShape, ForeignKeyQuery} +import slick.driver.H2Driver.api._ +import slick.lifted.{ProvenShape, ForeignKeyQuery} + // A Suppliers table with 6 columns: id, name, street, city, state, zip class Suppliers(tag: Tag) extends Table[(Int, String, String, String, String, String)](tag, "SUPPLIERS") { // This is the primary key column: - def id: Column[Int] = column[Int]("SUP_ID", O.PrimaryKey) - def name: Column[String] = column[String]("SUP_NAME") - def street: Column[String] = column[String]("STREET") - def city: Column[String] = column[String]("CITY") - def state: Column[String] = column[String]("STATE") - def zip: Column[String] = column[String]("ZIP") + def id = column[Int]("SUP_ID", O.PrimaryKey) + def name = column[String]("SUP_NAME") + def street = column[String]("STREET") + def city = column[String]("CITY") + def state = column[String]("STATE") + def zip = column[String]("ZIP") // Every table needs a * projection with the same type as the table's type parameter def * : ProvenShape[(Int, String, String, String, String, String)] = @@ -22,11 +23,11 @@ class Suppliers(tag: Tag) class Coffees(tag: Tag) extends Table[(String, Int, Double, Int, Int)](tag, "COFFEES") { - def name: Column[String] = column[String]("COF_NAME", O.PrimaryKey) - def supID: Column[Int] = column[Int]("SUP_ID") - def price: Column[Double] = column[Double]("PRICE") - def sales: Column[Int] = column[Int]("SALES") - def total: Column[Int] = column[Int]("TOTAL") + def name = column[String]("COF_NAME", O.PrimaryKey) + def supID = column[Int]("SUP_ID") + def price = column[Double]("PRICE") + def sales = column[Int]("SALES") + def total = column[Int]("TOTAL") def * : ProvenShape[(String, Int, Double, Int, Int)] = (name, supID, price, sales, total) diff --git a/src/test/scala/TablesSuite.scala b/src/test/scala/TablesSuite.scala deleted file mode 100644 index 4281636..0000000 --- a/src/test/scala/TablesSuite.scala +++ /dev/null @@ -1,50 +0,0 @@ -import org.scalatest._ -import scala.slick.driver.H2Driver.simple._ -import scala.slick.jdbc.meta._ - - -class TablesSuite extends FunSuite with BeforeAndAfter { - - val suppliers = TableQuery[Suppliers] - val coffees = TableQuery[Coffees] - - implicit var session: Session = _ - - def createSchema() = (suppliers.ddl ++ coffees.ddl).create - - def insertSupplier(): Int = suppliers += (101, "Acme, Inc.", "99 Market Street", "Groundsville", "CA", "95199") - - before { - session = Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver").createSession() - } - - test("Creating the Schema works") { - createSchema() - - val tables = MTable.getTables().list() - - assert(tables.size == 2) - assert(tables.count(_.name.name.equalsIgnoreCase("suppliers")) == 1) - assert(tables.count(_.name.name.equalsIgnoreCase("coffees")) == 1) - } - - test("Inserting a Supplier works") { - createSchema() - - val insertCount = insertSupplier() - assert(insertCount == 1) - } - - test("Query Suppliers works") { - createSchema() - insertSupplier() - val results = suppliers.list() - assert(results.size == 1) - assert(results.head._1 == 101) - } - - after { - session.close() - } - -} \ No newline at end of file