scala slick - how to name yielded fields? - scala

In Slick I can do this:
val joinQuery: Query[(Rep[String], Rep[String]), (String, String), Seq] = for {
c <- coffees if c.price > 9.0
s <- c.supplier
} yield (c.name, s.name)
Then I can access name like this:
joinQuery.map(_._1)
But I am not confortable using _1 to access the name.
Is there any way I can name the yielded fields?
Thank you.

Well... you can define your case class for result types, and use them to Shape the query result.
case class YourQueryResult(coffeeName: String, supplierName: String)
val joinQuery = for {
c <- coffees if c.price > 9.0
s <- c.supplier
} yield (c.name, s.name)
val joinQueryResultFuture = database.run(joinQuery).map(seq => {
seq.map({ case (coffeeName, supplierName) => YourQueryResult(coffeeName, supplierName) })
})
But, lets say you want to further modify the query itself, you can use pattern matching,
val joinQuery = for {
c <- coffees if c.price > 9.0
s <- c.supplier
} yield (c.name, s.name)
val modificationQuery = joinQuery.map({
case (coffeeName, supplierName) => coffeeName
})

That probably didn't work yet at that time, but now you can do the following by using mapTo[] - hope this helps whoever stumbles across this question.
case class YourQueryResult(coffeeName: String, supplierName: String)
val joinQuery = for {
c <- coffees if c.price > 9.0
s <- c.supplier
} yield (c.name, s.name).mapTo[YourQueryResult]
and then access as
joinQuery.map(_.coffeeName)

Related

How to define an empty query

Inside a for-comp I need to set up some queries that will be then run in a single shot later... say, something like:
val queries = for {
_ <- query1
_ <- query2
id <- someInsertQuery
} yield id
db.run(queries.transactionally)
Tip: the queries are instances of slick.jdbc.JdbcActionComponent#ProfileAction
Now, I need to set up conditions on the first 2 queries. If a condition is met, the queries should run, nothing otherwise. So I thought:
val queries = for {
_ <- if (condition1) query1 else Query.empty
_ <- if (condition2) query2 else Query.empty
id <- someInsertQuery
} yield id
db.run(queries.transactionally)
But this doesn't work:
value flatMap is not a member of Object
[error] _ <- if (condition1) query1 else Query.empty
[error] ^
So I bet what I am trying to do is not this way. What would be the proper way to achieve this?
Update1:
More details. The first item in the for-comp looks like this (considering Boris' idea):
val queries = for {
_ <- someOption.map(someRow => SomeTableQuery += someRow).geOrElse(Query.empty.result)
}
SomeTableQuery is an instance of slick.lifted.TableQuery[SomeClass] and someOption is Option[SomeClass]
I think you have some misunderstanding with for-yield construction.
In the scala, for-yield is just syntactic sugar under some combinations of flatMap, map functions.
val queries = for {
_ <- if (condition1) query1 else Query.empty
_ <- if (condition2) query2 else Query.empty
res <- someInsertQuery
} yield res
for the compiler it is the same as:
query1.flatMap(_ => query2.flatMap(_ => someInsertQuery.map(res => res)))
if your query1 and query2 is some ProfileAction:
val query1: ProfileAction[Int, NoStream, Effect.Write] = ???
val query2: ProfileAction[Int, NoStream, Effect.Write] = ???
so, the type of the expression if (condition1) query1 else Query.empty is Object, because Query.empty has type - Query[Unit, Unit, Seq] and the nearest common ancestor of Query and ProfileAction is an Object type, which has not flatMap function. To make your code compilable, you should make all branches of if ... else construction having the same type which has flatMap function, here it would be so if we call result on Query.empty:
val result1: FixedSqlAction[Any, NoStream, Effect.Write with Effect.Read] = if (condition1) query1 else Query.empty.result
val result2: FixedSqlAction[Any, NoStream, Effect.Write with Effect.Read] = if (condition2) query2 else Query.empty.result
Possible version of your code:
import slick.jdbc.JdbcBackend
import slick.lifted.Query
import slick.jdbc.H2Profile.api._
val db: JdbcBackend.Database = ???
val query1: ProfileAction[Int, NoStream, Effect.Write] = ???
val query2: ProfileAction[Int, NoStream, Effect.Write] = ???
val someInsertQuery: ProfileAction[Int, NoStream, Effect.Write] = ???
val condition1 = false
val condition2 = true
val queries = for {
_ <- if (condition1) query1 else Query.empty.result
_ <- if (condition2) query2 else Query.empty.result
res <- someInsertQuery
} yield res
db.run(queries.transactionally)
If your query is actually options and not just queries, you can write option composition using flatMap for filtering empty options and use seq for sequential execution of result queries sequence. Example, using table in slick documentation:
import slick.jdbc.H2Profile.api._
case class Coffees(tag: Tag) extends Table[(String, Double)](tag, "COFFEES") {
def name = column[String]("COF_NAME")
def price = column[Double]("PRICE")
def * = (name, price)
}
val coffees: TableQuery[Coffees] = TableQuery[Coffees]
val maybeQuery1 = Option(("Colombian", 7.99))
val maybeQuery2 = Option(("Another name", 14.59))
val maybeQuery3 = Option(("Name", 4.39))
val queries = Seq(maybeQuery1, maybeQuery2, maybeQuery3).flatMap(_.map(someRow => coffees += someRow))
val db: Database = ???
db.run(DBIO.seq(queries:_*).transactionally)
The best way I have been able to solve it til now is by getting the condition inside a filter. Roughly:
val queries = for {
_ <- query1.filter(condition1)
_ <- query2.filter(condition2)
id <- someInsertQuery
} yield id
db.run(queries.transactionally)
Let me know if you have other ideas.

Regex unapply in for comprehension with if guard not compiling

Why is that the following will not compile
for {
s <- List.empty[String]
regex <- List.empty[scala.util.matching.Regex]
regex(ss) = s
if ss == "foo"
} yield s
But removing the if
for {
s <- List.empty[String]
regex <- List.empty[scala.util.matching.Regex]
regex(ss) = s
} yield s
or rearranging the order of the two lists in the for comprehension
for {
regex <- List.empty[scala.util.matching.Regex]
s <- List.empty[String]
regex(ss) = s
if ss == "foo"
} yield s
compiles?
Scalafiddle: http://scalafiddle.net/console/2519ff98d434cb522589f54a9c5fcf55
You can see the translated for-comprehension using this command:
scalac -Xprint:all <file>.scala
In your first example the resulting code looks like this (I cleaned up the output a bit):
List.empty[String]
.flatMap(((s) =>
List.empty[scala.util.matching.Regex]
.map(regex => {
private[this] val x$2 =
s match {
case (x$1#regex((ss#_))) => scala.Tuple2(x$1, ss)
};
val x$1 = x$2._1;
val ss = x$2._2;
scala.Tuple2(regex, x$1)
}).withFilter((x$3 => x$3 match {
case scala.Tuple2((regex#_), regex((ss#_))) => ss.$eq$eq("foo")
})).map(((x$4) => x$4 match {
case scala.Tuple2((regex#_), regex((ss#_))) => s
})))
)
The problem seems to be that the withFilter clause uses the expression regex(ss) directly in the case statement, but the value regex it is not defined there. I'm not sure if this can be considered a flaw in the language specification, or in the compiler. The error message is certainly not very helpful.
You can read up on the details in chapter 6.19 of the Scala language specification.
This might help you.
import scala.util.matching.Regex
import scala.util.control.Exception._
for {
s <- List.empty[String]
regex <- List.empty[scala.util.matching.Regex]
ss <- extract(regex, s)
if ss == "foo"
} yield s
def extract(regex: Regex, s: String): Option[String] = allCatch.opt {
val regex(ss) = s
ss
}

Slick 3.0 many-to-many query with the join as an iterable

I've created a many-to-many collection using Slick 3.0, but I'm struggling to retrieve data in the way I want.
There is a many-to-many relationship between Events and Interests. Here are my tables:
case class EventDao(title: String,
id: Option[Int] = None)
class EventsTable(tag: Tag)
extends Table[EventDao](tag, "events") {
def id = column[Int]("event_id", O.PrimaryKey, O.AutoInc)
def title = column[String]("title")
def * = (
title,
id.?) <> (EventDao.tupled, EventDao.unapply)
def interests = EventInterestQueries.query.filter(_.eventId === id)
.flatMap(_.interestFk)
}
object EventQueries {
lazy val query = TableQuery[EventsTable]
val findById = Compiled { k: Rep[Int] =>
query.filter(_.id === k)
}
}
Here's EventsInterests:
case class EventInterestDao(event: Int, interest: Int)
class EventsInterestsTable(tag: Tag)
extends Table[EventInterestDao](tag, "events_interests") {
def eventId = column[Int]("event_id")
def interestId = column[Int]("interest_id")
def * = (
eventId,
interestId) <> (EventInterestDao.tupled, EventInterestDao.unapply)
def eventFk = foreignKey("event_fk", eventId, EventQueries.query)(e => e.id)
def interestFk = foreignKey("interest_fk", interestId, InterestQueries.query)(i => i.id)
}
object EventInterestQueries {
lazy val query = TableQuery[EventsInterestsTable]
}
And finally Interests:
case class InterestDao(name: String,
id: Option[Int] = None)
class InterestsTable(tag: Tag)
extends Table[InterestDao](tag, "interests") {
def id = column[Int]("interest_id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def name_idx = index("idx_name", name, unique = true)
def * = (
name,
id.?) <> (InterestDao.tupled, InterestDao.unapply)
def events = EventInterestQueries.query.filter(_.interestId === id)
.flatMap(_.eventFk)
}
object InterestQueries {
lazy val query = TableQuery[InterestsTable]
val findById = Compiled { k: Rep[Int] =>
query.filter(_.id === k)
}
}
I can query and retrieve tuples of (event.name, interest) with the following:
val eventInterestQuery = for {
event <- EventQueries.query
interest <- event.interests
} yield (event.title, interest.name)
Await.result(db.run(eventInterestQuery.result).map(println), Duration.Inf)
So this is what I currently have.
What I want is to be able to populate a case class like:
case class EventDao(title: String,
interests: Seq[InterestDao],
id: Option[Int] = None)
The trouble is that if I update my case class like this, it messes up my def * projection in EventsTable. Also, I'll have to rename the EventsTable.interests filter to something like EventsTable.interestIds which is a bit ugly but I could live with if necessary.
Also, I can't find a way of writing a for query that yields (event.name, Seq(interest.name)). Anyway, that's just a stepping stone to me being able to yield a (EventDao, Seq(InterestDao)) tuple which is what I really want to return.
Does anyone know how I can achieve these things? I also want to be able to 'take' a certain number of Interests, so for some queries all would be returned, but for others only the first 3 would be.
So after reading this page and chatting on the mailing list, I finally got it working:
val eventInterestQuery = for {
event <- EventQueries.query
interest <- event.interests
} yield (event, interest)
Await.result(db.run(eventInterestQuery.result
// convert the interests to a sequence.
.map {
_.groupBy(_._1)
.map {
case (k,v) => (k, v.map(_._2))
}.toSeq
}
), Duration.Inf)
The only issue with groupBy is you lose order. You could fold the result. I've written this helper for my current project:
def foldOneToMany[A, B](in: Seq[(A, Option[B])], eq: (A, B) => Boolean)
(f: (A, B) => A): Seq[A] =
in.foldLeft(List.empty[A]) {
case (head :: tail, (_, Some(rel))) if eq(head, rel) =>
f(head, rel) :: tail
case (r, (el, Some(rel))) => f(el, rel) :: r
case (r, (el, None)) => el :: r
}.reverse
It could do with some love. Now it takes in a function A,B => Boolean to determine if B belongs to A and a function A,B => A that adds B to A.
Virtualeyes also has a point. In Postgres you could use array_agg function to use a little less bandwidth from the db.

Scala+Slick - Get counts on GroupBy results

I'm trying to get counts from a DB using a groupBy on my Scala+Slick code.
Here's my partial code :
object DBJobs extends Table[DBJob]("encoder_job") {
object Status extends Enumeration {
val local = Value("LOCAL")
val encoding = Value("ENCODING")
val done = Value("DONE")
val error = Value("ERROR")
}
implicit val StatusMapper = MappedTypeMapper.base[Status.Value, String] (
{x => x.toString},
{x => x match {case "LOCAL"=>Status(0);case "ENCODING"=>Status(1);case "DONE"=>Status(2);case "ERROR"=>Status(3)}}
)
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def status = column[DBJobs.Status.Value]("status", O.NotNull)
def getStats()(implicit session:Session):mutable.Map[Status.Value, Int] = {
var map = mutable.Map[Column[Status.Value], Column[Int]]()
val q = (for { j <- DBJobs } yield (j)).groupBy(_.status).map{
case (s, results) =>
map = map += (s -> results.length)
}
map
}
}
My problem is how to put data in my Map as [DBJobs.Status, Int] instead of [Column[Status.Value], Column[Int]].
Here's the SQL equivalent :
SELECT COUNT( 1 ), status FROM encoder_job GROUP BY STATUS
Slick version: 1.0.1
Thanks
def getStats()(implicit session:Session):mutable.Map[Status.Value, Int] = {
Query(DBJobs).groupBy(_.status).map{
case (s, results) => (s -> results.length)
}
}
// usage
val results = getStats.run
Be aware that putting methods in the table object directly cannot be easily migrated to Slick 2.0. Put them separate, e.g. as method extensions. Also see https://groups.google.com/d/msg/scalaquery/xNtPT6sexXI/zlkgxv6lZ6YJ

Inner join doesn't work in Slick

Could you please tell me why I'm not getting inner join I'm expecting to get ?
I have following tables
case class Ability(id: UUID, can: Boolean, verb: String, subject: String, context: String)
object Abilities extends Table[Ability]("abilities"){
def id = column[UUID]("id", O.PrimaryKey)
def can = column[Boolean]("is_can")
def verb = column[String]("verb")
def subject = column[String]("subject")
def context = column[String]("context")
def * = id ~ can ~ verb ~ subject ~ context <> (Ability, Ability.unapply _)
}
case class Role(id: UUID, name : String)
object Roles extends Table[Role]("roles"){
def id = column[UUID]("id", O.PrimaryKey)
def name = column[String]("name")
def * = id ~ name <> (Role, Role.unapply _)
}
// And join table
case class AbilityRelationship(owner_id: UUID, obj_id: UUID, is_role: Boolean)
object AbilitiesMapping extends Table[AbilityRelationship]("abilities_mapping"){
def owner_id = column[UUID]("owner_id")
def obj_id = column[UUID]("obj_id")
def is_role = column[Boolean]("is_role")
def * = owner_id ~ obj_id ~ is_role <> (AbilityRelationship, AbilityRelationship.unapply _)
}
What I'm willing to do is to fetch list of Ability objects for particular owner (whether user or role). So following documentation I wrote following join query for it
val some_id = role.id
val q2 = for {
a <- Abilities
rel <- AbilitiesMapping
if rel.owner_id === some_id.bind
} yield (a)
But q2.selectStatement returns absolutely wrong query for it. Which is select x2."id", x2."is_can", x2."verb", x2."subject", x2."context" from "abilities" x2, "abilities_mapping" x3 where x3."owner_id" = ? in my case.
How should it be implemented?
Thanks.
Well, after multiple attempts I made it
val innerJoin = for {
(a, rel) <- Abilities innerJoin AbilitiesMapping on (_.id === _.obj_id) if rel.owner_id === some_id.bind
} yield a
But man... typesafe's documentation is really really weak for newcomers.
Try something like:
val q2 = for {
a <- Abilities
rel <- AbilitiesMapping
if a.id == rel.obj_id && rel.owner_id === some_id.bind
} yield (a)
BTW, you know you can annotate your foreign keys in the Table objects right?
Tried doing this as a comment to ruslan's answer, but I just dont have enough jedi powers:
Can you try if this desugar-ed version works?
val rightSide = AbilitiesMapping.filter(_.owner_id === some_id)
val innerJoin = (Abilities innerJoin (rightSide) on (
(l,r) => (l.id === r.obj_id)
).map { case (l, r) => l }