问题
Hi so I've been trying to create some generic functions using macros and Quill.
Here is my implementation of the macro:
class Impl(val c: Context) {
import c.universe._
def all[T](tblName: Tree, ctx: Tree)(implicit t: WeakTypeTag[T]): Tree =
q"""
import ${ctx}._
implicit val schema = schemaMeta[$t](${tblName})
run(quote {
query[$t]
})
"""
}
object Macros {
def all[T](tblName: String, ctx: MysqlAsyncContext[Literal.type]): Future[List[T]] = macro Impl.all[T]
}
And I've tried using it in the below code:
case class Language(id: Short, iso639_1: String, name: String)
object Language {
val tableName = "Languages"
def all()(implicit mysql: MysqlAsyncContext[Literal.type], ec: ExecutionContext): Future[List[Language]] =
Macros.all[Language](tableName, mysql)
}
But then I get the following error:
Language.scala:99:25: type mismatch;
[error] found : mysql.Quoted[mysql.EntityQuery[Language]]{def quoted: io.getquill.ast.Entity; def ast: io.getquill.ast.Entity; def id1101286793(): Unit; val liftings: Object}
[error] required: io.getquill.MysqlAsyncContext[io.getquill.Literal.type]#EntityQuery[com.github.pokeboston.libghpagesapi.normalized.Language]
[error] Macros.all[Language]("Languages", mysql)
[error] ^
However I know that the ctx being passed to the macro is indeed a MysqlAsyncContext because when I change the macro code to:
class Impl(val c: Context) {
import c.universe._
def all[T](tblName: Tree, ctx: Tree)(implicit t: WeakTypeTag[T]): Tree =
q"""
import ${ctx}._
implicit val schema = schemaMeta[$t](${tblName})
$ctx
"""
}
It gives me this following error:
Language.scala:99:25: type mismatch;
[error] found : io.getquill.MysqlAsyncContext[io.getquill.Literal.type]
[error] required: scala.concurrent.Future[List[Language]]
[error] Macros.all[Language]("Languages", mysql)
[error] ^
I'm guessing that there is something with macros that I am fundamentally misunderstanding. Any enlightenment would be much appreciated!
Thanks!
回答1:
Followed by quill-example I created project on github, see quill-generic
Example from sync implementation:
AbstractCrudMacro.scala
package pl.jozwik.quillgeneric.quillmacro
import scala.reflect.macros.whitebox.{ Context => MacroContext }
trait FilterCrudMacro {
val c: MacroContext
import c.universe._
import pl.jozwik.quillgeneric.quillmacro.Keys._
def callFilterOnIdTree[K: c.WeakTypeTag](id: Tree)(dSchema: c.Expr[_]): Tree =
callFilterOnId[K](c.Expr[K](q"$id"))(dSchema)
protected def callFilterOnId[K: c.WeakTypeTag](id: c.Expr[K])(dSchema: c.Expr[_]): Tree = {
val t = weakTypeOf[K]
t.baseClasses.find(c => compositeSet.contains(c.asClass.fullName)) match {
case None =>
q"$dSchema.filter(_.id == lift($id))"
case Some(base) =>
val query = q"$dSchema.filter(_.id.fk1 == lift($id.fk1)).filter(_.id.fk2 == lift($id.fk2))"
base.fullName match {
case `compositeKey4Name` =>
q"$query.filter(_.id.fk3 == lift($id.fk3)).filter(_.id.fk4 == lift($id.fk4))"
case `compositeKey3Name` =>
q"$query.filter(_.id.fk3 == lift($id.fk3))"
case `compositeKey2Name` =>
query
case x =>
c.abort(NoPosition, s"$x not supported")
}
}
}
protected def callFilter[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree = {
val id = c.Expr[K](q"$entity.id")
callFilterOnId[K](id)(dSchema)
}
}
trait AbstractCrudMacro extends FilterCrudMacro {
val c: MacroContext
import c.universe._
def all[T: c.WeakTypeTag](dSchema: c.Expr[_]): Tree =
q"""
import ${c.prefix}._
run($dSchema)
"""
def createAndGenerateId[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree =
q"""
import ${c.prefix}._
run($dSchema.insertValue($entity).returningGenerated(_.id))
"""
def update[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree = {
val filter = callFilter[K, T](entity)(dSchema)
q"""
import ${c.prefix}._
val q = $filter
run(q.updateValue($entity))
"""
}
def delete[K: c.WeakTypeTag](id: c.Expr[K])(dSchema: c.Expr[_]): Tree = {
val filter = callFilterOnId(id)(dSchema)
q"""
import ${c.prefix}._
val q = $filter
run(
q.delete
)
"""
}
def deleteByFilter(filter: Tree)(dSchema: c.Expr[_]): Tree =
q"""
import ${c.prefix}._
run(
$dSchema.filter($filter).delete
)
"""
def deleteAll[T: c.WeakTypeTag](dSchema: c.Expr[_]): Tree =
q"""
import ${c.prefix}._
run($dSchema.delete)
"""
def searchByFilter[T: c.WeakTypeTag](filter: Tree)(offset: c.Expr[Int], limit: c.Expr[Int])(dSchema: c.Expr[_]): Tree =
q"""
import ${c.prefix}._
run(
$dSchema.filter($filter).drop(lift($offset)).take(lift($limit))
)
"""
def count(filter: Tree)(dSchema: c.Expr[_]): Tree =
q"""
import ${c.prefix}._
run(
$dSchema.filter($filter).size
)
"""
}
CrudMacro.scala
package pl.jozwik.quillgeneric.quillmacro
import scala.reflect.macros.whitebox.{ Context => MacroContext }
class CrudMacro(val c: MacroContext) extends AbstractCrudMacro {
import c.universe._
def createAndGenerateIdOrUpdate[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree = {
val filter = callFilter[K, T](entity)(dSchema)
q"""
import ${c.prefix}._
val id = $entity.id
val q = $filter
val result = run(
q.updateValue($entity)
)
if (result == 0) {
run($dSchema.insertValue($entity).returningGenerated(_.id))
} else {
id
}
"""
}
def createWithGenerateIdOrUpdateAndRead[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree = {
val filter = callFilter[K, T](entity)(dSchema)
q"""
import ${c.prefix}._
val id = $entity.id
val q = $filter
val result = run(
q.updateValue($entity)
)
val newId =
if (result == 0) {
run($dSchema.insertValue($entity).returningGenerated(_.id))
} else {
id
}
run($dSchema.filter(_.id == lift(newId)))
.headOption
.getOrElse(throw new NoSuchElementException(s"$$newId"))
"""
}
def createWithGenerateIdAndRead[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree =
q"""
import ${c.prefix}._
val newId = run($dSchema.insertValue($entity).returningGenerated(_.id))
val q = $dSchema.filter(_.id == lift(newId))
run(q)
.headOption
.getOrElse(throw new NoSuchElementException(s"$$newId"))
"""
def createOrUpdate[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree = {
val filter = callFilter[K, T](entity)(dSchema)
q"""
import ${c.prefix}._
val id = $entity.id
val q = $filter
val result = run(q.updateValue($entity))
if(result == 0){
run($dSchema.insertValue($entity))
}
id
"""
}
def createOrUpdateAndRead[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree = {
val filter = callFilter[K, T](entity)(dSchema)
q"""
import ${c.prefix}._
val id = $entity.id
val q = $filter
val result = run(
q.updateValue($entity)
)
if(result == 0){
run($dSchema.insertValue($entity))
}
run(q)
.headOption
.getOrElse(throw new NoSuchElementException(s"$$id"))
"""
}
def create[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree =
q"""
import ${c.prefix}._
run(
$dSchema.insertValue($entity)
)
$entity.id
"""
def createAndRead[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree = {
val filter = callFilter[K, T](entity)(dSchema)
q"""
import ${c.prefix}._
val id = $entity.id
run($dSchema.insertValue($entity))
val q = $filter
run(q)
.headOption
.getOrElse(throw new NoSuchElementException(s"$$id"))
"""
}
def updateAndRead[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree = {
val filter = callFilter[K, T](entity)(dSchema)
q"""
import ${c.prefix}._
val q = $filter
run(q.updateValue($entity))
run(q)
.headOption
.getOrElse{
val id = $entity.id
throw new NoSuchElementException(s"$$id")
}
"""
}
def read[K: c.WeakTypeTag, T: c.WeakTypeTag](id: c.Expr[K])(dSchema: c.Expr[_]): Tree = {
val filter = callFilterOnId[K](id)(dSchema)
q"""
import ${c.prefix}._
val q = $filter
run(q)
.headOption
"""
}
def readUnsafe[K: c.WeakTypeTag, T: c.WeakTypeTag](id: c.Expr[K])(dSchema: c.Expr[_]): Tree = {
val filter = callFilterOnId[K](id)(dSchema)
q"""
import ${c.prefix}._
val q = $filter
run(q)
.headOption
.getOrElse(throw new NoSuchElementException(s"$$id"))
"""
}
}
CrudWithContext.scala
package pl.jozwik.quillgeneric.quillmacro.sync
import io.getquill.context.Context
import pl.jozwik.quillgeneric.quillmacro.{ CrudMacro, DateQuotes, WithId }
import scala.language.experimental.macros
object CrudWithContext {
type CrudWithContextDateQuotesUnit = CrudWithContextDateQuotes[Unit]
type CrudWithContextDateQuotesLong = CrudWithContextDateQuotes[Long]
}
trait CrudWithContextDateQuotes[U] extends CrudWithContext[U] with DateQuotes {
this: Context[_, _] =>
}
trait CrudWithContext[U] {
this: Context[_, _] =>
type dQuery[T] = this.DynamicEntityQuery[T]
def all[T](implicit dSchema: dQuery[T]): Seq[T] = macro CrudMacro.all[T]
def create[K, T <: WithId[K]](entity: T)(implicit dSchema: dQuery[T]): K = macro CrudMacro.create[K, T]
def createAndRead[K, T <: WithId[K]](entity: T)(implicit dSchema: dQuery[T]): T = macro CrudMacro.createAndRead[K, T]
def createAndGenerateId[K, T <: WithId[K]](entity: T)(implicit dSchema: dQuery[T]): K = macro CrudMacro.createAndGenerateId[K, T]
def createWithGenerateIdAndRead[K, T <: WithId[K]](entity: T)(implicit dSchema: dQuery[T]): T = macro CrudMacro.createWithGenerateIdAndRead[K, T]
def createOrUpdate[K, T <: WithId[K]](entity: T)(implicit dSchema: dQuery[T]): K = macro CrudMacro.createOrUpdate[K, T]
def createOrUpdateAndRead[K, T <: WithId[K]](entity: T)(implicit dSchema: dQuery[T]): T = macro CrudMacro.createOrUpdateAndRead[K, T]
def createAndGenerateIdOrUpdate[K, T <: WithId[K]](entity: T)(implicit dSchema: dQuery[T]): K = macro CrudMacro.createAndGenerateIdOrUpdate[K, T]
def createWithGenerateIdOrUpdateAndRead[K, T <: WithId[K]](entity: T)(implicit dSchema: dQuery[T]): T =
macro CrudMacro.createWithGenerateIdOrUpdateAndRead[K, T]
def update[K, T <: WithId[K]](entity: T)(implicit dSchema: dQuery[T]): U = macro CrudMacro.update[K, T]
def updateAndRead[K, T <: WithId[K]](entity: T)(implicit dSchema: dQuery[T]): T = macro CrudMacro.updateAndRead[K, T]
def read[K, T <: WithId[K]](id: K)(implicit dSchema: dQuery[T]): Option[T] = macro CrudMacro.read[K, T]
def readUnsafe[K, T <: WithId[K]](id: K)(implicit dSchema: dQuery[T]): T = macro CrudMacro.readUnsafe[K, T]
def delete[K, T <: WithId[K]](id: K)(implicit dSchema: dQuery[T]): U = macro CrudMacro.delete[K]
def deleteAll[T](implicit dSchema: dQuery[T]): U = macro CrudMacro.deleteAll[T]
def deleteByFilter[T](filter: T => Boolean)(implicit dSchema: dQuery[T]): Long = macro CrudMacro.deleteByFilter
def searchByFilter[T](filter: T => Boolean)(offset: Int, limit: Int)(implicit dSchema: dQuery[T]): Seq[T] = macro CrudMacro.searchByFilter[T]
def count[T](filter: T => Boolean)(implicit dSchema: dQuery[T]): Long = macro CrudMacro.count
}
Example of implementation: ConfigurationRepository.scala
package pl.jozwik.quillgeneric.sync.jdbc.repository
import io.getquill.NamingStrategy
import io.getquill.context.sql.idiom.SqlIdiom
import pl.jozwik.quillgeneric.model.{ Configuration, ConfigurationId }
import pl.jozwik.quillgeneric.quillmacro.sync.JdbcRepository
import pl.jozwik.quillgeneric.quillmacro.sync.JdbcRepository.JdbcContextDateQuotes
import scala.util.Try
class ConfigurationRepository[D <: SqlIdiom, N <: NamingStrategy](
protected val context: JdbcContextDateQuotes[D, N],
protected val tableName: String = "Configuration"
) extends JdbcRepository[ConfigurationId, Configuration, D, N] {
protected def dynamicSchema: context.DynamicEntityQuery[Configuration] = dSchema
private val aliases = {
import context._
Seq(
alias[Configuration](_.id, "key"),
alias[Configuration](_.value, "value")
)
}
private implicit val dSchema: context.DynamicEntityQuery[Configuration] =
context.dynamicQuerySchema[Configuration](tableName, aliases: _*)
override def all: Try[Seq[Configuration]] = Try {
context.all[Configuration]
}
override def create(entity: Configuration): Try[ConfigurationId] = Try {
context.create[ConfigurationId, Configuration](entity)
}
override def createAndRead(entity: Configuration): Try[Configuration] =
Try {
context.transaction {
context.createAndRead[ConfigurationId, Configuration](entity)
}
}
override def createOrUpdate(entity: Configuration): Try[ConfigurationId] = Try {
context.transaction {
context.createOrUpdate[ConfigurationId, Configuration](entity)
}
}
override def createOrUpdateAndRead(entity: Configuration): Try[Configuration] = Try {
context.transaction {
context.createOrUpdateAndRead[ConfigurationId, Configuration](entity)
}
}
override def read(id: ConfigurationId): Try[Option[Configuration]] =
Try {
context.read[ConfigurationId, Configuration](id)
}
override def readUnsafe(id: ConfigurationId): Try[Configuration] =
Try {
context.readUnsafe[ConfigurationId, Configuration](id)
}
override def update(entity: Configuration): Try[Long] = Try {
context.update[ConfigurationId, Configuration](entity)
}
override def updateAndRead(entity: Configuration): Try[Configuration] = Try {
context.transaction {
context.updateAndRead[ConfigurationId, Configuration](entity)
}
}
override def delete(id: ConfigurationId): Try[Long] =
Try {
context.delete[ConfigurationId, Configuration](id)
}
override def deleteAll: Try[Long] =
Try {
context.deleteAll
}
}
Missing types you will find on github repository quill-generic
来源:https://stackoverflow.com/questions/53180453/generic-macros-with-quill