Här är en lösning för Slick 3.2.3 (och lite bakgrund om mitt tillvägagångssätt):
Du kanske har märkt att du dynamiskt väljer kolumner är lätt så länge du kan anta en fast typ, t.ex.:
columnNames = List("col1", "col2")
tableQuery.map( r => columnNames.map(name => r.column[String](name)) )
Men om du provar en liknande metod
med en groupBy
operation kommer Slick att klaga på att den "does not know how to map the given types"
.
Så även om detta knappast är en elegant lösning, kan du åtminstone tillfredsställa Slicks typsäkerhet genom att statiskt definiera båda:
groupby
kolumntyp- Övre/nedre gräns för mängden
groupBy
kolumner
Ett enkelt sätt att implementera dessa två begränsningar är att återigen anta en fast typ och att förgrena koden för alla möjliga kvantiteter av groupBy
kolumner.
Här är hela den arbetande Scala REPL-sessionen för att ge 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)
Där detta blir fult är när du kan ha uppemot några groupBy
kolumner (dvs. har en separat if
gren för 10+ fall skulle bli monotont). Förhoppningsvis kommer någon att slå in och redigera det här svaret för hur man gömmer den där plattan bakom något syntaktiskt socker- eller abstraktionslager.