如何使用 Scala Quill.io 库编写泛型函数

How to write generic function with Scala Quill.io library

我正在尝试使用 Quill.io 库在对数据库进行操作的 Scala 中实现泛型方法。类型 T 将仅适用于 类 与 Quill.io.

一起使用的情况
def insertOrUpdate[T](inserting: T, equality: (T,T) => Boolean)(implicit ctx: Db.Context): Unit = {
  import ctx._

  val existingQuery = quote {
    query[T].filter { dbElement: T =>
      equality(dbElement, inserting)
    }
  }
  val updateQuery = quote {
    query[T].filter { dbElement =>
      equality(dbElement, lift(inserting))
    }.update(lift(inserting))
  }
  val insertQuery = quote { query[T].insert(lift(inserting)) }

  val existing = ctx.run(existingQuery)
  existing.size match {
    case 1 => ctx.run(updateQuery)
    case _ => ctx.run(insertQuery)

  }
}

但是我遇到了两种类型的编译错误

Error:(119, 12) Can't find an implicit `SchemaMeta` for type `T`
  query[T].filter { dbElement: T =>

Error:(125, 33) Can't find Encoder for type 'T'
    equality(dbElement, lift(inserting))

如何修改我的代码以让它工作?

正如其中一位羽毛笔贡献者所说 issue

If you want to make your solution generic then you have to use macros because Quill generates queries at compile time and T type has to be resolved at that time.

TL;DR下面的也没用,就玩

无论如何...出于好奇,我尝试按照您提到的错误来解决问题。我将函数的定义更改为:

def insertOrUpdate[T: ctx.Encoder : ctx.SchemaMeta](...)

产生了以下日志

[info] PopulateAnomalyResultsTable.scala:71: Dynamic query
[info]       case _ => ctx.run(insertQuery)
[info]  
[error] PopulateAnomalyResultsTable.scala:68: exception during macro expansion: 
[error] scala.reflect.macros.TypecheckException: Found the embedded 'T', but it is not a case class
[error]         at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$$anonfun$apply.apply(Typers.scala:34)
[error]         at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$$anonfun$apply.apply(Typers.scala:28)

它开始很有希望,因为 quill 显然放弃了静态编译并使查询动态化。我检查了失败 macro 的源代码,似乎 quill 正在尝试获取 T 的构造函数,这在当前上下文中是未知的。

正如我在 issue 中所说,@VojtechLetal 在他的回答中提到你必须使用宏。

我在 example Quill project.

中添加了实现通用插入或更新的代码

定义trait Queries that's mixed into context:

trait Queries {
  this: JdbcContext[_, _] =>
  def insertOrUpdate[T](entity: T, filter: (T) => Boolean): Unit = macro InsertOrUpdateMacro.insertOrUpdate[T]
}

此特征使用 macro 实现您的代码并稍作更改:

import scala.reflect.macros.whitebox.{Context => MacroContext}

class InsertOrUpdateMacro(val c: MacroContext) {

  import c.universe._

  def insertOrUpdate[T](entity: Tree, filter: Tree)(implicit t: WeakTypeTag[T]): Tree =
    q"""
      import ${c.prefix}._
      val updateQuery = ${c.prefix}.quote {
        ${c.prefix}.query[$t].filter($filter).update(lift($entity))
      }
      val insertQuery = quote {
        query[$t].insert(lift($entity))
      }
      run(${c.prefix}.query[$t].filter($filter)).size match {
          case 1 => run(updateQuery)
          case _ => run(insertQuery)
      }
      ()
    """
}

用法examples

import io.getquill.{PostgresJdbcContext, SnakeCase}

package object genericInsertOrUpdate {
  val ctx = new PostgresJdbcContext[SnakeCase]("jdbc.postgres") with Queries

  def example1(): Unit = {
    val inserting = Person(1, "")
    ctx.insertOrUpdate(inserting, (p: Person) => p.name == "")
  }

  def example2(): Unit = {
    import ctx._
    val inserting = Person(1, "")
    ctx.insertOrUpdate(inserting, (p: Person) => p.name == lift(inserting.name))
  }
}

P.S。因为 update() returns 条更新记录你的代码可以简化为:

class InsertOrUpdateMacro(val c: MacroContext) {

  import c.universe._

  def insertOrUpdate[T](entity: Tree, filter: Tree)(implicit t: WeakTypeTag[T]): Tree =
    q"""
      import ${c.prefix}._
      if (run(${c.prefix}.quote {
        ${c.prefix}.query[$t].filter($filter).update(lift($entity))
      }) == 0) {
          run(quote {
            query[$t].insert(lift($entity))
          })
      }
      ()
    """
}

有关详细信息,请参阅我的回答 Generic macro with quill 或实施 CrudMacro:

您将在 quill-generic

上找到完整的项目
   package pl.jozwik.quillgeneric.quillmacro

    import scala.reflect.macros.whitebox.{ Context => MacroContext }

    class CrudMacro(val c: MacroContext) extends AbstractCrudMacro {

      import c.universe._

  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")

        }
    }
  }

      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"))
        """
      }
    }