将数据库模式与现有的光滑表进行比较

Compare database schema with existing slick tables

我正在使用 ScalaSlickPostgres 构建应用程序。我已经使用 Slick code generator 生成了漂亮的 tables。

我想知道是否有任何方法可以验证数据库 table 模式和 slick table 模式是否匹配,并为我的应用程序中的所有 slick table 执行此操作.

例如:

class DepartmentTable(_tableTag: Tag) extends Table[Department](_tableTag, Some("base"), "Department") {
    val id: Rep[Long] = column[Long]("DepartmentId", O.AutoInc, O.PrimaryKey)
    val name: Rep[String] = column[String]("Name", O.Length(50,varying=true))
    val shortCode: Rep[String] = column[String]("ShortCode", O.Length(50,varying=true))
    def * = ???
    def ? = ???
} 

我更改了数据库 table,比如将列 parentDepartmentId 添加到 table,然后将其添加到 Slick table。很多时候,测试数据库上的更改脚本不是 运行,因此我们会遇到一些 运行 时间异常。

为了避免此类问题,我试图实现一些东西来检查 slick table 是否与实际的 postgres table 匹配。可以实现吗?

我尝试了反射,但无法从圆滑的 table 中获取所有细节。例如:实际列名

Slick Version : 3.0

我想达到什么目的?

在应用程序启动时,我想将数据库架构与 slick 架构进行比较。

我的计划:

  1. 从我的应用程序中获取所有 TableQuery/Slick Tables

  2. 使用 Slick Meta 获取实际的数据库模式

  3. 将光滑的table查询结构与实际的数据库进行比较

现在,按照 Maxim 的建议,我可以创建一个注册表并将每个 table 添加到注册表中。我只是想检查是否还有其他方法。原因是,如果我或其他人不小心删除了向注册表添加几个 table 查询,则不会完成对 table 的检查。我只是想更安全,但不确定是否存在任何此类方法。

您可以使用 slick.meta 来实现。您并不是说您使用的是哪个版本的 slick,所以我将展示一个使用 slick 3.0 的示例,但如果您使用 slick 2.x 将 DBIO 替换为旧 withSession API 并删除对 ExecutionContextFuture.

的引用

这是假设您在范围内有一个隐式 ExecutionContext,您导入 YourDriver.api._ 并替换 ??? 与一个实际的 Database 实例:

val db: Database = ???

val tablesWithCols = for {
  tables <- slick.jdbc.meta.MTable.getTables
  withCols <- DBIO.sequence(tables.map(t => t.getColumns.map((t, _))))
} yield withCols

val printLines: DBIO[Seq[String]] = tablesWithCols.map {
  _.map {
    case (t, cs) => s"Table: ${t.name.name} - columns: ${cs.map(_.name).mkString(", ")}"
  }
}

val res: Future[Seq[String]] = db.run(printLines)

res.foreach(println)

此外,请注意最后一个 foreach 调用是在 Future 上执行的,因此您可能希望等待未来完成或(更好)将其与相关计算链接起来;如果您的程序在没有 waiting/chaining 的情况下终止,您可能不会从那里看到任何东西。

令人惊讶的是,从光滑的 table 定义中获取信息有点复杂;我发现这样做的唯一方法是这样的:

TableQuery[YourTable].toNode.getDumpInfo

这会给你一个类似 AST 的结构,你可以遍历它来得到你需要的定义;结构本身并不是那么令人愉快,但它应该包含你需要的一切。

您可以探索的另一种避免这种麻烦的方法是创建一个层来包装光滑定义的生成并以更易于访问的方式公开相关元数据;不过不确定这是否会给您带来更大的麻烦。

这是一个示例,说明如何检测给定 Slick table 数据库模式中所有列的编号、名称和 SQL 类型是否应该与 table 等于 table

的 Slick table 描述中列的数量、名称和 SQL 类型
def ?[AT <: AbstractTable[_]](tableQuery: profile.api.TableQuery[AT])
                             (implicit ec: ExecutionContext) = {
  val table = tableQuery.baseTableRow.create_*.map(c =>
    (c.name, profile.jdbcTypeFor(c.tpe).sqlType)).toSeq.sortBy(_._1)
  MTable.getTables(tableQuery.baseTableRow.tableName).headOption.map(
    _.map{_.getColumns.map(
      _.sortBy(_.name).map(c => (c.name, c.sqlType)) == table
    )}
  ) flatMap (_.head)
}

您还可以检测索引、主键和外键是否在一定程度上相同。为此,您可以相应地组合

tableQuery.baseTableRow.indexes    
tableQuery.baseTableRow.primaryKeys
tableQuery.baseTableRow.foreignKeys

用MTable的以下方法

getIndexInfo    
getPrimaryKeys
getImportedKeys

正如我在摘录中对 tableQuery.baseTableRow.create_*getColumns 所做的那样。

现在有了这个方法,您可以轻松检查代码中的所有 table。唯一真正简单的问题是如何获得他们的名单。说实话,我什至不明白这怎么会是个问题,因为这只是保持一个集中式注册表的问题,每次在 您的 中创建它时,您都可以在其中登记 代码,您可以查询其中存储的对象。假设您有这样的 registry 方法 enlistTablelistTables 那么您的工作流程将类似于

val departmentTable = TableQuery[DepartmentTable]
regsitry.enlistTable(departmentTable)
...
val someTable = TableQuery[SomeTableStructureClass]
regsitry.enlistTable(someTable)   
...
val anotherTable = TableQuery[AnotherTableStructureClass]
regsitry.enlistTable(anotherTable)   
...
for(table <- regsitry.listTables)
  db run ?(table) map ( columnsAndTypesAreIdentical => ... ) 
...

.

您默认使用的 Slick 代码生成器 "generates Table classes, corresponding TableQuery values,..., as well as case classes for holding complete rows of values" 对应的 TableQuery 值 的格式完全是 val someTable = TableQuery[SomeTableStructureClass].