如何在函数中包含 slick API 隐式?

How to include slick API implicits in a function?

我正在使用 slick 3.1.0 实现一个根据需要创建表的功能,如下所示:

  def ensureTables(db: backend.DatabaseDef, ts: Seq[TableQuery[_]]) {
    val ts0 = Await.result(db.run(MTable.getTables), Duration.Inf)
    val ns = Set() ++ ts0.map(t => t.name.name)

    for {
      t <- ts
    } Await.result(db.run(t.schema.create), Duration.Inf)
  }

由于编译错误,还没有完成(我想为 for 表达式添加过滤器)。错误在第七行:value schema is not a member of slick.driver.SQLiteDriver.api.TableQuery[_]。这是因为 SQLiteDriver.API 中定义的隐式函数(在本例中为 tableQueryToTableQueryExtensionMethods)未包含在内。

如何正确地包含那些隐式函数?

当我想让我的数据库代码可插入 slick 中的不同驱动程序时,我通常使用配置文件类型对我的函数(或 traits/classes)进行参数化,并将配置文件作为参数提供;在你的情况下,函数看起来像这样:

def ensureTables[P <: JdbcProfile](profile: P)(db: profile.api.Database, ts: Seq[TableQuery[_]]) {
  import profile.api._
  ...
}

如果函数在特征或 class 中,它聚合了与数据库相关的函数,您可以在 class 级别提供配置文件并导入到那里。

顺便说一句,请注意每次使用 Await; 时都会阻塞两个线程并可能导致死锁;使用像 flatMap 这样的未来组合器来编写异步代码。

tl;dr

注意:这不是一个新答案,它只是详细阐述了 Aldo Stracquadanio 提供的答案。它被写成一个答案,以便在友好中显示更多细节方式。

起初,我尝试了以下版本,这是简单应用 Aldo 的提示的结果:

def ensureTables[P <: JdbcProfile]
                (profile: P)
                (db: profile.api.Database, ts: Seq[TableQuery[_]]) {
  import profile.api._
  val ts0 = Await.result(db.run(MTable.getTables), Duration.Inf)
  val ns = Set() ++ ts0.map(t => t.name.name)
  for (t <- ts) Await.result(db.run(t.schema.create), Duration.Inf)
}

错误仍然存​​在,因为 ts 的类型错误:tableQueryToTableQueryExtensionMethods 所需的类型是 Query[T, U, Seq] with TableQuery[T],而不是 TableQuery[_],因此没有隐式符合上述定义。

ensureTables 的正确版本应该这样实现:

def ensureTables[P <: RelationalProfile, T <: RelationalProfile#Table[_], C[_]]
                (profile: P)
                (db: profile.api.Database,
                 ts: Seq[Query[T, _, C] with TableQuery[T]]) {
  import profile.api._
  val ts0 = Await.result(db.run(MTable.getTables), Duration.Inf)
  val ns = Set() ++ ts0.map(t => t.name.name)
  for (t <- ts) Await.result(db.run(t.schema.create), Duration.Inf)
}

这个函数本身编译。但是,当我尝试将多个 TableQuery 值放在一起并使用它时,出现了两个新错误:

ensureTables(profile)(db, Seq(circles, rectangles))

结果

[error] SlickProg.scala:40: no type parameters for method ensureTables: (db: slick.driver.SQLiteDriver.profile.api.Database, ts: Seq[slick.driver.SQLiteDriver.api.Query[T, _, C] with slick.driver.SQLiteDriver.api.TableQuery[T]])Unit exist so that it can be applied to arguments (slick.driver.SQLiteDriver.backend.DatabaseDef, Seq[slick.lifted.TableQuery[_ >: slab.Rectangles with slab.Circles <: slick.driver.SQLiteDriver.Table[_ >: (Int, Double, Double, Double, Double, Double) with (Int, Double, Double, Double) <: Product with Serializable]]])
[error]  --- because ---
[error] argument expression's type is not compatible with formal parameter type;
[error]  found   : Seq[slick.lifted.TableQuery[_ >: slab.Rectangles with slab.Circles <: slick.driver.SQLiteDriver.Table[_ >: (Int, Double, Double, Double, Double, Double) with (Int, Double, Double, Double) <: Product with Serializable]]]
[error]  required: Seq[slick.driver.SQLiteDriver.api.Query[?T, _, ?C] with slick.driver.SQLiteDriver.api.TableQuery[?T]]
[error]     (which expands to)  Seq[slick.lifted.Query[?T, _, ?C] with slick.lifted.TableQuery[?T]]
[error]     ensureTables(profile)(db, Seq(circles, rectangles))
[error]                 ^
[error] SlickProg.scala:40: type mismatch;
[error]  found   : Seq[slick.lifted.TableQuery[_ >: slab.Rectangles with slab.Circles <: slick.driver.SQLiteDriver.Table[_ >: (Int, Double, Double, Double, Double, Double) with (Int, Double, Double, Double) <: Product with Serializable]]]
[error]  required: Seq[slick.driver.SQLiteDriver.api.Query[T, _, C] with slick.driver.SQLiteDriver.api.TableQuery[T]]
[error]     (which expands to)  Seq[slick.lifted.Query[T, _, C] with slick.lifted.TableQuery[T]]
[error]     ensureTables(profile)(db, Seq(circles, rectangles))
[error]                                  ^
[error] two errors found

这是由于将不同类型的值放入单个序列中导致意外键入的序列。编译器为序列的元素计算了一个通用类型 (_ >: slab.Rectangles with slab.Circles <: slick.driver.SQLiteDriver.Table[_ >: (Int, Double, Double, Double, Double, Double) with (Int, Double, Double, Double) <: Product with Serializable])。

我最终得到以下实现,从 ensureTable 函数外部迭代表:

def ignore[T](x: T): Unit = ()

def valueOf[T](f: Future[T]): T = Await.result(f, Duration.Inf)

def ensureTable[P <: RelationalProfile, T <: RelationalProfile#Table[_], C[_]]
               (p: P)
               (db: p.api.Database, ns: Set[String],
                t: Query[T, _, C] with TableQuery[T]) {
  import p.api._
  val n = t.shaped.value.tableName
  if (!ns.contains(n))
    ignore(valueOf(db.run(t.schema.create)))
}

def tablesOf[P <: RelationalProfile](p: P)(db: p.api.Database): Seq[MTable] =
   valueOf(db.run(MTable.getTables))

val names = Set() ++ tablesOf(profile)(db).map(_.name.name)
for (t <- Seq(circles, rectangles)) ensureTable(profile)(db, names, t)

谢谢 Aldo Stracquadanio。