|
Play Framework/Scala example source code file (Anorm.scala)
The Anorm.scala Play Framework example source code/* * Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com> */ package anorm import java.util.{ Date, UUID } import java.sql.{ Connection, PreparedStatement, ResultSet } import scala.language.{ postfixOps, reflectiveCalls } import scala.collection.TraversableOnce import resource.{ managed, ManagedResource } /** Error from processing SQL */ sealed trait SqlRequestError case class ColumnNotFound(column: String, possibilities: List[String]) extends SqlRequestError { override lazy val toString = s"$column not found, available columns : " + possibilities.map { _.dropWhile(_ == '.') }.mkString(", ") } case class TypeDoesNotMatch(message: String) extends SqlRequestError case class UnexpectedNullableFound(on: String) extends SqlRequestError case class SqlMappingError(msg: String) extends SqlRequestError @deprecated( message = "Do not use directly, but consider [[Id]] or [[NotAssigned]].", since = "2.3.0") trait Pk[+ID] extends NotNull { def toOption: Option[ID] = this match { case Id(x) => Some(x) case NotAssigned => None } def isDefined: Boolean = toOption.isDefined def get: ID = toOption.get def getOrElse[V >: ID](id: V): V = toOption.getOrElse(id) def map[B](f: ID => B) = toOption.map(f) def flatMap[B](f: ID => Option[B]) = toOption.flatMap(f) def foreach(f: ID => Unit) = toOption.foreach(f) } case class Id[ID](id: ID) extends Pk[ID] { override def toString() = id.toString } case object NotAssigned extends Pk[Nothing] { override def toString() = "NotAssigned" } /** * Untyped value wrapper. * * {{{ * SQL("UPDATE t SET val = {o}").on('o -> anorm.Object(val)) * }}} */ case class Object(value: Any) case class MetaDataItem(column: ColumnName, nullable: Boolean, clazz: String) case class ColumnName(qualified: String, alias: Option[String]) private[anorm] case class MetaData(ms: List[MetaDataItem]) { // Use MetaDataItem rather than (ColumnName, Boolean, String)? def get(columnName: String): Option[(ColumnName, Boolean, String)] = { val columnUpper = columnName.toUpperCase dictionary2.get(columnUpper).orElse(dictionary.get(columnUpper)) } // Use MetaDataItem rather than (ColumnName, Boolean, String)? def getAliased(aliasName: String): Option[(ColumnName, Boolean, String)] = aliasedDictionary.get(aliasName.toUpperCase) private lazy val dictionary: Map[String, (ColumnName, Boolean, String)] = ms.map(m => (m.column.qualified.toUpperCase(), (m.column, m.nullable, m.clazz))).toMap private lazy val dictionary2: Map[String, (ColumnName, Boolean, String)] = ms.map(m => { val column = m.column.qualified.split('.').last; (column.toUpperCase(), (m.column, m.nullable, m.clazz)) }).toMap private lazy val aliasedDictionary: Map[String, (ColumnName, Boolean, String)] = { ms.flatMap(m => { m.column.alias.map { a => Map(a.toUpperCase() -> Tuple3(m.column, m.nullable, m.clazz)) }.getOrElse(Map.empty) }).toMap } lazy val columnCount = ms.size lazy val availableColumns: List[String] = ms.flatMap(i => i.column.qualified :: i.column.alias.toList) } object Useful { case class Var[T](var content: T) @deprecated(message = "Use directly Stream value", since = "2.3.2") def unfold1[T, R](init: T)(f: T => Option[(R, T)]): (Stream[R], T) = f(init) match { case None => (Stream.Empty, init) case Some((r, v)) => (Stream.cons(r, unfold(v)(f)), v) } def unfold[T, R](init: T)(f: T => Option[(R, T)]): Stream[R] = f(init) match { case None => Stream.Empty case Some((r, v)) => Stream.cons(r, unfold(v)(f)) } } /** * Wrapper to use [[Seq]] as SQL parameter, with custom formatting. * * {{{ * SQL("SELECT * FROM t WHERE %s"). * on(SeqParameter(Seq("a", "b"), " OR ", Some("cat = "))) * // Will execute as: * // SELECT * FROM t WHERE cat = 'a' OR cat = 'b' * }}} */ sealed trait SeqParameter[A] { def values: Seq[A] def separator: String def before: Option[String] def after: Option[String] } /** SeqParameter factory */ object SeqParameter { def apply[A]( seq: Seq[A], sep: String = ", ", pre: String = "", post: String = ""): SeqParameter[A] = new SeqParameter[A] { val values = seq val separator = sep val before = Option(pre) val after = Option(post) } } /** Applied named parameter. */ sealed case class NamedParameter(name: String, value: ParameterValue) { lazy val tupled: (String, ParameterValue) = (name, value) } /** Companion object for applied named parameter. */ object NamedParameter { import scala.language.implicitConversions /** * Conversion to use tuple, with first element being name * of parameter as string. * * {{{ * val p: Parameter = ("name" -> 1l) * }}} */ implicit def string[V](t: (String, V))(implicit c: V => ParameterValue): NamedParameter = NamedParameter(t._1, c(t._2)) /** * Conversion to use tuple, * with first element being symbolic name or parameter. * * {{{ * val p: Parameter = ('name -> 1l) * }}} */ implicit def symbol[V](t: (Symbol, V))(implicit c: V => ParameterValue): NamedParameter = NamedParameter(t._1.name, c(t._2)) } /** Simple/plain SQL. */ case class SimpleSql[T](sql: SqlQuery, params: Map[String, ParameterValue], defaultParser: RowParser[T]) extends Sql { /** * Returns query prepared with named parameters. * * {{{ * import anorm.toParameterValue * * val baseSql = SQL("SELECT * FROM table WHERE id = {id}") // one named param * val preparedSql = baseSql.withParams("id" -> "value") * }}} */ def on(args: NamedParameter*): SimpleSql[T] = copy(params = this.params ++ args.map(_.tupled)) /** * Returns query prepared with parameters using initial order * of placeholder in statement. * * {{{ * import anorm.toParameterValue * * val baseSql = * SQL("SELECT * FROM table WHERE name = {name} AND lang = {lang}") * * val preparedSql = baseSql.onParams("1st", "2nd") * // 1st param = name, 2nd param = lang * }}} */ def onParams(args: ParameterValue*): SimpleSql[T] = copy(params = this.params ++ Sql.zipParams( sql.paramsInitialOrder, args, Map.empty)) // TODO: Scaladoc as `as` equivalent def list()(implicit connection: Connection): Seq[T] = as(defaultParser.*) // TODO: Scaladoc as `as` equivalent def single()(implicit connection: Connection): T = as(defaultParser.single) // TODO: Scaladoc, add to specs as `as` equivalent def singleOpt()(implicit connection: Connection): Option[T] = as(defaultParser.singleOpt) def getFilledStatement(connection: Connection, getGeneratedKeys: Boolean = false) = { val st: (String, Seq[(Int, ParameterValue)]) = Sql.prepareQuery( sql.statement, 0, sql.paramsInitialOrder.map(params), Nil) val stmt = if (getGeneratedKeys) connection.prepareStatement(st._1, java.sql.Statement.RETURN_GENERATED_KEYS) else connection.prepareStatement(st._1) sql.timeout.foreach(stmt.setQueryTimeout(_)) st._2 foreach { p => val (i, v) = p v.set(stmt, i + 1) } stmt } /** * Prepares query with given row parser. * * {{{ * import anorm.{ SQL, SqlParser } * * val res: Int = SQL("SELECT 1").using(SqlParser.scalar[Int]).single * // Equivalent to: SQL("SELECT 1").as(SqlParser.scalar[Int].single) * }}} */ def using[U](p: RowParser[U]): SimpleSql[U] = copy(sql, params, p) // Deprecates with .as ? def map[A](f: T => A): SimpleSql[A] = copy(defaultParser = defaultParser.map(f)) def withQueryTimeout(seconds: Option[Int]): SimpleSql[T] = copy(sql = sql.withQueryTimeout(seconds)) } private[anorm] trait Sql { // TODO: ManagedResource[PreparedStatement]? def getFilledStatement(connection: Connection, getGeneratedKeys: Boolean = false): PreparedStatement /** * Executes this SQL statement as query, returns result as Row stream. */ def apply()(implicit connection: Connection): Stream[Row] = Sql.resultSetToStream(resultSet()) /** * Executes this statement as query (see [[executeQuery]]) and returns result. */ private[anorm] def resultSet()(implicit connection: Connection): ManagedResource[ResultSet] = managed(getFilledStatement(connection)). flatMap(stmt => managed(stmt.executeQuery())) /** * Executes this statement as query and convert result as `T`, using parser. */ def as[T](parser: ResultSetParser[T])(implicit connection: Connection): T = Sql.as(parser, resultSet()) /** * Executes this SQL statement. * @return true if resultset was returned from execution * (statement is query), or false if it executed update */ def execute()(implicit connection: Connection): Boolean = // TODO: managed managed(getFilledStatement(connection)).acquireAndGet(_.execute()) @deprecated(message = "Will be made private, use [[executeUpdate]] or [[executeInsert]]", since = "2.3.2") def execute1(getGeneratedKeys: Boolean = false)(implicit connection: Connection): (PreparedStatement, Int) = { // TODO: managed val statement = getFilledStatement(connection, getGeneratedKeys) (statement, statement.executeUpdate()) } /** * Executes this SQL as an update statement. * @return Count of updated row(s) */ @throws[java.sql.SQLException]("If statement is query not update") def executeUpdate()(implicit connection: Connection): Int = getFilledStatement(connection).executeUpdate() /** * Executes this SQL as an insert statement. * * @param generatedKeysParser Parser for generated key (default: scalar long) * @return Parsed generated keys * * {{{ * import anorm.SqlParser.scalar * * val keys1 = SQL("INSERT INTO Test(x) VALUES ({x})"). * on("x" -> "y").executeInsert() * * val keys2 = SQL("INSERT INTO Test(x) VALUES ({x})"). * on("x" -> "y").executeInsert(scalar[String].singleOpt) * // ... generated string key * }}} */ def executeInsert[A](generatedKeysParser: ResultSetParser[A] = SqlParser.scalar[Long].singleOpt)(implicit connection: Connection): A = Sql.as(generatedKeysParser, managed(getFilledStatement(connection, true)). flatMap { stmt => stmt.executeUpdate() managed(stmt.getGeneratedKeys) }) /** * Executes this SQL query, and returns its result. * * {{{ * implicit val conn: Connection = openConnection * val res: SqlQueryResult = * SQL("SELECT text_col FROM table WHERE id = {code}"). * on("code" -> code).executeQuery() * // Check execution context; e.g. res.statementWarning * val str = res as scalar[String].single // going with row parsing * }}} */ def executeQuery()(implicit connection: Connection): SqlQueryResult = SqlQueryResult(resultSet()) } object Sql { // TODO: Rename to SQL import java.sql.ResultSetMetaData private[anorm] def metaData(rs: ResultSet): MetaData = { val meta = rs.getMetaData() val nbColumns = meta.getColumnCount() MetaData(List.range(1, nbColumns + 1).map(i => MetaDataItem(column = ColumnName({ // HACK FOR POSTGRES - Fix in https://github.com/pgjdbc/pgjdbc/pull/107 if (meta.getClass.getName.startsWith("org.postgresql.")) { meta.asInstanceOf[{ def getBaseTableName(i: Int): String }].getBaseTableName(i) } else { meta.getTableName(i) } } + "." + meta.getColumnName(i), alias = Option(meta.getColumnLabel(i))), nullable = meta.isNullable(i) == ResultSetMetaData.columnNullable, clazz = meta.getColumnClassName(i)))) } private[anorm] def as[T](parser: ResultSetParser[T], rs: ManagedResource[ResultSet])(implicit connection: Connection): T = parser(Sql.resultSetToStream(rs)) match { case Success(a) => a case Error(e) => sys.error(e.toString) } private def fold[T](res: ManagedResource[ResultSet])(initial: T)(f: (T, Row) => T): ManagedResource[T] = res map { rs => val rsMetaData: MetaData = metaData(rs) val columns: List[Int] = List.range(1, rsMetaData.columnCount + 1) @inline def data(rs: ResultSet) = columns.map(rs.getObject(_)) @annotation.tailrec def go(r: ResultSet, s: T): T = if (r.next()) go(r, f(s, new SqlRow(rsMetaData, data(rs)))) else s go(rs, initial) } private[anorm] def resultSetToStream(rs: ManagedResource[ResultSet]): Stream[Row] = fold(rs)(Stream.empty[Row])((s, r) => s :+ r) acquireAndGet identity private case class SqlRow(metaData: MetaData, data: List[Any]) extends Row { override lazy val toString = "Row(" + metaData.ms.zip(data).map(t => s"'${t._1.column}': ${t._2} as ${t._1.clazz}").mkString(", ") + ")" } @annotation.tailrec private[anorm] def zipParams(ns: Seq[String], vs: Seq[ParameterValue], ps: Map[String, ParameterValue]): Map[String, ParameterValue] = (ns.headOption, vs.headOption) match { case (Some(n), Some(v)) => zipParams(ns.tail, vs.tail, ps + (n -> v)) case _ => ps } /** * Rewrites next format placeholder (%s) in statement, with fragment using * [[java.sql.PreparedStatement]] syntax (with one or more '?'). * * @param statement SQL statement (with %s placeholders) * @param frag Statement fragment * @return Some rewrited statement, or None if there no available placeholder * * {{{ * Sql.rewrite("SELECT * FROM Test WHERE cat IN (%s)", "?, ?") * // Some("SELECT * FROM Test WHERE cat IN (?, ?)") * }}} */ private[anorm] def rewrite(stmt: String, frag: String): Option[String] = { val idx = stmt.indexOf("%s") if (idx == -1) None else { val parts = stmt.splitAt(idx) Some(parts._1 + frag + parts._2.drop(2)) } } @annotation.tailrec private[anorm] def prepareQuery(sql: String, i: Int, ps: Seq[ParameterValue], vs: Seq[(Int, ParameterValue)]): (String, Seq[(Int, ParameterValue)]) = { ps.headOption match { case Some(p) => val st: (String, Int) = p.toSql(sql, i) prepareQuery(st._1, st._2, ps.tail, vs :+ (i -> p)) case _ => (sql, vs) } } } Other Play Framework source code examplesHere is a short list of links related to this Play Framework Anorm.scala source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.