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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With