关于scala:通用T作为Spark Dataset [T]构造函数

Generic T as Spark Dataset[T] constructor

在以下代码段中,tryParquet函数尝试从Parquet文件中加载数据集(如果存在)。 如果没有,它将计算,保留并返回提供的数据集计划:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import scala.util.{Try, Success, Failure}
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.Dataset

sealed trait CustomRow

case class MyRow(
  id: Int,
  name: String
) extends CustomRow

val ds: Dataset[MyRow] =
  Seq((1,"foo"),
      (2,"bar"),
      (3,"baz")).toDF("id","name").as[MyRow]

def tryParquet[T <: CustomRow](session: SparkSession, path: String, target: Dataset[T]): Dataset[T] =
    Try(session.read.parquet(path)) match {
      case Success(df) => df.as[T] // <---- compile error here
      case Failure(_)  => {
        target.write.parquet(path)
        target
      }
    }

val readyDS: Dataset[MyRow] =
    tryParquet(spark,"/path/to/file.parq", ds)

但是,这会在df.as[T]上产生编译错误:

Unable to find encoder for type stored in a Dataset. Primitive types (Int, String, etc) and Product types (case classes) are supported by importing spark.implicits._

Support for serializing other types will be added in future releases.

case Success(df) => df.as[T]

可以通过使tryParquet强制转换为df来返回未类型化的DataFrame并让调用方强制转换为所需的构造函数来避免此问题。 但是,在我们希望通过函数在内部管理类型的情况下,有什么解决方案吗?


看起来可以通过在type参数中使用Encoder来实现:

1
2
3
import org.apache.spark.sql.Encoder

def tryParquet[T <: CustomRow: Encoder](...)

这样,编译器可以证明df.as[T]在构造对象时正在提供编码器。