Aquí hay una solución para Slick 3.2.3 (y algunos antecedentes sobre mi enfoque):
Es posible que haya notado dinámicamente seleccionando Las columnas son fáciles siempre que pueda asumir un tipo fijo, por ejemplo:
columnNames = List("col1", "col2")
tableQuery.map( r => columnNames.map(name => r.column[String](name)) )
Pero si prueba un enfoque similar
con un groupBy
operación, Slick se quejará de que "does not know how to map the given types"
.
Entonces, si bien esta no es una solución elegante, al menos puede satisfacer la seguridad de tipos de Slick definiendo estáticamente ambos:
groupby
tipo de columna- Límite superior/inferior de la cantidad de
groupBy
columnas
Una forma simple de implementar estas dos restricciones es asumir nuevamente un tipo fijo y bifurcar el código para todas las cantidades posibles de groupBy
columnas.
Aquí está la sesión completa de Scala REPL para darle una idea:
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)
Donde esto se pone feo es cuando puedes tener más de unos pocos groupBy
columnas (es decir, tener un if
separado rama para más de 10 casos se volvería monótono). Con suerte, alguien se abalanzará y editará esta respuesta sobre cómo ocultar ese texto modelo detrás de una capa de azúcar sintáctica o de abstracción.