我是Slick的新手。我正在使用Scala,ScalaTest和Slick为Java应用程序创建测试套件。我在测试之前使用slick来准备数据,并在测试之后对数据进行断言。使用的数据库有一些超过22列的表。我使用 slick-codegen 来生成我的架构代码。
对于列数超过22的表,slick-codegen不会生成案例类,而是基于HList的自定义类型和伴随“构造函数”方法。据我了解,这是因为元组和案例类只能有22个字段的限制。生成代码的方式,Row对象的字段只能通过索引访问。
我对此有几个问题:
override def hlistEnabled = false
中设置了SourceCodeGenerator
。但这导致Cannot generate tuple for > 22 columns, please set hlistEnable=true or override compound.
所以我没有能够解除HList的意义。可能是'或覆盖复合'部分,但我不明白这意味着什么。SourceCodeGenerator
。但除了这个用例之外,代码生成器的文档对我 没有多大帮助。我真的很感激这里的一些帮助。提前谢谢!
答案 0 :(得分:6)
我最终进一步定制了 slick-codegen 。首先,我会回答我自己的问题,然后我会发布我的解决方案。
所以,我最终为超过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。