Dynamic OR filtering - Slick - scala

Ok, I've got a method with multiple optional arguments like this
def(username: Option[String], petname: Option[String], favouritefood: Option[String])
and i want to write a dynamic query that will be capable of fetching the data of defined arguments in a way of this
select * from table where un like username or pn like pn or ff like ff;
so depending of which arguments are defined to add them to query with OR operator?

Something like this should work. I had to use a similiar fragment in my own code and it is also close to what cvogt proposes in above comment (I think).
val username = Option("")
val petname = Option("")
val ff:Option[String] = None
val default = LiteralColumn(1) === LiteralColumn(1)
yourTable.filter { it =>
List(
username.map(it.username === _),
petname.map(it.petname === _),
ff.map(it.ff === _)
).collect({case Some(it) => it}).reduceLeftOption(_ || _).getOrElse(default)
}

The thoefer is nice for simple use cases but has some limits. Like if all your options are None's the list is empty and you can't reduce an empty list :)
If you need something more composable, based on predicate, conjunctions and disjunctions (a bit like Hibernate/JPA Criteria API), you can check my answer in Slick: create query conjunctions/disjunctions dynamically

Related

How do I convert a List[Option[(A, List[B])]] to the Option[(A,List[B])]? (Basically retrieve Option[X] from List[Option[X]])

I have a List of “rules” tuples as follows:
val rules = List[(A, List[B])], where A and B are two separate case-classes
For the purposes of my use-case, I need to convert this to an Option[(A, List[B])]. The A case class contains a property id which is of type Option[String], based on which the tuple is returned.
I have written a function def findRule(entryId: Option[String]), from which I intend to return the tuple (A, List[B]) whose A.id = entryId as an Option. So far, I have written the following snippet of code:
def findRule(entryId: Option[String]) = {
for {
ruleId <- rules.flatMap(_._1.id) // ruleId is a String
id <- entryId // entryId is an Option[String]
} yield {
rules.find(_ => ruleId.equalsIgnoreCase(id)) // returns List[Option[(A, List[B])]
}
}
This snippet returns a List[Option[(A, List[B])] but I can’t figure out how to retrieve just the Option from it. Using .head() isn’t an option, since the rules list may be empty. Please help as I am new to Scala.
Example (the real examples are too large to post here, so please consider this representative example):
val rules = [(A = {id=1, ….}, B = [{B1}, {B2}, {B3}, …]), (A={id=2, ….}, B = [{B10}, {B11}, {B12}, …]), …. ] (B is not really important here, I need to find the tuple based on the id element of case-class A)
Now, suppose entryId = Some(1)
After the findRule() function, this would currently look like:
[Some((A = {id=1, …}, B = [{B1}, {B2}, {B3}, …]))]
I want to return:

Some((A = {id=1, …}, B = [{B1}, {B2}, {B3}, …])) , ie, the Option within the List returned (currently) from findRule()
From your question, it sounds like you're trying to pick a single item from your list based on some conditional, which means you'll probably want to start with rules.find. The problem then becomes how to express the predicate function that you pass to find.
From my read of your question, the conditional is
The id on the A part of the tuple needs to match the entryId that was passed in elsewhere
def findRule(entryId: Option[String]) =
rules.find { case (a, listOfB) => entryId.contains(a.id) }
The case expression is just nice syntax for dealing with the tuple. I could have also done
rules.find { tup => entryId.contains(tup._1.id) }
The contains method on Option roughly does "if I'm a Some, see if my value equals the argument; if I'm a None, just return false and ignore the argument". It works as a comparison between the Option[String] you have for entryId and the plain String you have for A's id.
Your first attempt didn't work because rules.flatMap got you a List, and using that after the <- in the for-comprehension means another flatMap, which keeps things as a List.
A variant that I personally prefer is
def findRule(entryId: Option[String]): Option[(A, List[B])] = {
entryId.flatMap { id =>
rules.find { case (a, _) => a.id == id }
}
}
In the case where entryId is None, it just immediately returns None. If you start with rules.find, then it will iterate through all of the rules and check each one even when entryId is None. It's unlikely to make any observable performance difference if the list of rules is small, but I also find it more intuitive to understand.

How to change many fields at the same time in Slick update query?

Lets say I have a table for movies which has many fields such as id, the name of the movie,year it was created etc. I know that the following works:
val updateMovieNameQuery = SlickTables.movieTable.filter(_.id === movieId).map(_.name).update("newName")
Is there any way how to update two or more fields in this way ? I tried the following but this doesnt work.
val updateMovieNameQuery = SlickTables.movieTable.filter(_.id === movieId).map(_.name,_.year).update("newName",1997)
I
Your answer is pretty close, but you need to extract the fields as a tuple and then pass a new tuple to update:
val updateMovieNameQuery = SlickTables
.movieTable
.filter(_.id === movieId)
.map(m => (m.name, m.year)) // Create tuple of field values
.update(("newName",1997)) // Pass new tuple of values

Slick filter when comparing value and column is Option

have a table where formDefinitionId is nullable column in Postgres DB (9.3) . Slick version is 2.1.0
we have a this filter on a query
table.filter(_.formDefinitionId === request.formDefinitionId)
request.formDefinitionId where request is nothing but a Scala case class with formDefinitionId as an Option[Int]
case class Request(formDefinitionId : Option[Int])
now when request.formDefinitionId is None slick generates following query
(x2."form_definition_id" = null)
vs
(x2."form_definition_id” IS NULL) - this is what Postgres expects
Work around is to create a different filter based on request.formDefinitionId
e.g.
request.formDefinitionId match {
case Some(fid) => table.filter(_.formDefinitionId === fid)
case None => table.filter(_.formDefinitionId.isNull)
}
however creating such condition for every property which is Option isn't feasible - there exists numerous Option properties across many tables. Is there a better/generic way to achieve this? I would imagine this is a common scenario with people using Slick
Didn't test (I don't have a Slick setup here).
But as = null returns nothing, shouldn't something like this work?
table.filter(r => r.formDefinitionId.isNull || r.formDefinitionId === fid)
You can use the following solution with Slick 3.3.x:
table
// case 1: both are empty
.filterIf(request.formDefinitionId.isEmpty)(_.formDefinitionId.isEmpty)
// case 2: both are defined and equal
.filterOpt(request.formDefinitionId)(_.formDefinitionId === _)
case 1 corresponds the following SQL:
select * from table1 when form_definition_id is null;
case 2:
select * from table1 when form_definition_id = ?;
I am a newer to slick and now also confused with this problem. Maybe it's toolate to you.I wanted to do some search with different column(Option[text]). Here is my code ,It is worked to me.
val someone1 = cou.filter(_.id === id_S)
val someone2 = cou.filter(_.first_name === first)
val someone3 = cou.filter(_.last_name === last)
val someone = (someone1) union (someone2) union (someone3)
dbConfig.db.run(someone.result).map(_.toList)

Scala slick query where in list

I am attempting to learn to use Slick to query MySQL. I have the following type of query working to get a single Visit object:
Q.query[(Int,Int), Visit]("""
select * from visit where vistor = ? and location_code = ?
""").firstOption(visitorId,locationCode)
What I would like to know is how can I change the above to query to get a List[Visit] for a collection of Locations...something like this:
val locationCodes = List("loc1","loc2","loc3"...)
Q.query[(Int,Int,List[String]), Visit]("""
select * from visit where vistor = ? and location_code in (?,?,?...)
""").list(visitorId,locationCodes)
Is this possible with Slick?
As the other answer suggests, this is cumbersome to do with static queries. The static query interface requires you to describe the bind parameters as a Product. (Int, Int, String*)
is not valid scala, and using (Int,Int,List[String]) needs some kludges as well. Furthermore, having to ensure that locationCodes.size is always equal to the number of (?, ?...) you have in your query is brittle.
In practice, this is not too much of a problem because you want to be using the query monad instead, which is the type-safe and recommended way to use Slick.
val visitorId: Int = // whatever
val locationCodes = List("loc1","loc2","loc3"...)
// your query, with bind params.
val q = for {
v <- Visits
if v.visitor is visitorId.bind
if v.location_code inSetBind locationCodes
} yield v
// have a look at the generated query.
println(q.selectStatement)
// run the query
q.list
This is assuming you have your tables set up like this:
case class Visitor(visitor: Int, ... location_code: String)
object Visitors extends Table[Visitor]("visitor") {
def visitor = column[Int]("visitor")
def location_code = column[String]("location_code")
// .. etc
def * = visitor ~ .. ~ location_code <> (Visitor, Visitor.unapply _)
}
Note that you can always wrap your query in a method.
def byIdAndLocations(visitorId: Int, locationCodes: List[String]) =
for {
v <- Visits
if v.visitor is visitorId.bind
if v.location_code inSetBind locationCodes
} yield v
}
byIdAndLocations(visitorId, List("loc1", "loc2", ..)) list
It doesn't work because the StaticQuery object (Q) expects to implicitly set the parameters in the query string, using the type parameters of the query method to create a sort of setter object (of type scala.slick.jdbc.SetParameter[T]).
The role of SetParameter[T] is to set a query parameter to a value of type T, where the required types are taken from the query[...] type parameters.
From what I see there's no such object defined for T = List[A] for a generic A, and it seems a sensible choice, since you can't actually write a sql query with a dynamic list of parameters for the IN (?, ?, ?,...) clause
I did an experiment by providing such an implicit value through the following code
import scala.slick.jdbc.{SetParameter, StaticQuery => Q}
def seqParam[A](implicit pconv: SetParameter[A]): SetParameter[Seq[A]] = SetParameter {
case (seq, pp) =>
for (a <- seq) {
pconv.apply(a, pp)
}
}
implicit val listSP: SetParameter[List[String]] = seqParam[String]
with this in scope, you should be able to execute your code
val locationCodes = List("loc1","loc2","loc3"...)
Q.query[(Int,Int,List[String]), Visit]("""
select * from visit where vistor = ? and location_code in (?,?,?...)
""").list(visitorId,locationCodes)
But you must always manually guarantee that the locationCodes size is the same as the number of ? in your IN clause
In the end I believe that a cleaner workaround could be created using macros, to generalize on the sequence type. But I'm not sure it would be a wise choice for the framework, given the aforementioned issues with the dynamic nature of the sequence size.
You can generate in clause automaticly like this:
def find(id: List[Long])(implicit options: QueryOptions) = {
val in = ("?," * id.size).dropRight(1)
Q.query[List[Long], FullCard](s"""
select
o.id, o.name
from
organization o
where
o.id in ($in)
limit
?
offset
?
""").list(id ::: options.limits)
}
And use implicit SetParameter as pagoda_5b says
def seqParam[A](implicit pconv: SetParameter[A]): SetParameter[Seq[A]] = SetParameter {
case (seq, pp) =>
for (a <- seq) {
pconv.apply(a, pp)
}
}
implicit def setLongList = seqParam[Long]
If you have a complex query and the for comprehension mentioned above is not an option, you can do something like the following in Slick 3. But you need to make sure you validate the data in your list query parameter yourself to prevent SQL injection:
val locationCodes = "'" + List("loc1","loc2","loc3").mkString("','") + "'"
sql"""
select * from visit where visitor = $visitor
and location_code in (#$locationCodes)
"""
The # in front of the variable reference disables the type validation and allows you to solve this without supplying a function for the implicit conversion of the list query parameter.

How should I use MayErr[IntegrityConstraintViolation,Int] in Scala and Anorm?

I use Anorm to do database queries. When I do an executeUpdate(), how should I do proper error handling? it has return type MayErr[IntegrityConstraintViolation,Int], is this a Set or a Map?
There is an example, but I don't understand how I should handle the return value:
val result = SQL("delete from City where id = 99").executeUpdate().fold(
e => "Oops, there was an error" ,
c => c + " rows were updated!"
)
How do I check if the query failed? (using result), and how do I get the numer of affected rows if the query was successful?
At the moment I use this code:
SQL(
"""
INSERT INTO users (firstname, lastname) VALUES ({firstname}, {lastname})
"""
).on("firstname" -> user.firstName, "lastname" -> user.lastName)
.executeUpdate().fold(
e => "Oops, therw was an error",
c => c + " rows were updated!"
)
But I don't know how my error-handling code should look like. Is there any example on how to use the return value of type MayErr[IntegrityConstraintViolation,Int]?
It looks like MayErr is wrapping Either. So it's neither a Map nor a Set, but rather an object that can contain one of two differently typed objects.
Take a look at this question, and you'll see some ways of processing an Either object, which in this case contains either an IntegrityConstraintViolation or an Int. Referring to http://scala.playframework.org/.../Scala$MayErr.html, it looks like you can grab an Either object by referring to the value member e. There seems to be an implicit conversion available too, so you can just treat a MayErr[IntegrityConstraintViolation,Int] as an Either[IntegrityConstraintViolation,Int] without further ceremony.
You could obviously do a
val updateResult = ....executeUpdate()
val success = updateResult.fold(e => false, c => true)
It looks like you can also call
val success = updateResult.isRight
More generally, you can access the wrapped Either with
updateResult.e match {
case Left(error) => ... do something with error ...
case Right(updateCount) => ...do something with updateCount...
}
Maybe someone more familiar with Play would explain why scala.Either is wrapped in MayErr?