从 Slick 连接到具有父子关系的实体的功能方法?

Functional way to go from a Slick join to entities with parent-child relationships?

我正在为新的 Scala Play 应用程序中的数据层尝试 Slick 3.2,我正在转动我的轮子,试图从针对一些相关 table 的查询中获得这些实体的 case class 表示。该应用程序立即(每当我们的系统中更新数据时)或按某个时间表将数据发送到任意目的地。即由以下三个 Postgres tables 表示:

CREATE TABLE destinations (
  destination_id SERIAL PRIMARY KEY,
  endpoint VARCHAR(100) NOT NULL,
  protocol VARCHAR(100) NOT NULL
);

CREATE TABLE export_triggers (
  export_trigger_id SERIAL PRIMARY KEY,
  job_id INTEGER NOT NULL REFERENCES jobs,
  destination_id INTEGER NOT NULL REFERENCES destinations,
  trigger_type VARCHAR(50) NOT NULL,
  export_schedule_id INTEGER REFERENCES export_schedules
);

CREATE TABLE export_schedules (
  export_schedule_id SERIAL PRIMARY KEY,
  description VARCHAR(1000),
  cron VARCHAR(100) NOT NULL
);

以下情况 classes 表示 Scala 中的实体(Protocol 和 TriggerType 是枚举,为简洁起见我省略了):

case class Destination(endpoint: String, protocol: Protocol, exportTriggers: Seq[ExportTrigger])
case class ExportTrigger(triggerType: TriggerType, schedule: Option[ExportSchedule])
case class ExportSchedule(description: Option[String], cron: String)

Slick Codegen 生成的案例 classes 代表每个 table 中的一行。它们是 DestinationsRowExportTriggersRowExportSchedulesRow

一个目的地可以包含多个 ExportTriggers,一个 ExportTrigger 可以包含 0 个或 1 个 ExportSchedules。我正在尝试实现一个函数 getDestinations(jobId: Int),它接受一个 jobId 并检索该作业的所有目的地(包括触发器和计划)。这是一个应该获取所有数据的 Slick 查询(为便于阅读而添加的新行):

ExportTriggers.filter(_.jobId === id) 
  join Destinations on (_.destinationId === _.destinationId) 
  joinLeft ExportSchedules on (_._1.exportScheduleId === _.exportScheduleId)

这个表达式的内部类型是 Seq[((ExportTriggersRow, DestinationsRow), Option[ExportSchedulesRow])],所以它应该包含创建我正在寻找的模型所需的映射,但是我很难从这个平面序列以功能方式将行添加到 Destination->[ExportTrigger]->ExportSchedule 结构,而不会陷入一些严重不可读的兔子洞。有什么想法吗?

根据您提供的信息,我会这样处理:

val result: Seq[((ExportTriggersRow, DestinationsRow), Option[ExportSchedulesRow])]

val grouped: Seq[Destination] = result.groupBy {
    case ((_, destinationRow), _) => destinationRow
} map {
    case (destinationRow, resultList) =>
        val triggers = resultList.map {
          case ((triggerRow, _), optScheduleRow) =>
             val schedule = optScheduleRow.map { row => 
                ExportSchedule(row.description, row.cron)
             }

             ExportTrigger(TriggerType(triggerRow.triggerType), schedule)
        }
        Destination(destinationRow.endpoint, Protocol(destinationRow.protocol), triggers)
}

首先按 destinationRow 分组,然后转换触发器和可选计划,最后创建 Destination。

编辑 更正了 grouped 的结果类型。它当然只是一个 Destinations 的序列,包含它们的 ExportTriggers,而 ExportTriggers 又可能包含一个 ExportSchedule