Optional nested mapped entity in Slick 2.1.0 - scala

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

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

Slick join two tables and get result of both

I have a Many to Many relationship setup like this:
Person <-> PersonField <-> Field
Now I want to query not only all the fields of a Person (I can do that), but a joined version of PersonField with Field of a Person. (I want to query/retrieve the Information in the Pivot/Intermediate Table "PersonField" as well!)
Person:
case class Person(id: Long, name: String)
{
def fields =
{
person <- Persons.all.filter(_.id === this.id)
field <- person.fields
} yield field
}
class Persons(tag: Tag) extends Table[Person](tag, "persons")
{
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name) <> (Person.tupled, Person.unapply)
def fields = PersonFields.all.filter(_.personID === id).flatMap(_.fieldFK)
}
object Persons
{
lazy val all = TableQuery[Persons]
}
Field:
case class Field(id: Long, name: String, description: Option[String])
class Fields(tag: Tag) extends Table[Field](tag, "fields")
{
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def description = column[Option[String]]("description")
def * = (id, name, description) <> (Field.tupled, Field.unapply)
}
object Fields
{
lazy val all = TableQuery[Fields]
}
PersonField:
case class PersonField(id: Long, personID: Long, fieldID: Long, value: String)
// TODO add constraint to make (personID, fieldID) unique
class PersonFields(tag: Tag) extends Table[PersonField](tag, "person_field")
{
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def personID = column[Long]("person_id")
def fieldID = column[Long]("field_id")
def value = column[String]("value")
def * = (id, personID, fieldID, value) <> (PersonField.tupled, PersonField.unapply)
def personFK = foreignKey("person_fk", personID, Persons.all)(_.id)
def fieldFK = foreignKey("field_fk", fieldID, Fields.all)(_.id)
}
object PersonFields
{
lazy val all = TableQuery[PersonFields]
}
Now to query all the fields of a Person I have a little helper-class:
def getFields(p: Person): Future[Seq[Field]] =
{
val query = p.fields
db.run(query.result)
}
So I can do
val personX ...
personX.onSuccess
{
case p: Person =>
{
val fields = helper.getFields(p)
fields.onSuccess
{
case f: Seq[Field] => f foreach println
}
}
}
Now each field of personX gets printed to the console. Works like a charm.
The thing is, I want to get the PersonField as well (with the Field)!
So I tried the following changes (among others that didn't work, which I can't remember)
In Person:
def fields =
{
for
{
person <- Persons.all.filter(_.id === this.id)
field <- person.fields join Fields.all on (_.fieldID === _.id)
} yield field
}
In PersonS
def fields = PersonFields.all.filter(_.personID === id) // No flatMap here!
then getFields(p: Person) looks like this:
def getFields(p: Person): Future[Seq[(PersonField, Field)]]
but
personX.onSuccess
{
case p: Person =>
{
val fields = helper.getFields(p)
fields.onSuccess
{
case f: Seq[(PersonField, Field)] => f map(f => println(f._1)}
}
}
}
gives me nothing, so I guess my join must be wrong. But what exactly am I doing wrong?
You can join all three, then yield the result
for {
((personField, person), field) <- PersonFields.all join Persons.all on (_.personId === _.id) join Fields.all on (_._1.fieldId === _.id)
if person.id === this.id
} yield (personField, person, field)
(I am not sure I got exactly what you were trying to get out of the query, so you can just edit the yield part )

Custom mapping to nested case class structure in Slick (more than 22 columns)

I'm trying to map a DB row with more than 22 columns to a case class tree.
I'd rather not using HList as I don't want to work with that API, and also for some exponential compilation time feedbacks that I've read somewhere...
I have read this thread answered by Stefan Zeiger: How can I handle a > 22 column table with Slick using nested tuples or HLists?
I've seen this test which shows how to define a custom mapping function and I'd like to do that:
https://github.com/slick/slick/blob/2.1/slick-testkit/src/main/scala/com/typesafe/slick/testkit/tests/JdbcMapperTest.scala#L129-141
def * = (
id,
(p1i1, p1i2, p1i3, p1i4, p1i5, p1i6),
(p2i1, p2i2, p2i3, p2i4, p2i5, p2i6),
(p3i1, p3i2, p3i3, p3i4, p3i5, p3i6),
(p4i1, p4i2, p4i3, p4i4, p4i5, p4i6)
).shaped <> ({ case (id, p1, p2, p3, p4) =>
// We could do this without .shaped but then we'd have to write a type annotation for the parameters
Whole(id, Part.tupled.apply(p1), Part.tupled.apply(p2), Part.tupled.apply(p3), Part.tupled.apply(p4))
}, { w: Whole =>
def f(p: Part) = Part.unapply(p).get
Some((w.id, f(w.p1), f(w.p2), f(w.p3), f(w.p4)))
})
The problem is that I can't make it!
I've tried by smaller steps.
class UserTable(tag: Tag) extends TableWithId[User](tag,"USER") {
override def id = column[String]("id", O.PrimaryKey)
def role = column[UserRole.Value]("role", O.NotNull)
def login = column[String]("login", O.NotNull)
def password = column[String]("password", O.NotNull)
def firstName = column[String]("first_name", O.NotNull)
def lastName = column[String]("last_name", O.NotNull)
//
def * = (id, role, login, password, firstName, lastName) <> (User.tupled,User.unapply)
//
def login_index = index("idx_user_login", login, unique = true)
}
It seems that when I call
(id, (firstName, lastName)).shaped
The type is ShapedValue[(Column[String], (Column[String], Column[String])), Nothing]
While this one seems to work fine
(id, firstName, lastName).shaped
The U type parameter is not Nothing but as expected (String, String, String)
I don't really understand how all the Slick internals are working. Can someone explain me why I can't make my code work? Is there a missing import or something?
I guess I need to get a value of type
ShapedValue[(Column[String], (Column[String], Column[String])), (String, (String, String))]
but I don't know why it returns me Nothing and don't really understand where these implicit Shape parameters come from...
What I want is just to be able to easily split my column into 2 case classes
Thanks
Also, have the same problem with the 22 columns limitation, the test case helps very much. Not sure why the example code not working for you, the following code works fine for me,
case class UserRole(var role: String, var extra: String)
case class UserInfo(var login: String, var password: String, var firstName: String, var lastName: String)
case class User(id: Option[String], var info: UserInfo, var role: UserRole)
class UserTable(tag: Tag) extends Table[User](tag, "USER") {
def id = column[String]("id", O.PrimaryKey)
def role = column[String]("role", O.NotNull)
def extra = column[String]("extra", O.NotNull)
def login = column[String]("login", O.NotNull)
def password = column[String]("password", O.NotNull)
def firstName = column[String]("first_name", O.NotNull)
def lastName = column[String]("last_name", O.NotNull)
/** Projection */
def * = (
id,
(login, password, firstName, lastName),
(role, extra)
).shaped <> (
{ case (id, userInfo, userRole) =>
User(Option[id], UserInfo.tupled.apply(userInfo), UserRole.tupled.apply(userRole))
},
{ u: User =>
def f1(p: UserInfo) = UserInfo.unapply(p).get
def f2(p: UserRole) = UserRole.unapply(p).get
Some((u.id.get, f1(u.info), f2(u.role)))
})
def login_index = index("id_user_login", login, unique = true)
}
As Izongren answered it works fine, but it can be hard to write such code as it's annoying to work with very long tuples... I decided to do it "step by step" and always providing types explicitly to avoid type inference problemes and now it works fine.
case class Patient(
id: String = java.util.UUID.randomUUID().toString,
companyScopeId: String,
assignedToUserId: Option[String] = None,
info: PatientInfo
) extends ModelWithId
case class PatientInfo(
firstName: Option[String] = None,
lastName: Option[String] = None,
gender: Option[Gender.Value] = None,
alias: Option[String] = None,
street: Option[String] = None,
city: Option[String] = None,
postalCode: Option[String] = None,
phone: Option[String] = None,
mobilePhone: Option[String] = None,
email: Option[String] = None,
age: Option[AgeRange.Value] = None,
companySeniority: Option[CompanySeniorityRange.Value] = None,
employmentContract: Option[EmploymentContract.Value] = None,
socialStatus: Option[SocialStatus.Value] = None,
jobTitle: Option[String] = None
)
class PatientTable(tag: Tag) extends TableWithId[Patient](tag,"PATIENT") {
override def id = column[String]("id", O.PrimaryKey)
def companyScopeId = column[String]("company_scope_id", O.NotNull)
def assignedToUserId = column[Option[String]]("assigned_to_user_id", O.Nullable)
def firstName = column[Option[String]]("first_name", O.Nullable)
def lastName = column[Option[String]]("last_name", O.Nullable)
def gender = column[Option[Gender.Value]]("gender", O.Nullable)
def alias = column[Option[String]]("alias", O.Nullable)
def street = column[Option[String]]("street", O.Nullable)
def city = column[Option[String]]("city", O.Nullable)
def postalCode = column[Option[String]]("postal_code", O.Nullable)
def phone = column[Option[String]]("phone", O.Nullable)
def mobilePhone = column[Option[String]]("mobile_phone", O.Nullable)
def email = column[Option[String]]("email", O.Nullable)
def age = column[Option[AgeRange.Value]]("age", O.Nullable)
def companySeniority = column[Option[CompanySeniorityRange.Value]]("company_seniority", O.Nullable)
def employmentContract = column[Option[EmploymentContract.Value]]("employment_contract", O.Nullable)
def socialStatus = column[Option[SocialStatus.Value]]("social_status", O.Nullable)
def jobTitle = column[Option[String]]("job_title", O.Nullable)
def role = column[Option[String]]("role", O.Nullable)
private type PatientInfoTupleType = (Option[String], Option[String], Option[Gender.Value], Option[String], Option[String], Option[String], Option[String], Option[String], Option[String], Option[String], Option[AgeRange.Value], Option[CompanySeniorityRange.Value], Option[EmploymentContract.Value], Option[SocialStatus.Value], Option[String])
private type PatientTupleType = (String, String, Option[String], PatientInfoTupleType)
//
private val patientShapedValue = (id, companyScopeId, assignedToUserId,
(
firstName, lastName, gender, alias, street, city, postalCode,
phone, mobilePhone,email, age, companySeniority, employmentContract, socialStatus, jobTitle
)
).shaped[PatientTupleType]
//
private val toModel: PatientTupleType => Patient = { patientTuple =>
Patient(
id = patientTuple._1,
companyScopeId = patientTuple._2,
assignedToUserId = patientTuple._3,
info = PatientInfo.tupled.apply(patientTuple._4)
)
}
private val toTuple: Patient => Option[PatientTupleType] = { patient =>
Some {
(
patient.id,
patient.companyScopeId,
patient.assignedToUserId,
(PatientInfo.unapply(patient.info).get)
)
}
}
def * = patientShapedValue <> (toModel,toTuple)
}
Also, you can use this way
class UserTable(tag: Tag) extends TableWithId[User](tag,"USER") {
def id = column[String]("id", O.PrimaryKey)
def role = column[String]("role", O.NotNull)
def extra = column[String]("extra", O.NotNull)
def login = column[String]("login", O.NotNull)
def password = column[String]("password", O.NotNull)
def firstName = column[String]("first_name", O.NotNull)
def lastName = column[String]("last_name", O.NotNull)
def loginMap = (login, password, firstName, lastName) <> (UserInfo.tupled, UserInfo.unapply)
def roleMap = (role, extra) <> (Role.tupled, Role.unapply)
override def * = (id, roleMap, loginMap) <> (User.tupled,User.unapply)
def login_index = index("idx_user_login", login, unique = true)
}

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