I have 2 case classes like this :
case class ClassTeacherWrapper(
success: Boolean,
classes: List[ClassTeacher]
)
2nd one :
case class ClassTeacher(
clid: String,
name: String
)
And a query like this :
val query =
SQL"""
SELECT
s.section_sk::text AS clid,
s.name AS name
from
********************
"""
P.S. I put * in place of query for security reasons :
So my query is returning 2 values. How do i map it to case class ClassTeacher
currently I am doing something like this :
def getClassTeachersByInstructor(instructor: String, section: String): ClassTeacherWrapper = {
implicit var conn: Connection = null
try {
conn = datamartDatasourceConnectionPool.getDBConnection()
// Define query
val query =
SQL"""
SELECT
s.section_sk::text AS clid,
s.name AS name
********
"""
logger.info("Read from DB: " + query)
// create a List containing all the datasets from the resultset and return
new ClassTeacherWrapper(
success =true,
query.as(Macro.namedParser[ClassTeacher].*)
)
//Trying new approch
//val users = query.map(user => new ClassTeacherWrapper(true, user[Int]("clid"), user[String]("name")).tolist
}
catch {
case NonFatal(e) =>
logger.error("getGradebookScores: error getting/parsing data from DB", e)
throw e
}
}
with is I am getting this exception :
{
"error": "ERROR: operator does not exist: uuid = character varying\n
Hint: No operator matches the given name and argument type(s). You
might need to add explicit type casts.\n Position: 324"
}
Can anyone help where am I going wrong. I am new to scala and Anorm
What should I modify in query.as part of code
Do you need the success field? Often an empty list would suffice?
I find parsers very useful (and reusable), so something like the following in the ClassTeacher singleton (or similar location):
val fields = "s.section_sk::text AS clid, s.name"
val classTeacherP =
get[Int]("clid") ~
get[String]("name") map {
case clid ~ name =>
ClassTeacher(clid,name)
}
def allForInstructorSection(instructor: String, section: String):List[ClassTeacher] =
DB.withConnection { implicit c => //-- or injected db
SQL(s"""select $fields from ******""")
.on('instructor -> instructor, 'section -> section)
.as(classTeacherP *)
}
In this Slick function I read from a User table and return a SessionUser object (SessionUser has fewer columns than User).
The problem is that this code does not compile, for each field in SessionUser it gives me the error type mismatch; found : slick.lifted.Rep[String] required: String. What's the meaning of this error and how to fix it?
def readByUserid (userid: String) : Option[SessionUser] = {
val db = Database.forConfig(Constant.dbBank)
try {
val users = TableQuery[UserDB]
val action = users.filter(_.userid === userid)
.map(u => SessionUser(u.userid, u.firstName, u.lastName)).result
val future = db.run(action)
val result = Await.result(future, Duration.Inf)
result
}
finally db.close
}
You're using map operation in a wrong place: mapping over a Query object is an equivalent of the SELECT statement in SQL. You should place your map operation right after the result invocation:
val action = users.filter(_.userid === userid).result.map(_.headOption.map(u => SessionUser(u.userid, u.firstName, u.lastName)))
Since we are using only three columns from users table, we could express that by using map operation on a Query object. This way the underlying SQL statement will select only the columns we need:
val action = users.filter(_.userid === userid).map(u => (u.userid, u.firstName, u.lastName)).result.map(_.headOption.map {
case (userid, firstName, lastName) => SessionUser(userid, firstName, lastName)
})
I'm trying to figure out Slick (the Scala functional relational model). I've started to build a prototype in Slick 3.0.0 but of course... most of the documentation is either out of date or incomplete.
I've managed to get to a point where I can create a schema and return an object from the database.
The problem is, what I'm getting back is a "Rep[Bind]" and not the object I would expect to get back. I can't figure out what to do this this value. For instance, if I try something like rep.countDistinct.result, I get a crash.
Here's a quick synopsis of the code... some removed for brevity:
class UserModel(tag: Tag) extends Table[User](tag, "app_dat_user_t") {
def id = column[Long]("n_user_id", O.PrimaryKey)
def created = column[Long]("d_timestamp_created")
def * = (id.?, created) <> (User.tupled, User.unapply)
}
case class User(id: Option[Long], created: Long)
val users = TableQuery[UserModel]
(users.schema).create
db.run(users += User(Option(1), 2))
println("ID is ... " + users.map(_.id)) // prints "Rep[Bind]"... huh?
val users = for (user <- users) yield user
println(users.map(_.id).toString) // Also prints "Rep[Bind]"...
I can't find a way to "unwrap" the Rep object and I can't find any clear explanation of what it is or how to use it.
Rep[] is a replacement to the Column[] datatype used in slick .
users.map(_.id) returns values of the Column('n_user_id') for all rows
val result : Rep[Long] = users.map(_.id)
users.map(_.id) // => select n_user_id from app_dat_user_t;
The obtained value is of type Column[Long] [ which is now Rep[Long] ].
You cannot directly print values of the above resultSet as it is not of any scala collection type
You can first convert it to some scala collection and then print it as
below :
var idList : List[Long] = List()
users.map(_.id).forEach(id =>
idList = idList :+ id
)
println(idList)** // if you need to print all ids at once
else you can simply use :
users.map(_.id).forEach(id =>
println(id)
) // print for each id
And ,
val users = TableQuery[UserModel] // => returns Query[UserModel, UserModel#TableElementType, Seq])
val users = for (user <- users) yield user // => returns Query[UserModel, UserModel#TableElementType, Seq])
both mean the same , So you can directly use the former and remove the latter
I have two Options:
val name: Option[String] = ...
val shared: Option[Boolean] = ...
I would like to build an UPDATE query that SETs these fields if the above values are Some, but leaves them unchanged if they are None.
I have managed to achieve this like so, but I am not so keen on the check for the comma, which is not going to scale if I need to add extra columns. I'm also not so keen on having to use a var.
var query = Q.u + "UPDATE wishlist SET"
if(name.isDefined) query = query + " name = " +? name + (if (share.isDefined) ", " else "")
if(shared.isDefined) query = query + " shared = " +? share
If I wasn't concerned with SQL injection, I could do something along the lines of:
val fields = List(
name.map(n => s"name = '$n'"),
shared.map(e => s"shared = $e")
).flatten
s"UPDATE wishlist SET ${fields.mkString(", ")}"
Question: Is there a nicer way to do this with Slick's plain SQL/String interpolation or is my only option to move to lifted embedded?
It's not super elegant, but I think it's at least flexible in that it will expand to support many inputs w/o changing the underlying logic:
val name:Option[String] = Some("foo")
val share:Option[String] = Some("bar")
val sets = List(
name.map(n => (" name = ", n)),
share.map(s => (" shared = ", s))
).flatten
val query2 = sets.zipWithIndex.foldLeft(Q.u + "UPDATE wishlist SET"){
case (q,(p,0)) => q + p._1 +? p._2
case (q,(p,i)) => q + "," + p._1 +? p._2
}
I'm putting the field name and value pairs into a list as Options for Tuples and then flattening to remove the Nones. I'm then folding to produce the final statement, taking into account that any piece of the query past the first piece will need a comma in it.
It seems no easy way to use "in" clause in anorm:
val ids = List("111", "222", "333")
val users = SQL("select * from users where id in ({ids})").on('ids-> ???).as(parser *)
How to replace the ??? part?
I tried:
on('ids -> ids)
on('ids -> ids.mkString("'","','","'"))
on('ids -> ids.mkString("','")
But none works.
I see in the discussion the exactly same problem: https://groups.google.com/d/topic/play-framework/qls6dhhdayc/discussion, the author has a complex solution:
val params = List(1, 2, 3)
val paramsList = for ( i <- 0 until params.size ) yield ("userId" + i)
// ---> results in List("userId0", "userId1", "userId2")
User.find("id in ({%s})"
// produces "id in ({userId0},{userId1},{userId2})"
.format(paramsList.mkString("},{"))
// produces Map("userId0" -> 1, "userId1" -> 2, ...)
.on(paramsList.zip(params))
.list()
This is too much complicated.
Is there any easier way? Or should play provide something to make it easier?
Anorm now supports such a case (and more) since 2.3: "Using multi-value parameter"
Back to initial example it gives:
val ids = Seq("111", "222", "333")
val users = SQL("select * from users where id in ({ids})").on('ids-> ids).as(parser *)
Nailed it! There haven't really been any more updates on this thread, but it seems to still be relevant. Because of that, and because there isn't an answer, I thought I'd throw mine in for consideration.
Anorm doesn't support 'IN' clauses. I doubt they ever will. There's nothing you can do to make them work, I even read a post where anorm specifically took out those clauses because they made Anorm feel 'like an ORM'.
It's fairly easy, however, to wrap the SqlQuery in a short class that supports the IN clause, and then convert that class into a SqlQuery when needed.
Instead of pasting the code in here, because it gets a little long, here is the link to my blog, where I've posted the code and how to use it.
In clause with Anorm
Basically, when you have the code from my blog, your statements look like this:
RichSQL(""" SELECT * FROM users WHERE id IN ({userIds}) """).onList("userIds" -> userIds).toSQL.as(userParser *)(connection)
Maybe it's too late but here is a tip for using custom string interpolation that also works for solve the problem of IN clause.
I have implemented a helper class to define a string interpolation. You can see it below, and you can simply copy and paste, but first let's see how you can use it.
Instead of write something
SQL("select * from car where brand = {brand} and color = {color} and year = {year} order by name").on("brand" -> brand, "color" -> color, "year" -> year).as(Car.simple *)
You can simply write:
SQL"select * from car where brand = $brand and color = $color and year = $year order by name".as(Car.simple *)
So using string interpolation it's more concise and easier to read.
And for the case of using the IN clause, you can write:
val carIds = List(1, 3, 5)
SQLin"select * from car where id in ($carIds)".as(Car.simple *)
Or for your example:
val ids = List("111", "222", "333")
val users = SQLin"select * from users where id in ($ids)".as(parser *)
For more information about string interpolation, check this link
The code for this implicit class is the following:
package utils
object AnormHelpers {
def wild (str: String) = "%" + str + "%"
implicit class AnormHelper (val sc: StringContext) extends AnyVal {
// SQL raw -> it simply create an anorm.Sql using string interpolation
def SQLr (args: Any*) = {
// Matches every argument to an arbitrary name -> ("p0", value0), ("p1", value1), ...
val params = args.zipWithIndex.map(p => ("p"+p._2, p._1))
// Regenerates the original query substituting each argument by its name with the brackets -> "select * from user where id = {p0}"
val query = (sc.parts zip params).map{ case (s, p) => s + "{"+p._1+"}" }.mkString("") + sc.parts.last
// Creates the anorm.Sql
anorm.SQL(query).on( params.map(p => (p._1, anorm.toParameterValue(p._2))) :_*)
}
// SQL -> similar to SQLr but trimming any string value
def SQL (args: Any*) = {
val params = args.zipWithIndex.map {
case (arg: String, index) => ("p"+index, arg.trim.replaceAll("\\s{2,}", " "))
case (arg, index) => ("p"+index, arg)
}
val query = (sc.parts zip params).map { case (s, p) => s + "{"+ p._1 + "}" }.mkString("") + sc.parts.last
anorm.SQL(query).on( params.map(p => (p._1, anorm.toParameterValue(p._2))) :_*)
}
// SQL in clause -> similar to SQL but expanding Seq[Any] values separated by commas
def SQLin (args: Any*) = {
// Matches every argument to an arbitrary name -> ("p0", value0), ("p1", value1), ...
val params = args.zipWithIndex.map {
case (arg: String, index) => ("p"+index, arg.trim.replaceAll("\\s{2,}", " "))
case (arg, index) => ("p"+index, arg)
}
// Expands the Seq[Any] values with their names -> ("p0", v0), ("p1_0", v1_item0), ("p1_1", v1_item1), ...
val onParams = params.flatMap {
case (name, values: Seq[Any]) => values.zipWithIndex.map(v => (name+"_"+v._2, anorm.toParameterValue(v._1)))
case (name, value) => List((name, anorm.toParameterValue(value)))
}
// Regenerates the original query substituting each argument by its name expanding Seq[Any] values separated by commas
val query = (sc.parts zip params).map {
case (s, (name, values: Seq[Any])) => s + values.indices.map(name+"_"+_).mkString("{", "},{", "}")
case (s, (name, value)) => s + "{"+name+"}"
}.mkString("") + sc.parts.last
// Creates the anorm.Sql
anorm.SQL(query).on(onParams:_*)
}
}
}
It's probably late, but I add this for others looking for the same.
You could use some built-in database features to overcome this. This is one of the advantages Anorm has over ORMs. For example, if you are using PostgreSQL you could pass your list as an array and unnest the array in your query:
I assume ids are integer.
val ids = List(1, 2, 3)
val idsPgArray = "{%s}".format(ids.mkString(",")) //Outputs {1, 2, 3}
val users = SQL(
"""select * from users where id in (select unnest({idsPgArray}::integer[]))"""
).on('ids-> ???).as(parser *)
Executed query will be
select * from users where id in (select unnest('{1, 2, 3}'::integer[]))
which is equal to
select * from users where id in (1, 2, 3)
I had the same problem recently. Unfortunately there doesn't seem to be a way without using string interpolation and thus vulnerable to SQL injection.
What I ended up doing was kinda sanitizing it by transforming it to a list of ints and back:
val input = "1,2,3,4,5"
// here there will be an exception if someone is trying to sql-inject you
val list = (_ids.split(",") map Integer.parseInt).toList
// re-create the "in" string
SQL("select * from foo where foo.id in (%s)" format list.mkString(","))
User.find("id in (%s)"
.format(params.map("'%s'".format(_)).mkString(",") )
.list()
val ids = List("111", "222", "333")
val users = SQL("select * from users
where id in
(" + ids.reduceLeft((acc, s) => acc + "," + s) + ")").as(parser *)