private def schemaToType()

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)
  }