I am rather new at Scala and have been struggling with slick and can't see how to return the results of a query to the calling method
I have a simple UserDto
case class UserDto(val firstName:String,
val lastName:String,
val userName:String,
val isAdmin:Boolean) {}
a User table object
object User extends Table[(String, String, String, Boolean)]("USER") {
def firstName = column[String]("FIRST_NAME")
def lastName = column[String]("LAST_NAME")
def userName = column[String]("USER_NAME")
def admin = column[Boolean]("IS_ADMIN")
def * = firstName ~ lastName ~ userName ~ admin
}
and a query class
class UserQuerySlickImpl(dataSource:DataSource) {
def getResults(userName:String):Option[UserDto] = {
var resultDto:Option[UserDto] = None
Database.forDataSource(dataSource) withSession {
val q = for {u <- User if u.userName is userName}
yield (u.firstName, u.lastName, u.userName, u.admin)
for (t <- q) {
t match {
case (f:String, l:String, u:String, a:Boolean) =>
resultDto = Some(new UserDto(f, l, u, a))
}
}
}
resultDto
}
}
I can query the database and get the user that matches the username, but the only way I could figure out how to return that user is by creating a var outside of the Database.forDataSource....{}.
Is there a better way that does not use the var but returns the resultDto directly.
also is there a way to construct the UserDto directly from the first for comprehension rather than needing the second for (t <- q) ...
I am using slick_2.10.0-M7, version 0.11.1.
Your q is a query, not a list of results. The presence of foreach might be a little confusing in that respect, but to obtain a List of results, you need to do q.list first. That gives you methods like map and foldLeft and so on.
If you want to get a single/the first result in an Option, use q.firstOption. Once you have that, you can map your function over the resulting 'Option[(...)]` to transform the tuple into the desired DTO.
An alternative way would be to specify a custom mapping that automatically maps your result tuples to some case class by using the <> operator, see http://slick.typesafe.com/doc/0.11.2/lifted-embedding.html#tables:
case class User(id: Option[Int], first: String, last: String)
object Users extends Table[User]("users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def first = column[String]("first")
def last = column[String]("last")
def * = id.? ~ first ~ last <> (User, User.unapply _)
}
I haven't toyed with Slick yet but if it's reasonable (by which I mean consistent with Scala conventions) you should be able to do something like
def getResults(userName:String):Option[UserDto] =
Database.forDataSource(dataSource) withSession {
val q = for {u <- User if u.userName is userName}
yield (u.firstName, u.lastName, u.userName, u.admin)
q.firstOption map { case (f, l, u, a) => UserDto(f, l, u, a) }
}
This is exactly what you would do if q was a List[(String, String, String, Boolean)].
Cleaning this up a bit, you can write
def getResults(userName:String):Option[UserDto] =
Database.forDataSource(dataSource) withSession {
(for (u <- User if u.userName is userName)
yield UserDto(u.firstName, u.lastName, u.userName, u.admin)).firstOption
}
Otherwise, you should be able to use
q foreach {
case (f, l, u, a) => return Some(UserDto(f, l, u, a))
}
return None
Generally, return statements like this one should be avoided, so hopefully q's type gives you something more functional to work with.
Related
I am trying to use Slick for database in a Scala application, and running into some issues (or my misunderstandings) of how to query (find) and convert the result to a case class.
I am not mapping the case class, but the actual values, with the intent of creating the case class on the fly. so, my table is:
object Tables {
class Names(tag: Tag) extends Table[Name](tag, "NAMES") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def first = column[String]("first")
def middle = column[String]("last")
def last = column[String]("last")
def * = (id.?, first, middle.?, last) <> ((Name.apply _).tupled, Name.unapply)
}
object NamesQueries {
lazy val query = TableQuery[Names]
val findById = Compiled { k: Rep[Long] =>
query.filter(_.id === k)
}
}
}
and here is the query:
object NamesDAO {
def insertName(name: Name) {
NamesQueries.query += name.copy(id = None)
}
def findName(nameId: Long) = {
val q = NamesQueries.findById(nameId) // AppliedCompiledFunction[Long, Query[Tables.Names, Tables.Names.TableElementType, Seq],Seq[Tables.Names.TableElementType]]
val resultSeq = Database.forConfig("schoolme").run(q.result) // Future[Seq[Tables.Names.TableElementType]]
val result = resultSeq.map { r => // val result: Future[(Option[Long], String, Option[String], String) => Name]
val rr = r.map{ name => // val rr: Seq[(Option[Long], String, Option[String], String) => Name]
Name.apply _
}
rr.head
}
result
}
}
however, the findName method seems to return Future((Option[Long], String, Option[String], String) => Name) instead of a Future(Name). What am i doing wrong? Is it just a matter of just using asInstanceOf[Name]?
EDIT: expanded findName to smaller chunks with comments for each one, as sap1ens suggested.
well, i'll be damned.
following sap1ens comment above, I broke findName to multiple steps (and edited the question). but after that, i went back and gave my val an explicit type, and that worked. see here:
def findName(nameId: Long) = {
val q = NamesQueries.findById(nameId)
val resultSeq: Future[Seq[Name]] = Database.forConfig("schoolme").run(q.result)
val result = resultSeq.map { r =>
val rr = r.map{ name =>
name
}
rr.head
}
result
}
so, type inference was the (/my) culprit this time. remember, remember.
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.
I have the following case class:
case class User (id: Option[Long] = None, name: String = "", lastName: String = "", login: String = "", password: String = "")
and the following table presentation:
class Users(tag: Tag) extends Table[User](tag,"t_users"){
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def lastName = column[String]("lastName")
def login = column[String]("login", O.NotNull)
def password = column[String]("password")
def * = (id, name, lastName, login, password) <> ((User.apply _).tupled, User.unapply _)
}
I'd like to perform a query, which would list some "users" but without the password.
I could do this:
def list(page: Int = 0, pageSize: Int = 10, filter: String = "%")(implicit s: Session): Page[User] = {
val offset = pageSize * (page -1)
val query = (for {
u <- users
if u.name.toLowerCase like filter.toLowerCase()
} yield u)
query.drop(offset).take(pageSize)
val totalRows = count(s)
val result = query.list
Page(result,page,offset,totalRows)
}
which would bring me the users full filled, but I'd like to perform a yield like this:
yield User(u.id,u.name,u.lastName,u.login) // bound to a User
Is it possible?
EDIT
I could make it accept the yield by:
yield User(u.id.asInstanceOf[Option[Long]],u.name.asInstanceOf[String],u.lastName.asInstanceOf[String],u.login.asInstanceOf[String],""))
but now it gives me an error at
u <- users
saying:
No matching Shape found. Slick does not know how to map the given types. Possible causes: T in Table[T] does not match your * projection. Or you use an unsupported type in a Query (e.g. scala List). Required level: scala#23.slick#2774.lifted#19028.FlatShapeLevel#24408 Source type: models#29.User#17383 Unpacked type: T#10265565 Packed type: G#10265564
not enough arguments for method map#59065: (implicit shape#10265576: scala#23.slick#2774.lifted#19028.Shape#24129[_ <: scala#23.slick#2774.lifted#19028.FlatShapeLevel#24408, models#29.User#17383, T#10265565, G#10265564])scala#23.slick#2774.lifted#19028.Query#23982[G#10265564,T#10265565,Seq#3031]. Unspecified value parameter shape.
I am not sure about this, but you can try:
val query = (for {
u <- users
if u.name.toLowerCase like filter.toLowerCase()
} yield u.copy(password = ""))
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 }
Assume these two simple queries:
def findById(id: Long): Option[Account] = database.withSession { implicit s: Session =>
val query = for (a <- Accounts if a.id === id) yield a.*
query.list.headOption
}
def findByUID(uid: String): Option[Account] = database.withSession { implicit s: Session =>
val query = for (a <- Accounts if a.uid === uid) yield a.*
query.list.headOption
}
I would like to rewrite it to remove the boilerplate duplication to something like this:
def findBy(criteria: ??? => Boolean): Option[Account] = database.withSession {
implicit s: Session =>
val query = for (a <- Accounts if criteria(a)) yield a.*
query.list.headOption
}
def findById(id: Long) = findBy(_.id === id)
def findByUID(uid: Long) = findBy(_.uid === uid)
I don't know how to achieve it for there are several implicit conversions involved in the for comprehension I haven't untangled yet. More specifically: what would be the type of ??? => Boolean in the findBy method?
EDIT
These are Account and Accounts classes:
case class Account(id: Option[Long], uid: String, nick: String)
object Accounts extends Table[Account]("account") {
def id = column[Option[Long]]("id")
def uid = column[String]("uid")
def nick = column[String]("nick")
def * = id.? ~ uid ~ nick <> (Account, Account.unapply _)
}
I have this helper Table:
abstract class MyTable[T](_schemaName: Option[String], _tableName: String) extends Table[T](_schemaName, _tableName) {
import scala.slick.lifted._
def equalBy[B: BaseTypeMapper]
(proj:this.type => Column[B]):B => Query[this.type,T] = { (str:B) =>
Query[this.type,T,this.type](this) where { x => proj(x) === str} }
}
Now you can do:
val q=someTable.equalBy(_.someColumn)
q(someValue)