Skip to content

Instantly share code, notes, and snippets.

@dylemma
Last active November 7, 2017 13:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dylemma/5461249 to your computer and use it in GitHub Desktop.
Save dylemma/5461249 to your computer and use it in GitHub Desktop.
Scala Slick's `groupBy` causes all sorts of problems. This is a simple SBT project that uses Slick 1.0.0 and attempts to use groupBy in a few different situations. I encounter 4 distinct exceptions,and have made note of them in the comments of "FailingApp.scala".
name := "SlickTest"
scalaVersion := "2.10.0"
libraryDependencies ++= Seq(
"com.typesafe.slick" %% "slick" % "1.0.0",
"com.h2database" % "h2" % "1.3.170"
)
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession
object FailingApp extends App with Tables with PopulateTables {
val db = Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver")
/** Run a labeled test in a block, catching and printing Exceptions.
* Set quiet to `false` for the whole stack trace. (default)
* Set quiet to `true` for just the exception's `toString`.
*/
def test(label: String, quiet: Boolean = false)(body: => Unit) = {
println
println(label)
try {
body
} catch {
case e: Exception =>
println(" Failed:")
if (quiet) println(s" $e")
else {
print(" ")
e.printStackTrace(System.out)
}
}
}
db.withSession {
populateTables()
// works fine
test("Getting all things:") {
val allRecordsQuery = for {
r <- RecordTable
c <- r.category
t <- c.tool
} yield (r, c, t)
allRecordsQuery.list foreach {
case (r, c, t) => println(s" r = $r, c = $c, t = $t")
}
}
// works fine
test("Number of records per category:") {
val q1 = (for {
r <- RecordTable
c <- r.category
} yield (r, c)).groupBy { case (r, c) => c.id }.map {
case (cid, q) =>
cid ~ q.length
}
q1.list foreach {
case (cid, count) => println(s" Category $cid has $count records")
}
}
// should be functionally the same as the previous, but dies with an exception:
// Unsupported query shape containing .groupBy without subsequent .map
test("Alternate approach for records per category:", quiet = true) {
val q1 = for {
r <- RecordTable
c <- r.category
} yield (r, c)
val grouped = q1.groupBy { case (r, c) => c.id }
val query = for {
(cid, q) <- grouped
} yield cid ~ q.length
query.list foreach {
case (cid, count) => println(s" Category $cid has $count records")
}
}
// Unexpected node Select _1 -- SQL prefix: select
test("Get more info out of grouping:", quiet = true) {
val query = (for {
r <- RecordTable
c <- r.category
} yield (r, c)).groupBy { case (r, c) => c.id ~ c.name }.map {
case (cid ~ cname, q) =>
cid ~ cname ~ q.length
}
query.list foreach {
case (cid, cname, count) => println(s" Category $cname ($cid) has $count records")
}
}
// Unexpected LetDynamic after Inliner
test("Get a bunch of info out of the grouping:", quiet = true) {
val query = (for {
r <- RecordTable
c <- r.category
t <- c.tool
} yield (r, c, t)).groupBy { case (r, c, t) => c.id ~ c.name ~ t.id ~ t.name }.map {
case (groupInfo, q) => groupInfo ~ q.length
}
query.list foreach {
case (cid, cname, tid, tname, count) => println(
s" Tool($tid, $tname), Category($cid, $cname) => $count")
}
}
// Unexpected node Apply Function count(*) -- SQL prefix: select x2."id",
test("Number of records per tool:", quiet = true) {
val q1 = (for {
r <- RecordTable
c <- r.category
t <- c.tool
} yield (r, t, c)).groupBy { case (r, c, t) => t.id }.map {
case (tid, q) => tid ~ q.length
}
q1.list foreach {
case (id, count) => println(s" Tool $id has $count records")
}
}
}
}
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession
trait PopulateTables { self: Tables =>
def populateTables() = {
(ToolTable.ddl ++ CategoryTable.ddl ++ RecordTable.ddl).create
println("Created tables")
val toolA = Tool(1, "A")
val toolB = Tool(2, "B")
val toolC = Tool(3, "C")
ToolTable.insertAll(toolA, toolB, toolC)
val catA = Category(11, "A", toolA.id)
val catB = Category(12, "B", toolB.id)
val catC = Category(13, "C", toolC.id)
val catX = Category(14, "X", toolA.id)
val catY = Category(15, "Y", toolB.id)
val catZ = Category(16, "Z", toolC.id)
CategoryTable.insertAll(catA, catB, catC, catX, catY, catZ)
RecordTable.insertAll(
Record(1, "cool things", catA.id),
Record(2, "uncool", catB.id),
Record(3, "apples", catA.id),
Record(4, "oranges", catZ.id),
Record(5, "bananas", catB.id),
Record(6, "pears", catX.id),
Record(7, "limes", catY.id),
Record(8, "cantelope", catC.id))
}
}
import scala.slick.driver.H2Driver.simple._
trait Tables {
case class Record(id: Int, text: String, categoryId: Int)
case class Category(id: Int, name: String, toolId: Int)
case class Tool(id: Int, name: String)
object RecordTable extends Table[Record]("RECORDS") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def text = column[String]("text")
def categoryId = column[Int]("category_id")
def * = id ~ text ~ categoryId <> (Record, Record.unapply _)
def category = foreignKey("fk_record_category", categoryId, CategoryTable)(_.id)
}
object CategoryTable extends Table[Category]("CATEGORIES") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def toolId = column[Int]("tool_id")
def * = id ~ name ~ toolId <> (Category, Category.unapply _)
def tool = foreignKey("fk_category_tool", toolId, ToolTable)(_.id)
}
object ToolTable extends Table[Tool]("TOOLS") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = id ~ name <> (Tool, Tool.unapply _)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment