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

anorm.AkkaStream.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

import scala.util.control.NonFatal

import scala.concurrent.{ Future, Promise }

import akka.stream.Materializer
import akka.stream.scaladsl.Source

/**
 * Anorm companion for the [[http://doc.akka.io/docs/akka/2.4.4/scala/stream/]].
 *
 * @define materialization It materializes a [[scala.concurrent.Future]] of [[scala.Int]] containing the number of rows read from the source upon completion, and a possible exception if row parsing failed.
 * @define sqlParam the SQL query
 * @define materializerParam the stream materializer
 * @define connectionParam the JDBC connection, which must not be closed until the source is materialized.
 * @define columnAliaserParam the column aliaser
 */
object AkkaStream {

  /**
   * Returns the rows parsed from the `sql` query as a reactive source.
   *
   * $materialization
   *
   * @tparam T the type of the result elements
   * @param sql $sqlParam
   * @param parser the result (row) parser
   * @param as $columnAliaserParam
   * @param m $materializerParam
   * @param connection $connectionParam
   *
   * {{{
   * import java.sql.Connection
   *
   * import scala.concurrent.Future
   *
   * import akka.stream.Materializer
   * import akka.stream.scaladsl.Source
   *
   * import anorm._
   *
   * def resultSource(implicit m: Materializer, con: Connection): Source[String, Future[Int]] = AkkaStream.source(SQL"SELECT * FROM Test", SqlParser.scalar[String], ColumnAliaser.empty)
   * }}}
   */
  @SuppressWarnings(Array("UnusedMethodParameter"))
  def source[T](sql: => Sql, parser: RowParser[T], as: ColumnAliaser)(implicit
      @deprecated("No longer required (will be removed)", "2.5.4") m: Materializer,
      con: Connection
  ): Source[T, Future[Int]] = Source.fromGraph(new ResultSource[T](con, sql, as, parser))

  /**
   * Returns the rows parsed from the `sql` query as a reactive source.
   *
   * $materialization
   *
   * @tparam T the type of the result elements
   * @param sql $sqlParam
   * @param parser the result (row) parser
   * @param m $materializerParam
   * @param connection $connectionParam
   */
  @SuppressWarnings(Array("UnusedMethodParameter"))
  def source[T](sql: => Sql, parser: RowParser[T])(implicit
      @deprecated("No longer required (will be removed)", "2.5.4") m: Materializer,
      con: Connection
  ): Source[T, Future[Int]] = Source.fromGraph(new ResultSource[T](con, sql, ColumnAliaser.empty, parser))

  /**
   * Returns the result rows from the `sql` query as an enumerator.
   * This is equivalent to `source[Row](sql, RowParser.successful, as)`.
   *
   * $materialization
   *
   * @param sql $sqlParam
   * @param as $columnAliaserParam
   * @param m $materializerParam
   * @param connection $connectionParam
   */
  def source(sql: => Sql, as: ColumnAliaser)(implicit
      @deprecated("No longer required (will be removed)", "2.7.1") m: Materializer,
      @deprecatedName(Symbol("connnection")) con: Connection
  ): Source[Row, Future[Int]] = Source.fromGraph(new ResultSource[Row](con, sql, as, RowParser.successful))

  /**
   * Returns the result rows from the `sql` query as an enumerator.
   * This is equivalent to
   * `source[Row](sql, RowParser.successful, ColumnAliaser.empty)`.
   *
   * $materialization
   *
   * @param sql $sqlParam
   * @param m $materializerParam
   * @param connection $connectionParam
   */
  def source(sql: => Sql)(implicit m: Materializer, connnection: Connection): Source[Row, Future[Int]] =
    source(sql, RowParser.successful, ColumnAliaser.empty)

  // Internal stages

  import scala.util.{ Failure, Success }
  import java.sql.ResultSet
  import akka.stream.{ Attributes, Outlet, SourceShape }
  import akka.stream.stage.{ GraphStageWithMaterializedValue, GraphStageLogic, OutHandler }

  private[anorm] class ResultSource[T](connection: Connection, sql: Sql, as: ColumnAliaser, parser: RowParser[T])
      extends GraphStageWithMaterializedValue[SourceShape[T], Future[Int]] {

    private[anorm] var resultSet: ResultSet = _

    override val toString     = "AnormQueryResult"
    val out: Outlet[T]        = Outlet(s"${toString}.out")
    val shape: SourceShape[T] = SourceShape(out)

    override def createLogicAndMaterializedValue(inheritedAttributes: Attributes): (GraphStageLogic, Future[Int]) = {
      val result = Promise[Int]()

      val logic = new GraphStageLogic(shape) with OutHandler {
        private var cursor: Option[Cursor] = None
        private var counter: Int           = 0

        private def failWith(cause: Throwable): Unit = {
          result.failure(cause)
          fail(out, cause)
          ()
        }

        override def preStart(): Unit = {
          try {
            resultSet = sql.unsafeResultSet(connection)
            nextCursor()
          } catch {
            case NonFatal(cause) => failWith(cause)
          }
        }

        override def postStop() = release()

        private def release(): Unit = {
          val stmt: Option[java.sql.Statement] = {
            if (resultSet != null && !resultSet.isClosed) {
              val s = resultSet.getStatement
              resultSet.close()
              Option(s)
            } else None
          }

          stmt.foreach { s =>
            if (!s.isClosed) s.close()
          }
        }

        private def nextCursor(): Unit = {
          cursor = Sql.unsafeCursor(resultSet, sql.resultSetOnFirstRow, as)
        }

        def onPull(): Unit = cursor match {
          case Some(c) =>
            c.row.as(parser) match {
              case Success(parsed) => {
                counter += 1
                push(out, parsed)
                nextCursor()
              }

              case Failure(cause) =>
                failWith(cause)
            }

          case _ => {
            result.success(counter)
            complete(out)
          }
        }

        override def onDownstreamFinish() = {
          result.tryFailure(new InterruptedException("Downstream finished"))
          release()
          super.onDownstreamFinish()
        }

        setHandler(out, this)
      }

      logic -> result.future
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy