Dynamically generate mappings between columns and reps.

285 views
Skip to first unread message

Emiliano Busiello

unread,
Jun 30, 2016, 4:01:28 AM6/30/16
to Slick / ScalaQuery
I'm trying to dynamically add sorting to my queries, what I'd like to do on an high level is to translate strings to pieces of slick queries.

case class MyTableElem(id: Long, created: DateTime, ...) // contains more than 22 fields

class MyTable(tag: Tag) extends Table[MyTableElem](tag, "my_table") {
 
def id = column[Long]("id")
  def created = column[Long]("created")
 
...

  lazy val hlist = (id :: created :: ... :: HNil)

 
def * = hlist.shaped <> (MyTableElem(...), {el: MyTableElm => Option((el.id, el.created, ...))})
}

object DbTables {
  val myTable
= TableQuery[MyTable]
}


case class
SortingRequest(rep: Rep[_], direction: slick.ast.Ordering)

// store column names.
val myTableColumns = DbTables.myTable.baseTableRow.create_*.map(_.name).toList

def mapSortToRequest(s: Option[String]): MyTable => SortingRequest = { myTable =>
 
val (sort, direction) = s.map(str => str.toList match {
   
case prefix :: tail if prefix == '-' => (tail.mkString(""), slick.ast.Ordering(slick.ast.Ordering.Asc))
   
case prefix :: tail                  => (str, slick.ast.Ordering(slick.ast.Ordering.Desc))
   
case Nil                             => ("created", slick.ast.Ordering(slick.ast.Ordering.Desc)) // default to created.
  }).getOrElse(("created", slick.ast.Ordering(slick.ast.Ordering.Desc)))

 
val myTableReps = myTable.hlist.toList

 
val mapping = myTableReps.foldLeft((0, Map.empty[String, slick.lifted.Rep[_]])) {
   
case (((index, accumulator), next)) =>
     
(index + 1, accumulator + ((myTableColumns(index), next.asInstanceOf[slick.lifted.Rep[_]])))
 
}._2

  mapping
.find(s => s._1 == sort) match {
   
case Some((_, rep)) => SortingRequest(rep, direction)
   
case None           => throw new Exception("sort")
 
}
}

This works (almost couldn't believe it myself), the sorting request object will be used to build a slick ordered:

class PgDynamicSortable[TB <: Table[EL], EL, EX[_]](q: Query[TB, EL, EX]) {

  private def generateOrdFromRep: SortingRequest => slick.lifted.Ordered = {
    t
=> new slick.lifted.Ordered(Vector((t.rep.toNode, t.direction)))
 
}

  def dynamicSort(f: TB => (SortingRequest)): Query[TB, EL, EX] = {
    q
.sortBy(e => f(e))(generateOrdFromRep)
 
}
}

What I tried to do afterwards is to move the `mapping` and `myTableReps` variable outside of the method since they don't have to be recalculated every time, this causes a lot of troubles:

val myTableColumns = DbTables. myTable.baseTableRow.create_*.map(_.name).toList
val myTableReps = DbTables. myTable.baseTableRow.hlist.toList
val mapping = myTableReps.foldLeft((0, Map.empty[String, slick.lifted.Rep[_]])) {
  case (((index, accumulator), next)) =>
    (index + 1, accumulator + ((myTableColumns(index), next.asInstanceOf[slick.lifted.Rep[_]])))
}._2

// if this would work I could remove the need for the myTable variable
def mapSortToRequest(s: Option[String]): MyTable => SortingRequest = { myTable => 
  val (sort, direction) = s.map(str => str.toList match {
    case prefix :: tail if prefix == '-' => (tail.mkString(""), slick.ast.Ordering(slick.ast.Ordering.Asc))
    case prefix :: tail                  => (str, slick.ast.Ordering(slick.ast.Ordering.Desc))
    case Nil                             => ("created", slick.ast.Ordering(slick.ast.Ordering.Desc)) // default to created.
  }).getOrElse(("created", slick.ast.Ordering(slick.ast.Ordering.Desc)))

  mapping.find(s => s._1 == sort) match {
    case Some((_, rep)) => SortingRequest(rep, direction)
    case None           => throw new Exception("sort")
  }
}

this throws an exception:

slick.SlickException: No type for symbol i_id found in Vector[t2<@t3<UnassignedType>>]

at slick.ast.Type$class.select(Type.scala:24)

at slick.ast.CollectionType.select(Type.scala:132)

at slick.ast.Select.buildType(Node.scala:530)

at slick.ast.SimplyTypedNode$class.withInferredType(Node.scala:124)

at slick.ast.Select.withInferredType(Node.scala:520)

at slick.ast.Select.withInferredType(Node.scala:520)

at slick.ast.Node$class.infer(Node.scala:90)

at slick.ast.Select.infer(Node.scala:520)

at slick.ast.ComplexFilteredQuery$$anonfun$4.apply(Node.scala:304)

at slick.ast.ComplexFilteredQuery$$anonfun$4.apply(Node.scala:303)


When printing the mappings I get something like this:

Map(id -> mytableelem.id, created -> mytableelm, ...)

When generating the mappings at every invocation I get

Map(id-> (mytablelem Ref @1356960571).id, created -> (mytablelem Ref @1356960571).created, ...)

I can kinda guess why this happens, the table is actually a class and probably slick is instantiating it on the fly that's why in the second map we actually see object references being printed, can this be actually done?



Reply all
Reply to author
Forward
0 new messages