Assuming I have a Person case class and a PersonTable defined in Slick, I was wondering if it's possible to have something like this compiling and working:
case class Person(id: Long, ....)
class PersonTable(tag: Tag) extends Table[Person](tag, "PERSON") {
def serverId = column[Long]("ID")
...
def * = (id, ...).mapTo[Person]
}
sql"select person.* from PERSON JOIN USER ON USER.ID = PERSON.ID".as[List[Person]]
I could see that
sql"select person.* from PERSON JOIN USER ON USER.ID = PERSON.ID".as[(a,b,c,...,z)]
seems to at least compile, but I'd like to have this working with my already defined entities instead, and to work with lists.
Thanks
For most queries the Slick query DSL is way nicer/easier, definitely do that first! But for complex queries I agree that sometimes good old sql works well. Sometimes it gets really complex to figure out the DSL or (worse) in some cases it produces very inefficient SQL. (though it has gotten better)
AFAIK you still need the somewhat annoying GetResult mapping from result tuples to case classes for the .as[] to work. If you don't add a .headOption it produces a list.
Something like this:
implicit val getPersonResult = GetResult(r => Target(r.<<, r.<<, r.<<, ... ))
db.run(sql"select person.* from PERSON JOIN USER ON USER.ID = PERSON.ID".as[Person])
.map(_.toList)
Hope this helps.
how can I search NOT CONTAINS in a set ?
Say I have the following model:
case class ClassRoom(id:String, age:Int, name:String , kids: Set[String])
abstract class PersonModel extends CassandraTable[PersonModel, Person] {
override def tableName = "ClassRooms"
object id extends StringColumn(this) with PartitionKey[String]
object age extends DoubleColumn(this) with PrimaryKey[Double]
object kids extends SetColumn[String](this)
I want to do the following query
def findMissing(minAge:Double, kid:String) = select
.where(_.age > age)
.and (_.kids not contain kid)
.fetch()
Sadly because in Cassandra the engine is based on "map style" indexing, the NOT operator is not an inherently suitable concept. Arguably it could be exposed through things like the key cache for partition key hits, but for things like secondary indexes they may not be a technical reality.
First of all, you will need object kids extends SetColumn[String](this) with Index[Set[String]] for the normal contains to work, hence my mention of a secondary index.
To achieve what you want, based on cardinality you have two ways, you either use secondary indexes and a normal CONTAIN or you de-normalise and store in a separate table, which may improve performance but it will still require "diffing".
i'm trying to map my class with slick so i can persist them.
My business objects are defined this way
case class Book(id : Option[Long], author : Author, title : String, readDate : Date, review : String){}
case class Author(id : Option[Long], name : String, surname : String) {}
Then I defined the "table" class for authors:
class Authors(tag : Tag) extends Table[Author](tag,"AUTHORS") {
def id = column[Option[Long]]("AUTHOR_ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def surname = column[String]("SURNAME")
def * = (id, name, surname) <> ((Author.apply _).tupled , Author.unapply)
}
And for Books:
class Books (tag : Tag) extends Table[Book](tag, "BOOKS") {
implicit val authorMapper = MappedColumnType.base[Author, Long](_.id.get, AuthorDAO.DAO.findById(_))
def id = column[Option[Long]]("BOOK_ID", O.PrimaryKey, O.AutoInc)
def author = column[Author]("FK_AUTHOR")
def title = column[String]("TITLE")
def readDate = column[Date]("DATE")
def review = column[Option[String]]("REVIEW")
def * = (id, author, title, readDate, review) <> ((Book.apply _).tupled , Book.unapply)
}
But when I compile i get this error
Error:(24, 51) 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.slick.lifted.FlatShapeLevel
Source type: (scala.slick.lifted.Column[Option[Long]], scala.slick.lifted.Column[model.Author], scala.slick.lifted.Column[String], scala.slick.lifted.Column[java.sql.Date], scala.slick.lifted.Column[Option[String]])
Unpacked type: (Option[Long], model.Author, String, java.sql.Date, String)
Packed type: Any
def * = (id, author, title, readDate, review) <> ((Book.apply _).tupled , Book.unapply)
^
and also this one:
Error:(24, 51) not enough arguments for method <>: (implicit evidence$2: scala.reflect.ClassTag[model.Book], implicit shape: scala.slick.lifted.Shape[_ <: scala.slick.lifted.FlatShapeLevel, (scala.slick.lifted.Column[Option[Long]], scala.slick.lifted.Column[model.Author], scala.slick.lifted.Column[String], scala.slick.lifted.Column[java.sql.Date], scala.slick.lifted.Column[Option[String]]), (Option[Long], model.Author, String, java.sql.Date, String), _])scala.slick.lifted.MappedProjection[model.Book,(Option[Long], model.Author, String, java.sql.Date, String)].
Unspecified value parameter shape.
def * = (id, author, title, readDate, review) <> ((Book.apply _).tupled , Book.unapply)
^
What's the mistake here?
What am I not getting about slick?
Thank you in advance!
Slick is not an ORM so there's no auto mapping from a foreign key to an entity, the question has been asked many times on SO (here, here just to name two).
Let's assume for a moment that what you are trying to do is possible:
implicit val authorMapper =
MappedColumnType.base[Author, Long](_.id.get, AuthorDAO.DAO.findById(_))
So you are telling the projection to use the row id and fetch the entity related to that id, there are three problems in your case, first you don't handle failures (id.get), second you primary key is optional (which shouldn't be).
The third problem is that slick will fetch each entity in a separate way, what I mean by this is, you execute some query and get 100 books, slick will make 100 other queries only to fetch the related entity, performance wise is suicide, you are completely bypassing the SQL layer (joins) which has the best performance only to have the possibility of shortening your DAOs.
Fortunately this doesn't seem to be possible, mappers are used for non supported types by slick (for example different date formats without having to explicitly using functions) or to inject format conversion when fetching/inserting rows, have a look at the documentation on how to use joins (depending on your version).
Ende Neu's answer is more knowledgeable and relevant to the use case described in the question, and probably a more proper and correct answer.
The following is merely an observation I made which may have helped tmnd91 by answering the question:
What's the mistake here?
I noticed that:
case class Book( ... review : String){}
does not match with:
def review = column[Option[String]]("REVIEW")
It should be:
def review = column[String]("REVIEW")
Is it possible to use a foreign key field in a Slick where or filter statement?
Something like (where the user field is a foreign key to a Table for which User is its mapped projection) (this does not compile):
def findByUser(user: User)(implicit s: Session): Option[Token] =
tokens.where(_.user === user).firstOption
Or should we use the foreign key explicitely?
def findByUser(user: User)(implicit s: Session): Option[Token] =
tokens.where(_.userId === user.id).firstOption
Yes, ids are good.
def findByUserId(userId: Long)(implicit s: Session): Option[Token] =
tokens.filter(_.userId === userId).firstOption
findByUserId( user.id )
This allows the method to be used, even when the only thing you have is an id. Slick deliberately exposes the relational model (with a functional touch) and does not hide it, among other reasons because using id's instead of object references allows to refer to rows that are not loaded into memory merely by their id.
Or even better and simpler, because built into Slick:
val findByUserId = tokens.findBy(_.userId) // pre-compiles SQL for better performance
findByUserId(user.id).firstOption
I'm following the Slick documentation example for autoincrementing fields and I'm having trouble creating a mapped projection that ... well, only has one column.
case class UserRole(id: Option[Int], role: String)
object UserRoles extends Table[UserRole]("userRole") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def role = column[String]("ROLE")
// ...
def * = id.? ~ role <> (UserRole, UserRole.unapply _)
// NEXT LINE ERRORS OUT
def forInsert = role <> ({t => UserRole(None, t._1)}, {(r: UserRole) => Some((r.role))}) returning id
}
The error is "value <> is not a member of scala.slick.lifted.Column[String]"
I also thought it'd be more efficient to design my schema like so:
case class UserRole(role: String)
object UserRoles extends Table[UserRole]("userRole") {
def role = column[Int]("ROLE", O.PrimaryKey)
// ...
def * = role <> (UserRole, UserRole.unapply _)
}
But then I start getting the same error as above, too. "value <> is not a member of scala.slick.lifted.Column[String]"
What am I really doing? Do I just not have a projection anymore because I only have one column? If so, what should I be doing?
This is a known issue with Slick; mapped projections do not work with a single column. See https://github.com/slick/slick/issues/40
Luckily, you don't need a mapped projection for your code to work. Just omit everything after and including the <>. See scala slick method I can not understand so far for a great explanation of projections. It includes the information you need to get going.