Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala compiler infers Nothing for generic arguments

I'm trying something that I've seen in different shapes in different contexts before: extending scala's query extensions with filterById(id: Id)

This is what I've tried:

trait TableWithId { self: Profile =>

    import profile.simple._

    trait HasId[Id] { self: Table[_] =>
      def id: Column[Id]
    }

    implicit class HasIdQueryExt[Id: BaseColumnType, U]
    (query: Query[Table[U] with HasId[Id], U]) {
      def filterById(id: Id)(implicit s: Session) = query.filter(_.id === id)
      def insertReturnId(m: U)(implicit s: Session): Id = query.returning(query.map(_.id)) += m
    }
}

This works fine, no real magic there. But because there is no type constraint on the Table type, any query to which I apply filterById, looses it's specificness (is is now a generic Table with HasId[Id]), and I can no longer reach it's columns (except for _.id ofcourse).

I don't know how to type this implicit conversion, such that this is prevented. Is it possible? The following "naieve" solution does not work, because Scala infers Nothing for the Id type now:

implicit class HasIdQueryExt[Id: BaseColumnType, U, T <: Table[U] with HasId[Id]]
(query: Query[T, U]) { ... }

I find it kind of strange that suddenly the Id type is inferred as Nothing. How do I hint the compiler where to look for that Id type?

like image 365
A.J.Rouvoet Avatar asked Nov 11 '22 01:11

A.J.Rouvoet


1 Answers

This is my solution for a similar problem. I did use specific type for id though.:

trait GenericComponent { this: Profile =>
  import profile.simple._

  abstract class TableWithId[A](tag:Tag, name:String) extends Table[A](tag:Tag, name) {
    def id = column[Option[UUID]]("id", O.PrimaryKey)
  }

  abstract class genericTable[T <: Table[A] , A] {
    val table: TableQuery[T]

    /**
     * generic methods
     */

    def insert(entry: A)(implicit session:Session): A = session.withTransaction { 
      table += entry
      entry
    }

    def insertAll(entries: List[A])(implicit session:Session) = session.withTransaction { table.insertAll(entries:_*) }

    def all: List[A] = database.withSession { implicit session =>
      table.list.map(_.asInstanceOf[A])
    }
  }

  /**
   * generic queries for any table which has id:Option[UUID]
   */
  abstract class genericTableWithId[T <: TableWithId[A], A <:ObjectWithId ] extends genericTable[T, A] {

    def forIds(ids:List[UUID]): List[A] = database.withSession { implicit session =>
      ids match {
        case Nil => Nil
        case x::xs =>table.filter(_.id inSet(ids)).list.map(_.asInstanceOf[A])
      }
    }

    def forId(id:UUID):Option[A] = database.withSession { implicit session =>table.filter(_.id === id).firstOption }

  }
}

and then for your concrete component:

case class SomeObjectRecord(
  override val id:Option[UUID] = None,
  name:String) extends ObjectWithId(id){
  // your function definitions there
} 

trait SomeObjectComponent extends GenericComponent { this: Profile =>
  import profile.simple._

  class SomeObjects(tag: Tag) extends TableWithId[SomeObjectRecord](tag, "some_objects") {
    def name = column[String]("name", O.NotNull)

    def * = (id, name) <> (SomeObjectRecord.tupled, SomeObjectRecord.unapply)
    def nameIdx = index("u_name", (name), unique = true)
  }

  object someobjects extends genericTableWithId[SomeObjects, SomeObjectRecord] {
    val table = TableQuery[Units]

   // your additional methods there; you get insert and insertAll from the parent    
  }
}
like image 121
Ashalynd Avatar answered Nov 15 '22 12:11

Ashalynd