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:
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.