1
votes

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

  // Remaining methods shown on GitHub
}

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:

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?

2
Take a look this exampleSky
Looks interesting. I'd like to try it, but I am away from my computer right now. Any chance you could fork my project and show your code?Mike Slinn
Now that I look at the code for a bit, I think this is Slick 2.1.x code and would not work with Slick 3.1.x. Things were simpler with Slick 2.1.x - I had something similar which worked then. This is a Slick 3.1.x problem, unfortunatelyMike Slinn

2 Answers

2
votes

Full working example:

 package com.knol.db.repo

import com.knol.db.connection.DBComponent
import com.knol.db.connection.MySqlDBComponent
import scala.concurrent.{Await, Future}
import concurrent.duration.Duration

trait LiftedHasId {
  def id: slick.lifted.Rep[Int]
}

trait HasId {
  def id: Option[Int]
}

trait GenericAction[T <: HasId]{this: DBComponent =>
  import driver.api._

  type QueryType <: slick.lifted.TableQuery[_ <: Table[T] with LiftedHasId]

  val tableQuery: QueryType

  @inline def deleteAsync(id: Int): Future[Int] = db.run { tableQuery.filter(_.id === id).delete }
  @inline def delete(id: Int): Int = Await.result(deleteAsync(id), Duration.Inf)

  @inline def deleteAllAsync(): Future[Int] = db.run { tableQuery.delete }
  @inline def deleteAll(): Int = Await.result(deleteAllAsync(), Duration.Inf)

  @inline def getAllAsync: Future[List[T]] = db.run { tableQuery.to[List].result }
  @inline def getAll: List[T] = Await.result(getAllAsync, Duration.Inf)

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

  @inline def getById(id: Int): Option[T] = Await.result(getByIdAsync(id), Duration.Inf)

  @inline def deleteById(id: Option[Int]): Unit =
    db.run { tableQuery.filter(_.id === id).delete }

  @inline def findAll: Future[List[T]] = db.run { tableQuery.to[List].result }




}
trait BankInfoRepository extends BankInfoTable  with GenericAction[BankInfo] { this: DBComponent =>

  import driver.api._

  type QueryType  = TableQuery[BankInfoTable]

  val tableQuery=bankInfoTableQuery

  def create(bankInfo: BankInfo): Future[Int] = db.run { bankTableInfoAutoInc += bankInfo }

  def update(bankInfo: BankInfo): Future[Int] = db.run { bankInfoTableQuery.filter(_.id === bankInfo.id.get).update(bankInfo) }

  /**
   * Get bank and info using foreign key relationship
   */
  def getBankWithInfo(): Future[List[(Bank, BankInfo)]] =
    db.run {
      (for {
        info <- bankInfoTableQuery
        bank <- info.bank
      } yield (bank, info)).to[List].result
    }

  /**
   * Get all bank and their info.It is possible some bank do not have their product
   */
  def getAllBankWithInfo(): Future[List[(Bank, Option[BankInfo])]] =
    db.run {
      bankTableQuery.joinLeft(bankInfoTableQuery).on(_.id === _.bankId).to[List].result
    }
}

private[repo] trait BankInfoTable extends BankTable{ this: DBComponent =>

  import driver.api._

  class BankInfoTable(tag: Tag) extends Table[BankInfo](tag, "bankinfo") with LiftedHasId {
    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 bank = foreignKey("bank_product_fk", bankId, bankTableQuery)(_.id)
    def * = (owner, branches, bankId, id.?) <> (BankInfo.tupled, BankInfo.unapply)

  }

  protected val bankInfoTableQuery = TableQuery[BankInfoTable]

  protected def bankTableInfoAutoInc = bankInfoTableQuery returning bankInfoTableQuery.map(_.id)

}

object BankInfoRepository extends BankInfoRepository with MySqlDBComponent

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

You're trying to abstract over the result type with HasId but your code doesn't actually care about that. The id values that you're using are the ones from the lifted type, i.e. the table row class, so you need an abstraction at this level:

trait LiftedHasId {
  def id: slick.lifted.Rep[Int]
}

Then in DbAction:

type QueryType <: slick.lifted.TableQuery[_ <: Table[T] with LiftedHasId]

And BankInfoTable must define a concrete type for it:

type QueryType = slick.lifted.TableQuery[BankInfoTable]

Or you could add it as a second type parameter to DbAction (just like Query has two type parameters for the lifted type and the result type).