3

我正在尝试使用 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))

如何修改我的代码以使其正常工作?

4

3 回答 3

3

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

我在示例 Quill 项目中添加了实现通用插入或更新的代码。

它定义trait Queries混合到上下文中

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

此 trait 使用来实现您的代码,只需稍作更改:

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

用法示例

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

PS 因为update()返回更新记录的数量,您的代码可以简化为:

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))
          })
      }
      ()
    """
}
于 2017-06-28T08:20:37.743 回答
0

有关更多详细信息,请参阅我的答案Generic macro with quill或 implementation 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"))
        """
      }
    }
于 2019-07-06T19:44:39.060 回答
0

正如 quill 贡献者之一在本期中所说:

如果您想让您的解决方案通用,那么您必须使用,因为 Quill 在编译时生成查询,并且T必须在那时解析类型。

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$2$$anonfun$apply$1.apply(Typers.scala:34)
[error]         at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2$$anonfun$apply$1.apply(Typers.scala:28)

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

于 2017-06-27T16:58:36.733 回答