如何使用 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 在他的回答中提到你必须使用宏。
中添加了实现通用插入或更新的代码
定义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"))
"""
}
}
我正在尝试使用 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 在他的回答中提到你必须使用宏。
中添加了实现通用插入或更新的代码定义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"))
"""
}
}