我的目标是创建一个知道如何在给定路径获取数据的MyDataFrame
类,但我想提供类型安全。我在远程数据上使用带有类型边界的frameless.TypedDataset
时遇到一些问题。例如
sealed trait Schema
final case class TableA(id: String) extends Schema
final case class TableB(id: String) extends Schema
class MyDataFrame[T <: Schema](path: String, implicit val spark: SparkSession) {
def read = TypedDataset.create(spark.read.parquet(path)).as[T]
}
但我不断得到could not find implicit value for evidence parameter of type frameless.TypedEncoder[org.apache.spark.sql.Row]
.我知道TypedDataset.create
需要一个Injection
才能做到这一点。但我不确定我将如何为通用T
写这个.我认为编译器能够推断出,由于Schema
的所有子类型都case class
es,因此它将起作用。
有人遇到过这种情况吗?
所有隐式参数都应位于最后一个参数列表中,并且此参数列表应与非隐式参数列表分开。
如果您尝试编译
class MyDataFrame[T <: Schema](path: String)(implicit spark: SparkSession) {
def read = TypedDataset.create(spark.read.parquet(path)).as[T]
}
您会看到错误
Error:(11, 35) could not find implicit value for evidence parameter of type frameless.TypedEncoder[org.apache.spark.sql.Row]
def read = TypedDataset.create(spark.read.parquet(path)).as[T]
所以让我们只添加相应的隐式参数
class MyDataFrame[T <: Schema](path: String)(implicit spark: SparkSession, te: TypedEncoder[Row]) {
def read = TypedDataset.create(spark.read.parquet(path)).as[T]
}
我们会有错误
Error:(11, 64) could not find implicit value for parameter as: frameless.ops.As[org.apache.spark.sql.Row,T]
def read = TypedDataset.create(spark.read.parquet(path)).as[T]
因此,让我们再添加一个隐式参数
import frameless.ops.As
import frameless.{TypedDataset, TypedEncoder}
import org.apache.spark.sql.{Row, SparkSession}
class MyDataFrame[T <: Schema](path: String)(implicit spark: SparkSession, te: TypedEncoder[Row], as: As[Row, T]) {
def read = TypedDataset.create(spark.read.parquet(path)).as[T]
}
或带投影仪
class MyDataFrame[T <: Schema : As[Row, ?]](path: String)(implicit spark: SparkSession, te: TypedEncoder[Row]) {
def read = TypedDataset.create(spark.read.parquet(path)).as[T]
}
<小时 />您可以创建自定义类型类
trait Helper[T] {
implicit def te: TypedEncoder[Row]
implicit def as: As[Row, T]
}
object Helper {
implicit def mkHelper[T](implicit te0: TypedEncoder[Row], as0: As[Row, T]): Helper[T] = new Helper[T] {
override implicit def te: TypedEncoder[Row] = te0
override implicit def as: As[Row, T] = as0
}
}
class MyDataFrame[T <: Schema : Helper](path: String)(implicit spark: SparkSession) {
val h = implicitly[Helper[T]]
import h._
def read = TypedDataset.create(spark.read.parquet(path)).as[T]
}
或
class MyDataFrame[T <: Schema](path: String)(implicit spark: SparkSession, h: Helper[T]) {
import h._
def read = TypedDataset.create(spark.read.parquet(path)).as[T]
}
或
trait Helper[T] {
def create(dataFrame: DataFrame): TypedDataset[T]
}
object Helper {
implicit def mkHelper[T](implicit te: TypedEncoder[Row], as: As[Row, T]): Helper[T] =
(dataFrame: DataFrame) => TypedDataset.create(dataFrame).as[T]
}
class MyDataFrame[T <: Schema : Helper](path: String)(implicit spark: SparkSession) {
def read = implicitly[Helper[T]].create(spark.read.parquet(path))
}
或
class MyDataFrame[T <: Schema](path: String)(implicit spark: SparkSession, h: Helper[T]) {
def read = h.create(spark.read.parquet(path))
}
<小时 />更正版本:
import org.apache.spark.sql.Encoder
import frameless.{TypedDataset, TypedEncoder}
class MyDataFrame[T <: Schema](path: String)(implicit
spark: SparkSession,
e: Encoder[T],
te: TypedEncoder[T]
) {
def read: TypedDataset[T] = TypedDataset.create[T](spark.read.parquet(path).as[T])
}
或使用上下文边界
class MyDataFrame[T <: Schema : Encoder : TypedEncoder](path: String)(implicit
spark: SparkSession
) {
def read: TypedDataset[T] = TypedDataset.create[T](spark.read.parquet(path).as[T])
}
测试:我将 json 文件{"id": "xyz"}
转换为镶木地板文件,然后
sealed trait Schema
final case class TableA(id: String) extends Schema
final case class TableB(id: String) extends Schema
import org.apache.spark.sql.SparkSession
implicit val spark: SparkSession = SparkSession.builder
.master("local")
.appName("Spark SQL basic example")
.getOrCreate()
import spark.implicits._
import frameless.syntax._
val res: TypedDataset[TableA] = new MyDataFrame[TableA]("path/to/parquet/file").read
println(res) // [id: string]
res.foreach(println).run() // TableA(xyz)