光滑的codegen&表> 22列

时间:2016-04-14 08:59:57

标签: scala slick slick-codegen

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

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

我对此有几个问题:

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

4 个答案:

答案 0 :(得分:6)

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

问题解答

  1. 对于案例类,可以解除22个arity限制,而不是元组。而且slick-codegen也会产生一些元组,当我提出这个问题时,我并不完全清楚这一点。
  2. 不相关,请参阅答案1.(如果元组的22个arity限制也被提升,这可能会变得相关。)
  3. 我选择不再对此进行调查,所以现在这个问题仍然没有答案。
  4. 这是我最终采取的方法。
  5. 解决方案:生成的代码

    所以,我最终为超过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的{​​{1}}函数。

    所以,我现在可以做这样的事情:

    HList

    对于这段代码,它是完全透明的,在引擎盖下使用了元组或HLists。

    解决方案:如何生成

    我将在这里发布我的整个生成器代码。它并不完美;如果您有改进建议,请告诉我!只需从// 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) 及相关类中复制大量部分,然后稍加更改。还有一些与此问题没有直接关系的事情,例如添加slick.codegen.AbstractSourceCodeGenerator数据类型和过滤特定表。我把它们留了下来,因为它们可能有用。另请注意,此示例适用于Postgres数据库。

    java.time.*

答案 1 :(得分:4)

更新2019-02-15 随着Slick 3.3.0的发布,as answered由@Marcus发布,内置支持代码生成表&gt; 22列。但是,code generation of views is still an open issue。以下解决方案虽然仅适用于Slick 3.2.0,但使用&gt;为表和视图提供代码生成。 22列。

从Slick 3.2.0开始,&gt; 22 param案例类的最简单解决方案是使用mapTo代替* method <> operator来定义documented unit test中的默认投影。 {3}}):

case class BigCase(id: Int,
                   p1i1: Int, p1i2: Int, p1i3: Int, p1i4: Int, p1i5: Int, p1i6: Int,
                   p2i1: Int, p2i2: Int, p2i3: Int, p2i4: Int, p2i5: Int, p2i6: Int,
                   p3i1: Int, p3i2: Int, p3i3: Int, p3i4: Int, p3i5: Int, p3i6: Int,
                   p4i1: Int, p4i2: Int, p4i3: Int, p4i4: Int, p4i5: Int, p4i6: Int)

class bigCaseTable(tag: Tag) extends Table[BigCase](tag, "t_wide") {
      def id = column[Int]("id", O.PrimaryKey)
      def p1i1 = column[Int]("p1i1")
      def p1i2 = column[Int]("p1i2")
      def p1i3 = column[Int]("p1i3")
      def p1i4 = column[Int]("p1i4")
      def p1i5 = column[Int]("p1i5")
      def p1i6 = column[Int]("p1i6")
      def p2i1 = column[Int]("p2i1")
      def p2i2 = column[Int]("p2i2")
      def p2i3 = column[Int]("p2i3")
      def p2i4 = column[Int]("p2i4")
      def p2i5 = column[Int]("p2i5")
      def p2i6 = column[Int]("p2i6")
      def p3i1 = column[Int]("p3i1")
      def p3i2 = column[Int]("p3i2")
      def p3i3 = column[Int]("p3i3")
      def p3i4 = column[Int]("p3i4")
      def p3i5 = column[Int]("p3i5")
      def p3i6 = column[Int]("p3i6")
      def p4i1 = column[Int]("p4i1")
      def p4i2 = column[Int]("p4i2")
      def p4i3 = column[Int]("p4i3")
      def p4i4 = column[Int]("p4i4")
      def p4i5 = column[Int]("p4i5")
      def p4i6 = column[Int]("p4i6")

      // HList-based wide case class mapping
      def m3 = (
        id ::
        p1i1 :: p1i2 :: p1i3 :: p1i4 :: p1i5 :: p1i6 ::
        p2i1 :: p2i2 :: p2i3 :: p2i4 :: p2i5 :: p2i6 ::
        p3i1 :: p3i2 :: p3i3 :: p3i4 :: p3i5 :: p3i6 ::
        p4i1 :: p4i2 :: p4i3 :: p4i4 :: p4i5 :: p4i6 :: HNil
      ).mapTo[BigCase]

      def * = m3
}

修改

因此,如果您希望slick-codegen使用上述mapTo方法生成大型表,则将the relevant parts覆盖到代码生成器并添加mapTo语句:

package your.package
import slick.codegen.SourceCodeGenerator
import slick.{model => m}


class HugeTableCodegen(model: m.Model) extends SourceCodeGenerator(model) with GeneratorHelpers[String, String, String]{


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

    // always defines types using case classes
    override def EntityType = new EntityTypeDef{
      override def classEnabled = true
    }

    // allow compound statements using HNil, but not for when "def *()" is being defined, instead use mapTo statement
    override def compoundValue(values: Seq[String]): String = {
      // values.size>22 assumes that this must be for the "*" operator and NOT a primary/foreign key
      if(hlistEnabled && values.size > 22) values.mkString("(", " :: ", s" :: HNil).mapTo[${StringExtensions(model.name.table).toCamelCase}Row]")
      else if(hlistEnabled) values.mkString(" :: ") + " :: HNil"
      else if (values.size == 1) values.head
      else s"""(${values.mkString(", ")})"""
    }

    // should always be case classes, so no need to handle hlistEnabled here any longer
    override def compoundType(types: Seq[String]): String = {
      if (types.size == 1) types.head
      else s"""(${types.mkString(", ")})"""
    }
  }
}

然后在单独的项目as documented中构造codegen代码,以便它在编译时生成源代码。与记录的内容不同,您不需要编写自己的主要方法。相反,您可以将您的类名作为参数传递给您正在扩展的SourceCodeGenerator

lazy val generateSlickSchema = taskKey[Seq[File]]("Generates Schema definitions for SQL tables")
generateSlickSchema := {

  val managedSourceFolder = sourceManaged.value / "main" / "scala"
  val packagePath = "your.sql.table.package"

  (runner in Compile).value.run(
    "slick.codegen.SourceCodeGenerator", (dependencyClasspath in Compile).value.files,
    Array(
      "env.db.connectorProfile",
      "slick.db.driver",
      "slick.db.url",
      managedSourceFolder.getPath,
      packagePath,
      "slick.db.user",
      "slick.db.password",
      "true",
      "your.package.HugeTableCodegen"
    ),
    streams.value.log
  )
  Seq(managedSourceFolder / s"${packagePath.replace(".","/")}/Tables.scala")
}

答案 2 :(得分:1)

您可以找到的选项很少 - 嵌套元组,从Slick HList转换为Shapeless HList,然后转换为案例类等等。

我发现所有这些选项对于任务而言过于复杂,并使用自定义的Slick Codegen生成带有访问器的简单包装器类。

看看这个gist

class MyCodegenCustomisations(model: Model) extends slick.codegen.SourceCodeGenerator(model){
import ColumnDetection._


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

    val columnIndexByName = columns.map(_.name).zipWithIndex.toMap
    def getColumnIndex(columnName: String): Option[Int] = {
        columnIndexByName.get(columnName)

    }

    private def getWrapperCode: Seq[String] = {
        if (columns.length <= 22) {
            //do not generate wrapper for tables which get case class generated by Slick
            Seq.empty[String]
        } else {
            val lines =
                columns.map{c =>
                    getColumnIndex(c.name) match {
                        case Some(colIndex) =>
                            //lazy val firstname: Option[String] = row.productElement(1).asInstanceOf[Option[String]]
                            val colType = c.exposedType
                            val line = s"lazy val ${c.name}: $colType = values($colIndex).asInstanceOf[$colType]"
                            line
                        case None => ""
                    }
                }
            Seq("",
                "/*",
                "case class Wrapper(private val row: Row) {",
                "// addressing HList by index is very slow, let's convert it to vector",
                "private lazy val values = row.toList.toVector",
                ""

            ) ++ lines ++ Seq("}", "*/", "")

        }
    }


    override def code: Seq[String] = {
        val originalCode = super.code
        originalCode ++ this.getWrapperCode
    }


}

}

答案 3 :(得分:1)

此问题在Slick 3.3中得到解决: https://github.com/slick/slick/pull/1889/

此解决方案提供def *def ?,并且还支持纯SQL。