Apache spark 使用嵌套的用户数据类型保存Spark数据帧

Apache spark 使用嵌套的用户数据类型保存Spark数据帧,apache-spark,apache-spark-sql,Apache Spark,Apache Spark Sql,我想将包含自定义类的Spark数据框保存为列(作为拼花文件)。该类由另一个自定义类的Seq组成。为此,我为每个类创建了一个UserDefinedType类,方法与VectorUDT类似。我可以按预期使用数据框,但不能将其作为拼花(或jason)保存到磁盘 我报告它是一个bug,但我的代码可能有问题。我实现了一个更简单的示例来说明问题: import org.apache.spark.sql.SaveMode import org.apache.spark.{SparkConf, SparkCon

我想将包含自定义类的Spark数据框保存为列(作为拼花文件)。该类由另一个自定义类的Seq组成。为此,我为每个类创建了一个UserDefinedType类,方法与VectorUDT类似。我可以按预期使用数据框,但不能将其作为拼花(或jason)保存到磁盘 我报告它是一个bug,但我的代码可能有问题。我实现了一个更简单的示例来说明问题:

import org.apache.spark.sql.SaveMode
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.catalyst.expressions.GenericMutableRow
import org.apache.spark.sql.types._

@SQLUserDefinedType(udt = classOf[AUDT])
case class A(list:Seq[B])

class AUDT extends UserDefinedType[A] {
  override def sqlType: DataType = StructType(Seq(StructField("list", ArrayType(BUDT, containsNull = false), nullable = true)))
  override def userClass: Class[A] = classOf[A]
  override def serialize(obj: Any): Any = obj match {
    case A(list) =>
      val row = new GenericMutableRow(1)
      row.update(0, new GenericArrayData(list.map(_.asInstanceOf[Any]).toArray))
      row
  }

  override def deserialize(datum: Any): A = {
    datum match {
      case row: InternalRow => new A(row.getArray(0).toArray(BUDT).toSeq)
    }
  }
}

object AUDT extends AUDT

@SQLUserDefinedType(udt = classOf[BUDT])
case class B(num:Int)

class BUDT extends UserDefinedType[B] {
  override def sqlType: DataType = StructType(Seq(StructField("num", IntegerType, nullable = false)))
  override def userClass: Class[B] = classOf[B]
  override def serialize(obj: Any): Any = obj match {
    case B(num) =>
      val row = new GenericMutableRow(1)
      row.setInt(0, num)
      row
  }

  override def deserialize(datum: Any): B = {
    datum match {
      case row: InternalRow => new B(row.getInt(0))
    }
  }
}

object BUDT extends BUDT

object TestNested {
  def main(args:Array[String]) = {
    val col = Seq(new A(Seq(new B(1), new B(2))),
                  new A(Seq(new B(3), new B(4))))

    val sc = new SparkContext(new SparkConf().setMaster("local[1]").setAppName("TestSpark"))
    val sqlContext = new org.apache.spark.sql.SQLContext(sc)
    import sqlContext.implicits._

    val df = sc.parallelize(1 to 2 zip col).toDF()
    df.show()

    df.write.mode(SaveMode.Overwrite).save(...)
  }
}
这将导致以下错误:

15/09/16 16:44:39错误执行者:任务0.0在阶段1.0中出现异常 (TID 1)java.lang.IllegalArgumentException:嵌套类型应为 重复:必需的组数组{required int32 num;}位于 org.apache.parquet.schema.ConversionPatterns.listWrapper(ConversionPatterns.java:42) 在 org.apache.parquet.schema.ConversionPatterns.listType(ConversionPatterns.java:97) 在 org.apache.spark.sql.execution.datasources.parquet.CatalystSchemaConverter.convertField(CatalystSchemaConverter.scala:460) 在 org.apache.spark.sql.execution.datasources.parquet.CatalystSchemaConverter.convertField(CatalystSchemaConverter.scala:318) 在 org.apache.spark.sql.execution.datasources.parquet.CatalystSchemaConverter$$anonfun$convertField$1.apply(CatalystSchemaConverter.scala:522) 在 org.apache.spark.sql.execution.datasources.parquet.CatalystSchemaConverter$$anonfun$convertField$1.apply(CatalystSchemaConverter.scala:521) 在 scala.collection.IndexedSeqOptimized$class.foldl(IndexedSeqOptimized.scala:51) 在 scala.collection.IndexedSeqOptimized$class.foldLeft(IndexedSeqOptimized.scala:60) 在 scala.collection.mutable.ArrayOps$ofRef.foldLeft(ArrayOps.scala:108) 在 org.apache.spark.sql.execution.datasources.parquet.CatalystSchemaConverter.convertField(CatalystSchemaConverter.scala:521) 在 org.apache.spark.sql.execution.datasources.parquet.CatalystSchemaConverter.convertField(CatalystSchemaConverter.scala:318) 在 org.apache.spark.sql.execution.datasources.parquet.CatalystSchemaConverter.convertField(CatalystSchemaConverter.scala:526) 在 org.apache.spark.sql.execution.datasources.parquet.CatalystSchemaConverter.convertField(CatalystSchemaConverter.scala:318) 在 org.apache.spark.sql.execution.datasources.parquet.CatalystSchemaConverter$$anonfun$convert$1.apply(CatalystSchemaConverter.scala:311) 在 org.apache.spark.sql.execution.datasources.parquet.CatalystSchemaConverter$$anonfun$convert$1.apply(CatalystSchemaConverter.scala:311) 在 scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244) 在 scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244) 位于scala.collection.Iterator$class.foreach(Iterator.scala:727) scala.collection.AbstractIterator.foreach(迭代器.scala:1157)位于 scala.collection.IterableLike$class.foreach(IterableLike.scala:72)位于 org.apache.spark.sql.types.StructType.foreach(StructType.scala:92)位于 scala.collection.TraversableLike$class.map(TraversableLike.scala:244) 位于org.apache.spark.sql.types.StructType.map(StructType.scala:92) org.apache.spark.sql.execution.datasources.parquet.CatalystSchemaConverter.convert(CatalystSchemaConverter.scala:311) 在 org.apache.spark.sql.execution.datasources.parquet.ParquetTypesConverter$.convertFromAttributes(ParquetTypesConverter.scala:58) 在 org.apache.spark.sql.execution.datasources.parquet.RowWriteSupport.init(ParquetTableSupport.scala:55) 在 org.apache.parquet.hadoop.ParquetOutputFormat.getRecordWriter(ParquetOutputFormat.java:288) 在 org.apache.parquet.hadoop.ParquetOutputFormat.getRecordWriter(ParquetOutputFormat.java:262) 在 org.apache.spark.sql.execution.datasources.parquet.ParquetOutputWriter.(ParquetRelation.scala:94) 在 org.apache.spark.sql.execution.datasources.parquet.ParquetRelation$$anon$3.newInstance(ParquetRelation.scala:272) 在 org.apache.spark.sql.execution.datasources.DefaultWriterContainer.writeRows(WriterContainer.scala:234) 在 org.apache.spark.sql.execution.datasources.InsertIntoHadoopFsRelation$$anonfun$run$1$$anonfun$apply$mcV$sp$3.apply(InsertIntoHadoopFsRelation.scala:150) 在 org.apache.spark.sql.execution.datasources.InsertIntoHadoopFsRelation$$anonfun$run$1$$anonfun$apply$mcV$sp$3.apply(InsertIntoHadoopFsRelation.scala:150) 位于org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:66) 位于org.apache.spark.scheduler.Task.run(Task.scala:88) org.apache.spark.executor.executor$TaskRunner.run(executor.scala:214) 在 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) 在 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) 在java.lang.Thread.run(Thread.java:745)15/09/16:44:39警告 TaskSetManager:在阶段1.0中丢失了任务0.0(TID 1,本地主机):


如果a使用B而不是a保存数据帧,则不存在任何问题,因为B不是嵌套的自定义类。我错过什么了吗

我必须对您的代码进行四次更改才能使其正常工作(在Linux上的Spark 1.6.0中进行了测试),我想我可以大致解释为什么需要它们。然而,我确实感到疑惑,是否有更简单的解决方案。所有更改都在
AUDT
中,如下所示:

  • 定义
    sqlType
    时,使其依赖于
    BUDT.sqlType
    ,而不仅仅是
    BUDT
  • serialize()
    中,对每个列表元素调用
    BUDT.serialize()
  • 反序列化()
    中:
    • 调用
      toArray(BUDT.sqlType)
      而不是
      toArray(BUDT)
    • 对每个元素调用
      BUDT.deserialize()
  • 下面是生成的代码:

    class AUDT扩展了UserDefinedType[A]{
    覆盖def sqlType:数据类型=
    结构类型(
    Seq(结构域(“列表”),
    ArrayType(BUDT.sqlType,containsnall=false),
    nullable=true)))
    推翻