So I wanted to implement a trait to have a common execute function to run slick's query.
As you can see from the code below, I have one trait that has a type parameter on the class and the other define the type parameter on the method.
When I compile, the trait with method generic type compiles(without giving any type argument) but the other one does not.
Why??? I tried to give the class type arguments UserTable or User (my slick table def and projected case class) but none of them works. The error just says "expects DBIO[UserTable] but actual MySQLDriver.StreamingDriverAction"
Any help really appreciated.
Thanks a lot!!!
class DAO #Inject()(val configProvider: DatabaseConfigProvider) extends
ManagementAppDatabase {
private val users = TableQuery[UserTable]
def findUserByEmail(email: String): Future[Option[User]] = {
execute(users.filter(_.email === email).result.headOption)
}
}
trait ManagementAppDatabase {
val configProvider: DatabaseConfigProvider
def execute[T](dBIO:DBIO[T]): Future[T] = configProvider.get[JdbcProfile].db.run(dBIO)
}
trait ManagementAppDatabase[T] {
val configProvider: DatabaseConfigProvider
def execute (dBIO:DBIO[T]):Future[T]=configProvider.get[JdbcProfile].db.run(dBIO)
}
If you extend e.g. ManagementAppDatabase[User], then you can only call execute on DBIO[User]. But users.filter(_.email === email).result.headOption is DBIO[Option[User]]. That's it.
Related
So I have this simple Scala trait with a method that requires a type parameter specified.
The DAO class extends the trait and uses the trait's method. Even if I do not provide a concrete type to the method, the code still compiles, and I suppose this is achieved by Scala auto inferring the generic type (guessing what the type value should be)? Is it right?
And also how does Scala infer types in situations like this in general?
Thanks a lot!!
class DAO #Inject()(val configProvider: DatabaseConfigProvider) extends
ManagementAppDatabase {
private val users = TableQuery[UserTable]
def findUserByEmail(email: String): Future[Option[User]] = {
execute(users.filter(_.email === email).result.headOption)
}
}
trait ManagementAppDatabase {
val configProvider: DatabaseConfigProvider
def execute[T](dBIO:DBIO[T]): Future[T] = configProvider.get[JdbcProfile].db.run(dBIO)
}
It's not a guess, the compiler can infer the type in this case as the object passed to the method has the type defined:
def execute[T](dBIO:DBIO[T]): Future[T] = configProvider.get[JdbcProfile].db.run(dBIO)
So if you pass a type DBIO[Int], the compiler can fill in the rest:
def execute[Int](dBIO:DBIO[Int]): Future[Int] = configProvider.get[JdbcProfile].db.run(dBIO)
I am experiencing issues making Slick's TableQuery used in a generic fashion.
Observe the regular situation:
class AccountRepository {
override protected val dbConfig = DatabaseConfigProvider.get[JdbcProfile](Play.current)
val accounts = TableQuery[Accounts]
def all = db.run(accounts.result)
...
The idea would be to extract everything possible into generic trait or abstract class in order to avoid repetition. For the sake of simplicity I included only the problematic code.
abstract class GenericRepository[T] extends HasDatabaseConfig[JdbcProfile] {
override protected val dbConfig = DatabaseConfigProvider.get[JdbcProfile(Play.current)
val table = TableQuery[T]
}
And to use it like:
class AccountRepository extends GenericRepository[Accounts] {
However, that creates a compilation error:
type arguments [T] conform to the bounds of none of the overloaded alternatives of value apply: [E <: slick.lifted.AbstractTable[]]=> slick.lifted.TableQuery[E] [E <: slick.lifted.AbstractTable[]](cons: slick.lifted.Tag => E)slick.lifted.TableQuery[E]
Trying to fix the issue by setting a boundary doesn't help as well.
abstract class GenericRepository[T <: slick.lifted.AbstractTable[T]] extends HasDatabaseConfig[JdbcProfile] {
However, we end up with a different error:
class type required but T found
at following place:
val table = TableQuery[T]
Any idea about the solution?
You have to pass table query manually,
abstract class GenericRepository[T <: slick.lifted.AbstractTable[_]](query: TableQuery[T])
and in implementation,
class AccountRepository extends GenericRepository[Accounts](TableQuery[Accounts])
I hope this will solve your problem.
I guess if you can solve the initialization of tableQuery, then you can continue your GenericRepository. I am using Slick 3.0 with PostgreSQL.
In the slick.lifted.TableQuery, there is a method like the following
// object TableQuery
def apply[E <: AbstractTable[_]](cons: Tag => E): TableQuery[E] =
new TableQuery[E](cons)
So if we can get an instance of E on the fly, then we can get a generic way to create TableQuery. So reflection seems to be a possible way to solve it.
import scala.reflect.runtime.{ universe => ru }
import slick.lifted.{ AbstractTable, ProvenShape, Tag }
import slick.driver.PostgresDriver.api._
object Reflection {
val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)
def getTypeTag[T: ru.TypeTag] = ru.typeTag[T]
def createClassByConstructor[T: ru.TypeTag](args: Any*) =
runtimeMirror.reflectClass(getTypeTag[T].tpe.typeSymbol.asClass)
.reflectConstructor(ru.typeOf[T].declaration(ru.nme.CONSTRUCTOR)
.asMethod)(args: _*).asInstanceOf[T]
}
// context bound here is for createClassByConstructor to use
abstract class GenericTableQuery[U, T <: AbstractTable[U]: ru.TypeTag] {
import Reflection._
// look at following code: Students, if you want to initialize Students
// you're gonna need a tag parameter, that's why we pass tag here
val tableQuery = TableQuery.apply(tag => createClassByConstructor[T](tag))
}
// Sample Table
case class Student(name: String, age: Int)
class Students(tag: Tag) extends Table[Student](tag, "students") {
def name = column[String]("name")
def age = column[Int]("age")
override def * : ProvenShape[Student] = (name, age)
<> (Student.tupled, Student.unapply _)
}
// get TableQuery
object TestGenericTableQuery extends GenericTableQuery[Student, Students] {
val studentQuery = tableQuery
}
The codes mentioned above is just focused on the issue of generic TableQuery, try to combine it with your GenericRepository and your problem may get solved.
Anyway, hope it helps.
I have this base trait
trait MyBase {
type M
type T <: Table[M]
val query: TableQuery[T]
}
Where TableQuery is scala.slick.lifted.TableQuery
My subclasses instantiate TableQuery like so:
type M = Account
type T = AccountsTable
val query = TableQuery[T]
I'd like to instantiate the TableQuery in the base trait, possibly by using a lazy val, i.e.
lazy val query: TableQuery[T] = {
...
}
I've been playing around with reflection, but haven't had much luck.
If I understand correctly, what you want is to be able to extend
MyBase by simply defining M and T but without having to explicitly instantiate the TableQuery in each derived class.
Using reflection is not really an option because normally you use TableQuery.apply
for that (as in val query = TableQuery[MyTable]), and this is implemented through a macro,
so you've got a "runtime vs compile-time" issue.
If you absolutely need MyBase to be a trait (as opposed to a class), then I don't see any viable solution.
However if you can turn MyBase into a class and turn M and T into type parameters (instead of abstract types), then there is at least one solution.
As I hinted in another related question (How to define generic type in Scala?), you can
define a type class (say TableQueryBuilder) to capture the call to TableQuery.apply (at the point where the concrete type is known) along with an implicit macro (say TableQueryBuilder.builderForTable) to provide
an instance of this type class. You can then define a method (say TableQueryBuilder.build) to actually instantiate the TableQuery, which will just delegate to job to the type class.
// NOTE: tested with scala 2.11.0 & slick 3.0.0
import scala.reflect.macros.Context
import scala.language.experimental.macros
object TableQueryBuilderMacro {
def createBuilderImpl[T<:AbstractTable[_]:c.WeakTypeTag](c: Context) = {
import c.universe._
val T = weakTypeOf[T]
q"""new TableQueryBuilder[$T]{
def apply(): TableQuery[$T] = {
TableQuery[$T]
}
}"""
}
}
trait TableQueryBuilder[T<:AbstractTable[_]] {
def apply(): TableQuery[T]
}
object TableQueryBuilder {
implicit def builderForTable[T<:AbstractTable[_]]: TableQueryBuilder[T] = macro TableQueryBuilderMacro.createBuilderImpl[T]
def build[T<:AbstractTable[_]:TableQueryBuilder](): TableQuery[T] = implicitly[TableQueryBuilder[T]].apply()
}
The net effect is that you don't need anymore to know the concrete value of the type T in order to be able to instantiate a TableQuery[T],
provided that you have an implicit instance of TableQueryBuilder[T] in scope. In other words, you can shift the need to know the concrete value of T
up to the point where you actually know it.
MyBase (now a class) can then be implemented like this:
class MyBase[M, T <: Table[M] : TableQueryBuilder] {
lazy val query: TableQuery[T] = TableQueryBuilder.build[T]
}
And you can then extend it without the need to explcitly call TableQuery.apply:
class Coffees(tag: Tag) extends Table[(String, Double)](tag, "COFFEES") {
def name = column[String]("COF_NAME")
def price = column[Double]("PRICE")
def * = (name, price)
}
class Derived extends MyBase[(String, Double), Coffees] // That's it!
What happens here is that in Derived's constructor, an implicit value for TableQueryBuilder[Coffees] is implicitly
passed to MyBase's constructor.
The reason why you cannot apply this pattern if MyBase were a trait is pretty mundane: trait constructors cannot have parameters, let alone implicit parameters, so there would be no implicit way
to pass the TableQueryBuilder instance.
I am trying to write a generic CRUD trait for Slick 2.0. The trait should a) provide generic methods to read/update/delete entities as well as b) abstract from the database. Following this slick example (database abstraction) and this article (CRUD trait) I came up with the following (shortened) code snippet:
trait Profile {
val profile: JdbcProfile
}
trait Crud[T <: AbstractTable[A], A] { this: Profile =>
import profile.simple._
val qry: TableQuery[T]
def countAll()(implicit session: Session): Int = {
qry.length.run
}
def getAll()(implicit session: Session): List[A] = {
qry.list // <-- type mismatch; found: List[T#TableElementType] required: List[A]
}
}
The code is invalid due to a type mismatch. The return type of the 2nd function seems to be of type List[T#TableElementType] but needs to be List[A]. Any ideas on how to solve the issue. Additional references to further readings on generic Slick 2.0 operations are welcome too.
type TableElementType is abstract inside of class AbstractTable[A]. Scala doesn't know about any relationship between A and TableElementType. class Table on the other hand defines final type TableElementType = A, which tells Scala about this relationship (and apparently Scala is smart enough to use the final annotation to know that the relationanship even holds for a subtype T <: Table[A] eventhough Table[A] is not co-variant in A).
So you need to use T <: Table[A] instead of T <: AbstractTable[A]. And because Table is inside the Slick driver cake (as in cake pattern), you need to move your Crud into your cake as well. Cakes are viral.
trait Profile {
val profile: JdbcProfile
}
trait CrudComponent{ this: Profile =>
import profile.simple._
trait Crud[T <: Table[A], A] {
val qry: TableQuery[T]
def countAll()(implicit session: Session): Int = {
qry.length.run
}
def getAll()(implicit session: Session): List[A] = {
qry.list // <-- works as intended
}
}
}
In Slick 2, we can map tables like this:
case class Cooler(id: Option[Int], minTemp: Option[Double], maxTemp: Option[Double])
/**
* Define table "cooler".
*/
class Coolers(tag: Tag) extends Table[Cooler](tag, "cooler") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def minTemp = column[Double]("min_temp", O.Nullable)
def maxTemp = column[Double]("max_temp", O.Nullable)
def * = (id.?, minTemp.?, maxTemp.?) <> (Cooler.tupled, Cooler.unapply _)
}
object Coolers {
val tableQuery = TableQuery[Coolers]
}
because I have a lot of tables, I want to define generic methods for them, like find, delete, update so I have to define these methods in a super class from where to extend my objects (object Coolers extends TableUtils[Coolers, Cooler]). In order to define those methods, I need tableQuery to move out of my object in this super class, so I tried it like:
abstract class TableUtils[T <: Table[A] , A] {
val tableQuery = TableQuery[T]
}
but I receive an error on tableQuery definition:
class type required but T found
Does anybody know what I am doing wrong?
When you do TableQuery[T] you are in fact calling TableQuery.apply, which is actually a macro.
The body of this macro tries to instantiate T, but in your case T has become an (unknown) type parameter that the compiler does not know how to instantiate. The problem is similar to trying to compile this:
def instantiate[T]: T = new T
// Does not compile ("class type required but T found")
The net effect is that TableQuery.apply can only be used on concrete types.
You could work around that using a type class to capture the call to TableQuery.apply (at the point where the concrete type is known) along with an implicit macro to provide an instance of this type class. Then you would have something like:
abstract class TableUtils[T <: Table[A] : TableQueryBuilder, A] {
val tableQuery = BuildTableQuery[T]
}
Where TableQueryBuilder is the type class and BuildTableQuery is an alternate version of TableQuery.apply that will forward to the TableQueryBuilder instance to perform the actual instantiation.
I've added an implementation as part of another answer here.
It will be much easier (if less convenient) to just declare tableQuery as an abstract value and define it in every concrete derived class of TableUtils:
abstract class TableUtils[T <: Table[A] , A] {
val tableQuery: TableQuery[T, T#TableElementType]
// define here your helper methods operating on `tableQuery`
}
object Coolers extends TableUtils[Coolers, Cooler] {
val tableQuery = TableQuery[Coolers]
}
Here is one solution:
At first, define this to avoid class type issue..
class Service[T <: Table[_]](path: String, cons: Tag => T){
lazy val db = Database.forConfig(path)
def query = TableQuery[T](cons)
}
Then use it this way, Post is sub class of Table:
object Abcd {
object Def extends Service[Post]("mydb", abc) {
def test = {
//db
val q = query.drop(1).take(20)
val r = db.run(q.result)
println(q.result.statements.head)
println(r)
r
}
}
private def abc(tag: Tag) = new Post(tag)
}
This solution tested ok in slick 3.x, and Play slick 1.x, since the slick 2.0 Query.scala comply to slick 3.0 Query.scala, this might work at 2 too.