in scio-google-cloud-platform/src/main/scala/com/spotify/scio/bigquery/types/TypeProvider.scala [253:366]
private def schemaToType(c: blackbox.Context)(
schema: TableSchema,
annottees: Seq[c.Expr[Any]],
traits: Seq[c.Tree],
overrides: Seq[c.Tree]
): c.Expr[Any] = {
import c.universe._
checkMacroEnclosed(c)
val provider: OverrideTypeProvider = OverrideTypeProviderFinder.getProvider
// Returns: (raw type, e.g. Int, String, NestedRecord, nested case class definitions)
def getRawType(tfs: TableFieldSchema): (Tree, Seq[Tree]) = {
// format: off
tfs.getType match {
case _ if provider.shouldOverrideType(tfs) => (provider.getScalaType(c)(tfs), Nil)
case "BOOLEAN" | "BOOL" => (tq"_root_.scala.Boolean", Nil)
case "INTEGER" | "INT64" => (tq"_root_.scala.Long", Nil)
case "FLOAT" | "FLOAT64" => (tq"_root_.scala.Double", Nil)
case "STRING" => (tq"_root_.java.lang.String", Nil)
case "NUMERIC" => (tq"_root_.scala.BigDecimal", Nil)
case "BYTES" => (tq"_root_.com.google.protobuf.ByteString", Nil)
case "TIMESTAMP" => (tq"_root_.org.joda.time.Instant", Nil)
case "DATE" => (tq"_root_.org.joda.time.LocalDate", Nil)
case "TIME" => (tq"_root_.org.joda.time.LocalTime", Nil)
case "DATETIME" => (tq"_root_.org.joda.time.LocalDateTime", Nil)
case "GEOGRAPHY" => (tq"_root_.com.spotify.scio.bigquery.types.Geography", Nil)
case "JSON" => (tq"_root_.com.spotify.scio.bigquery.types.Json", Nil)
case "BIGNUMERIC" => (tq"_root_.com.spotify.scio.bigquery.types.BigNumeric", Nil)
case "RECORD" | "STRUCT" =>
val name = NameProvider.getUniqueName(tfs.getName)
val (fields, records) = toFields(tfs.getFields)
(q"${Ident(TypeName(name))}", Seq(q"case class ${TypeName(name)}(..$fields)") ++ records)
case t => c.abort(c.enclosingPosition, s"type: $t not supported")
}
// format: on
}
// Returns: (field type, e.g. T/Option[T]/List[T], nested case class definitions)
def getFieldType(tfs: TableFieldSchema): (Tree, Seq[Tree]) = {
val (t, r) = getRawType(tfs)
val ft = tfs.getMode match {
case "NULLABLE" | null => tq"_root_.scala.Option[$t]"
case "REQUIRED" => t
case "REPEATED" => tq"_root_.scala.List[$t]"
case m => c.abort(c.enclosingPosition, s"mode: $m not supported")
}
(ft, r)
}
// Returns: ("fieldName: fieldType", nested case class definitions)
def toField(tfs: TableFieldSchema): (Tree, Seq[Tree]) = {
val (ft, r) = getFieldType(tfs)
val params = q"val ${TermName(tfs.getName)}: $ft @${typeOf[BigQueryTag]}"
(params, r)
}
def toFields(fields: JList[TableFieldSchema]): (Seq[Tree], Seq[Tree]) = {
val f = fields.asScala.map(s => toField(s))
(f.map(_._1).toSeq, f.flatMap(_._2).toSeq)
}
val (fields, records) = toFields(schema.getFields)
val (r, caseClassTree, name) = annottees.map(_.tree) match {
case (clazzDef @ q"$mods class $cName[..$_] $_(..$cfields) extends { ..$_ } with ..$parents { $_ => ..$body }") :: tail
if mods.asInstanceOf[Modifiers].flags == NoFlags =>
if (parents.map(_.toString).toSet != Set("scala.AnyRef")) {
c.abort(c.enclosingPosition, s"Invalid annotation, don't extend the class $clazzDef")
}
if (cfields.nonEmpty) {
c.abort(c.enclosingPosition, s"Invalid annotation, don't provide class fields $clazzDef")
}
val desc = getTableDescription(c)(clazzDef.asInstanceOf[ClassDef])
val defTblDesc =
desc.headOption.map(d => q"override def tableDescription: _root_.java.lang.String = $d")
val defTblTrait =
defTblDesc.map(_ => tq"${p(c, SType)}.HasTableDescription").toSeq
val defSchema = {
schema.setFactory(GsonFactory.getDefaultInstance)
q"override def schema: ${p(c, GModel)}.TableSchema = ${p(c, SUtil)}.parseSchema(${schema.toString})"
}
val defAvroSchema =
q"override def avroSchema: org.apache.avro.Schema = ${p(c, BigQueryUtils)}.toGenericAvroSchema(${cName.toString}, this.schema.getFields)"
val defToPrettyString =
q"override def toPrettyString(indent: Int = 0): String = ${p(c, s"$SBQ.types.SchemaUtil")}.toPrettyString(this.schema, ${cName.toString}, indent)"
val caseClassTree = q"""${caseClass(c)(mods, cName, fields, body)}"""
val maybeCompanion = tail.headOption
(
q"""$caseClassTree
${companion(c)(
cName,
traits ++ defTblTrait,
Seq(defSchema, defAvroSchema, defToPrettyString) ++ overrides ++ defTblDesc,
fields.size,
maybeCompanion
)}
..$records
""",
caseClassTree,
cName.toString
)
case t => c.abort(c.enclosingPosition, s"Invalid annotation $t")
}
debug(c)(s"TypeProvider.schemaToType[$schema]:", r)
if (shouldDumpClassesForPlugin) {
dumpCodeForScalaPlugin(c)(records, caseClassTree, name)
}
c.Expr[Any](r)
}