0
votes

I can do this mapping a case class to a slick database table:-

case class SomeTimeStamp(id: Option[Long], timestamp: java.sql.Timestamp )

class TimeStampTable(tag: Tag) extends Table[SomeTimeStamp](tag, "TSTAMP_TABLE") {
  def id    = column[Long]("ID", O.AutoInc, O.PrimaryKey)
  def time  = column[java.sql.Timestamp]("TIME")

  def * = (id.?, time) <> (SomeTimeStamp.tupled, SomeTimeStamp.unapply)
}

The case class has fields that are converted to database types by default by slick, so all is well.

But this doesn't work, if a field in my case class is not a default database type. Slick allows me to provide an implicit conversion to and from a valid Database type using the MappedColumnType, so I tried this, notice that I now use the Java LocalDatetime thather than sql Timestamp.

case class SomeLocalDate(id: Option[Long], timestamp: java.time.LocalDateTime )

class LocalDateTable(tag: Tag) extends Table[SomeLocalDate](tag, "TLDATE_TABLE") {
import LocalDateTable._
  def id    = column[Long]("ID", O.AutoInc, O.PrimaryKey)
  def time  = column[java.time.LocalDateTime]("TIME")

  def * = (id.?, time) <> (SomeLocalDate.tupled, SomeLocalDate.unapply)
}

object LocalDateTable {
  implicit val localDateTimeToTimestamp = MappedColumnType.base[LocalDateTime, Timestamp](
    { Timestamp.valueOf(_) } ,
    { ts => ts.toLocalDateTime }
  )
}

I have added and implicit mapped column I still get a compile error in the projection and the implicit resolution The compile error is:-

[info] Compiling 1 Scala source to ...target/scala-2.11/classes...
[error] db/Tables.scala:92: could not find implicit value for parameter tt: slick.ast.TypedType[java.time.LocalDateTime]
[error]   def time: Rep[LocalDateTime]  = column[java.time.LocalDateTime]("TIME")

If I modify the def time and add a type ascription I get a different error, see below.

case class SomeLocalDate(id: Option[Long], timestamp: java.time.LocalDateTime )

class LocalDateTable(tag: Tag) extends Table[SomeLocalDate](tag, "TLDATE_TABLE") {
import LocalDateTable._
  def id    = column[Long]("ID", O.AutoInc, O.PrimaryKey)
  def time: Rep[LocalDateTime]  = column[java.time.LocalDateTime]("TIME")

  def * = (id.?, time) <> (SomeLocalDate.tupled, SomeLocalDate.unapply)
}

object LocalDateTable {
  implicit val localDateTimeToTimestamp = MappedColumnType.base[LocalDateTime, Timestamp](
    { Timestamp.valueOf(_) } ,
    { ts => ts.toLocalDateTime }
  )
}

gives this error:-

[info] Compiling 1 Scala source to ...target/scala-2.11/classes...
[error] db/Tables.scala:92: could not find implicit value for parameter tt: slick.ast.TypedType[java.time.LocalDateTime]
[error]   def time: Rep[LocalDateTime]  = column[java.time.LocalDateTime]("TIME")
[error]                                                                  ^
[error]db/Tables.scala:94: No matching Shape found.
[error] Slick does not know how to map the given types.
[error] Possible causes: T in Table[T] does not match your * projection. Or you use an unsupported type in a Query (e.g. scala List).
[error]   Required level: slick.lifted.FlatShapeLevel
[error]      Source type: (slick.lifted.Rep[Option[Long]], slick.lifted.Rep[java.time.LocalDateTime])
[error]    Unpacked type: (Option[Long], java.time.LocalDateTime)
[error]      Packed type: Any
[error]   def * = (id.?, time) <> (SomeLocalDate.tupled, SomeLocalDate.unapply)
[error]                        ^
[error] two errors found

What do I have to do to map a case class to a table if the case class does not have the default supported database types (eg LocalDateTime) ?

cheers

1
What version of Slick are you using? - Paweł Jurczenko
using 3.1.1 , I think I have found the answer see below, it is a scala implicit resolution foible. thanks for responding. - Karl

1 Answers

1
votes

After days of struggling I found the answer :-/

The problem was that the implicit MappedColumnType was declared after the Table class. for some reason this upset the implicit resolution even though I imported it above the table.

I found the answer when I removed the case class and simply used Tuples. When I did that the following error was presented

[info] Compiling 1 Scala source to target/scala-2.11/classes...
[error] /db/Tables.scala:100: could not find implicit value for parameter tt: slick.ast.TypedType[java.time.LocalDateTime]
[error]   def time: Rep[LocalDateTime]  = column[java.time.LocalDateTime]("TIME")
[error]                                                                  ^
[error] /db/Tables.scala:102: type mismatch;
[error]  found   : (slick.lifted.Rep[Option[Long]], slick.driver.H2Driver.api.Rep[java.time.LocalDateTime])
[error]     (which expands to)  (slick.lifted.Rep[Option[Long]], slick.lifted.Rep[java.time.LocalDateTime])
[error]  required: slick.lifted.ProvenShape[(Option[Long], java.time.LocalDateTime)]
[error]  Note: implicit value localDateTimeToTimestamp is not applicable here because it comes after the application point and it lacks an explicit result type
[error]   def * : ProvenShape[(Option[Long], LocalDateTime)] = (id.?, time)
[error]                                                        ^

So to be clear the solution was to put the implicit conversion above the table definition if in the same file or put it in another file and import it, see working solution below. Thanks.

case class SomeLocalDate(id: Option[Long], timestamp: java.time.LocalDateTime )

object LocalDateTableConversions {
  implicit val localDateTimeToTimestamp = MappedColumnType.base[LocalDateTime, Timestamp](
    { Timestamp.valueOf(_) } ,
    { ts => ts.toLocalDateTime }
  )
}
import LocalDateTableConversions._

class LocalDateTable(tag: Tag) extends Table[SomeLocalDate](tag, "TLDATE_TABLE") {
  def id    = column[Long]("ID", O.AutoInc, O.PrimaryKey)
  def time: Rep[LocalDateTime]  = column[java.time.LocalDateTime]("TIME")

  def * = (id.?, time) <> (SomeLocalDate.tupled, SomeLocalDate.unapply)
}