Slick: LIKE query with column as parameter - postgresql

I have the following SQL query, which I'd like to map to Slick
SELECT * FROM rates
WHERE '3113212512' LIKE (prefix || '%') and present = true
ORDER BY prefix DESC
LIMIT 1;
However the like symbol is not defined for String:
case class Rate(grid: String, prefix: String, present: Boolean)
class Rates(tag: Tag) extends Table[Rate](tag, "rates") {
def grid = column[String]("grid", O.PrimaryKey, O.NotNull)
def prefix = column[String]("prefix", O.NotNull)
def present = column[Boolean]("present", O.NotNull)
// Foreign keys
def ratePlan = foreignKey("rate_plan_fk", ratePlanId, RatePlans)(_.grid)
def ratePlanId = column[String]("rate_plan_id", O.NotNull)
def * = (grid, prefix, present) <>(Rate.tupled, Rate.unapply)
}
object Rates extends TableQuery(new Rates(_)) {
def findActiveRateByRatePlanAndPartialPrefix(ratePlan: String, prefix: String) = {
DB withSession {
implicit session: Session =>
Rates.filter(_.ratePlanId === ratePlan)
.filter(_.present === true)
.filter(prefix like _.prefix)
.sortBy(_.prefix.desc).firstOption
}
}
}
Obviously it's logical that something like this won't work:
.filter(prefix like _.prefix)
And:
.filter(_.prefix like prefix)
Will render incorrect SQL, and I'm not even considering the '%' right now (only WHERE clause concerning the prefix:
(x2."prefix" like '3113212512')
Instead of:
'3113212512' LIKE (prefix || '%')
Of course I can solve this using a static query, but I'd like to know whether this would be possible at all?
For clarity, here are some prefixes in the database
31
3113
312532623
31113212
And 31113212 is expected as a result

You could simply rewrite your query in a style that is supported. I think this should be equivalent:
.filter(s => (s.prefix === "") || s.prefix.isEmpty || LiteralColumn(prefix) like s.prefix)
If the conditions are more complex and you need a Case expression, see http://slick.typesafe.com/doc/2.1.0/sql-to-slick.html#case
If you still want the || operator you may be able to define it this way:
/** SQL || for String (currently collides with Column[Boolean] || so other name) */
val thisOrThat = SimpleBinaryOperator[String]("||")
...
.filter(s => LiteralColumn(prefix) like thisOrThat(s.prefix,"%"))
I didn't try this though and I saw we don't have a test for SimpleBinaryOperator. Open a ticket on github.com/slick/slick if it doesn't work. Probably we should also change it to return a typed function. I added a ticket for that https://github.com/slick/slick/issues/1073.
Also see http://slick.typesafe.com/doc/2.1.0/userdefined.html#scalar-database-functions
Luckily SimpleBinaryOperator is not even needed here.

UPDATE 2
One actually does not require the concat, as it can be written as follows as well:
filter(s => LiteralColumn(prefix) like (s.prefix ++ "%"))
UPDATE:
After hints by cvogt this is the final result without using internal Slick APIs:
val concat = SimpleBinaryOperator[String]("||")
Rates.filter(_.ratePlanId === ratePlan)
.filter(_.present === true)
.filter(s => LiteralColumn(prefix) like concat(s.prefix, "%"))
.sortBy(_.prefix.desc).firstOption
Which perfectly gives:
and ('3113212512' like (x2."prefix" || '%'))
Alternative:
This seems to possible. Note however that the following method uses the Internal API of Slick and may not be compatible in the future due to API changes (see comment by cvogt) I used Slick 2.1.0.
Basically I created an extension to Slick in which I define a custom scheme to implement the required result:
package utils
import scala.language.{higherKinds, implicitConversions}
import scala.slick.ast.ScalaBaseType._
import scala.slick.ast._
import scala.slick.lifted.{Column, ExtensionMethods}
object Helpers {
implicit class PrefixExtensionMethods[P1](val c: scala.slick.lifted.Column[P1]) extends AnyVal with ExtensionMethods[String, P1] {
def subPrefixFor[P2, R](prefix: Column[P2])(implicit om: o#arg[String, P2]#to[Boolean, R]) = {
om.column(Library.Like, prefix.toNode, om.column(new Library.SqlOperator("||"), n, LiteralNode("%")).toNode)
}
}
}
This can now be used as follows:
import utils.Helpers._
[...]
def findActiveRateByRatePlanAndPartialPrefix(ratePlan: String, prefix: String) = {
DB withSession {
implicit session: Session =>
Rates.filter(_.ratePlanId === ratePlan)
.filter(_.present === true)
.filter(_.prefix subPrefixFor prefix)
.sortBy(_.prefix.desc).firstOption
}
}
And will render correct result:
and ('3113212512' like (x2."prefix" || '%'))
Notes:
I could probably have some better naming
Had to chain the || and Like using om.column
I introduced new SqlOperator since Or operator renders or and I need concatenation in Postgres ||

Related

In Anorm is it possible to apply multiple ColumnAliaser to the same query

The scenario is similar to the question at How to better parse the same table twice with Anorm? however the described solutions on that question can no longer be used.
On the scenario where a Message has 2 users I need to parse the from_user and to_user with SQL joins.
case class User(id: Long, name: String)
case class Message(id: Long, body: String, to: User, from: User)
def userParser(alias: String): RowParser[User] = {
get[Long](alias + "_id") ~ get[String](alias + "_name") map {
case id~name => User(id, name)
}
}
val parser: RowParser[Message] = {
userParser("from_user") ~
userParser("to_user") ~
get[Long]("messages.id") ~
get[String]("messages.name") map {
case from~to~id~body => Message(id, body, to, from)
}
}
// More alias here possible ?
val aliaser: ColumnAliaser = ColumnAliaser.withPattern((0 to 2).toSet, "from_user.")
SQL"""
SELECT from_user.* , to_user.*, message.* FROM MESSAGE
JOIN USER from_user on from_user.id = message_from_user_id
JOIN USER to_user on to_user.id = message.to_user
"""
.asTry(parser, aliaser)
If I'm right thinking you want to apply multiple ColumnAliaser with different aliasing policies to the same query, it's important to understand that ColumnAliaser is "just" a specific implementation of Function[(Int, ColumnName), Option[String]], so it can be defined/composed as any Function, and is simplified by the factory functions in its companion object.
import anorm.{ ColumnAliaser, ColumnName }
val aliaser = new ColumnAliaser {
def as1 = ColumnAliaser.withPattern((0 to 2).toSet, "from_user.")
def as2 = ColumnAliaser.withPattern((2 to 4).toSet, "to_user.")
def apply(column: (Int, ColumnName)): Option[String] =
as1(column).orElse(as2(column))
}

Unexplainable Scala compiler error

I'm creating a OData query language in Scala. It's going pretty well, but there is one error I get which I can't explain.
First let me show you the Query class I created (omitted some code out for brevity):
case class Query(val name: String, val args: Seq[(String, String)] = Seq())
(val parent: Option[Query] = None)
(val options: Seq[QueryOption] = Seq()) {
def /(newChild: Query): Query = new Query(newChild.name, newChild.args)(Some(this))(options)
def $(newOptions: QueryOption*): Query = new Query(name, args)(parent)(options ++ newOptions)
def |(newArgs: (String, String)*): Query = new Query(name, args ++ newArgs)(parent)(options)
}
object Query {
private def emptyQueryWithName(name: String): Query = Query(name, Seq())(None)(Seq())
def /(name: String): Query = emptyQueryWithName(name)
implicit def createQuery(name: String): Query = emptyQueryWithName(name)
}
package object queries {
implicit class QueryOps(name: String) {
def ===(attr: Any): (String, String) = (name, attr.toString)
}
}
I've written some tests for this DSL and they work mostly. For instance this code:
Query / "Pages" / "Component" | ("ItemId" === 123, "PublicationId" === 1) $ ("Title" ==| "Test Title")
Gives me the expected query: /Pages/Component(ItemId=123,PublicationId=1)?$filter=Title eq 'Test Title'
But this one:
Query / "Pages" | ("ItemId" === 123) / "Title" $ jsonFormat $ ("Url" ==| "Bla")
Complains about the '/ "Title"' part. As if the compiler is not aware that the proceeding code results in a Query instance. It can't find the '/' method. To me this seems very strange as the '$' method is found, which has the same scope; the Query class.
I'm probably running into some kind of limitation I can't fathom, but I would like to understand. Thanks for any help!
The fact that parentheses fix the problem usually means an operator precedence problem. Have a look at http://books.google.es/books?id=MFjNhTjeQKkC&pg=PA90&lpg=PA90&dq=scala+operator+precedence+reference&source=bl&ots=FMlkUEDSpq&sig=pf3szEM4GExN_UCsgaxcQNBegPQ&hl=en&sa=X&ei=ZezQU_-SDszY7Ab-pIDQDQ&redir_esc=y#v=onepage&q=scala%20operator%20precedence%20reference&f=false
| has a lower precedence than /, and $ has the highest precedence, so your expression is being interpreted as:
(Query / "Pages") | (("ItemId" === 123) / (("Title" $ jsonFormat) $ ("Url" ==| "Bla")))
Also, providing the exact error message is usually useful.

Trouble updating a record with Slick

With a class and table definition looking like this:
case class Group(
id: Long = -1,
id_parent: Long = -1,
label: String = "",
description: String = "")
object Groups extends Table[Group]("GROUPS") {
def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
def id_parent = column[Long]("ID_PARENT")
def label = column[String]("LABEL")
def description = column[String]("DESC")
def * = id ~ id_parent ~ label ~ design <> (Group, Group.unapply _)
def autoInc = id_parent ~ label ~ design returning id into {
case ((_, _, _), id) => id
}
}
To update a record, I can do this:
def updateGroup(id: Long) = Groups.where(_.id === id)
def updateGroup(g: Group)(implicit session: Session) = updateGroup(g.id).update(g)
But I can't get updates to work using for expressions:
val findGById = for {
id <- Parameters[Long]
g <- Groups; if g.id === id
} yield g
def updateGroupX(g: Group)(implicit session: Session) = findGById(g.id).update(g)
----------------------------------------------------------------------------^
Error: value update is not a member of scala.slick.jdbc.MutatingUnitInvoker[com.exp.Group]
I'm obviously missing something in the documentation.
The update method is supplied by the type UpdateInvoker. An instance of that type can be implicitly created from a Query by the methods productQueryToUpdateInvoker and/or tableQueryToUpdateInvoker (found in the BasicProfile), if they are in scope.
Now the type of your findById method is not a Query but a BasicQueryTemplate[Long, Group]. Looking at the docs, I can find no way from a BasicQueryTemplate (which is a subtype of StatementInvoker) to an UpdateInvoker, neither implicit nor explicit. Thinking about it, that makes kinda sense to me, since I understand a query template (invoker) to be something that has already been "compiled" from an abstract syntax tree (Query) to a prepared statement rather early, before parameterization, whereas an update invoker can only be built from an abstract syntax tree, i.e. a Query object, because it needs to analyze the query and extract its parameters/columns. At least that's the way it appears to work at present.
With that in mind, a possible solution unfolds:
def findGById(id: Long) = for {
g <- Groups; if g.id === id
} yield g
def updateGroupX(g: Group)(implicit session: Session) = findGById(g.id).update(g)
Where findById(id: Long) has the type Query[Groups, Group] which is converted by productQueryToUpdateInvoker to an UpdateInvoker[Group] on which the update method can finally be called.
Hope this helped.
Refer to http://madnessoftechnology.blogspot.ru/2013/01/database-record-updates-with-slick-in.html
I stuck with the updating today, and this blog post helped me much. Also refer to the first comment under the post.

How to write nested queries in select clause

I'm trying to produce this SQL with SLICK 1.0.0:
select
cat.categoryId,
cat.title,
(
select
count(product.productId)
from
products product
right join products_categories productCategory on productCategory.productId = product.productId
right join categories c on c.categoryId = productCategory.categoryId
where
c.leftValue >= cat.leftValue and
c.rightValue <= cat.rightValue
) as productCount
from
categories cat
where
cat.parentCategoryId = 2;
My most successful attempt is (I dropped the "joins" part, so it's more readable):
def subQuery(c: CategoriesTable.type) = (for {
p <- ProductsTable
} yield(p.id.count))
for {
c <- CategoriesTable
if (c.parentId === 2)
} yield(c.id, c.title, (subQuery(c).asColumn))
which produces the SQL lacking parenthesis in subquery:
select
x2.categoryId,
x2.title,
select count(x3.productId) from products x3
from
categories x2
where x2.parentCategoryId = 2
which is obviously invalid SQL
Any thoughts how to have SLICK put these parenthesis in the right place? Or maybe there is a different way to achieve this?
I never used Slick or ScalaQuery so it was quite an adventure to find out how to achieve this. Slick is very extensible, but the documentation on extending is a bit tricky. It might already exist, but this is what I came up with. If I have done something incorrect, please correct me.
First we need to create a custom driver. I extended the H2Driver to be able to test easily.
trait CustomDriver extends H2Driver {
// make sure we create our query builder
override def createQueryBuilder(input: QueryBuilderInput): QueryBuilder =
new QueryBuilder(input)
// extend the H2 query builder
class QueryBuilder(input: QueryBuilderInput) extends super.QueryBuilder(input) {
// we override the expr method in order to support the 'As' function
override def expr(n: Node, skipParens: Boolean = false) = n match {
// if we match our function we simply build the appropriate query
case CustomDriver.As(column, LiteralNode(name: String)) =>
b"("
super.expr(column, skipParens)
b") as ${name}"
// we don't know how to handle this, so let super hanle it
case _ => super.expr(n, skipParens)
}
}
}
object CustomDriver extends CustomDriver {
// simply define 'As' as a function symbol
val As = new FunctionSymbol("As")
// we override SimpleSql to add an extra implicit
trait SimpleQL extends super.SimpleQL {
// This is the part that makes it easy to use on queries. It's an enrichment class.
implicit class RichQuery[T: TypeMapper](q: Query[Column[T], T]) {
// here we redirect our as call to the As method we defined in our custom driver
def as(name: String) =
CustomDriver.As.column[T](Node(q.unpackable.value), name)
}
}
// we need to override simple to use our version
override val simple: SimpleQL = new SimpleQL {}
}
In order to use it we need to import specific things:
import CustomDriver.simple._
import Database.threadLocalSession
Then, to use it you can do the following (I used the tables from the official Slick documentation in my example).
// first create a function to create a count query
def countCoffees(supID: Column[Int]) =
for {
c <- Coffees
if (c.supID === supID)
} yield (c.length)
// create the query to combine name and count
val coffeesPerSupplier =
for {
s <- Suppliers
} yield (s.name, countCoffees(s.id) as "test")
// print out the name and count
coffeesPerSupplier foreach { case (name, count) =>
println(s"$name has $count type(s) of coffee")
}
The result is this:
Acme, Inc. has 2 type(s) of coffee
Superior Coffee has 2 type(s) of coffee
The High Ground has 1 type(s) of coffee

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.