BrokenGlass
BrokenGlass

Reputation: 161002

Slick 3.1 - Retrieving subset of columns as a case class

I'm working with Slick 3.1.1 and the problem is that in some cases I want to omit some columns that are fairly heavy and still materialize that subset of columns as a case class.

Consider the following table definition:

class AuditResultTable(tag: Tag) extends Table[AuditResult](tag, AuditResultTableName) {
    def auditResultId: Rep[Long] = column[Long]("AuditResultId", O.PrimaryKey, O.AutoInc)
    def processorId: Rep[Long] = column[Long]("ProcessorId")
    def dispatchedTimestamp: Rep[Timestamp] = column[Timestamp]("DispatchedTimestamp", O.SqlType("timestamp(2)"))
    def SystemAOutput: Rep[Array[Byte]] = column[Array[Byte]]("SystemAOutput", O.SqlType("LONGBLOB"))
    def SystemBOutput: Rep[Array[Byte]]  = column[Array[Byte]]("SystemBOutput", O.SqlType("LONGBLOB"))
    def isSuccessful: Rep[Boolean] = column[Boolean]("IsSuccessful")


def * : ProvenShape[AuditResult] = (processorId, dispatchedTimestamp, systemAOutput, systemBOutput, isSuccessful, auditResultId) <>
  (AuditResult.tupled, AuditResult.unapply) 

}  

val auditResults = TableQuery[AuditResultTable]  

The corresponding case class:

case class AuditResult (
   ProcessorId: Long,
   DispatchedTimestamp: Timestamp,
   SystemAOutput: Array[Byte],
   SystemBOutput: Array[Byte],
   IsSuccessful: Boolean,
   AuditResultId: Long = 0L
 )

And finally the data access query:

def getRecentFailedAuditsQuery(): Query[AuditResultTable, AuditResult, Seq] = {
  auditResults.filterNot(r => r.isSuccessful)
}

I've considered and looked into options presented in this (outdated) answer and others:

What is the idiomatic / best practice for Slick 3.1 for this problem? Can I use a custom projection for this and if so what would that look like for this particular example / query with SystemAOutput and SystemBOutput being the heavy columns I want to omit?

Upvotes: 17

Views: 1771

Answers (1)

Wojciech Jurczyk
Wojciech Jurczyk

Reputation: 470

I had a similar problem! You have to define the Shape! With help of the documentation I managed to make the approach with a "light" case class work.

First, define the simpler class:

case class AuditResultLight(
  ProcessorId: Long,
  DispatchedTimestamp: Timestamp,
  IsSuccessful: Boolean,
  AuditResultId: Long = 0L
)

Then, you need to create a lifted version of the case class:

case class AuditResultLightLifted(
  ProcessorId: Rep[Long],
  DispatchedTimestamp: Rep[Timestamp],
  IsSuccessful: Rep[Boolean],
  AuditResultId: Rep[Long]
)

Also, you need an implicit object (the Shape) to tell slick how to map one into another:

implicit object AuditResultLightShape 
  extends CaseClassShape(AuditResultLightLifted.tupled, AuditResultLight.tupled)

Now, you can define a query that returns AuditResultLight (not exactly a projection, but as far as I understand it works similarly):

val auditResultsLight = auditResults.map(r => AuditResultLightLifted(r.ProcessorId, r.DispatchedTimestamp, r.IsSuccessful, r.AuditResultId))  

Then, you can define the function that returns failed audits in a light form:

def getRecentFailedAuditsQuery(): Query[AuditResultTable, AuditResultLight, Seq] = {
  auditResultsLight.filterNot(r => r.isSuccessful)
}

A gist with the code: https://gist.github.com/wjur/93712a51d392d181ab7fc2408e4ce48b

The code compiles and executes, but in my case, the issue is that my IDE (IntelliJ) reports Query[Nothing, Nothing, scala.Seq] type for auditResultsLight. I get syntax errors whenever I use auditResultsLight and refer to a field of AuditResultLight in a query. However, because of that, in the end, I decided to use the second approach you suggested (the one with an abstract table). Almost the same amount of code, but with IDE support.

Upvotes: 9

Related Questions