我有一个简单的案例类:
case class Geometry(id: Int, multiPolygon: MultiPolygon)
MultiPolygon
也是案例类:
case class Pt(x: Double, y: Double)
case class Polygon(outer: List[Pt], inner: List[List[Pt]])
case class MultiPolygon(polygons: List[Polygon]) {
def area(): Double = ...
}
我试图将area
函数称为UDF,因此通过像.map(g => (g.id, g.multiPolygon.area))
这样的而不是,将整个Geometry
对象反序列化在JVM内存中。
由于我需要在 SQL 中使用此功能,我注册了一个UDF:
session.udf.register[Double, MultiPolygon]("my_area", { mp: MultiPolygon => mp.area })
测试数据集为Dataset[Geometry]
,注册为geometries
。好。然后问题是,当我尝试做类似的事情时:
SELECT id, my_area(multiPolygon) FROM geometries
我得到一个例外:
org.apache.spark.SparkException: Failed to execute user defined function(anonfun$19: (struct<polygons:array<struct<outer:array<struct<x:double,y:double>>,inner:array<array<struct<x:double,y:double>>>>>>) => double)
Caused by: java.lang.ClassCastException: org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema cannot be cast to Polygons
数据集的架构是:
root
|-- id: integer (nullable = false)
|-- multiPolygon: struct (nullable = true)
| |-- polygons: array (nullable = true)
| | |-- element: struct (containsNull = true)
| | | |-- outer: array (nullable = true)
| | | | |-- element: struct (containsNull = true)
| | | | | |-- x: double (nullable = false)
| | | | | |-- y: double (nullable = false)
| | | |-- inner: array (nullable = true)
| | | | |-- element: array (containsNull = true)
| | | | | |-- element: struct (containsNull = true)
| | | | | | |-- x: double (nullable = false)
| | | | | | |-- y: double (nullable = false)
使用数据集接口抛出相同的异常:
val areaUDF = udf[Double, MultiPolygon](_.area)
val area = geometries.withColumn("areas", areaUDF('multiPolygon))
虽然外部数据集正确定义为Dataset[Geometry]
,而不仅仅是DataFrame
,并且UDF具有类型MultiPolygon
的输入参数,但Spark SQL尝试强制转换附加到名为multiPolygon
到MultiPolygon
的字段的结构对象,失败。
相反,它应该反序列化将MultiPolygon
返回到结构中的JVM对象表示,这是UDF可以使用与.select(...).as[MultiPolygon].collect()
相同的机制消化的唯一类型。
似乎数据集接口将案例类简化为本机类型,结构和数组的混合,而不保留类名,并且没有简单的方法。似乎并不总是可以将兼容的结构反序列化回案例类,或者至少,当它应该自动发生时,它并不总是发生。