Hier ist eine Problemumgehung für Slick 3.2.3 (und einige Hintergrundinformationen zu meinem Ansatz):
Sie haben vielleicht bemerkt, dass dynamisch ausgewählt wird Spalten ist einfach, solange Sie einen festen Typ annehmen können, z. (Name)) )
Aber wenn Sie einen ähnlichen Ansatz versuchen
mit einem groupBy
Operation, wird sich Slick darüber beschweren, dass es "nicht weiß, wie man die angegebenen Typen abbildet"
.
Obwohl dies keine elegante Lösung ist, können Sie zumindest die Typsicherheit von Slick erfüllen, indem Sie beide statisch definieren:
gruppieren nach
Spaltentyp- Obere/untere Grenze der Menge von
groupBy
Spalten
Eine einfache Möglichkeit, diese beiden Einschränkungen zu implementieren, besteht darin, wieder einen festen Typ anzunehmen und den Code für alle möglichen Mengen von groupBy
zu verzweigen Spalten.
Hier ist die voll funktionsfähige Scala REPL-Sitzung, um Ihnen eine Vorstellung zu geben:
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)
Wo dies hässlich wird, wenn Sie mehr als ein paar groupBy
haben können Spalten (d.h. mit einem separaten if
Verzweigung für 10+ Fälle würde eintönig werden). Hoffentlich kommt jemand herein und bearbeitet diese Antwort, um zu erfahren, wie man diese Boilerplate hinter einer syntaktischen Zucker- oder Abstraktionsschicht verbirgt.