从字符串文字

时间:2016-09-29 18:36:31

标签: scala apache-spark types spark-dataframe introspection

我正在尝试编写一个可以根据提供的输入字符串推断Spark DataTypes的Scala函数:

/**
 * Example:
 * ========
 * toSparkType("string")  =>    StringType
 * toSparkType("boolean") =>    BooleanType
 * toSparkType("date")    =>    DateType
 * etc.
 */
def toSparkType(inputType : String) : DataType = {
    var dt : DataType = null

    if(matchesStringRegex(inputType)) {
        dt = StringType
    } else if(matchesBooleanRegex(inputType)) {
        dt = BooleanType
    } else if(matchesDateRegex(inputType)) {
        dt = DateType
    } else if(...) {
        ...
    }

    dt
}

我的目标是支持可用DataTypes的大部分(如果不是全部)。当我开始实现这个功能时,我开始思考:“ Spark / Scala可能已经有了一个helper / util方法,可以为我做这个。”毕竟,我知道我可以这样做:

var structType = new StructType()

structType.add("some_new_string_col", "string", true, Metadata.empty)
structType.add("some_new_boolean_col", "boolean", true, Metadata.empty)
structType.add("some_new_date_col", "date", true, Metadata.empty)

Scala和/或Spark会隐式地将我的"string"参数转换为StringType等等。所以我问:我可以用Spark或Scala做些什么来帮助我实现我的转换方法?

4 个答案:

答案 0 :(得分:15)

  

Spark / Scala可能已经有了一个helper / util方法,可以为我做这个。

你是对的。 Spark已经拥有自己的架构和数据类型推断代码,用于从底层数据源(csv,json等)推断架构。所以你可以看看它实现自己的(实际的实现被标记为Spark的私有,是绑定到RDD和内部类,因此它不能直接从Spark之外的代码中使用,但应该让你知道如何去做它。)

鉴于csv是平面类型(并且json可以具有嵌套结构),csv模式推断相对更直接,并且可以帮助您完成上述尝试实现的任务。所以我将解释csv推理是如何工作的(json推理只需要考虑可能的嵌套结构,但数据类型推断非常类似)。

有了这个序幕,你要看的东西是CSVInferSchema对象。特别是,请查看infer方法,该方法采用RDD[Array[String]]并在整个RDD 中推断数组的每个元素的数据类型。它的作用是 - 它将每个字段标记为NullType开始,然后当它迭代Array[String]中的下一行值RDD时,它会更新已经推断的{如果新DataType更具体,则{1}}为新DataType。这种情况正在发生here

DataType

现在val rootTypes: Array[DataType] = tokenRdd.aggregate(startType)(inferRowType(options), mergeRowTypes) calls inferRowType代表该行中的每个字段。 inferField implementation是你可能正在寻找的东西 - 到目前为止它为特定字段推断类型,并将当前行的字段的字符串值作为参数。然后它返回现有的推断类型,或者如果推断的新类型更具体,则返回新类型。

代码的相关部分如下:

inferField

请注意,如果typeSoFar match { case NullType => tryParseInteger(field, options) case IntegerType => tryParseInteger(field, options) case LongType => tryParseLong(field, options) case _: DecimalType => tryParseDecimal(field, options) case DoubleType => tryParseDouble(field, options) case TimestampType => tryParseTimestamp(field, options) case BooleanType => tryParseBoolean(field, options) case StringType => StringType case other: DataType => throw new UnsupportedOperationException(s"Unexpected data type $other") } 为NullType,则它首先尝试将其解析为typeSoFar,但Integer调用是对较低类型解析的调用链。因此,如果它无法将值解析为Integer,那么它将调用tryParseInteger,失败后将调用tryParseLong,失败时将调用tryParseDecimal w.o.f.w.i. tryParseDouble w.o.f.w.i tryParseTimestamp w.o.f.w.i.最后tryParseBoolean

所以你几乎可以使用类似的逻辑来实现你的用例。 (如果您不需要跨行合并,那么您只需逐字实现所有stringType方法,只需调用tryParse*。无需编写自己的正则表达式。)

希望这有帮助。

答案 1 :(得分:8)

是的,当然Spark有你需要的魔力。

在Spark 2.x中,它是CatalystSqlParser对象,定义为here

例如:

import org.apache.spark.sql.catalyst.parser.CatalystSqlParser

CatalystSqlParser.parseDataType("string") // StringType
CatalystSqlParser.parseDataType("int") // IntegerType

等等。

但据我了解,它不是公共API的一部分,因此可能会在下一版本中发生变化而不会发出任何警告。

所以你可以将你的方法实现为:

def toSparkType(inputType: String): DataType = CatalystSqlParser.parseDataType(inputType)

答案 2 :(得分:0)

来看,你似乎无法做出你想要的魔法,例如检查这个例子:

import com.scalakata._

@instrument class Playground {
  val x = 5
  def f[T](v: T) = v
  f(x)
  val y = "boolean"
  f(y)  
  def manOf[T: Manifest](t: T): Manifest[T] = manifest[T]
  println(manOf(y))
}
我在阅读I want to get the type of a variable at runtime后撰写的

现在来自,由于我现在没有安装,我无法撰写示例,但没有什么明显可以使用,所以我建议你继续写{ {1}}已经开始,但请先查看Source code for pyspark.sql.types

你看到的问题是你总是传递一个字符串。

答案 3 :(得分:0)

如果您将String Literals写为DataType名称,即“ StringType”,“ IntegerType”- 使用此功能-

def StrtoDatatype(str: String): org.apache.spark.sql.types.DataType = {
    val m = ru.runtimeMirror(getClass.getClassLoader)
    val module = m.staticModule(s"org.apache.spark.sql.types.$str")
    m.reflectModule(module).instance.asInstanceOf[org.apache.spark.sql.types.DataType]
  }

如果您的字符串文字为-字符串,整数等。

def sqlStrtoDatatype(str: String): org.apache.spark.sql.types.DataType = {
    CatalystSqlParser.parseDataType(str)
  }