anorm.Macro.scala Maven / Gradle / Ivy
package anorm
import com.github.ghik.silencer.silent
/**
* @define caseTParam the type of case class
* @define namingParam the column naming, to resolve the column name for each case class property
* @define namesParam the names of the columns corresponding to the case class properties
* @define sealedParserDoc Returns a row parser generated
* for a sealed class family.
* Each direct known subclasses `C` must be provided with an appropriate
* `RowParser[C]` in the implicit scope.
*
* @define discriminatorNamingParam the naming function for the discriminator column
* @define discriminateParam the discriminating function applied to each name of the family type
* @define familyTParam the type of the type family (either a sealed trait or abstract class)
* @define separatorParam the separator used with nested properties
* @define projectionParam The optional projection for the properties as parameters; If none, using the all the class properties.
* @define valueClassTParam the type of the value class
*/
object Macro {
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
/** Only for internal purposes */
final class Placeholder {}
/** Only for internal purposes */
object Placeholder {
implicit object Parser extends RowParser[Placeholder] {
val success = Success(new Placeholder())
def apply(row: Row) = success
}
}
/**
* Naming strategy, to map each class property to the corresponding column.
*/
trait ColumnNaming extends (String => String) {
/**
* Returns the column name for the class property.
*
* @param property the name of the case class property
*/
def apply(property: String): String
}
/** Naming companion */
object ColumnNaming {
/** Keep the original property name. */
object Identity extends ColumnNaming {
def apply(property: String) = property
}
/**
* For each class property, use the snake case equivalent
* to name its column (e.g. fooBar -> foo_bar).
*/
object SnakeCase extends ColumnNaming {
private val re = "[A-Z]+".r
def apply(property: String): String =
re.replaceAllIn(property, { m => s"_${m.matched.toLowerCase}" })
}
/** Naming using a custom transformation function. */
def apply(transformation: String => String): ColumnNaming =
new ColumnNaming {
def apply(property: String): String = transformation(property)
}
}
trait Discriminate extends (String => String) {
/**
* Returns the value representing the specified type,
* to be used as a discriminator within a sealed family.
*
* @param tname the name of type (class or object) to be discriminated
*/
def apply(tname: String): String
}
object Discriminate {
sealed class Function(f: String => String) extends Discriminate {
def apply(tname: String) = f(tname)
}
/** Uses the type name as-is as value for the discriminator */
object Identity extends Function(identity[String])
/** Returns a `Discriminate` function from any `String => String`. */
def apply(discriminate: String => String): Discriminate =
new Function(discriminate)
}
trait DiscriminatorNaming extends (String => String) {
/**
* Returns the name for the discriminator column.
* @param familyType the name of the famility type (sealed trait)
*/
def apply(familyType: String): String
}
object DiscriminatorNaming {
sealed class Function(f: String => String) extends DiscriminatorNaming {
def apply(familyType: String) = f(familyType)
}
/** Always use "classname" as name for the discriminator column. */
object Default extends Function(_ => "classname")
/** Returns a naming according from any `String => String`. */
def apply(naming: String => String): DiscriminatorNaming =
new Function(naming)
}
// ---
def namedParserImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[T] = {
import c.universe._
parserImpl[T](c) { (t, n, _) => q"anorm.SqlParser.get[$t]($n)" }
}
def namedParserImpl1[T: c.WeakTypeTag](c: whitebox.Context)(naming: c.Expr[ColumnNaming]): c.Expr[T] = {
import c.universe._
parserImpl[T](c) { (t, n, _) => q"anorm.SqlParser.get[$t]($naming($n))" }
}
@deprecated("Use [[namedParserImpl2]]", "2.5.2")
@SuppressWarnings(Array("MethodNames" /*deprecated*/ ))
def namedParserImpl_[T: c.WeakTypeTag](c: whitebox.Context)(names: c.Expr[String]*): c.Expr[T] = namedParserImpl2[T](c)(names: _*)
def namedParserImpl2[T: c.WeakTypeTag](c: whitebox.Context)(names: c.Expr[String]*): c.Expr[T] = {
import c.universe._
namedParserImpl4[T](c)(names) { n => q"$n" }
}
def namedParserImpl3[T: c.WeakTypeTag](c: whitebox.Context)(naming: c.Expr[ColumnNaming], names: c.Expr[String]*): c.Expr[T] = {
import c.universe._
namedParserImpl4[T](c)(names) { n => q"$naming($n)" }
}
private def namedParserImpl4[T: c.WeakTypeTag](c: whitebox.Context)(names: Seq[c.Expr[String]])(naming: c.Expr[String] => c.universe.Tree): c.Expr[T] = {
import c.universe._
val tpe = c.weakTypeTag[T].tpe
val ctor = tpe.decl(termNames.CONSTRUCTOR).asMethod
val params = ctor.paramLists.flatten
@SuppressWarnings(Array("ListSize"))
def psz = params.size
if (names.size < psz) {
c.abort(
c.enclosingPosition,
s"no column name for parameters: ${show(names)} < $params")
} else {
parserImpl[T](c) { (t, _, i) =>
names.lift(i) match {
case Some(n) => {
val cn = naming(n)
q"anorm.SqlParser.get[$t]($cn)"
}
case _ => c.abort(
c.enclosingPosition,
s"missing column name for parameter $i")
}
}
}
}
def offsetParserImpl[T: c.WeakTypeTag](c: whitebox.Context)(offset: c.Expr[Int]): c.Expr[T] = {
import c.universe._
parserImpl[T](c) { (t, _, i) =>
q"anorm.SqlParser.get[$t]($offset + ${i + 1})"
}
}
def indexedParserImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[T] = {
import c.universe._
@silent def p = reify(0)
offsetParserImpl[T](c)(p)
}
def sealedParserImpl1[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[RowParser[T]] = {
import c.universe.reify
@silent def discriminator = reify(DiscriminatorNaming.Default)
@silent def discriminate = reify(Discriminate.Identity)
sealedParserImpl(c)(discriminator, discriminate)
}
def sealedParserImpl2[T: c.WeakTypeTag](c: whitebox.Context)(naming: c.Expr[DiscriminatorNaming]): c.Expr[RowParser[T]] = sealedParserImpl(c)(naming, c.universe.reify(Discriminate.Identity))
def sealedParserImpl3[T: c.WeakTypeTag](c: whitebox.Context)(discriminate: c.Expr[Discriminate]): c.Expr[RowParser[T]] = sealedParserImpl(c)(c.universe.reify(DiscriminatorNaming.Default), discriminate)
def sealedParserImpl[T: c.WeakTypeTag](c: whitebox.Context)(naming: c.Expr[DiscriminatorNaming], discriminate: c.Expr[Discriminate]): c.Expr[RowParser[T]] = anorm.macros.SealedRowParserImpl[T](c)(naming, discriminate)
private def parserImpl[T: c.WeakTypeTag](c: whitebox.Context)(genGet: (c.universe.Type, String, Int) => c.universe.Tree): c.Expr[T] = anorm.macros.RowParserImpl[T](c)(genGet)
/**
* Returns a row parser generated for a case class `T`,
* getting column values by name.
*
* @tparam T $caseTParam
*
* {{{
* import anorm.{ Macro, RowParser }
*
* case class YourCaseClass(v: Int)
*
* val p: RowParser[YourCaseClass] = Macro.namedParser[YourCaseClass]
* }}}
*/
def namedParser[T]: RowParser[T] = macro namedParserImpl[T]
/**
* Returns a row parser generated for a case class `T`,
* getting column values by name.
*
* @tparam T $caseTParam
* @param naming $namingParam
*
* {{{
* import anorm.{ Macro, RowParser }
*
* case class YourCaseClass(v: Int)
*
* val p: RowParser[YourCaseClass] = Macro.namedParser[YourCaseClass]
* }}}
*/
@SuppressWarnings(Array("UnusedMethodParameter" /* macro */ ))
def namedParser[T](naming: Macro.ColumnNaming): RowParser[T] = macro namedParserImpl1[T]
/**
* Returns a row parser generated for a case class `T`,
* getting column values according the property `names`.
*
* @tparam T $caseTParam
* @param names $namesParam
*
* {{{
* import anorm.{ Macro, RowParser }
*
* case class YourCaseClass(a: Int, b: String)
*
* val p: RowParser[YourCaseClass] =
* Macro.parser[YourCaseClass]("foo", "bar")
* }}}
*/
@SuppressWarnings(Array("UnusedMethodParameter" /* macro */ ))
def parser[T](names: String*): RowParser[T] = macro namedParserImpl2[T]
/**
* Returns a row parser generated for a case class `T`,
* getting column values according the property `names`.
*
* @tparam T $caseTParam
*
* @param naming $namingParam
* @param names $namesParam
*
* {{{
* import anorm.{ Macro, RowParser }
*
* case class YourCaseClass(a: Int, b: String)
*
* val p: RowParser[YourCaseClass] =
* Macro.parser[YourCaseClass]("foo", "loremIpsum")
* }}}
*/
@SuppressWarnings(Array("UnusedMethodParameter" /* macro */ ))
def parser[T](naming: Macro.ColumnNaming, names: String*): RowParser[T] = macro namedParserImpl3[T]
/**
* Returns a row parser generated for a case class `T`,
* getting column values by position.
*
* @tparam T $caseTParam
*
* {{{
* import anorm.{ Macro, RowParser }
*
* case class YourCaseClass(v: Int)
*
* val p: RowParser[YourCaseClass] = Macro.indexedParser[YourCaseClass]
* }}}
*/
def indexedParser[T]: RowParser[T] = macro indexedParserImpl[T]
/**
* Returns a row parser generated for a case class `T`,
* getting column values by position, with an offset.
*
* @tparam T $caseTParam
* @param offset the offset of column to be considered by the parser
*
* {{{
* import anorm.{ Macro, RowParser }
*
* case class YourCaseClass(v: Int)
*
* val p: RowParser[YourCaseClass] = Macro.offsetParser[YourCaseClass](2)
* }}}
*/
@SuppressWarnings(Array("UnusedMethodParameter" /* macro */ ))
def offsetParser[T](offset: Int): RowParser[T] = macro offsetParserImpl[T]
/**
* $sealedParserDoc
* The default naming is used.
*
* @tparam T $familyTParam
*/
def sealedParser[T]: RowParser[T] = macro sealedParserImpl1[T]
/**
* $sealedParserDoc
*
* @param naming $discriminatorNamingParam
* @tparam T $familyTParam
*/
@SuppressWarnings(Array("UnusedMethodParameter" /* macro */ ))
def sealedParser[T](naming: Macro.DiscriminatorNaming): RowParser[T] = macro sealedParserImpl2[T]
/**
* $sealedParserDoc
*
* @param discriminate $discriminateParam
* @tparam T $familyTParam
*/
@SuppressWarnings(Array("UnusedMethodParameter" /* macro */ ))
def sealedParser[T](discriminate: Macro.Discriminate): RowParser[T] = macro sealedParserImpl3[T]
/**
* $sealedParserDoc
*
* @param naming $discriminatorNamingParam
* @param discriminate $discriminateParam
* @tparam T $familyTParam
*/
@SuppressWarnings(Array("UnusedMethodParameter" /* macro */ ))
def sealedParser[T](naming: Macro.DiscriminatorNaming, discriminate: Macro.Discriminate): RowParser[T] = macro sealedParserImpl[T]
/**
* Returns a column parser for specified value class.
*
* {{{
* import anorm._
*
* class ValueClassType(val v: Int) extends AnyVal
*
* implicit val column: Column[ValueClassType] =
* Macro.valueColumn[ValueClassType]
* }}}
*
* @tparam T $valueClassTParam
*/
def valueColumn[T <: AnyVal]: Column[T] = macro anorm.macros.ValueColumnImpl[T]
// --- ToParameter ---
import anorm.macros.ToParameterListImpl
/**
* @param separator $separatorParam
* @tparam T $caseTParam
*
* {{{
* import anorm.{ Macro, ToParameterList }
*
* case class Bar(v: Float)
*
* // Bar must be a case class, or a sealed trait with known subclasses
* implicit val toParams: ToParameterList[Bar] = Macro.toParameters[Bar]
* }}}
*/
def toParameters[T]: ToParameterList[T] = macro defaultParameters[T]
def defaultParameters[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[ToParameterList[T]] = {
val tpe = c.weakTypeTag[T].tpe
val tpeSym = tpe.typeSymbol.asClass
@inline def abort(msg: String) = c.abort(c.enclosingPosition, msg)
if (tpeSym.isSealed && tpeSym.isAbstract) {
ToParameterListImpl.sealedTrait[T](c)
} else if (!tpeSym.isClass || !tpeSym.asClass.isCaseClass) {
abort(s"Either a sealed trait or a case class expected: $tpe")
} else {
@silent def p = c.universe.reify("_")
ToParameterListImpl.caseClass[T](c)(
Seq.empty[c.Expr[Macro.ParameterProjection]], p)
}
}
/**
* @param separator $separatorParam
* @tparam T $caseTParam
*
* {{{
* import anorm.{ Macro, ToParameterList }
*
* case class Bar(v: String)
*
* // Bar must be a case class
* implicit val toParams: ToParameterList[Bar] =
* Macro.toParameters[Bar]("_")
* }}}
*/
@SuppressWarnings(Array("UnusedMethodParameter" /*macro*/ ))
def toParameters[T](separator: String): ToParameterList[T] = macro parametersDefaultNames[T]
def parametersDefaultNames[T: c.WeakTypeTag](c: whitebox.Context)(separator: c.Expr[String]): c.Expr[ToParameterList[T]] = ToParameterListImpl.caseClass[T](c)(Seq.empty[c.Expr[Macro.ParameterProjection]], separator)
/**
* @param projection $projectionParam
* @tparam T $caseTParam
*
* {{{
* import anorm.{ Macro, ToParameterList }
*
* case class Bar(v: Int)
*
* // Bar must be a case class
* implicit val toParams: ToParameterList[Bar] =
* Macro.toParameters[Bar]()
* }}}
*/
@SuppressWarnings(Array("UnusedMethodParameter" /*macro*/ ))
def toParameters[T](projection: Macro.ParameterProjection*): ToParameterList[T] = macro configuredParameters[T]
def configuredParameters[T: c.WeakTypeTag](c: whitebox.Context)(projection: c.Expr[Macro.ParameterProjection]*): c.Expr[ToParameterList[T]] = {
import c.universe.reify
@silent def p = reify("_")
ToParameterListImpl.caseClass[T](c)(projection, p)
}
/**
* @param separator $separatorParam
* @param projection $projectionParam
* @tparam T $caseTParam
*/
@SuppressWarnings(Array("UnusedMethodParameter" /*macro*/ ))
def toParameters[T](separator: String, projection: Macro.ParameterProjection*): ToParameterList[T] = macro parametersWithSeparator[T]
def parametersWithSeparator[T: c.WeakTypeTag](c: whitebox.Context)(separator: c.Expr[String], projection: c.Expr[Macro.ParameterProjection]*): c.Expr[ToParameterList[T]] = ToParameterListImpl.caseClass[T](c)(projection, separator)
/**
* Returns a `ToStatement` for the specified ValueClass.
*
* {{{
* import anorm._
*
* class ValueClassType(val i: Int) extends AnyVal
*
* implicit val instance: ToStatement[ValueClassType] =
* Macro.valueToStatement[ValueClassType]
* }}}
*
* @tparam T $valueClassTParam
*/
def valueToStatement[T <: AnyVal]: ToStatement[T] = macro anorm.macros.ValueToStatement[T]
// ---
/**
* @param propertyName the name of the class property
* @param parameterName the name of for the parameter,
* if different from the property one, otherwise `None`
*/
case class ParameterProjection(
propertyName: String,
parameterName: Option[String] = None)
object ParameterProjection {
def apply(
propertyName: String,
parameterName: String): ParameterProjection =
ParameterProjection(propertyName, Option(parameterName))
}
private[anorm] lazy val debugEnabled =
Option(System.getProperty("anorm.macro.debug")).
filterNot(_.isEmpty).map(_.toLowerCase).map { v =>
"true".equals(v) || v.substring(0, 1) == "y"
}.getOrElse(false)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy