2
votes

When creating a table query, I would like to modify my select statement by mapping the default table query. However, I cannot find a way to map the value of a column and still map to my case class

case class MyRecord(id: Int, name: String, value: Int)

class MyTable(tag: Tag) extends Table[MyRecord](tag, "MYTABLE") {
    def id = column[Int]("id")
    def name = column[String]("name")
    def value = column[Int]("value")

    def * = (id, name, value) <> (MyRecord.tupled, MyRecord.unapply)
  }

lazy val tableQuery = TableQuery[MyTable]

I would like to trim the value of name with this function:

def trimLeading0: (Rep[String]) => Rep[String] = SimpleExpression.unary[String, String] {
    (str, queryBuilder) =>
      import slick.util.MacroSupport._
      import queryBuilder._
      b"TRIM(LEADING 0 FROM $str)"
  }

Now I am at a loss about what to do here:

val trimmedTableQuery: Query[MyTable, MyRecord, Seq] = tableQuery.map(s => ???)

I have tried mapping the Rep like I would do with a case class:

val trimmedTableQuery = tableQuery.map(s => s.copy(name = trimLeading0(s.name)))

This refuses to compile with value copy is not a member of MyTable

My current workaround is to use a custom function instead of MyRecord.tupled for the default projection:

def trimming(t: (Int, String, Int)) = MyRecord(t._1, t._2.dropWhile(_ == "0"), t._3)
def * = (id, name, value) <> (trimming, MyRecord.unapply)

Alternatively, I could map the returned result of the DBIOAction returning a tuple to the case class, which is much less elegant:

val action = tableQuery.map{ s => (s.id, trimLeading0(s.name), s.value)}.result
val futureTuples: Future[Seq[(Int, String, Int)]] = db.run(action)
val records = futureTuples map (s => s.map(MyRecord.tupled))

But how can I do it inside the map method while building the query? OR would it be better to change the def name column description?

1

1 Answers

2
votes

You can't mess with the default projection (i.e. def *) in MyTable as it needs to be symmetric. It's used for query and insert. But you can create a trimmedTableQuery based on a specialisation of MyTable with an overridden default projection. Then you can also have tableQuery based on the symmetric default projection. You will get an error if you try to do inserts based on the trimmedTableQuery (but you shouldn't need to do that, just use tableQuery for inserts).

lazy val tableQuery = TableQuery[MyTable]
lazy val trimmedTableQuery = new TableQuery(new MyTable(_) {
  override def * = (id, trimLeading0(name), value) <> (MyRecord.tupled, MyRecord.unapply)
})