Using a function in an insert statement in Slick - scala

Is it possible to use a user defined function in an insert statement in slick? This is what my tables look like, but I'm getting an error when I try to run it.
case class User(username: String, email: String)
class Users(tag: Tag)
extends Table[User](tag, "users") {
def username = column[String]("username", O.PrimaryKey)
def email = column[String]("email", O.NotNull)
def password = column[String]("password", O.NotNull)
def preferences = column[JsValue]("preferences")
def idx = index("idx_users_email", email, unique = true)
def group = foreignKey("fk_group", username, groups)(_.groupname)
def * = (username, email) <> (User.tupled, User.unapply)
def withPassword = (username, email, password) <>[(User, String), (String, String, String)](
{ case (u: String, e: String, p: String) => (User(u, e), p)},
{ case (r: User, p: String) => Some((r.username, r.email, p))}
)
}
object users extends TableQuery(new Users(_)) {
val crypt = SimpleFunction.binary[String, String, String]("crypt")
val gen_salt = SimpleFunction.binary[String, Option[Int], String]("gen_salt")
def createUser(u: String, e: String, p: String)(implicit session: Session) = {
val user = User(u, e)
val s = this.map {c =>(c.username, c.password, users.crypt(c.password, users.gen_salt(LiteralColumn("bf"), LiteralColumn(7))))}
s insertExpr (u, e, p)
}
}
When I run this code, I get the error
SlickException: : Cannot use node
scala.slick.lifted.SimpleFunction$$anon$2#69ade8b0 crypt, false for
inserting data
I've tried several different ways of trying to get my inserts to work. Including this:
def withPassword = (username, email, users.crypt(password, users.gen_salt(LiteralColumn("bf"), LiteralColumn(7)))) <>[(User, String), (String, String, String)](
{ case (u: String, e: String, p: String) => (User(u, e), p)},
{ case (r: User, p: String) => Some((r.username, r.email, p))}
)
Am I just expecting too much for slick to be able to do what I want to and just break down and use raw SQL?

Not supported, but we have a ticket for it with unclear future: https://github.com/slick/slick/issues/928
Besides, encryption is almost always better handled on the client-level. Database-level encryption has very limited security guarantees, because you are sending the keys to the place where the encrypted data is stored. More info in the above ticket.

Related

How to optionally update field if parameters not None

I have a table with non nullable columns:
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def surname = column[String]("surname")
}
I want update some columns only (if not None):
def update(id: String, name: Option[String], surname: Option[String]) = {
(name, surname) match {
case (Some(n), Some(s)) => byId(id)
.map(l => (l.name, l.surname))
.update((n, s))
case (None, Some(s)) => byId(id)
.map(l => (l.surname))
.update(s)
case (Some(n),None) => byId(id)
.map(l => (l.name))
.update(n)
}
}
Is there more elegant way to do this? What if there are lot of update parameters?
Although I am able to make two queries, I am left with the option to use the existing one and always make only one update:
def byId(id: String) = ???
def update(id: String, name: Option[String], surname: Option[String]) = {
val filterById = byId(id).map(u => (u.name, u.surname))
for {
(existingName, existingSurname) <- filterById.result.head
rowsAffected <- filterById.update((name.getOrElse(existingName), surname.getOrElse(existingSurname)))
} yield rowsAffected
}
PD: Same for large objects .. we map the entire row and then make a kind of patch to update it again

Slick 3.0.0 - update row with only non-null values

Having a table with the columns
class Data(tag: Tag) extends Table[DataRow](tag, "data") {
def id = column[Int]("id", O.PrimaryKey)
def name = column[String]("name")
def state = column[State]("state")
def price = column[Int]("price")
def * = (id.?, name, state, price) <> ((DataRow.apply _).tupled, DataRow.unapply)
}
I'd like to write a function that would select a single row, and update the columns where the supplied values are not null.
def update(id: Int, name: Option[String], state: Option[State], price: Option[Int])
eg.
update(1, None, None, Some(5)) would update only the price of the data row 1, leaving the name and state intact
update(1, Some("foo"), None, Some(6)) would update the name and price, but leave its state intact.
I guess some smart mapping could be used, but I'm having a hard time expressing it, not sure how it could spit out different length tuples depending on the inputs (wether their value is defined), since they are more or less "unrelated" classes.
def update(id: Int, name: Option[String], state: Option[State], price: Option[Int]) = {
table.fiter(_.id == id). ???? .update(name, state, price)
}
I solved it in the following way.
The implementation below works only if it is a Product object.
Execute the update statement except for None for the Option type and null for the object type.
package slick.extensions
import slick.ast._
import slick.dbio.{ Effect, NoStream }
import slick.driver.JdbcDriver
import slick.jdbc._
import slick.lifted._
import slick.relational.{ CompiledMapping, ProductResultConverter, ResultConverter, TypeMappingResultConverter }
import slick.util.{ ProductWrapper, SQLBuilder }
import scala.language.{ existentials, higherKinds, implicitConversions }
trait PatchActionExtensionMethodsSupport { driver: JdbcDriver =>
trait PatchActionImplicits {
implicit def queryPatchActionExtensionMethods[U <: Product, C[_]](
q: Query[_, U, C]
): PatchActionExtensionMethodsImpl[U] =
createPatchActionExtensionMethods(updateCompiler.run(q.toNode).tree, ())
}
///////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////// Patch Actions
///////////////////////////////////////////////////////////////////////////////////////////////
type PatchActionExtensionMethods[T <: Product] = PatchActionExtensionMethodsImpl[T]
def createPatchActionExtensionMethods[T <: Product](tree: Node, param: Any): PatchActionExtensionMethods[T] =
new PatchActionExtensionMethodsImpl[T](tree, param)
class PatchActionExtensionMethodsImpl[T <: Product](tree: Node, param: Any) {
protected[this] val ResultSetMapping(_, CompiledStatement(_, sres: SQLBuilder.Result, _),
CompiledMapping(_converter, _)) = tree
protected[this] val converter = _converter.asInstanceOf[ResultConverter[JdbcResultConverterDomain, Product]]
protected[this] val TypeMappingResultConverter(childConverter, toBase, toMapped) = converter
protected[this] val ProductResultConverter(elementConverters # _ *) =
childConverter.asInstanceOf[ResultConverter[JdbcResultConverterDomain, Product]]
private[this] val updateQuerySplitRegExp = """(.*)(?<=set )((?:(?= where)|.)+)(.*)?""".r
private[this] val updateQuerySetterRegExp = """[^\s]+\s*=\s*\?""".r
/** An Action that updates the data selected by this query. */
def patch(value: T): DriverAction[Int, NoStream, Effect.Write] = {
val (seq, converters) = value.productIterator.zipWithIndex.toIndexedSeq
.zip(elementConverters)
.filter {
case ((Some(_), _), _) => true
case ((None, _), _) => false
case ((null, _), _) => false
case ((_, _), _) => true
}
.unzip
val (products, indexes) = seq.unzip
val newConverters = converters.zipWithIndex
.map(c => (c._1, c._2 + 1))
.map {
case (c: BaseResultConverter[_], idx) => new BaseResultConverter(c.ti, c.name, idx)
case (c: OptionResultConverter[_], idx) => new OptionResultConverter(c.ti, idx)
case (c: DefaultingResultConverter[_], idx) => new DefaultingResultConverter(c.ti, c.default, idx)
case (c: IsDefinedResultConverter[_], idx) => new IsDefinedResultConverter(c.ti, idx)
}
val productResultConverter =
ProductResultConverter(newConverters: _*).asInstanceOf[ResultConverter[JdbcResultConverterDomain, Any]]
val newConverter = TypeMappingResultConverter(productResultConverter, (p: Product) => p, (a: Any) => toMapped(a))
val newValue: Product = new ProductWrapper(products)
val newSql = sres.sql match {
case updateQuerySplitRegExp(prefix, setter, suffix) =>
val buffer = StringBuilder.newBuilder
buffer.append(prefix)
buffer.append(
updateQuerySetterRegExp
.findAllIn(setter)
.zipWithIndex
.filter(s => indexes.contains(s._2))
.map(_._1)
.mkString(", ")
)
buffer.append(suffix)
buffer.toString()
}
new SimpleJdbcDriverAction[Int]("patch", Vector(newSql)) {
def run(ctx: Backend#Context, sql: Vector[String]): Int =
ctx.session.withPreparedStatement(sql.head) { st =>
st.clearParameters
newConverter.set(newValue, st)
sres.setter(st, newConverter.width + 1, param)
st.executeUpdate
}
}
}
}
}
Example
// Model
case class User(
id: Option[Int] = None,
name: Option[String] = None,
username: Option[String] = None,
password: Option[String] = None
)
// Table
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def username = column[String]("username")
def password = column[String]("password")
override def * = (id.?, name.?, username.?, password.?) <>(User.tupled, User.unapply)
}
// TableQuery
object Users extends TableQuery(new Users(_))
// CustomDriver
trait CustomDriver extends PostgresDriver with PatchActionExtensionMethodsSupport {
override val api: API = new API {}
trait API extends super.API with PatchActionImplicits
}
// Insert
Users += User(Some(1), Some("Test"), Some("test"), Some("1234"))
// User patch
Users.filter(_.id === 1).patch(User(name = Some("Change Name"), username = Some("")))
https://gist.github.com/bad79s/1edf9ea83ba08c46add03815059acfca
Building on JonasAnso's answer, converting that to slick v3.0+, and putting it into a transaction:
def partialUpdate(id: Int, name: Option[String], login: Option[String]): Future[Int] = {
val selectQ = users.filter(_.id === id)
val query = selectQ.result.head.flatMap { data =>
selectQ.update(data.patch(name, login))
}
db.run(query)
}
As I commented the question is similar to an existing one, but you don't seem to have any extra requirements.
The simplest approach is just SELECT + UPDATE. For example you add a patch function in your DataRow class defining how you want to update your model
def patch(name: Option[String], state: Option[State], price: Option[Int]): Data {
this.copy(name = name.getOrElse(this.name), ...)
}
And you add a partialUpdate method in your repo class
class DataRepo {
private val Datas = TableQuery[Data]
val db = ???
def partialUpdate(id: Int, name: Option[String], state: Option[State], price: Option[Int]): Future[Int] = {
val query = Datas.filter(_.id === id)
for {
data <- db.run(query.result.head)
result <- db.run(query.update(data.patch(name, state, price)))
} yield result
}
}
As you see the main problem of this solution is that there are 2 SQL statements, SELECT and UPDATE.
Other solution is to use plain SQL (http://slick.typesafe.com/doc/3.0.0/sql.html) but of course this gives other problems.

Slick: how to get an object attribute in a result set?

Given the following Scala class enhanced with Slick:
class Users(tag: Tag) extends Table[(Int, String, String)](tag, "users") {
def id: Rep[Int] = column[Int]("sk", O.PrimaryKey)
def firstName: Rep[String] = column[String]("first_name")
def lastName: Rep[String] = column[String]("last_name")
def * : ProvenShape[(Int, String, String)] = (id, firstName, lastName)
}
I need to print the last names in a query loop:
val db = Database.forConfig("dbconfig")
try {
val users: TableQuery[Users] = TableQuery[Users]
val action = users.result
val future = db.run(action)
future onComplete {
case Success(u) => u.foreach { user => println("last name : " + **user.lastName**) }
case Failure(t) => println("An error has occured: " + t.getMessage)
}
} finally db.close
But Scala doesn't recognize user.lastName (I get an error saying that "Scala doesn't recognize the symbol"). How to print the last names ?
The problem is you're using Table[(Int, String, String)]. user in your case is therefore an instance of type (Int, String, String), so it doesn't have a lastName. Use user._3 to get at the tuple's third element (the last name). Even better might be to use a case class instead of a tuple:
case class DBUser(id: Int, firstName: String, lastName: String)
class Users(tag: Tag) extends Table[DBUser](tag, "users") {
def id: Rep[Int] = column[Int]("sk", O.PrimaryKey)
def firstName: Rep[String] = column[String]("first_name")
def lastName: Rep[String] = column[String]("last_name")
def * = (id, firstName, lastName) <> (DBUser.tupled, DBUser.unapply)
}

No Json deserializer found for type List[(java.util.UUID, String, String, String, Int, Int, Int, Int, java.sql.Timestamp)]

My scala skill is lacking but I've been wrestling with this. But I'm having a problem serializing and deserializing JSON. I've googled and searched StackOverflow but unfortunately I cannot piece it together.
So this is my last resort..
My model is:
package models
import java.util.UUID
import java.sql.Timestamp
import play.api.db._
import play.api.Play.current
import play.api.libs.json._
import slick.driver.PostgresDriver.simple._
import Database.threadLocalSession
case class User(
id:UUID,
username: String,
password: String,
email: String,
comment_score_down: Int,
comment_score_up: Int,
post_score_down: Int,
post_score_up: Int,
created_on: Timestamp)
object Users extends
Table[(UUID, String, String, String, Int, Int, Int, Int, Timestamp)]("users"){
implicit object UserFormat extends Format[User] {
implicit object UUIDFormatter extends Format[UUID] {
def reads(s: JsString): UUID = java.util.UUID.fromString(s.toString)
def writes(uuid: UUID) = JsString(uuid.toString)
}
implicit object TimestampFormatter extends Format[Timestamp] {
def reads(s: JsValue): Timestamp = new Timestamp(s.toString.toLong)
def writes(timestamp: Timestamp) = JsString(timestamp.toString)
}
def reads(json: JsValue): User = User(
(json \ "id").as[UUID],
(json \ "username").as[String],
(json \ "password").as[String],
(json \ "email").as[String],
(json \ "comment_score_down").as[Int],
(json \ "comment_score_up").as[Int],
(json \ "post_score_down").as[Int],
(json \ "post_score_up").as[Int],
(json \ "created_on").as[Timestamp]
)
def writes(u: User): JsValue = JsObject(List(
"id" -> JsString(u.id.toString),
"username" -> JsString(u.username),
"password" -> JsString(u.password),
"email" -> JsString(u.email),
"comment_score_down" -> JsString(u.comment_score_down.toString),
"comment_score_up" -> JsString(u.comment_score_up.toString),
"post_score_down" -> JsString(u.post_score_down.toString),
"post_score_up" -> JsString(u.post_score_up.toString),
"created_on" -> JsString(u.created_on.toString)
))
}
def id = column[UUID]("ID", O.PrimaryKey) // This is the primary key column
def username = column[String]("username")
def password = column[String]("password")
def email = column[String]("email")
def comment_score_down = column[Int]("comment_score_down")
def comment_score_up = column[Int]("comment_score_up")
def post_score_down = column[Int]("post_score_down")
def post_score_up = column[Int]("post_score_up")
def created_on = column[Timestamp]("created_on")
def * = id ~ username ~ password ~ email ~ comment_score_down ~
comment_score_up ~ post_score_down ~ post_score_up ~ created_on
}
My controller:
def getUsers = Action {
val json = database withSession {
val users = for (u <- Users) yield u.*
Json.toJson(users.list)
}
Ok(json).as(JSON)
}
Thank you for your time!
Kay, I got it sweet.
Made some edit to my model:
case class User(
id:UUID,
username: String,
password: String,
email: String,
comment_score_down: Option[Int],
comment_score_up: Option[Int],
post_score_down: Option[Int],
post_score_up: Option[Int],
created_on: Timestamp)
object Users extends Table[User]("users"){
I also changed my object signature so that it can return type User instead of just User's parameters. And I just have to append <> (User, User.unapply _) to my projection method (the *).
But in my controller:
I just needed:
implicit object UserWrites extends Writes[User] {
def writes(u: User) = Json.obj(
"id" -> JsString(u.id.toString),
"username" -> JsString(u.username),
"password" -> JsString(u.password),
"email" -> JsString(u.email),
"comment_score_down" -> JsNumber(u.comment_score_down.getOrElse(0).toInt),
"comment_score_up" -> JsNumber(u.comment_score_up.getOrElse(0).toInt),
"post_score_down" -> JsNumber(u.post_score_down.getOrElse(0).toInt),
"post_score_up" -> JsNumber(u.post_score_up.getOrElse(0).toInt),
"created_on" -> JsString(u.created_on.toString)
)
}
as member of the controller class.
So now my controller action is just:
def getUsers = Action {
val json = database withSession {
val users = for (u <- Users) yield u
Json.toJson(users.list)
}
Ok(json).as(JSON)
}
Edit:
Alternatively, I've moved the getUsers code to my model as a findAll method and also moved my writable to there too. I didn't like the data logic being in the controller...
So in my controller I only have a method/action:
def getUsers = Action {
Ok(Users.findAll).as(JSON)
}
My model now looks like:
package models
import java.util.UUID
import java.sql.Timestamp
import play.api.db._
import play.api.Play.current
import play.api.libs.json._
import slick.driver.PostgresDriver.simple._
import Database.threadLocalSession
case class User(
id:UUID,
username: String,
password: String,
email: String,
comment_score_down: Option[Int],
comment_score_up: Option[Int],
post_score_down: Option[Int],
post_score_up: Option[Int],
created_on: Timestamp)
object Users extends Table[User]("users") {
lazy val database = Database.forDataSource(DB.getDataSource())
def id = column[UUID]("id", O.PrimaryKey) // This is the primary key column
def username = column[String]("username")
def password = column[String]("password")
def email = column[String]("email")
def comment_score_down = column[Option[Int]]("comment_score_down")
def comment_score_up = column[Option[Int]]("comment_score_up")
def post_score_down = column[Option[Int]]("post_score_down")
def post_score_up = column[Option[Int]]("post_score_up")
def created_on = column[Timestamp]("created_on")
implicit object UserWrites extends Writes[User] {
def writes(u: User) = Json.obj(
"id" -> JsString(u.id.toString),
"username" -> JsString(u.username),
"password" -> JsString(u.password),
"email" -> JsString(u.email),
"comment_score_down" -> JsNumber(u.comment_score_down.getOrElse(0).toInt),
"comment_score_up" -> JsNumber(u.comment_score_up.getOrElse(0).toInt),
"post_score_down" -> JsNumber(u.post_score_down.getOrElse(0).toInt),
"post_score_up" -> JsNumber(u.post_score_up.getOrElse(0).toInt),
"created_on" -> JsString(u.created_on.toString)
)
}
def * = id ~ username ~ password ~ email ~ comment_score_down ~
comment_score_up ~ post_score_down ~ post_score_up ~ created_on <>
(User, User.unapply _)
def findByPK(pk: UUID) =
for (entity <- Users if entity.id === pk) yield entity
def findAll = database withSession {
val users = for (u <- Users) yield u
Json.toJson(users.list)
}
}
It looks like your error has to do with your writes method. You have implemented a deserializer for User, but there isn't one implemented for a list of Users. The best advice I can provide is to create a writes method with the parameter type of List[User].

How to parametrize Scala Slick queries by WHERE clause conditions?

Assume these two simple queries:
def findById(id: Long): Option[Account] = database.withSession { implicit s: Session =>
val query = for (a <- Accounts if a.id === id) yield a.*
query.list.headOption
}
def findByUID(uid: String): Option[Account] = database.withSession { implicit s: Session =>
val query = for (a <- Accounts if a.uid === uid) yield a.*
query.list.headOption
}
I would like to rewrite it to remove the boilerplate duplication to something like this:
def findBy(criteria: ??? => Boolean): Option[Account] = database.withSession {
implicit s: Session =>
val query = for (a <- Accounts if criteria(a)) yield a.*
query.list.headOption
}
def findById(id: Long) = findBy(_.id === id)
def findByUID(uid: Long) = findBy(_.uid === uid)
I don't know how to achieve it for there are several implicit conversions involved in the for comprehension I haven't untangled yet. More specifically: what would be the type of ??? => Boolean in the findBy method?
EDIT
These are Account and Accounts classes:
case class Account(id: Option[Long], uid: String, nick: String)
object Accounts extends Table[Account]("account") {
def id = column[Option[Long]]("id")
def uid = column[String]("uid")
def nick = column[String]("nick")
def * = id.? ~ uid ~ nick <> (Account, Account.unapply _)
}
I have this helper Table:
abstract class MyTable[T](_schemaName: Option[String], _tableName: String) extends Table[T](_schemaName, _tableName) {
import scala.slick.lifted._
def equalBy[B: BaseTypeMapper]
(proj:this.type => Column[B]):B => Query[this.type,T] = { (str:B) =>
Query[this.type,T,this.type](this) where { x => proj(x) === str} }
}
Now you can do:
val q=someTable.equalBy(_.someColumn)
q(someValue)