2
votes

I have a database for objects called Campaigns containing three fields :

  • Id (int, not nullable)
  • Version (int, not nullable)
  • Stuff (Text, nullable)

Let's call CampaignsRow the corresponding slick entity class

When I select line from Campaigns, I don't always need to read stuff, which contains big chunks of text.

However, I'd very much like to work in the codebase with the class CampaignsRow instead of a tuple, and so to be able to sometimes just drop the Stuff column, while retaining the original type

Basically, I'm trying to write the following function :

  //Force dropping the Stuff column from the current Query
  def smallCampaign(campaigns: Query[Campaigns, CampaignsRow, Seq]): Query[Campaigns, CampaignsRow, Seq] = {

    val smallCampaignQuery = campaigns.map {
      row => CampaignsRow(row.id, row.version , None : Option[String]) 
    } 
    smallCampaignQuery /* Fails because the type is now wrong, I have a Query[(Rep[Int], Rep[Int], Rep[Option[String]), (Int, Int, Option[String], Seq] */
  }

Any idea how to do this ? I suspect this has to do with Shape in slick, but I can't find a resource to start understanding this class, and the slick source code is proving too complex for me to follow.

2
so, I stumbled upon mapTo[], which is not far off (even though it's not exactly the same signature), but using it in my use case breaks the scala compiler in a weird way.C4stor

2 Answers

2
votes

You're actually already doing almost what you want in def *, the default mapping. You can use the same tools in the map method. Your two tools are mapTo and <>.

As you've found, there is the mapTo method which you can only use if your case class exactly matches the shape of the tuple, so if you wanted a special case class just for this purpose:

case class CampaignLite(id: Int, version: Int)

val smallCampaignQuery = campaigns.map {
  row => (row.id, row.version).mapTo[CampaignLite]
}

As you want to reuse your existing class, you can write your own convert functions instead of using the standard tupled and unapply and pass those to <>:

object CampaignRow {
  def tupleLite(t: (Int, Int)) = CampaignRow(t._1, t._2, None)
  def unapplyLite(c: CampaignRow) = Some((c.id, c.version))
}

val smallCampaignQuery = campaigns.map {
  row => (row.id, row.version) <> (CampaignRow.tupleLite, CampaignRow.unapplyLite)
}

This gives you the most flexibility, as you can do whatever you like in your convert functions, but it's a bit more wordy.

As row is an instance of the Campaigns table you could always define it there alongside *, if you need to use it regularly.

class Campaigns ... {
  ...
  def * = (id, version, stuff).mapTo[CampaignRow]
  def liteMapping = (id, version) <> (CampaignRow.tupleLite, CampaignRow.unapplyLite)
}

val liteCampaigns = campaigns.map(_.liteMapping)

Reference: Essential Slick 3, section 5.2.1

1
votes

If I understand your requirement correctly, you could consider making CampaignRow a case class that models your Campaigns table class by having Campaigns extend Table[CampaignRow] and providing the bidirectional mapping for the * projection:

case class CampaignRow(id: Int, version: Int, stuff: Option[String])

class Campaigns(tag: Tag) extends Table[CampaignRow](tag, "CAMPAIGNS") {
  // ...
  def * = (id, version, stuff) <> (CampaignRow.tupled, CampaignRow.unapply)
}

You should then be able to do something like below:

val campaigns = TableQuery[CampaignRow]

val smallCampaignQuery = campaigns.map( _.copy(stuff = None) )

For a relevant example, here's a Slick doc.