in scalding-db/src/main/scala/com/twitter/scalding/db/macros/impl/ColumnDefinitionProviderImpl.scala [49:213]
private[scalding] def getColumnFormats[T](
c: Context
)(implicit T: c.WeakTypeTag[T]): List[ColumnFormat[c.type]] = {
import c.universe._
if (!IsCaseClassImpl.isCaseClassType(c)(T.tpe))
c.abort(
c.enclosingPosition,
s"""We cannot enforce ${T.tpe} is a case class, either it is not a case class or this macro call is possibly enclosed in a class.
This will mean the macro is operating on a non-resolved type."""
)
// Field To JDBCColumn
@tailrec
def matchField(
accessorTree: List[MethodSymbol],
oTpe: Type,
fieldName: FieldName,
defaultValOpt: Option[c.Expr[String]],
annotationInfo: List[(Type, Option[Int])],
nullable: Boolean
): scala.util.Try[List[ColumnFormat[c.type]]] =
oTpe match {
// String handling
case tpe if tpe =:= typeOf[String] =>
StringTypeHandler(c)(accessorTree, fieldName, defaultValOpt, annotationInfo, nullable)
case tpe if tpe =:= typeOf[Array[Byte]] =>
BlobTypeHandler(c)(accessorTree, fieldName, defaultValOpt, annotationInfo, nullable)
case tpe if tpe =:= typeOf[Byte] =>
NumericTypeHandler(c)(accessorTree, fieldName, defaultValOpt, annotationInfo, nullable, "TINYINT")
case tpe if tpe =:= typeOf[Short] =>
NumericTypeHandler(c)(accessorTree, fieldName, defaultValOpt, annotationInfo, nullable, "SMALLINT")
case tpe if tpe =:= typeOf[Int] =>
NumericTypeHandler(c)(accessorTree, fieldName, defaultValOpt, annotationInfo, nullable, "INT")
case tpe if tpe =:= typeOf[Long] =>
NumericTypeHandler(c)(accessorTree, fieldName, defaultValOpt, annotationInfo, nullable, "BIGINT")
case tpe if tpe =:= typeOf[Double] =>
NumericTypeHandler(c)(accessorTree, fieldName, defaultValOpt, annotationInfo, nullable, "DOUBLE")
case tpe if tpe =:= typeOf[Boolean] =>
NumericTypeHandler(c)(accessorTree, fieldName, defaultValOpt, annotationInfo, nullable, "BOOLEAN")
case tpe if tpe =:= typeOf[java.util.Date] =>
DateTypeHandler(c)(accessorTree, fieldName, defaultValOpt, annotationInfo, nullable)
case tpe if tpe.erasure =:= typeOf[Option[Any]] && nullable == true =>
Failure(
new Exception(
s"Case class ${T.tpe} has field $fieldName which contains a nested option. This is not supported by this macro."
)
)
case tpe if tpe.erasure =:= typeOf[Option[Any]] && nullable == false =>
if (defaultValOpt.isDefined)
Failure(
new Exception(
s"Case class ${T.tpe} has field $fieldName: ${oTpe.toString}, with a default value. Options cannot have default values"
)
)
else {
matchField(
accessorTree,
tpe.asInstanceOf[TypeRefApi].args.head,
fieldName,
None,
annotationInfo,
true
)
}
case tpe if IsCaseClassImpl.isCaseClassType(c)(tpe) => expandMethod(accessorTree, tpe)
// default
case _ =>
Failure(
new Exception(
s"Case class ${T.tpe} has field $fieldName: ${oTpe.toString}, which is not supported for talking to JDBC"
)
)
}
def expandMethod(
outerAccessorTree: List[MethodSymbol],
outerTpe: Type
): scala.util.Try[List[ColumnFormat[c.type]]] = {
val defaultArgs = getDefaultArgs(c)(outerTpe)
// Intializes the type info
outerTpe.declarations.foreach(_.typeSignature)
// We have to build this up front as if the case class definition moves to another file
// the annotation moves from the value onto the getter method?
val annotationData: Map[String, List[(Type, List[Tree])]] = outerTpe.declarations
.map { m =>
val mappedAnnotations = m.annotations.map(t => (t.tpe, t.scalaArgs))
m.name.toString.trim -> mappedAnnotations
}
.groupBy(_._1)
.map { case (k, l) =>
(k, l.map(_._2).reduce(_ ++ _))
}
.filter { case (_, v) =>
v.nonEmpty
}
outerTpe.declarations
.collect { case m: MethodSymbol if m.isCaseAccessor => m }
.map { m =>
val fieldName = m.name.toString.trim
val defaultVal = defaultArgs.get(fieldName)
val annotationInfo: List[(Type, Option[Int])] = annotationData
.getOrElse(m.name.toString.trim, Nil)
.collect {
case (tpe, List(Literal(Constant(siz: Int))))
if tpe =:= typeOf[com.twitter.scalding.db.macros.size] =>
(tpe, Some(siz))
case (tpe, _) if tpe =:= typeOf[com.twitter.scalding.db.macros.size] =>
c.abort(
c.enclosingPosition,
"Hit a size macro where we couldn't parse the value. Probably not a literal constant. Only literal constants are supported."
)
case (tpe, _) if tpe <:< typeOf[com.twitter.scalding.db.macros.ScaldingDBAnnotation] =>
(tpe, None)
}
matchField(
outerAccessorTree :+ m,
m.returnType,
FieldName(fieldName),
defaultVal,
annotationInfo,
false
)
}
.toList
// This algorithm returns the error from the first exception we run into.
.foldLeft(scala.util.Try[List[ColumnFormat[c.type]]](Nil)) { case (pTry, nxt) =>
(pTry, nxt) match {
case (Success(l), Success(r)) => Success(l ::: r)
case (f @ Failure(_), _) => f
case (_, f @ Failure(_)) => f
}
}
}
val formats = expandMethod(Nil, T.tpe) match {
case Success(s) => s
case Failure(e) => (c.abort(c.enclosingPosition, e.getMessage))
}
val duplicateFields = formats
.map(_.fieldName)
.groupBy(identity)
.filter(_._2.size > 1)
.keys
if (duplicateFields.nonEmpty) {
c.abort(
c.enclosingPosition,
s"""
Duplicate field names found: ${duplicateFields.mkString(",")}.
Please check your nested case classes.
"""
)
} else {
formats
}
}