Last active
November 7, 2017 13:11
-
-
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".
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name := "SlickTest" | |
scalaVersion := "2.10.0" | |
libraryDependencies ++= Seq( | |
"com.typesafe.slick" %% "slick" % "1.0.0", | |
"com.h2database" % "h2" % "1.3.170" | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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