gpt4 book ai didi

scala - 流畅的代码生成器和 > 22 列的表格

转载 作者:行者123 更新时间:2023-12-04 16:51:38 27 4
gpt4 key购买 nike

我是 Slick 的新手。我正在使用 Scala、ScalaTest 和 Slick 为 Java 应用程序创建测试套件。我使用 slick 在测试前准备数据并在测试后对数据进行断言。使用的数据库有一些超过 22 列的表。我用 slick-codegen 生成我的架构代码。

对于超过 22 列的表,slick-codegen 不会生成案例类,而是基于 HList 的自定义类型和伴随的“构造函数”方法。据我了解,这是因为元组和案例类只能有 22 个字段的限制。代码的生成方式,Row-object 的字段只能通过索引访问。

我对此有几个问题:

  • 据我了解,案例类的 22 个字段限制已在 Scala 2.11 中修复,对吗?
  • 如果是这种情况,是否可以自定义 slick-codegen 来为所有表生成案例类?我调查了一下:我设法设置了 override def hlistEnabled = false在一个被覆盖的 SourceCodeGenerator .但这会导致 Cannot generate tuple for > 22 columns, please set hlistEnable=true or override compound.所以我不明白能够取消 HList 的意义。可能问题在于“或覆盖复合”部分,但我不明白这是什么意思。
  • 在 slick 和 22 列上搜索互联网,我遇到了一些基于嵌套元组的解决方案。是否可以自定义代码生成器以使用这种方法?
  • 如果生成具有 > 22 个字段的 case 类的代码不是一个可行的选择,我认为可以生成一个普通类,它对每一列都有一个“访问器”功能,从而从基于索引的访问提供“映射”到基于名称的访问。我很乐意自己实现这一代,但我想我需要一些指示从哪里开始。我认为它应该能够为此覆盖标准代码生成器。我已经使用了一个被覆盖的 SourceCodeGenerator对于某些自定义数据类型。但是除了这个用例之外,代码生成器的文档对我帮助不大。

  • 我真的很感激这里的一些帮助。提前致谢!

    最佳答案

    我最终进一步定制 slick-codegen .首先,我会回答我自己的问题,然后我会发布我的解决方案。

    问题的答案

  • 对于案例类,可能会取消 22 元数限制,但不适用于元组。并且 slick-codegen 也会生成一些元组,当我问这个问题时我并没有完全意识到。
  • 不相关,请参阅答案 1。(如果元组的 22 个元数限制也被取消,这可能会变得相关。)
  • 我选择不进一步调查这个问题,所以这个问题暂时没有答案。
  • 这是我最终采取的方法。

  • 解决方案:生成的代码

    所以,我最终为超过 22 列的表生成了“普通”类。让我举例说明我现在生成的内容。 (生成器代码如下。)(出于简洁和可读性原因,此示例少于 22 列。)
    case class BigAssTableRow(val id: Long, val name: String, val age: Option[Int] = None)

    type BigAssTableRowList = HCons[Long,HCons[String,HCons[Option[Int]]], HNil]

    object BigAssTableRow {
    def apply(hList: BigAssTableRowList) = new BigAssTableRow(hlist.head, hList.tail.head, hList.tail.tail.head)
    def unapply(row: BigAssTableRow) = Some(row.id :: row.name :: row.age)
    }

    implicit def GetResultBoekingenRow(implicit e0: GR[Long], e1: GR[String], e2: GR[Optional[Int]]) = GR{
    prs => import prs._
    BigAssTableRow.apply(<<[Long] :: <<[String] :: <<?[Int] :: HNil)
    }

    class BigAssTable(_tableTag: Tag) extends Table[BigAssTableRow](_tableTag, "big_ass") {
    def * = id :: name :: age :: :: HNil <> (BigAssTableRow.apply, BigAssTableRow.unapply)

    val id: Rep[Long] = column[Long]("id", O.PrimaryKey)
    val name: Rep[String] = column[String]("name", O.Length(255,varying=true))
    val age: Rep[Option[Int]] = column[Option[Int]]("age", O.Default(None))
    }

    lazy val BigAssTable = new TableQuery(tag => new BigAssTable(tag))

    最难的部分是找出 *映射在 Slick 中工作。文档不多,但我找到了 this Stackoverflow answer比较有启发性。

    我创建了 BigAssTableRow object使用 HList对客户端代码透明。请注意 apply对象中的函数重载 apply从案例类。所以我仍然可以通过调用 BigAssTableRow(id: 1L, name: "Foo") 创建实体, 而 *投影仍然可以使用 apply接受 HList 的函数.

    所以,我现在可以做这样的事情:
    // I left out the driver import as well as the scala.concurrent imports 
    // for the Execution context.

    val collection = TableQuery[BigAssTable]
    val row = BigAssTableRow(id: 1L, name: "Qwerty") // Note that I leave out the optional age

    Await.result(db.run(collection += row), Duration.Inf)

    Await.result(db.run(collection.filter(_.id === 1L).result), Duration.Inf)

    对于此代码,无论在后台使用元组还是 HList,它都是完全透明的。

    解决方案:这是如何生成的

    我将在这里发布我的整个生成器代码。它并不完美;如果您有改进建议,请告诉我!巨大的部分只是从 slick.codegen.AbstractSourceCodeGenerator 复制而来的和相关的类,然后略有变化。还有一些和这个问题没有直接关系的东西,比如加了 java.time.*数据类型和特定表的过滤。我把它们留在里面,因为它们可能有用。另请注意,此示例适用于 Postgres 数据库。
    import slick.codegen.SourceCodeGenerator
    import slick.driver.{JdbcProfile, PostgresDriver}
    import slick.jdbc.meta.MTable
    import slick.model.Column

    import scala.concurrent.Await
    import scala.concurrent.ExecutionContext.Implicits.global
    import scala.concurrent.duration.Duration

    object MySlickCodeGenerator {
    val slickDriver = "slick.driver.PostgresDriver"
    val jdbcDriver = "org.postgresql.Driver"
    val url = "jdbc:postgresql://localhost:5432/dbname"
    val outputFolder = "/path/to/project/src/test/scala"
    val pkg = "my.package"
    val user = "user"
    val password = "password"

    val driver: JdbcProfile = Class.forName(slickDriver + "$").getField("MODULE$").get(null).asInstanceOf[JdbcProfile]
    val dbFactory = driver.api.Database
    val db = dbFactory.forURL(url, driver = jdbcDriver, user = user, password = password, keepAliveConnection = true)

    // The schema is generated using Liquibase, which creates these tables that I don't want to use
    def excludedTables = Array("databasechangelog", "databasechangeloglock")

    def tableFilter(table: MTable): Boolean = {
    !excludedTables.contains(table.name.name) && schemaFilter(table.name.schema)
    }

    // There's also an 'audit' schema in the database, I don't want to use that one
    def schemaFilter(schema: Option[String]): Boolean = {
    schema match {
    case Some("public") => true
    case None => true
    case _ => false
    }
    }

    // Fetch data model
    val modelAction = PostgresDriver.defaultTables
    .map(_.filter(tableFilter))
    .flatMap(PostgresDriver.createModelBuilder(_, ignoreInvalidDefaults = false).buildModel)

    val modelFuture = db.run(modelAction)

    // customize code generator
    val codegenFuture = modelFuture.map(model => new SourceCodeGenerator(model) {

    // add custom import for added data types
    override def code = "import my.package.Java8DateTypes._" + "\n" + super.code

    override def Table = new Table(_) {
    table =>

    // Use different factory and extractor functions for tables with > 22 columns
    override def factory = if(columns.size == 1) TableClass.elementType else if(columns.size <= 22) s"${TableClass.elementType}.tupled" else s"${EntityType.name}.apply"
    override def extractor = if(columns.size <= 22) s"${TableClass.elementType}.unapply" else s"${EntityType.name}.unapply"

    override def EntityType = new EntityTypeDef {
    override def code = {
    val args = columns.map(c =>
    c.default.map( v =>
    s"${c.name}: ${c.exposedType} = $v"
    ).getOrElse(
    s"${c.name}: ${c.exposedType}"
    )
    )
    val callArgs = columns.map(c => s"${c.name}")
    val types = columns.map(c => c.exposedType)

    if(classEnabled){
    val prns = (parents.take(1).map(" extends "+_) ++ parents.drop(1).map(" with "+_)).mkString("")
    s"""case class $name(${args.mkString(", ")})$prns"""
    } else {
    s"""
    /** Constructor for $name providing default values if available in the database schema. */
    case class $name(${args.map(arg => {s"val $arg"}).mkString(", ")})
    type ${name}List = ${compoundType(types)}
    object $name {
    def apply(hList: ${name}List): $name = new $name(${callArgs.zipWithIndex.map(pair => s"hList${tails(pair._2)}.head").mkString(", ")})
    def unapply(row: $name) = Some(${compoundValue(callArgs.map(a => s"row.$a"))})
    }
    """.trim
    }
    }
    }

    override def PlainSqlMapper = new PlainSqlMapperDef {
    override def code = {
    val positional = compoundValue(columnsPositional.map(c => if (c.fakeNullable || c.model.nullable) s"<<?[${c.rawType}]" else s"<<[${c.rawType}]"))
    val dependencies = columns.map(_.exposedType).distinct.zipWithIndex.map{ case (t,i) => s"""e$i: GR[$t]"""}.mkString(", ")
    val rearranged = compoundValue(desiredColumnOrder.map(i => if(columns.size > 22) s"r($i)" else tuple(i)))
    def result(args: String) = s"$factory($args)"
    val body =
    if(autoIncLastAsOption && columns.size > 1){
    s"""
    val r = $positional
    import r._
    ${result(rearranged)} // putting AutoInc last
    """.trim
    } else {
    result(positional)
    }

    s"""
    implicit def $name(implicit $dependencies): GR[${TableClass.elementType}] = GR{
    prs => import prs._
    ${indent(body)}
    }
    """.trim
    }
    }

    override def TableClass = new TableClassDef {
    override def star = {
    val struct = compoundValue(columns.map(c=>if(c.fakeNullable)s"Rep.Some(${c.name})" else s"${c.name}"))
    val rhs = s"$struct <> ($factory, $extractor)"
    s"def * = $rhs"
    }
    }

    def tails(n: Int) = {
    List.fill(n)(".tail").mkString("")
    }

    // override column generator to add additional types
    override def Column = new Column(_) {
    override def rawType = {
    typeMapper(model).getOrElse(super.rawType)
    }
    }
    }
    })

    def typeMapper(column: Column): Option[String] = {
    column.tpe match {
    case "java.sql.Date" => Some("java.time.LocalDate")
    case "java.sql.Timestamp" => Some("java.time.LocalDateTime")
    case _ => None
    }
    }

    def doCodeGen() = {
    def generator = Await.result(codegenFuture, Duration.Inf)
    generator.writeToFile(slickDriver, outputFolder, pkg, "Tables", "Tables.scala")
    }

    def main(args: Array[String]) {
    doCodeGen()
    db.close()
    }
    }

    关于scala - 流畅的代码生成器和 > 22 列的表格,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36618280/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com