Suggest 'MultiColumn' Type
See original GitHub issueCurrently, when we want to create a mapping for a Scala type T
to Slick, we have two options which depends on the number of fields the underlying representation in the database will represent (for that type T
)
- If the type
T
can be sanely represented as a single field in a database, we generally use aMappedColumnType[A,T]
which allows us to go from A to T. Obvious use case scenario is doing something like mapping aJodaTime
DateTime
to a SQL Date - If the type
T
can’t be sanely represented as a single field, but instead 2 or more fields, we use custom extractors/constructors in the*=
table definition (which is usually represented as aclass
extending theTable
trait). An example might be representing Scala’sEither
Type, we would want to represent this as 2 Not Null database fields
The problem with scenario 2, is that we haven’t really created a “proper” mapping, and this issue becomes obvious when you only want to update a select number of fields in a table. Lets say you have a User
table, and you want to update only a certain number of fields, you would probably do something like this
val q = Users.filter(u => u.id === 1).map(user => user.firstName)
q.update("Bob")
However, lets assume that the User
table has some custom type (which we represented in the way that point 2 describes), in this, lets assume the type is a Either[Int,String]
which is called ‘someField’. We first have to represent this in table form, so we might do something like this
def someField_Left:Column[Option[Int]]("some_field_LEFT")
def someField_Right:Column[Option[String]]("some_field_RIGHT")
This issue is, if we want to update this field, like we did previously, we have to do something like this
val updateValue:Either[Int,String] // Assume this field is already defined
val (left,right) = updateValue match {
case Left(i) => (Option(i),None)
case Right(s) => (None,Option(s))
}
val q = Users.filter(u => u.id === 1).map(user => user.firstName,user.someField_Left,someField_Right)
q.update(("Bob",left,right))
When ideally, we should be doing this
val q = Users.filter(u => u.id === 1).map(user => user.firstName,user.someField)
q.update(("Bob",updateValue))
This issue occurs because we can’t (sanely) represent the Either[A,B]
as a single column type, even though throughout our whole application code, that is how we want to represent it as. The proposal? Some way to define a MultiColumn[T]
. A MultiColumn
works the exact same way the standard Column
would, so you can extract it as a single field when using map
on a TableQuery
. The difference is, you would have to specify how to represent it as multiple fields, maybe something like this
class EitherRep extends MultiColumnRepresentation[Either[Int,String]] {
def left:ColumnAbstract[Option[Int]](this.originalFieldName + "_LEFT")
def right:ColumnAbstract[Option[String]](this.originalFieldName + "_RIGHT")
def * = (left,right) <> ((constructEither _).tupled,extractEither)
private def constructEither(left:Option[Int],right:Option[String]) = {
val either = (left,right) match {
case (Some(i),None) => Left(i)
case (None,Some(s)) => Right(s)
case _ => throw SomeError // This shouldn't happen, throw some error
}
either
}
private def extractEither(either:Either[Int,String]) = {
val (left,right) = either match {
case Left(i) => (Option(i), None)
case Right(s) => (None,Option(s))
}
Option((left,right))
}
}
// This is what the field would look like in our actual `Table` definition
def someField:MultiColumn[Either[Int,String]]("some_field",new EitherRep)
// So we can do stuff like this
val q = Users.filter(u => u.id === 1).map(user => user.firstName,user.someField)
// Instead of having to do this
val q = Users.filter(u => u.id === 1).map(user => user.firstName,user.someField_Left,someField_Right)
Note that in the actual database, we would still have 2 columns representing the Either
, but in our application, for all intents and purposes, we treat it as a single “field”.
Issue Analytics
- State:
- Created 9 years ago
- Comments:10 (7 by maintainers)
Im giving this a bump, does anyone know if this is any easier in Slick 3.0?
Unless we can think of a better solution, Shapes are the way to go (and it took us long enough to come up with those, so I wouldn’t hold my breath for something better)