Scala Slick,如何仅在不存在时创建模式

Scala Slick, how to create Schema ONLY if it does not exist

在 Scala Slick 中,可以使用以下内容创建数据库模式:

val schema = coffees.schema ++ suppliers.schema
db.run(DBIO.seq(
  schema.create
))

从本文档页面的底部开始http://slick.typesafe.com/doc/3.0.0/schemas.html

但是,如果数据库模式已经存在,则会引发异常。

当且仅当它不存在时,是否有正常或正确的方法来创建模式?

为什么不简单地检查 create 之前是否存在?

val schema = coffees.schema ++ suppliers.schema
db.run(DBIO.seq(
  if (!MTable.getTables.list.exists(_.name.name == MyTable.tableName)){
    schema.create
  }
))

在 Slick 3.0 中,Mtable.getTables 是一个 DBAction,所以像这样的东西可以工作:

val coffees = TableQuery[Coffees]
try {
  Await.result(db.run(DBIO.seq(
    MTable.getTables map (tables => {
      if (!tables.exists(_.name.name == coffees.baseTableRow.tableName))
        coffees.schema.create
    })
  )), Duration.Inf)
} finally db.close

points out about the 一样,table没有创建。我设法通过稍微修改第一个答案的代码使其工作:

val coffees = TableQuery[Coffees]

try {
  def createTableIfNotInTables(tables: Vector[MTable]): Future[Unit] = {
    if (!tables.exists(_.name.name == events.baseTableRow.tableName)) {
      db.run(coffees.schema.create)
    } else {
      Future()
    }
  }

  val createTableIfNotExist: Future[Unit] = db.run(MTable.getTables).flatMap(createTableIfNotInTables)

  Await.result(createTableIfNotExist, Duration.Inf)
} finally db.close

使用以下导入:

import slick.jdbc.meta.MTable
import slick.driver.SQLiteDriver.api._

import scala.concurrent.{Await, Future}
import scala.concurrent.duration.Duration
import scala.concurrent.ExecutionContext.Implicits.global

这就是我使用 slick 3.1.1 和 Postgres 对多个表所做的

import slick.driver.PostgresDriver.api._
import slick.jdbc.meta.MTable
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import scala.concurrent.ExecutionContext.Implicits.global

val t1 = TableQuery[Table1]
val t2 = TableQuery[Table2]
val t3 = TableQuery[Table3]
val tables = List(t1, t2, t3)

val existing = db.run(MTable.getTables)
val f = existing.flatMap( v => {
    val names = v.map(mt => mt.name.name)
    val createIfNotExist = tables.filter( table =>
        (!names.contains(table.baseTableRow.tableName))).map(_.schema.create)
    db.run(DBIO.sequence(createIfNotExist))
})
Await.result(f, Duration.Inf)

在 Slick 3.3.0 中添加了 createIfNotExistsdropIfExists 模式方法。所以:

db.run(coffees.schema.createIfNotExists)

用谷歌搜索这个问题,并根据答案尝试了几种解决方案,直到弄明白。

不能在由 3 个 table 组成的架构上使用 createIfNotExists,其中一个 table 具有复合主键。这里,第 3 个 table 有一个主键,由第 1 个和第 2 个 table 中的每一个的主键组成。第二次遇到 .createIfNotExists 时,此模式出现错误。我在 scala 2.12.8 上使用 slick 3.3.1。

    class UserTable(tag: Tag) extends Table[User](tag, "user") {
      def id    = column[Long]("id", O.AutoInc, O.PrimaryKey)
      def name  = column[String]("name")
      def email = column[Option[String]]("email")

      def * = (id.?, name, email).mapTo[User]
    }
    val users = TableQuery[UserTable]
    lazy val insertUser = users returning users.map(_.id)

    case class Room(title: String, id: Long = 0L)
    class RoomTable(tag: Tag) extends Table[Room](tag, "room") {
     def id    = column[Long]("id", O.PrimaryKey, O.AutoInc)
     def title = column[String]("title")
     def * = (title, id).mapTo[Room]
    }
    val rooms = TableQuery[RoomTable]
    lazy val insertRoom = rooms returning rooms.map(_.id)

    case class Occupant(roomId: Long, userId: Long)
    class OccupantTable(tag: Tag) extends Table[Occupant](tag, "occupant") {
      def roomId = column[Long]("room")
      def userId = column[Long]("user")

      def pk = primaryKey("room_user_pk", (roomId, userId) )

      def * = (roomId, userId).mapTo[Occupant]
    }
    val occupants = TableQuery[OccupantTable]

首先我可以成功创建模式并添加用户、房间和居住者。在如下所示第二次使用 .createIfNotExists 时,出现重复主键错误:

  println("\n2nd run on .createIfNotExists using different values for users, rooms and occupants")
  val initdup = for {
    _         <- users.schema.createIfNotExists
    _         <- rooms.schema.createIfNotExists
    _         <- occupants.schema.createIfNotExists
      curlyId   <- insertUser += User(None, "Curly", Some("curly@example.org"))
      larryId   <- insertUser += User(None, "Larry")
      moeId     <- insertUser += User(None, "Moe", Some("moe@example.org"))
      shedId   <- insertRoom += Room("Shed")
      _         <- occupants += Occupant(shedId, curlyId)
      _         <- occupants += Occupant(shedId, moeId)
    } yield ()

异常情况如下:

2nd run on .createIfNotExists using different values for users, rooms and occupants
[error] (run-main-2) org.h2.jdbc.JdbcSQLException: Constraint "room_user_pk" already exists; SQL statement:
[error] alter table "occupant" add constraint "room_user_pk" primary key("room","user") [90045-197]
[error] org.h2.jdbc.JdbcSQLException: Constraint "room_user_pk" already exists; SQL statement:
[error] alter table "occupant" add constraint "room_user_pk" primary key("room","user") [90045-197]
[error]         at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
[error]         at org.h2.message.DbException.get(DbException.java:179)
[error]         at org.h2.message.DbException.get(DbException.java:155)
[error]         at org.h2.command.ddl.AlterTableAddConstraint.tryUpdate(AlterTableAddConstraint.java:110)
[error]         at org.h2.command.ddl.AlterTableAddConstraint.update(AlterTableAddConstraint.java:78)
[error]         at org.h2.command.CommandContainer.update(CommandContainer.java:102)
[error]         at org.h2.command.Command.executeUpdate(Command.java:261)
[error]         at org.h2.jdbc.JdbcPreparedStatement.execute(JdbcPreparedStatement.java:249)
[error]         at slick.jdbc.JdbcActionComponent$SchemaActionExtensionMethodsImpl$$anon.$anonfun$run(JdbcActionComponent.scala:292)
[error]         at slick.jdbc.JdbcActionComponent$SchemaActionExtensionMethodsImpl$$anon.$anonfun$run$adapted(JdbcActionComponent.scala:292)
[error]         at slick.jdbc.JdbcBackend$SessionDef.withPreparedStatement(JdbcBackend.scala:425)
[error]         at slick.jdbc.JdbcBackend$SessionDef.withPreparedStatement$(JdbcBackend.scala:420)
[error]         at slick.jdbc.JdbcBackend$BaseSession.withPreparedStatement(JdbcBackend.scala:489)
[error]         at slick.jdbc.JdbcActionComponent$SchemaActionExtensionMethodsImpl$$anon.$anonfun$run(JdbcActionComponent.scala:292)
[error]         at slick.jdbc.JdbcActionComponent$SchemaActionExtensionMethodsImpl$$anon.$anonfun$run$adapted(JdbcActionComponent.scala:292)
[error]         at scala.collection.Iterator.foreach(Iterator.scala:941)
[error]         at scala.collection.Iterator.foreach$(Iterator.scala:941)
[error]         at scala.collection.AbstractIterator.foreach(Iterator.scala:1429)
[error]         at scala.collection.IterableLike.foreach(IterableLike.scala:74)
[error]         at scala.collection.IterableLike.foreach$(IterableLike.scala:73)
[error]         at scala.collection.AbstractIterable.foreach(Iterable.scala:56)
[error]         at slick.jdbc.JdbcActionComponent$SchemaActionExtensionMethodsImpl$$anon.run(JdbcActionComponent.scala:292)
[error]         at slick.jdbc.JdbcActionComponent$SchemaActionExtensionMethodsImpl$$anon.run(JdbcActionComponent.scala:290)
[error]         at slick.jdbc.JdbcActionComponent$SimpleJdbcProfileAction.run(JdbcActionComponent.scala:28)
[error]         at slick.jdbc.JdbcActionComponent$SimpleJdbcProfileAction.run(JdbcActionComponent.scala:25)
[error]         at slick.basic.BasicBackend$DatabaseDef$$anon.liftedTree1(BasicBackend.scala:276)
[error]         at slick.basic.BasicBackend$DatabaseDef$$anon.run(BasicBackend.scala:276)
[error]         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
[error]         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
[error]         at java.lang.Thread.run(Thread.java:748)
[error] Nonzero exit code: 1
[error] (Compile / run) Nonzero exit code: 1

此外,我可以在所有 table 都是按照 O.PrimaryKey 约定创建的架构上多次使用 .createIfNotExists。

我可以做一些按摩代码吗?是否有解决方法,使 .createIfNotExists 仍可用于复合主键案例?