Вчера (практически полный журнал) я попытался придумать элегантный способ представления модели с циклическими ссылками в Scala / Spark SQL 2.2.1 .

Допустим, это исходный модельный подход, который, конечно, не работает (имейте в виду, что реальная модель имеет десятки атрибутов):

case class Branch(id: Int, branches: List[Branch] = List.empty)
case class Tree(id: Int, branches: List[Branch])

val trees = Seq(Tree(1, List(Branch(2, List.empty), Branch(3, List(Branch(4, List.empty))))))

val ds = spark.createDataset(trees)
ds.show

И это ошибка, которую он выдает:

java.lang.UnsupportedOperationException: cannot have circular references in class, but got the circular reference of class Branch

Я знаю, что у нас есть максимальный уровень иерархии 5 . Итак, в качестве обходного пути я подумал о чем-то вроде:

case class BranchLevel5(id: Int)
case class BranchLevel4(id: Int, branches: List[BranchLevel5] = List.empty)
case class BranchLevel3(id: Int, branches: List[BranchLevel4] = List.empty)
case class BranchLevel2(id: Int, branches: List[BranchLevel3] = List.empty)
case class BranchLevel1(id: Int, branches: List[BranchLevel2] = List.empty)
case class Tree(id: Int, branches: List[BranchLevel1])

Конечно, работает. Но это совсем не элегантно, и вы можете представить себе боль, связанную с реализацией (читаемость, связывание, обслуживание, удобство использования, дублирование кода и т. Д.)

Итак, вопрос в том, как обрабатывать случаи с циклическими ссылками в модели?

0
angelcervera 22 Сен 2018 в 10:48

1 ответ

Лучший ответ

Если вас устраивает использование частного API, то я нашел один способ, который просто работает: рассматривать всю самореферентную структуру как пользовательский тип. Я следую подходу из этого ответа: https://stackoverflow.com/a/51957666/1823254.

package org.apache.spark.custom.udts // we're calling some private API so need to be under 'org.apache.spark'

import java.io._
import org.apache.spark.sql.types.{DataType, UDTRegistration, UserDefinedType}

class BranchUDT extends UserDefinedType[Branch] {

  override def sqlType: DataType = org.apache.spark.sql.types.BinaryType
  override def serialize(obj: Branch): Any = {
    val bos = new ByteArrayOutputStream()
    val oos = new ObjectOutputStream(bos)
    oos.writeObject(obj)
    bos.toByteArray
  }
  override def deserialize(datum: Any): Branch = {
    val bis = new ByteArrayInputStream(datum.asInstanceOf[Array[Byte]])
    val ois = new ObjectInputStream(bis)
    val obj = ois.readObject()
    obj.asInstanceOf[Branch]
  }

  override def userClass: Class[Branch] = classOf[Branch]
}

object BranchUDT {
  def register() = UDTRegistration.register(classOf[Branch].getName, classOf[BranchUDT].getName)
}

Просто создайте и зарегистрируйте пользовательский UDT, и все!

BranchUDT.register()
val trees = Seq(Tree(1, List(Branch(2, List.empty), Branch(3, List(Branch(4, List.empty))))))

val ds = spark.createDataset(trees)
ds.show(false)

//+---+----------------------------------------------------+
//|id |branches                                            |
//+---+----------------------------------------------------+
//|1  |[Branch(2,List()), Branch(3,List(Branch(4,List())))]|
//+---+----------------------------------------------------+
0
Worakarn Isaratham 24 Сен 2018 в 09:22