3

I'm attempting to dynamically include a sortBy to my query which sorts based on its string name from a query parameter. In Slick 3 this has proven to be quite tricky. Currently my setup is:

trait Model {
    type ATable <: AbstractTable[_]

    def tableQuery: TableQuery[ATable]

    def sortMap: Map[String, Rep[_]]

    private def sortKey[T](e: ATable, sort: (String, SortOrder)): ColumnOrdered[_] = sort match {
       case (field, SortOrder.Asc) => ColumnOrdered(sortMap.getOrElse(field, throw new ClientException(s"Can't sort by $field")), Ordering(Ordering.Asc))
       case (field, SortOrder.Desc) => ColumnOrdered(sortMap.getOrElse(field, throw new ClientException(s"Can't sort by $field")), Ordering(Ordering.Desc))
    }
    def all(sort: (String, SortOrder)) = tableQuery.sortBy(sortKey(_, sort)).result
}

object User extends Model {
    type ATable = Tables.User
    val tableQuery = Tables.User

    val sortMap = Map( "id" -> tableQuery.id )
}

But running db.run(User.all(("id", SortOrder.Asc)) throws the following error:

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

Does anyone know of a better solution or where I'm going wrong?

Andreas Jarbol
  • 745
  • 2
  • 11
  • 27

2 Answers2

0

The way I solved this was to change my sortMap to contain the table type with column type implicit (from Map[String, Rep[_]] to Map[String, ATable => Rep[_]]):

trait Model {
    type ATable <: AbstractTable[_]

    def tableQuery: TableQuery[ATable]

    def sortMap: Map[String, ATable => Rep[_]]

    private def sortKey(baseQ: Query[ATable, ATable#TableElementType, Seq], sort: (String, SortOrder)) = {
       val rep = sortMap.getOrElse(sort._1, throw new ClientException(s"Can't sort by ${sort._1}"))
       val orderedRep = sort._2 match {
          case SortOrder.Asc => (t: ATable) => ColumnOrdered(rep(t), slick.ast.Ordering(slick.ast.Ordering.Asc))
          case SortOrder.Desc => (t: ATable) => ColumnOrdered(rep(t), slick.ast.Ordering(slick.ast.Ordering.Desc))
       }
       q.sortBy(orderedRep)
   }

   def all(sort: (String, SortOrder)) = sortKey(tableQuery, sort).result
}

object User extends Model {
    type ATable = Tables.User
    val tableQuery = Tables.User

    val sortMap = Map( "id" -> { (t: ATable) => t.id } )
}

The only suboptimal aspect of this solution is that if a new column is added to the database it has to be manually added to the sortKey. I'm looking into including these maps in the table code generation scripts.

Andreas Jarbol
  • 745
  • 2
  • 11
  • 27
0

You just need to include in generation something like

val columns =  Map( "id" -> { (t: User) => t.id } )

and use it like so:

class UserDAO{ 
//...
baseQuery = filterContext.sort.foldLeft[Query[User, UserRow, Seq]](User) ((_, s) => UserColumnFilter.getSort(s,User.baseTableRow.columns));
//...
}

case class Sort(columnName:String,value: String) {}

case class FilterContext(filters:List[Filter],page:Int,pageSize:Int,sort:List[Sort])
    object UserColumnSort extends ColumnSort[User,UserRow](User)
    class ColumnSort[T<:Table[C],C](query:TableQuery[T]) 
    {      

      def getSort(s:Sort,columns:Map[String, T => Rep[_]]):Query[T, C, Seq] =  {
        val aux = columns.getOrElse(s.columnName, throw new Exception(s"Can't sort by ${s.columnName}"));
          val orderedRep = s.value match {
              case "asc" => (t: T) => ColumnOrdered(aux(t), slick.ast.Ordering(slick.ast.Ordering.Asc))
              case _ => (t: T) => ColumnOrdered(aux(t), slick.ast.Ordering(slick.ast.Ordering.Desc))
           }
           return query.sortBy(orderedRep);

      }
    }
ADyson
  • 57,178
  • 14
  • 51
  • 63
L.A.M.
  • 1
  • 1