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:
- Having a different projection than the default projection which maps to a "light version of the
AuditResult
, e.g.AuditResultLight
that omits those columns - despite my best effort I couldn't make this work - I feel like this should be the right approach - once I had a "working" projection I still got a Slick error "No matching Shape found. Slick does not know how to map the given types" - Building a class hierarchy with an abstract
AuditResultTableBase
class and two classes that derive from it - one that adds the "heavy" columns and one without them, both with their respective default projection and case classes. This works nicely, but the approach seems wrong and requires a relatively large code change for such an easy thing. - Materializing tuples instead of case classes - this of course would work, but I want my data access layer to be strongly typed.
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?
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:
Then, you need to create a lifted version of the case class:
Also, you need an implicit object (the Shape) to tell slick how to map one into another:
Now, you can define a query that returns AuditResultLight (not exactly a projection, but as far as I understand it works similarly):
Then, you can define the function that returns failed audits in a light form:
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 forauditResultsLight
. I get syntax errors whenever I useauditResultsLight
and refer to a field ofAuditResultLight
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.