I'm working on a legacy system that uses Scala Quill. I want to have a generic update function in my DAO and only update the columns that are passed. For example:
override def updateStatus(
personId: Int,
name: Option[String] = None,
address: Option[String] = None): Long = ctx.run(
query[Person]
.filter(_.id == lift(personId))
.update(
p => if (name.isDefined) p.name -> lift(name.get) else p.name -> p.name,
p => if (address.isDefined) p.address -> lift(address.get) else p.address = p.address
)
)
The example above, although compilable, incurs in the following runtime exception:
org.postgresql.util.PSQLException: ERROR: column "unused" of relation "person" does not exist
Position: 99
Any suggestion?
Quill just translates your code to sql, so every part of code should be wrapeed in lift.
You will see translated sql code when you compile your scala code.
the if (name.isDefined) p.name -> lift(name.get) else p.name -> p.name cant be translated to sql.
And your code has some typo. such as p.address = p.address
BTW, it's not recommend to use .get in Option value. When we forget check the value, None.get will throw NoSuchElementException.
Try this
def updateStatus(
personId: Int,
name: Option[String] = None,
address: Option[String] = None
) = ctx.run {
query[Person]
.filter(_.id == lift(personId))
.update(
p => p.name -> lift(name.getOrElse(p.name)),
p => p.address -> lift(address.getOrElse(p.address))
)
}
it will be translate to sql like following
UPDATE person SET name = ?, address = ? WHERE id = ?
Related
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 *)
}
I have the following case class:
case class Block(
id: Option[Int] = None,
blockId: Int,
name: String,
location: Option[Point] = None,
geometry: Option[Geometry] = None,
)
In postgres i have a table SubBlock contient
id : int,
block_id: Int,
name: String,
geom_location: geography,
sub_block_geom: geography
And I define a function to return a subBlock nearest of a specified point
override def getNearestSubBlock(point: Point): Future[SubBlock] = {
val query = sql"""SELECT sub_block_id,block_id,name,ST_AsText(geom_location),sub_block_geom from now.sub_block order by ST_Distance(geom_location, ST_MakePoint(${point.getX()}, ${point.getY()})::geography) limit 1""".as[SubBlock].head
db.run(query)
}
implicit val getSubBlock = GetResult(r => SubBlock(r.nextIntOption(), r.nextInt(), r.nextString(), Option(Location.location2Point(Location.fromWKT(r.nextString()))), Option(new WKTReader().read(r.nextString())))
And my request return the right result, but after I got « Exception in thread "main" java.lang.NullPointerException « because the sub_block_geom is null in my database, so I think that the solution is to change implicit val getSubBlock or to write query with filter, sortedBy , … and I don’t know how to do that
Well... I am not too sure about your problem, as a lot of required details are missing. But from what I can see, you just need to properly handle possibility of null in your getSubBlock.
implicit val getSubBlock = GetResult(r => {
val id = r.nextIntOption()
val blockId = r.nextInt()
val location: Option[Point] = r.nextStringOption().map(s => Location.location2Point(Location.fromWKT(s)))
val geometry: Option[Geometry] = r.nextStringOption().map(s => new WKTReader().read(s)))
SubBlock(id, blockId, location, geometry)
}
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)
})
In order to save me having to create so many methods, I tried passing in Option's into my method and then checking if the Option is defined, if so, then apply the filter.
def getUsers(locationId: Option[Int], companyId: Int, salary: Option[Int]): List[User] = {
val query = for {
u <- users if u.companyId === companyId && (locationId.isDefined && u.locationId === locationId.get) && (salary.isDefined && u.salary >= salary.get)
}
query.list()
}
I am getting errors saying:
polymorphic expression cannot be instantiated to expected type;
IntelliJ errors are expected Boolean actual Column[Boolean].
Is this type of clause just not possible in a slick query or I'm just doing it wrong?
I can't tell you why but this compiles for me:
def getUsers(locationId: Option[Int], companyId: Int, salary: Option[Int]): List[User] = {
val query = for {
u <- users if u.companyId === companyId && locationId.isDefined && u.locationId === locationId.get && salary.isDefined && u.salary >= salary.get
} yield(u)
query.list()
}
Note that there are no parenthesis and that you have to yield something otherwise the return type for query would be Unit.
Sure, don't see any issue here, just use filter (or withFilter) and map over the options.
def getUsers(locationId: Option[Int], companyId: Int, salary: Option[Int]): List[User] = (for {
u <- users filter(u=>
if (u.companyId === companyId.bind) &&
(locationId.map(_.bind === u.locationId).getOrElse(true)) &&
(salary.map(_.bind <= u.salary).getOrElse(true))
)
} yield u).list()
Using filter allows you to drop down to Scala for the map or true fallback expressions. If you start with u < users if... then there's no way to use Scala conditionals. The bind calls just escape potential malicious input (i.e. if params are coming from outside the application).
Why it doesn't work
As cvot has noted in his comment, the reason this doesn't work is because:
Slick translates the None as SQL NULL including SQLs 3-valued-logic NULL propagation, so (None === a) is None regardless of the value of a ... basically if anything is None in the expression, the whole expression will be None, so the filter expression will be treated as false and the query result will be empty.
That said, there is a way to get the same behavior you want (filtering only if an optional value is provided).
A way to arrive at the desired behavior
The key thing to note is that for comprehensions get compiled down by Scala to a combination of map / flatMap / withFilter / filter calls. Slick, if I understand it correctly, works with the resulting structure when it compiles the Scala comprehension into a SQL query.
This lets us build up a query in parts:
val baseQuery = for {
u <- users if u.companyId === companyId
} yield u
val possiblyFilteredByLocation = if (locationId.isDefined) {
baseQuery.withFilter(u => u.locationId === locationId.get
} else baseQuery
val possiblyFilteredBySalaryAndOrLocation = if (salary.isDefined) {
possiblyFilteredByLocation.withFilter(u => u.salary >= salary.get)
} else possiblyFilteredByLocation
possiblyFilteredBySalaryAndOrLocation.list()
We can simplify this by using a var and fold:
var query = for {
u <- users if u.companyId === companyId
} yield u
query = locationId.fold(query)(id => query.withFilter(u => u.locationId === id))
query = salary.fold(query)(salary => query.withFilter(u => u.salary >= salary))
query.list()
If we do this frequently, we can generalize this pattern of filtering on an Option into something like this:
// Untested, probably does not compile
implicit class ConditionalFilter(query: Query) {
def ifPresent[T](value: Option[T], predicate: (Query, T) => Query) = {
value.fold(query)(predicate(query, _))
}
}
Then we can simplify our whole filter chain to:
query
.ifPresent[Int](locationId, (q, id) => q.withFilter(u => u.locationId === id))
.ifPresent[Int](salary, (q, s) => q.withFilter(u => u.salary >= s))
.list()
You can use the following solution (with Slick 3.3.x):
def getUsers(locationId: Option[Int], companyId: Int, minSalary: Option[Int]) =
users.
.filter(_.company === companyId)
.filterOpt(locationId)(_.locationId === _)
.filterOpt(minSalary)(_.salary >= _)
Because the Slick query gets translated into SQL, which has no notion of the isDefined and get methods of the Option class.
But you can fix this by calling the methods outside the query and passing the results (via the map function on the options).
The following code should fix it:
def getUsers(locationId: Option[Int], companyId: Int, salary: Option[Int]): List[User] = {
val locationAndSalary = for {
locId <- locationId;
sal <- salary
} yield (locId, sal)
locationAndSalary.map(locAndSal => {
val query = for {
u <- users if u.companyId === companyId && u.locationId === locAndSal._1 && u.salary >= locAndSal._2)
} yield u
query.list()
}).getOrElse(List[User]()) //If the locationID or salary is None, return empty list.
}
The locationAndSalary may seem strange, but we are using for comprehensions to give use a value only when both locationId and salary has a value and storing the result in a tuple, with the locationId in the first position and salary at the second. The following links explains it: Scala: for comprehensions with Options.
Edit: According to #Ende Neu answer the code compiles if you add the yield-statement, but I still think my solution is more the "Scala way".
could you help me concerning a basic question?
I have a list of "Rdv" (meetings) where Rdv is a case class having 3 fields storing phone numbers as Strings: telBureau, telPortable & TelPrivé.
I get this list from slick, via a native SQL query; this query fills the 3 phone number fields with either a String or "null"(a null object, not the "null" string).
I would like to remove these null fields, so I wrote this:
var l2:List[Rdv] = liste.list()
l2=l2.map( (w:Rdv) =>{
if ( w.telPrivé==null ) w.copy( telPrivé = "" )
})
but I get this error:
found:List[Any], required:List[Rdv]
so after the map I added ".asInstanceOf[List[Rdv]]" but then I get this error:
java.lang.ClassCastException: scala.runtime.BoxedUnit cannot be cast to metier.Objets$Rdv
It seems to ba a basic question, but I can't do that.
olivier.
Try this:
var l2: List[Rdv] = liste.list()
l2 = l2 map ((w: Rdv => if (w.telPrivé == null) w.copy( telPrivé = "" ) else w)
Could you try doing something like this?
val l2: List[Rdv] = liste list ()
val l3 = ls map{
case x # Rdv(_, null, _) => x.copy(telPrive = "")
case x => x
}
Honestly, should should make that field, if it's nullable to be an Option and then have a member function which you call defined such that:
case class Rdv(a: String, b: Option[String], c: String){
def realC = b getOrElse ""
}