如何使用新的Slick 2.0 HList来克服22列限制?

16

我正在编写Slick代码以针对一个包含两个表和超过22列的旧模式。我该如何使用新的HList代码?在Scala 2.10.3下,除此之外我已经成功地使用了2.0-M3。

这是我目前使用的与case类/元组相关的语法。如果我要使用文档中提到的新的HLists,我该怎么做?

  case class Joiner(
      id: Int,
      name: Option[String],
      contact: Option[String]
  )

  class Joiners(tag: Tag) extends Table[Joiner](tag, "joiner") {
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc, O.DBType("int(11)"))
    def name = column[Option[String]]("name", O.DBType("varchar(255)"))
    def contact = column[Option[String]]("contact", O.DBType("text"))
    def * = (id, name.?, contact.?) <> (Joiner.tupled, Joiner.unapply)
  }
  val joiners = TableQuery[Joiners]

我在示例中没有看到任何相关内容,并且在最新更新的文档中只有简短的提及。我不仅对Scala,也对Slick都是新手。


最初我使用的是 O.Nullable 而不是 Option[String]。在 Vogt 先生的建议下进行了更正。 - sventechie
也发布在这里:https://groups.google.com/d/msg/scalaquery/xNtPT6sexXI/DgW5CQkfgaMJ 请在以后进行相互链接。 - cvogt
抱歉,我收到一条消息说我的Google Groups帖子被拒绝了,所以我认为它没有起作用。 - sventechie
这是我之前的问题,当我尝试使用Scala 2.11预发布版本时: https://dev59.com/V2Ik5IYBdhLWcg3wDKRb?rq=1 - sventechie
1
注意:如果有人尝试按照下面的解决方案进行操作,在Slick 2.0-M3版本中,HList代码将无法正常工作——编译超过22列将需要数小时和几GB的内存(它呈指数级增长)。https://groups.google.com/forum/#!topic/scalaquery/xNtPT6sexXI - sventechie
1
只是为了确保,实现是正确的(据我们所知)。然而,它目前似乎会在Scala编译器中触发指数级编译时间,在大小为25及以上时会严重影响您。我们将研究解决此问题。 - cvogt
2个回答

10

定义

在Scala >= 2.10.4-RC2(也适用于Slick 2.0.0代码生成器)中:

import scala.slick.collection.heterogenous._
import syntax._
class Joiners(tag: Tag) extends Table[
    Int :: Option[String] :: Option[String] :: HNil
](tag, "joiner") {
  ...
  def * = id :: name :: contact :: HNil
}

以上引起了Scala 2.10.3 / 2.10.4-RC1编译时间指数增长的问题。超过26个列不可行,因为编译时间非常长。

对于Scala <= 2.10.3 / 2.10.4-RC1的解决方法(也由Slick 2.0.1代码生成器发出)

import scala.slick.collection.heterogenous._
import syntax._
class Joiners(tag: Tag) extends Table[
    HCons[Int, HCons[Option[String], HCons[Option[String], HNil]]]
](tag, "joiner") {
  ...
  def * = id :: name :: contact :: HNil
}

我们测试了30-40列,没有出现问题。

目前在Scala 2.10.4-RC2中偶尔会出现编译错误的问题,看起来这将在即将发布的2.10.4-RC3版本中得到解决。请参见https://issues.scala-lang.org/browse/SI-8146

使用示例

Joiners.run.map( r => r(2) ) // Gets column contact. It's typesafe. .apply is a macro. Only works for literals not for variables as positions.

当元素数量<=22时,使用元组(tuples)以便能够将它们映射到一个case类。当元素数量>22时,使用HLists,而无需映射到case类中(在Scala 2.10中字段数的最大限制为22)。

此外:不要使用O.Nullable。相反,请使用column[Option[String]]。它可以推断出可空性。


我应该使用什么来替代 case class,因为在 Scala 2.11 之前它只能支持 22 列?我该如何将以前的 case class 声明为 HList,并将其作为类型参数传递给 table class? - sventechie
3
我更新了示例以显示类型。关于 case class,您可以简单地省略它。在结果中,您将无法通过名称访问列,只能通过位置 r(2) 访问。如果您真的想要命名访问,可以编写适当的工厂和提取函数,将其映射到 <> 中的普通类。 - cvogt
如果你仔细想一下,映射到 case classes 其实并不是那么重要。在查询期间,你确实可以对列进行命名访问。而使用 Slick 的最佳方式是在运行查询之前将其映射到你实际需要的列,这样你无法使用 case class 来表示整个行,因为你只获取了其中的一些列。这样说清楚了吗? - cvogt
谢谢,我想我明白了。我可能会尝试实现一个常规类。我在最近的演示中找到了你的示例代码: http://slick.typesafe.com/talks/2013-12-04_So-Slick-SUG-Berlin-Brandenburg/2013-12-04_So-Slick-SUG-Berlin-Brandenburg.pdf - sventechie
结果表明,指数级编译时间只在2.10.4-RC2中得到修复。我更新了我的答案以反映这一点,并展示了2.10.3 / 2.10.4-RC1的解决方法。 - cvogt
显示剩余7条评论

1
这段代码旨在演示编译器性能问题(它会卡住),当列数超过 26 时,仍然会影响 Scala v2.10.4_RC1。
import java.sql.Timestamp
import scala.slick.driver.MySQLDriver.simple._
import scala.slick.collection.heterogenous._
// **** Uncomment this ****
//import scala.slick.collection.heterogenous.syntax._  



object DealSlick {


    class Deals(tag: Tag) extends Table[
      Long :: String :: String :: Option[String] :: Option[String] :: Option[String] ::
      // urlKeywords
      Option[String] :: Option[String] :: Option[String] :: Option[String] :: Option[String] ::
      // extTags
      Option[String] :: Option[String] :: Option[String] :: Option[String] :: Option[String] :: 
      // currency
      Option[String] :: Option[String] :: 
      // price
      Option[Double] :: Option[Double] :: Option[Double] :: Option[Double] ::
      // extStatus
      Option[String] :: Option[String] :: Option[Int] :: Option[Int] ::

     /* If you add more columns the compiler get stuck in a never-ending
      * compilation possibly related to 
      * https://github.com/slick/slick/issues/577
      */

     // endAt
     Option[Timestamp] :: /*Option[Timestamp] :: Option[Timestamp] :: Option[Timestamp] ::
     // timeZoneOffset
     Option[Int] :: Option[String] :: Option[Timestamp] :: Option[String] ::
     // locationName
     Option[String] :: Option[String] :: Option[String] :: Option[String] ::
     // city
     Option[String] :: Option[String] :: Option[String] :: Option[String] :: Option[String] :: 
     // latitude
     Option[Double] :: Option[Double] :: 
     // merchantTitle
     Option[String] :: */
     // End of list
     HNil
     ](tag, "deal") {
     def                id = column[Long]("id", O.PrimaryKey)
     def          siteName = column[String]("partner_site_name", O.NotNull)
     def        siteDomain = column[String]("partner_site_domain", O.NotNull)
     def    localeLanguage = column[Option[String]]("deal_language") 
     def     localeCountry = column[Option[String]]("deal_country") 
     def             extId = column[Option[String]]("deal_ext_id") 
     def       urlKeywords = column[Option[String]]("deal_url_keywords") 
     def          keywords = column[Option[String]]("deal_keywords") 
     def     extCategories = column[Option[String]]("deal_ext_categories") 
     def      categoryText = column[Option[String]]("deal_category_text") 
     def          coverage = column[Option[String]]("deal_coverage") 
     def           extTags = column[Option[String]]("deal_ext_tags") 
     def             title = column[Option[String]]("deal_title") 
     def       description = column[Option[String]]("deal_description")
     def          extImage = column[Option[String]]("deal_ext_image") 
     def               url = column[Option[String]]("deal_url") 
     def          currency = column[Option[String]]("deal_currency") 
     def       currencySym = column[Option[String]]("deal_currency_sym") 
     def             price = column[Option[Double]]("deal_price") 
     def            saving = column[Option[Double]]("deal_saving") 
     def          discount = column[Option[Double]]("deal_discount") 
     def            dvalue = column[Option[Double]]("deal_value") 
     def         extStatus = column[Option[String]]("deal_ext_status") 
     def            status = column[Option[String]]("deal_status") 
     def           soldQty = column[Option[Int]]("deal_sold_qty") 
     def           leftQty = column[Option[Int]]("deal_left_qty") 
     def             endAt = column[Option[Timestamp]]("deal_end_at") 
 /*    def          endAtUtc = column[Option[Timestamp]]("deal_end_at_utc") 
     def         expiresAt = column[Option[Timestamp]]("deal_expires_at") 
     def      expiresAtUtc = column[Option[Timestamp]]("deal_expires_at_utc") 
     def    timeZoneOffset = column[Option[Int]]("time_zone_offset") 
     def      timeZoneName = column[Option[String]]("time_zone_name")
     def       timeGrabbed = column[Option[Timestamp]]("time_grabbed") 
     def  timeRemainingStr = column[Option[String]]("time_remaining_str") 
     def      locationName = column[Option[String]]("location_name")
     def           address = column[Option[String]]("location_address") 
     def            street = column[Option[String]]("location_street") 
     def        postalCode = column[Option[String]]("location_postalcode")
     def              city = column[Option[String]]("location_city") 
     def          province = column[Option[String]]("location_province") 
     def            region = column[Option[String]]("location_region") 
     def             state = column[Option[String]]("location_state") 
     def           country = column[Option[String]]("location_country") 
     def          latitude = column[Option[Double]]("location_latitude") 
     def         longitude = column[Option[Double]]("location_longitude") 
     def     merchantTitle = column[Option[String]]("merchant_title") 

*/
def * = (id :: siteName :: siteDomain :: localeLanguage :: localeCountry :: extId :: 
         urlKeywords :: keywords :: extCategories :: categoryText :: coverage :: 
         extTags :: title :: description :: extImage :: url ::
         currency :: currencySym :: price :: saving :: discount :: dvalue ::
         extStatus :: status :: soldQty :: leftQty ::
         endAt :: /*endAtUtc :: expiresAt :: expiresAtUtc ::
         timeZoneOffset :: timeZoneName :: timeGrabbed :: timeRemainingStr ::
         locationName :: address :: street :: postalCode :: 
         city :: province :: region :: state :: country ::
         latitude :: longitude ::
         merchantTitle :: */
         HNil )  
   }

}

** 更新 **

更新到Scala 2.10.4-RC2后,编译器在编译过程中进展了几步,但又卡住了:

这是编译器输出仅定义少量表列时的情况

[info] [在1毫秒内加载了/Users/max/.ivy2/cache/com.typesafe.slick/slick_2.10/jars/slick_2.10-2.0.0.jar(scala / slick / backend / DatabaseComponent.class)类文件]

[info] [在2毫秒内加载了/Users/max/.ivy2/cache/com.typesafe.slick/slick_2.10/jars/slick_2.10-2.0.0.jar(scala / slick / lifted / ShapedValue.class)类文件]

[info] [在2毫秒内加载了名称为“util”的包装载程序] 当使用超过26个列时,此输出不会在屏幕上打印


这个问题可能在RC1之后已经被修复了,但我不确定。我试图追踪提交和错误跟踪消息,但没有找到。如果您想要打补丁Scala或Slick,那么有一些解决方案。 - sventechie
Christopher,感谢你的提示,但我已经尝试过Scala v2.10.4-RC2了,我必须说我刚刚注意到编译器不再在那个点上阻塞,但它会在后续步骤中卡住。如果你和你的团队认为值得调查这个问题,你可以使用我发布的代码进行测试。 - Max

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接