I am trying to run a query like .filter(_.id === 1).firstOption but the compiler complains there is no symbol firstOption. Was this removed in slick 3? What can I use instead?
To limit the number of results before calling result, use take(num). For example like this:
val result: Future[Option[Whatever]] = db.run((query.filter(_.id === 1).take(1)).result).map(_.headOption)
According to the official docs, the above statement boils down using headOption on the result method.
val result: Future[Option[Whatever]] = db.run((query.filter(_.id === 1)).result.headOption)
query.result returns an object of type DBIOAction. An action in slick is something that can be executed on a database. The actual execution is done by passing the action to db.run() or db.stream(). You can find a more detailed explanation here: http://slick.typesafe.com/doc/3.0.0/api/index.html#slick.dbio.DBIOAction
Related
I have a DBIO[Seq[tuple]] and I would like to map it to DBIO[Seq[customCaseClass]].
I am aware that I could do the transformation on the db.run() result with something like: customCaseClass.tupled(row) (see this answer). However, I am interested in composing the DBIO return value in varying functions.
There are three places where you can do this: at the Query level, DBIO level, and (as you noted, and rejected) at the Future level.
Query
At the query level, the conversion will happen as part of the execution of the query on Slick's own execution context.
It would look something like this:
// Given some query that returns a tuple...
val tupleQ: Query[(Rep[String],Rep[String]), (String,String), Seq] =
table.map{ row => (row.column1, row.column2) }
// ...which we'd like to project into this:
case class SomeCaseClass(v1: String, v2: String)
// ...we can use the mapTo macro to generate the conversion:
val ccQ: Query[Rep[SomeCaseClass], SomeCaseClass, Seq] =
tupleQ.map{ _.mapTo[SomeCaseClass] }
If this was all you're doing, then maybe the default projection (def * ...) is the place for this.
If you need more control over the conversion logic, you can use the lower-level <> in place of mapTo. Section 5.2 of Essential Slick gives more detail on this.
DBIO
The question was specifically about DBIO. The conversion there is going to run on your own execution context.
That would look something like this:
// Given a DBIO that returns a tuple...
val tupleD: DBIO[Seq[(String,String)]] =
table.map(row => (row.column1, row.column2)).result
// ... we can use any of the DBIO combinators to convert it, such as map:
val ccD: DBIO[Seq[SomeCaseClass]] =
dQ.map{ pairs => pairs.map{ case (a, b) => SomeCaseClass(a,b) } }
(...or dQ.map(pairs => pairs.map(SomeCaseClass.tupled)) as you noted).
The two big benefit you get at this level are:
you have access to the values, such as (a,b), and so can make decisions about what you want to do with the values.
being part of an action means you could take part in a transcation.
Chapter 4 of Essential Slick lists out many of the DBIO combinators. The Slick Manual also describes the combinators.
Future
The final place is in the Future, which looks very much like the DBIO version but after the db.run (as you've already found).
I am writting a scala function which follows the following workflow:
take an id as parameter.
use the id to get a object from mongo database. Here I am using reactive mongo.
after getting the object make another query to the database to get a list of items and return it.
I have implemented this workflow in the following way:
def functionA(id:String):Future[List[Hotel]]={
var futureHotel = hotelRepository.getHotel(id) // returns Futue[Option[Hotel]]
val result = for {
r<-futureHotel
}yield (hotelRepository.getHotels(r.get.giataid)) // this is supposed to be Future[List[Hotel]]
}
It gives me error message like: Type mismatch. Found Unit require Future[List[Hotel]]
As I am new to Scala, I am sure I am missing something. Will be cool if someone can point out. Any clue or help will be appreciated.
You are assigning the result to result. Assignment in scala is of type Unit (doesn't return anything). Remove the assignment, since the last expression becomes the return value.
Also, if the getHotels method returns Future[List[...]] you'll end up with Future[Future[List[...]]. You probably want something like this:
def functionA(id:String):Future[List[Hotel]]= {
for {
r <- hotelRepository.getHotel(id)
result <- hotelRepository.getHotels(r.get.giataid)
} yield result
}
This gets translated to a call to flatMap that doesn't produce the doubly nested future.
There is a similar question here but it doesn't actually answer the question.
Is it possible to use IN clause in plain sql Slick?
Note that this is actually part of a larger and more complex query, so I do need to use plain sql instead of slick's lifted embedding. Something like the following will be good:
val ids = List(2,4,9)
sql"SELECT * FROM coffee WHERE id IN ($ids)"
The sql prefix unlocks a StringContext where you can set SQL parameters. There is no SQL parameter for a list, so you can easily end up opening yourself up to SQL injection here if you're not careful. There are some good (and some dangerous) suggestions about dealing with this problem with SQLServer on this question. You have a few options:
Your best bet is probably to use the #$ operator together with mkString to interpolate dynamic SQL:
val sql = sql"""SELECT * FROM coffee WHERE id IN (#${ids.mkString(",")})"""
This doesn't properly use parameters and therefore might be open to SQL-injection and other problems.
Another option is to use regular string interpolation and mkString to build the statement:
val query = s"""SELECT * FROM coffee WHERE id IN (${ids.mkString(",")})"""
StaticQuery.queryNA[Coffee](query)
This is essentially the same approach as using #$, but might be more flexible in the general case.
If SQL-injection vulnerability is a major concern (e.g. if the elements of ids are user provided), you can build a query with a parameter for each element of ids. Then you'll need to provide a custom SetParameter instance so that slick can turn the List into parameters:
implicit val setStringListParameter = new SetParameter[List[String]]{
def apply(v1: List[String], v2: PositionedParameters): Unit = {
v1.foreach(v2.setString)
}
}
val idsInClause = List.fill(ids.length)("?").mkString("(", ",", ")")
val query = s"""SELECT * FROM coffee WHERE id IN ($idsInClause)"""
Q.query[List[String], String](query).apply(ids).list(s)
Since your ids are Ints, this is probably less of a concern, but if you prefer this method, you would just need to change the setStringListParameter to use Int instead of String:
val ids = List(610113193610210035L, 220702198208189710L)
implicit object SetListLong extends SetParameter[List[Long]] {
def apply(vList: List[Long], pp: PositionedParameters) {
vList.foreach(pp.setLong)
}
}
val select = sql"""
select idnum from idnum_0
where idnum in ($ids#${",?" * (ids.size - 1)})
""".as[Long]
#Ben Reich is right.
this is another sample code, test on slick 3.1.0.
($ids#${",?" * (ids.size - 1)})
Although this is not universal answer and may not be what the author wanted, I still want to point this out to whoever views this question.
Some DB backends support array types, and there are extensions to Slick that allow setting these array types in the interpolations.
For example, Postgres has the syntax where column = any(array), and with slick-pg you can use this syntax like so:
def query(ids: Seq[Long]) = db.run(sql"select * from table where ids = any($ids)".as[Long])
This brings a much cleaner syntax, which is friendlier to the statement compiler cache and also safe from SQL injections and overall danger of creating a malformed SQL with the #$var interpolation syntax.
I have written a small extension to Slick that addresses exactly this problem: https://github.com/rtkaczyk/inslick
For the given example the solution would be:
import accode.inslick.syntax._
val ids = List(2,4,9)
sqli"SELECT * FROM coffee WHERE id IN *$ids"
Additionally InSlick works with iterables of tuples or case classes. It's available for all Slick 3.x versions and Scala versions 2.11 - 2.13. We've been using it in production for several months at the company I work for.
The interpolation is safe from SQL injection. It utilises a macro which rewrites the query in a way similar to trydofor's answer
Ran into essentially this same issue in Slick 3.3.3 when trying to use a Seq[Long] in an IN query for MySQL. Kept getting a compilation error from Slick of:
could not find implicit value for parameter e: slick.jdbc.SetParameter[Seq[Long]]
The original question would have been getting something like:
could not find implicit value for parameter e: slick.jdbc.SetParameter[List[Int]]
Slick 3.3.X+ can handle binding the parameters for the IN query, as long as we provide the implicit definition of how Slick should do so for the types we're using. This means adding the implicit val definition somewhere at the class level. So, like:
class MyClass {
// THIS IS THIS KEY LINE TO ENABLE SLICK TO BIND THE PARAMS
implicit val setListInt = SetParameter[List[Int]]((inputList, params) => inputList.foreach(params.setInt))
def queryByHardcodedIds() = {
val ids: List[Int] = List(2,4,9)
sql"SELECT * FROM coffee WHERE id IN ($ids)" // SLICK CAN AUTO-HANDLE BINDING NOW
}
}
Similar for the case of Seq[Long] & others. Just make sure your types/binding aligns to what you need Slick to handle:
implicit val setSeqLong = SetParameter[Seq[Long]]((inputList, params) => inputList.foreach(params.setLong))
// ^^Note the `SetParameter[Seq[Long]]` & `.setLong` for type alignment
I have a table called Materials. I used slicks schema auto generation to create the TableQuery classes for me.
I can't figure out how to just print the results of a simple query.
Materials.map(_.name)
I've tried
val m = Materials.map(_.name).toString()
println(m)
and get the result
Rep(Bind)
if I try
Materials.map(_.name).forEach(m => println(m))
I get a compile error
value forEach is not a member of slick.lifted.Query[slick.lifted.Rep[Option[String]],Option[String],Seq]
To clarify I'm using just slick 3.1.0 not play slick
You have written a Query, but it needs converted into an Action by calling its result method
val query = materials.map(_.name)
val action = query.result
val results: Future[ Seq[Option[ String ] ]] = db.run( action)
results.foreach( println )
The db object needs to be initialized depending the Slick version that you are using . e.g Slick or Play Slick
I assume that you have this
val materials = TableQuery[Materials]
You can evaluate function with side effects using map:
Materials.map(println(_.name))
To SQL-compile a query, you need to compile a function which takes, for each query parameter arg: type, a lifted parameter of type Rep[type] .
I have a case class JobRecord and a TableQuery jobRecords.
So to insert a JobRecord case-class instance, I need to be able to say something like:
val qMapToId = (jobRecords returning jobRecords.map(_.id))
def ucCreate(jobRecord: Rep[JobRecord]) = qMapToId += jobRecord
val cCreate = Compiled(ucCreate _)
But of course this doesn't compile, because += doesn't take a Rep, and I'm not sure Rep[JobRecord] is valid either.
I've tried many things, not worth showing, including mixing in the Monomorphic Case Classes guidance. I probably got a step away from a solution a few times. A pointer to a working example would be great!
You don't have to do anything, val qMapToId = (jobRecords returning jobRecords.map(_.id)) generates the statement once, at compile time (i.e. on container startup).
Compiled replaces Parameters in the new API, and comes into play for selects, updates, and (I believe) deletes where you are binding placeholders for generating a prepared statement. For insert statements there's nothing to bind, you already have an instance for +=.
You can use TableQuery[] as follows.
# define TableQuery of JobRecord
case class JobRecordRow(...)
class JobRecord(tag:Tag) extends Table[JobRecordRow](tag, "JOB_TABLE_NAME") {
}
# define compiled query
val insert = Compiled( TableQuery[JobRecord].filter(_ => true:Rep[Boolean]))
val stmt = (insert += JobRecordRow(...))
db.run( stmt)
The Compiled query seems little bit tricky. However, when I tried Compiled(TableQuery[JobRecord]) as suggested in other articles, it did not work. By adding filter(), I could build insert query.
Updated on 2019-07-21
Instead of filter(), map(identity) can be used.
TableQuery[JobRecord].map(identity)