Friday, August 17, 2018

Silently dropping a nullable database column with slick

Leave a Comment

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 Answers

Answers 1

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

Answers 2

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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment