All Downloads are FREE. Search and download functionalities are using the official Maven repository.

anorm.Anorm.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 
 */

package anorm

import java.sql.{ Connection, PreparedStatement, ResultSet }

import scala.util.{ Failure, Try }

import scala.reflect.ClassTag

import resource.{ managed, ManagedResource, Resource }

/**
 * Untyped value wrapper.
 *
 * {{{
 * import anorm._
 *
 * def foo(v: Any) = SQL("UPDATE t SET val = {o}").on("o" -> anorm.Object(v))
 * }}}
 */
case class Object(value: Any)

/**
 * Wrapper to use a value sequence as SQL parameter, with custom formatting.
 *
 * {{{
 * import anorm._
 *
 * def foo(implicit con: java.sql.Connection) = {
 *   SQL("SELECT * FROM t WHERE {s}").
 *     on("s" -> SeqParameter(Seq("a", "b"), " OR ", "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)
    }
}

private[anorm] trait Sql extends WithResult {
  private implicit val statementCls: ClassTag[PreparedStatement] =
    statementClassTag

  private implicit val resultSetCls: ClassTag[ResultSet] = resultSetClassTag

  private[anorm] def unsafeStatement(connection: Connection, getGeneratedKeys: Boolean = false): PreparedStatement

  private[anorm] def unsafeStatement(
      connection: Connection,
      generatedColumn: String,
      generatedColumns: Seq[String]
  ): PreparedStatement

  protected final def preparedStatement(
      connection: Connection,
      getGeneratedKeys: Boolean = false
  ): ManagedResource[PreparedStatement] = {
    implicit val res: Resource[PreparedStatement] = StatementResource

    managed(unsafeStatement(connection, getGeneratedKeys))
  }

  final def preparedStatement(
      connection: Connection,
      generatedColumn: String,
      generatedColumns: Seq[String]
  ): ManagedResource[PreparedStatement] = {
    implicit val res: Resource[PreparedStatement] = StatementResource

    managed(unsafeStatement(connection, generatedColumn, generatedColumns))
  }

  /**
   * Executes this statement as query (see [[executeQuery]]) and returns result.
   */
  protected def resultSet(connection: Connection): ManagedResource[ResultSet] =
    preparedStatement(connection).flatMap { stmt =>
      implicit val res: Resource[ResultSet] = ResultSetResource

      managed(stmt.executeQuery())
    }

  private[anorm] def unsafeResultSet(connection: Connection): ResultSet =
    unsafeStatement(connection).executeQuery()

  /**
   * Executes this SQL statement.
   * @return true if resultset was returned from execution
   * (statement is query), or false if it executed update.
   *
   * {{{
   * import anorm._
   *
   * def res(implicit con: java.sql.Connection): Boolean =
   *   SQL"""INSERT INTO Test(a, b) VALUES(\\${"A"}, \\${"B"}""".execute()
   * }}}
   */
  def execute()(implicit connection: Connection): Boolean =
    preparedStatement(connection).acquireAndGet(_.execute())
  // TODO: Safe alternative

  /**
   * 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 =
    preparedStatement(connection).acquireAndGet(_.executeUpdate())
  // TODO: Safe alternative

  /**
   * Executes this SQL as an insert statement.
   *
   * @param generatedKeysParser Parser for generated key (default: scalar long)
   * @return Parsed generated keys
   *
   * {{{
   * import anorm._, SqlParser.scalar
   *
   * def foo(implicit con: java.sql.Connection) = {
   *   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
   * }
   * }}}
   */
  @SuppressWarnings(Array("TryGet" /* TODO: Make it safer */ ))
  def executeInsert[A](generatedKeysParser: ResultSetParser[A] = SqlParser.scalar[Long].singleOpt)(implicit
      connection: Connection
  ): A = execInsert[A](preparedStatement(_, true), generatedKeysParser, ColumnAliaser.empty).get

  /**
   * Executes this SQL as an insert statement.
   *
   * @param generatedColumn the first (mandatory) column name to consider from the generated keys
   * @param otherColumns the other (possibly none) column name(s) from the generated keys
   * @param generatedKeysParser the parser for generated key (default: scalar long)
   * @return Parsed generated keys
   *
   * {{{
   * import anorm._, SqlParser.scalar
   *
   * def foo(implicit con: java.sql.Connection) = {
   *   val keys1 = SQL("INSERT INTO Test(x) VALUES ({x})").
   *     on("x" -> "y").executeInsert1("generatedCol", "colB")()
   *
   *   val keys2 = SQL("INSERT INTO Test(x) VALUES ({x})").
   *     on("x" -> "y").executeInsert1("generatedCol")(scalar[String].singleOpt)
   *   // ... generated string key
   * }
   * }}}
   */
  def executeInsert1[A](generatedColumn: String, otherColumns: String*)(
      generatedKeysParser: ResultSetParser[A] = SqlParser.scalar[Long].singleOpt
  )(implicit connection: Connection): Try[A] =
    execInsert[A](preparedStatement(_, generatedColumn, otherColumns), generatedKeysParser, ColumnAliaser.empty)

  /**
   * Executes this SQL as an insert statement.
   *
   * @param generatedColumn the first (mandatory) column name to consider from the generated keys
   * @param otherColumns the other (possibly none) column name(s) from the generated keys
   * @param generatedKeysParser the parser for generated key (default: scalar long)
   * @param aliaser the column aliaser
   * @return Parsed generated keys
   *
   * {{{
   * import anorm._, SqlParser.scalar
   *
   * def foo(implicit con: java.sql.Connection) = {
   *   val keys1 = SQL("INSERT INTO Test(x) VALUES ({x})").
   *     on("x" -> "y").executeInsert1("generatedCol", "colB")()
   *
   *   val keys2 = SQL("INSERT INTO Test(x) VALUES ({x})").
   *     on("x" -> "y").executeInsert1("generatedCol")(scalar[String].singleOpt)
   *   // ... generated string key
   * }
   * }}}
   */
  def executeInsert2[A](generatedColumn: String, otherColumns: String*)(
      generatedKeysParser: ResultSetParser[A] = SqlParser.scalar[Long].singleOpt,
      aliaser: ColumnAliaser
  )(implicit connection: Connection): Try[A] =
    execInsert[A](preparedStatement(_, generatedColumn, otherColumns), generatedKeysParser, aliaser)

  private def execInsert[A](
      prep: Connection => ManagedResource[PreparedStatement],
      generatedKeysParser: ResultSetParser[A],
      as: ColumnAliaser
  )(implicit connection: Connection): Try[A] = {
    Sql.asTry(
      generatedKeysParser,
      prep(connection).flatMap { stmt =>
        stmt.executeUpdate()
        managed(stmt.getGeneratedKeys)
      },
      resultSetOnFirstRow,
      as
    )
  }

  /**
   * Executes this SQL query, and returns its result.
   *
   * {{{
   * import anorm._, SqlParser.scalar
   *
   * def foo(code: String)(implicit con: java.sql.Connection) = {
   *   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(connection), resultSetOnFirstRow)

}

object Sql { // TODO: Rename to SQL
  import scala.util.{ Success => TrySuccess, Try }
  import scala.util.control.NoStackTrace

  private[anorm] def unsafeCursor(res: ResultSet, onFirstRow: Boolean, as: ColumnAliaser): Option[Cursor] = {
    if (onFirstRow) Cursor.onFirstRow(res, as) else Cursor(res, as)
  }

  private[anorm] def withResult[T](res: ManagedResource[ResultSet], onFirstRow: Boolean, as: ColumnAliaser)(
      op: Option[Cursor] => T
  ): ManagedResource[T] =
    res.map(rs => op(unsafeCursor(rs, onFirstRow, as)))

  private[anorm] def asTry[T](
      parser: ResultSetParser[T],
      rs: ManagedResource[ResultSet],
      onFirstRow: Boolean,
      as: ColumnAliaser
  ): Try[T] = Try(withResult(rs, onFirstRow, as)(parser).acquireAndGet(identity))
    .flatMap(_.fold[Try[T]](_.toFailure, TrySuccess.apply))

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

  @inline
  private def toSql(ts: List[StatementToken], buf: StringBuilder): StringBuilder = ts.foldLeft(buf) {
    case (sql, StringToken(t)) => sql ++= t
    case (sql, PercentToken)   => sql += '%'
  }

  @SuppressWarnings(Array("IncorrectlyNamedExceptions"))
  final class MissingParameter(after: String, placeholder: String)
      extends java.util.NoSuchElementException(s"Missing parameter value for '$placeholder' after: $after")
      with NoStackTrace {
    @deprecated("Create this exception while supplying the missing placeholder value", "2.6.1")
    def this(after: String) = this(after, "")
  }

  object NoMorePlaceholder extends Exception("No more placeholder") with NoStackTrace {}

  @deprecated("Internal function: will be made private", "2.5.2")
  def prepareQuery(
      tok: List[TokenGroup],
      ns: List[String],
      ps: Map[String, ParameterValue],
      i: Int,
      buf: StringBuilder,
      vs: List[(Int, ParameterValue)]
  ): Try[(String, Seq[(Int, ParameterValue)])] = query(tok, ns, ps, i, buf, vs)

  @annotation.tailrec
  private[anorm] def query(
      tok: Seq[TokenGroup],
      ns: List[String],
      ps: Map[String, ParameterValue],
      i: Int,
      buf: StringBuilder,
      vs: List[(Int, ParameterValue)]
  ): Try[(String, Seq[(Int, ParameterValue)])] =
    (tok.headOption, ns.headOption.flatMap(ps.lift(_))) match {
      case (Some(TokenGroup(pr, Some(_))), Some(p)) => {
        val (frag, c): (String, Int) = p.toSql
        val prepared                 = toSql(pr, buf) ++= frag

        query(tok.tail, ns.tail, ps, i + c, prepared, (i, p) :: vs)
      }

      case (Some(TokenGroup(pr, Some(placeholder))), _) =>
        Failure(new MissingParameter(pr.mkString(", "), placeholder))

      case (Some(TokenGroup(pr, None)), _) =>
        query(tok.tail, ns, ps, i, toSql(pr, buf), vs)

      case (_, Some(p)) => {
        val c: Int = p.toSql._2
        query(tok, ns.tail, ps, i + c, buf, (i, p) :: vs)
      }

      case (None, _) | (_, None) => TrySuccess(buf.toString -> vs.reverse)
      case _                     => Failure(NoMorePlaceholder)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy