Upper bound for query type

28 views
Skip to first unread message

Michael Slinn

unread,
May 20, 2016, 9:46:21 AM5/20/16
to Slick / ScalaQuery
I have a DAO helper trait that provides common functionality to DAOs. It needs to be able to access the table query, and run actions. I'm having trouble defining or otherwise providing the query type to the helper trait.

Below is some code, also available in a short demo project on GitHub, in the action branch.
First, db is defined in trait DBComponent:

  trait DBComponent {
    import slick.driver.JdbcProfile

    val driver: JdbcProfile
    import driver.api._

    val db: Database
  }


The classes to be persisted extend HasId:

trait HasId {
  def id: Option[Int] = None
}

Here is one such class to be persisted:

case class BankInfo(
  owner: String,
  branches: Int,
  bankId: Int,
  override val id: Option[Int] = None
) extends HasId

The problem is that I don't know how to set QueryType in the following DAO helper trait; I expect that most of the errors that follow are a consequence of the improper type that I used:

/** Handles all actions pertaining to HasId or that do not require parameters */
trait DbAction[T <: HasId] { this: DBComponent =>
  import driver.api._ // defines DBIOAction

  type QueryType <: slick.lifted.TableQuery[Table[T]] // this type is wrong! What should it be?
  def tableQuery: QueryType

  // Is this defined correctly?
  def run[R](action: DBIOAction[R, NoStream, Nothing]): Future[R] = db.run { action }

  def deleteById(id: Option[Long]): Unit =
    for { i <- id } run { tableQuery.filter(_.id === id).delete } // id is unknown because QueryType is wrong

  def findAll: Future[List[T]] = run { tableQuery.to[List].result } // also b0rked
}


FYI, here is how the above will be used. First, the trait that defines the table query:

trait BankInfoTable extends BankTable { this: DBComponent =>
  import driver.api._

  class BankInfoTable(tag: Tag) extends Table[BankInfo](tag, "bankinfo") {
    val id       = column[Int]("id", O.PrimaryKey, O.AutoInc)
    val owner    = column[String]("owner")
    val bankId   = column[Int]("bank_id")
    val branches = column[Int]("branches")

    def bankFK = foreignKey("bank_product_fk", bankId, bankTableQuery)(_.id)

    def * = (owner, branches, bankId, id.?) <> (BankInfo.tupled, BankInfo.unapply)
  }

  val tableQuery = TableQuery[BankInfoTable]

  def autoInc = tableQuery returning tableQuery.map(_.id)
}

It all comes together here:

private[repo] trait BankInfoRepositoryLike extends BankInfoTable with DbAction[BankInfo]
{ this: DBComponent =>

  import driver.api._

  @inline def updateAsync(bankInfo: BankInfo): Future[Int] =
    run { tableQuery.filter(_.id === bankInfo.id.get).update(bankInfo) }

  @inline def getByIdAsync(id: Int): Future[Option[BankInfo]] =
    run { tableQuery.filter(_.id === id).result.headOption }
}

Suggestions?

Mike

Naftoli Gugenheim

unread,
Jun 27, 2016, 6:13:28 AM6/27/16
to Slick / ScalaQuery

You need to give the user a way to specify the type of the subclass of Table[T], and to constrain it so that it has an id column method.

Incidentally you could easily generalize your question such that it has nothing to do with slick; it's a general case of abstracting over types. If you'd make stubs that replace the slick types and then give them meaningless names, it might help to see the actual problem.


--

---
You received this message because you are subscribed to the Google Groups "Slick / ScalaQuery" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scalaquery+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/scalaquery/4b3ad643-c8ca-4194-b147-368ed7ace287%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Noah Mercer

unread,
Oct 7, 2016, 6:25:16 PM10/7/16
to Slick / ScalaQuery
I have the same question you do, and while I can help you get a little further I haven't found the entire answer myself. First of all, I've structured things a little differently, in that rather than your DBAction trait I have an abstract BaseDao class, which I then extend for UserDao, ProductDao, etc:

trait IdModel {
 
def id: Long
}

trait
IdTable {
 
import db.RepoPostgresDriver.api._
 
def id: Rep[Long]
}

/** Entity class storing rows of table Users */
case class User(id: Int, email: Option[String] = None) extends IdModel

/** Table description of table users. Objects of this class serve as prototypes for rows in queries. */
class Users(_tableTag: Tag) extends Table[User](_tableTag, "users") with IdTable {
 
// Usual slick-generated *, ?, id email definitions here
}

/** Collection-like TableQuery object for table Users */
lazy val Users = new TableQuery(tag => new Users(tag))


abstract class BaseDao[T <: AbstractTable[E] with IdTable, E <: T#TableElementType with IdModel](currentEnv:CurrentEnv, app:Application) extends HasDatabaseConfig[RepoPostgresDriver] {
 
protected val dbConfig = DatabaseConfigProvider.get[RepoPostgresDriver](currentEnv.env)(app)
 
protected val _db = dbConfig.db
 
import driver.api._


 
val query: TableQuery[T] //<--slick.lifted.Aliases.TableQuery, overridden in subclasses e.g. val query = Users, where Users is the slick-generated lazy val Users = new TableQuery(tag => new Users(tag))

 
def findById(id: Long) = _db.run(query.filter(_.id === id).result.headOption) // works fine


 
def delete(id: Long) = _db.run(query.filter(_.id === id).delete) // doesn't work - delete method isn't found on query
}


As you can see from the comments, this approach allows me to do some things (filter, +=, update, result) but not others (delete, probably other things I have't found yet).

Although my approach is slightly different, my question is basically the same as yours: 
How do I define a type bound for T so that a TableQuery[T] can not only do filter, update, etc. but also delete (and similar methods I may be missing)?

Reply all
Reply to author
Forward
0 new messages