简体   繁体   中英

Generic Macros with Quill

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!

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM