How to optionally update field if parameters not None - scala

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

Related

in scala, how to create a list of immutable objects with references among them (when instances not know in advance)?

This answer https://stackoverflow.com/a/41717310/280393 explains how to create a list of immutable objects with references among them; However, the solution provided requires the objects to be known in advance.
How to achieve this when the objects are created on demand?
case class PersonA(id: Int, name: String, friends: Set[Int])
val john = PersonA(0, "john", Set(1,2))
val maria = PersonA(1, "maria", Set(0))
val georges = PersonA(2, "georges", Set(1))
val peopleA = Set(john, maria, georges)
case class PersonB(id: Int, name: String, friends: Set[PersonB])
// case class PersonB(id: Int, name: String, friends: () => Set[PersonB])
def convert(peopleA: Set[PersonA]): Set[PersonB] = ???
val peopleB = convert(peopleA)
println(peopleB)
println(peopleB.toList.map(_.friends.size))
peopleB.toList.map {
case PersonB(id, name, friends) => friends.size
}.foreach(println)
So, without modifying the implementation of case class PersonA and val peopleA, how to implement convert?
assuming that two PersonB instances are equal iff their id is equal,
one solution would be like this:
class PersonB(val id: Int, val name: String) {
var friends0: Set[PersonB] = _
def setFriends(friends: Set[PersonB]) {
require(friends0 == null)
friends0 = friends
}
def friends: Set[PersonB] = {
require(friends0 != null)
friends0
}
override def equals(that: Any): Boolean = that match {
case t: PersonB => t.id == id
case _ => false
}
override def hashCode(): Int = id.hashCode
override def toString = s"PersonB($id, $name, List(${friends.map(_.id).mkString(", ")}))"
}
object PersonB {
def apply(id: Int, name: String) = new PersonB(id, name)
def apply(id: Int, name: String, friends: Set[PersonB]): PersonB = {
val p = new PersonB(id, name)
p.setFriends(friends)
p
}
def unapply(p: PersonB): Option[(Int, String, Set[PersonB])] =
Some((p.id, p.name, p.friends))
}
def convert(peopleA: Set[PersonA]): Set[PersonB] = {
val peopleB = peopleA.map(p => new PersonB(p.id, p.name))
val peopleBMap = peopleB.map(p => (p.id, p)).toMap
peopleA.foreach(p =>
peopleBMap(p.id).setFriends(p.friends.map(peopleBMap))
)
peopleB
}
Is there a simpler way?
Udate Solution based on #sjrd answer:
class PersonB(val id: Int, val name: String, friends0: => Set[PersonB]) {
lazy val friends: Set[PersonB] = friends0
override def equals(that: Any): Boolean = that match {
case t: PersonB => t.id == id
case _ => false
}
override def hashCode(): Int = id.hashCode
override def toString = s"PersonB($id, $name, List(${friends.map(_.id).mkString(", ")}))"
}
object PersonB {
def apply(id: Int, name: String, friends: => Set[PersonB]): PersonB =
new PersonB(id, name, friends)
def unapply(p: PersonB): Option[(Int, String, Set[PersonB])] =
Some((p.id, p.name, p.friends))
}
def convert(peopleA: Set[PersonA]): Set[PersonB] = {
lazy val peopleB: Map[Int, PersonB] =
(for (PersonA(id, name, friendsIDs) <- peopleA)
yield (id, PersonB(id, name, friendsIDs.map(peopleB)))).toMap
peopleB.values.toSet
}
If you want to create immutable data you need to have a DAG / directed acyclic graph for creation order of you objects.
I do not think that you can do this since just the first 2 records have a cycle:
val john = PersonB(0, "john", Set(maria, ...))
val maria = PersonB(1, "maria", Set(john))
john is dependent on maria and maria on john.
So john is has to be created after maria and maria be created after john.
So you might have to compromise with your immutable data structures and not use case class but maybe an assign once instead.
Using this definition of PersonB:
case class PersonB(id: Int, name: String, friends: () => Set[PersonB])
you can write:
def convert(peopleA: Set[PersonA]): Set[PersonB] = {
lazy val peopleB: Map[Int, PersonB] =
(for (PersonA(id, name, friendsIDs) <- peopleA)
yield PersonB(id, name, () => friendsIDs.map(peopleB)).toMap
peopleB.values.toSet
}
Not tested, but something close to this should work.

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)
}

Optional nested mapped entity in Slick 2.1.0

I Understand that nested classes work fine with Slick and we have that working in our product. We are facing an issue when the nested class is optional. For instance, look at the following code
class EmpTable(tag: Tag) extends Table[Emp](tag, "emp") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def aId = column[Option[Int]]("a_id")
def location = column[Option[String]]("address")
def address = (aId, location) <> (Address.tupled, Address.unapply)
def * = (id, name, address.?) <> (Emp.tupled, Emp.unapply)
}
case class Emp(id: Int, name: String, address: Option[Address])
case class Address(aId: Option[Int], location: Option[String])
This code does not compile because address does not have method ? .
Is there an easy way to get this working?
I got a solution for this:
class EmpTable(tag: Tag) extends Table[Emp](tag, "emp") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def aId = column[Option[Int]]("a_id")
def location = column[Option[String]]("address")
def * = (id, name, address, auth) <> (Emp.tupled, Emp.unapply)
def address = (aId, location).<>[Option[Address], (Option[Int], Option[String])]({ ad: (Option[Int], Option[String]) =>
ad match {
case (Some(id), Some(loc)) => Some(Address(id, loc))
case _ => None
}
}, {
adObj: Option[Address] =>
adObj match {
case add: Option[Address] => Some((add.map(_.aId), add.map(_.location)))
}
})

SLICK How to define bidirectional one-to-many relationship for use in case class

I am using SLICK 1.0.0-RC2. I have defined the following two tables Directorate and ServiceArea where Directorate has a one to many relationship with ServiceArea
case class Directorate(dirCode: String, name: String)
object Directorates extends Table[Directorate]("DIRECTORATES") {
def dirCode = column[String]("DIRECTORATE_CODE", O.PrimaryKey)
def name = column[String]("NAME")
def * = dirCode ~ name <> (Directorate, Directorate.unapply _)
}
case class ServiceArea(areaCode: String, dirCode: String, name: String)
object ServiceAreas extends Table[ServiceArea]("SERVICE_AREAS") {
def areaCode = column[String]("AREAE_CODE", O.PrimaryKey)
def dirCode = column[String]("DIRECTORATE_CODE")
def name = column[String]("NAME")
def directorate = foreignKey("DIR_FK", dirCode, Directorates)(_.dirCode)
def * = areaCode ~ dirCode ~ name <> (ServiceArea, ServiceArea.unapply _)
}
To make the Directorate case class useful in my Play application form I am trying to redefine the Directorate case class to have a Seq of ServiceAreas that are related to that Directorate.
case class Directorate(dirCode: String, name: String, serviceAreas: Seq[ServiceArea])
My problem is now with the Directorate table projection. I have attempted to create a method in Directorates:
def serviceAreas = (for { a <- ServiceAreas
if (a.dirCode === dirCode)
} yield (a)).list map {
case t: ServiceArea => t
}
so that I can try something like
def * = dirCode ~ name ~ serviceAreas <> (Directorate, Directorate.unapply _)
but this cannot not work as serviceAreas only goes one way.
It seems reasonable to me that for the Directorate case class to be a useful domain object that it should be able contain the related ServiceAreas.
I'm wondering how I should traverse the inverse relationship so that Directorate table projection will work.
I'm sure there is a more elegant solution, but this should do the trick:
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession
object SlickExperiments2 {
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
(Directorates.ddl ++ ServiceAreas.ddl).create
case class Directorate(dirCode: String, name: String) {
def serviceAreas: Seq[ServiceArea] = (for {
a <- ServiceAreas
if (a.dirCode === dirCode)
} yield (a)).list
}
object Directorates extends Table[Directorate]("DIRECTORATES") {
def dirCode = column[String]("DIRECTORATE_CODE", O.PrimaryKey)
def name = column[String]("NAME")
def * = dirCode ~ name <> (Directorate, Directorate.unapply _)
}
case class ServiceArea(areaCode: String, dirCode: String, name: String)
object ServiceAreas extends Table[ServiceArea]("SERVICE_AREAS") {
def areaCode = column[String]("AREAE_CODE", O.PrimaryKey)
def dirCode = column[String]("DIRECTORATE_CODE")
def name = column[String]("NAME")
def directorate = foreignKey("DIR_FK", dirCode, Directorates)(_.dirCode)
def * = areaCode ~ dirCode ~ name <> (ServiceArea, ServiceArea.unapply _)
}
Directorates.insert(Directorate("Dircode", "Dirname"))
ServiceAreas.insertAll(ServiceArea("a", "Dircode", "A"), ServiceArea("b", "Dircode", "B"))
val sa = (for{
d <- Directorates
} yield d).list map { case t: Directorate => t.serviceAreas}
println(sa)
}
//> List(List(ServiceArea(a,Dircode,A), ServiceArea(b,Dircode,B)))
}