Her er en løsning til Slick 3.2.3 (og lidt baggrund om min tilgang):
Du har måske bemærket dynamisk valg kolonner er let, så længe du kan antage en fast type, f.eks.:
columnNames = List("col1", "col2")
tableQuery.map( r => columnNames.map(name => r.column[String](name)) )
Men hvis du prøver en lignende tilgang
med en groupBy
operation, vil Slick klage over, at den "does not know how to map the given types"
.
Så selvom dette næppe er en elegant løsning, kan du i det mindste tilfredsstille Slicks typesikkerhed ved statisk at definere begge dele:
groupby
kolonnetype- Øvre/nedre grænse for mængden af
groupBy
kolonner
En simpel måde at implementere disse to begrænsninger på er igen at antage en fast type og at forgrene koden for alle mulige mængder af groupBy
kolonner.
Her er hele den arbejdende Scala REPL-session for at give dig en idé:
import java.io.File
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import slick.jdbc.H2Profile.api._
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
val confPath = getClass.getResource("/application.conf")
val config = ConfigFactory.parseFile(new File(confPath.getPath)).resolve()
val db = Database.forConfig("slick.db", config)
implicit val system = ActorSystem("testSystem")
implicit val executionContext = system.dispatcher
case class AnyData(a: String, b: String)
case class GroupByFields(a: Option[String], b: Option[String])
class AnyTable(tag: Tag) extends Table[AnyData](tag, "macro"){
def a = column[String]("a")
def b = column[String]("b")
def * = (a, b) <> ((AnyData.apply _).tupled, AnyData.unapply)
}
val table = TableQuery[AnyTable]
def groupByDynamically(groupBys: Seq[String]): DBIO[Seq[GroupByFields]] = {
// ensures columns are returned in the right order
def selectGroups(g: Map[String, Rep[Option[String]]]) = {
(g.getOrElse("a", Rep.None[String]), g.getOrElse("b", Rep.None[String])).mapTo[GroupByFields]
}
val grouped = if (groupBys.lengthCompare(2) == 0) {
table
.groupBy( cols => (cols.column[String](groupBys(0)), cols.column[String](groupBys(1))) )
.map{ case (groups, _) => selectGroups(Map(groupBys(0) -> Rep.Some(groups._1), groupBys(1) -> Rep.Some(groups._2))) }
}
else {
// there should always be at least one group by specified
table
.groupBy(cols => cols.column[String](groupBys.head))
.map{ case (groups, _) => selectGroups(Map(groupBys.head -> Rep.Some(groups))) }
}
grouped.result
}
val actions = for {
_ <- table.schema.create
_ <- table.map(a => (a.column[String]("a"), a.column[String]("b"))) += ("a1", "b1")
_ <- table.map(a => (a.column[String]("a"), a.column[String]("b"))) += ("a2", "b2")
_ <- table.map(a => (a.column[String]("a"), a.column[String]("b"))) += ("a2", "b3")
queryResult <- groupByDynamically(Seq("b", "a"))
} yield queryResult
val result: Future[Seq[GroupByFields]] = db.run(actions.transactionally)
result.foreach(println)
Await.ready(result, Duration.Inf)
Hvor dette bliver grimt, er når du kan have op mod et par groupBy
kolonner (dvs. have en separat if
filial for 10+ tilfælde ville blive ensformig). Forhåbentlig vil nogen snuppe ind og redigere dette svar for, hvordan man skjuler den kedelplade bag et eller andet syntaktisk sukker- eller abstraktionslag.