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

oriana.package.scala Maven / Gradle / Ivy

Go to download

Oriana is a small layer on top of slick that allows easier access to the database. It allows peudo-syntactic methods to inject a database context into arbitrary code, and simplifies deployment, updates and initialization.

The newest version!
import akka.NotUsed
import akka.actor.ActorRefFactory
import akka.pattern.ask
import akka.stream.javadsl.GraphDSL
import akka.stream.scaladsl.{Flow, Keep, Sink, Source}
import akka.util.Timeout
import slick.dbio.{DBIOAction, Effect, NoStream, Streaming}

import scala.concurrent.{ExecutionContext, Future}

package object oriana {
  /**
    * Context type for operations - mixes in the [[DatabaseCommandExecution]] trait in to grant access to the database
    * object of the context
    */
  type ExecutableDatabaseContext = DatabaseContext with DatabaseCommandExecution
  /**
    * Type for operations - simple DB interactions that are not part of a transaction. Since they may have multiple, disjoined
    * stages, they can not be retried and can have partial results (depending on which parts of them were executed).
    *
    * @tparam Context context type of the interaction (usually some subclass of `DatabaseContext`
    * @tparam Result type of result produced by the operation
    */
  type DBOperation[-Context <: ExecutableDatabaseContext, +Result] = (Context) => Future[Result]


  /**
    * Models a streamable DB operation. These operations should not modify state. Note the missing [DatabaseCommandExecution] type
    * to prevent direct execution
    * @tparam Context context type
    * @tparam Result result type
    * @tparam E effect type
    */
  type DBStreamOperation[-Context <: DatabaseContext, +Result, -E <: Effect] = (Context) => DBIOAction[_, Streaming[Result], E]

  /**
    * Syntactic sugar for access to the database. The block argument is scheduled as a [DBOperation] to the implicit
    * database.
    *
    * {{{
    * executeDBOperation { ctx: MyContext with DatabaseCommandExecution =>
    *   // ... generate actions, and execute them with ctx.database.run
    *   ctx.database.run(DBIO.successful(42))
    * }
    * }}}
    *
    * @param op operation to perform
    * @param actorRefFactory most likely an actor system
    * @param timeout timeout for ask operation(s) used under the hood to implement the Future
    * @param ec execution context for ask operation(s) used under the hood to implement the Future
    * @param actorName name for the database actor
    * @tparam T operation return type
    * @return future with operation result
    */
  def executeDBOperation[T: Manifest](op: DBOperation[_ <: ExecutableDatabaseContext, T])(implicit actorRefFactory: ActorRefFactory, timeout: Timeout, ec: ExecutionContext, actorName: DatabaseName): Future[T] = {
    (actorRefFactory.actorSelection(actorName.name) ? op).mapTo[T]
  }

  /**
    * Performs a database-using operation as a flow element.
    * @param op operation to execute
    * @param actorRefFactory factory for accessing the lower-level ([executeDBOperation] operation.
    * @param timeout ask timeout for each execution
    * @param ec context to use
    * @param actorName database actor name
    * @tparam Context Context type (useful for table access)
    * @tparam In input type
    * @tparam Out output type
    * @return flow modelling the specified transformation
    */

  def executeAsFlow[Context <: DatabaseContext, In, Out: Manifest](op: In => DBStreamOperation[Context, Out, _])(implicit actorRefFactory: ActorRefFactory, timeout: Timeout, ec: ExecutionContext, actorName: DatabaseName): Flow[In, Out, NotUsed] = {
    Flow[In] map op flatMapConcat (executeAsSource(_))
  }

  /**
    * Creates a sink that executes the specified database transaction for every element published to the stream. The sink
    * has some options that can be set to modify its operation. The operation materializes a future that returns
    * the number of successfully-processed transactions.
    *
    * @param op operation to perform
    * @param settings settings for the sink
    * @param actorRefFactory factory for accessing the lower-level ([executeDBOperation] operation.
    * @param timeout ask timeout for each execution
    * @param ec context to use
    * @param actorName database actor name
    * @tparam Context Context type (useful for table access)
    * @tparam T input type
    * @return sink instantiated to execute the specified transaction per-element.
    */
  def executeAsSink[Context <: DatabaseContext, T](op: T => DBTransaction[Context, _], settings: DBSinkSettings = DBSinkSettings())(implicit actorRefFactory: ActorRefFactory, timeout: Timeout, ec: ExecutionContext, actorName: DatabaseName): Sink[T, Future[Int]] = {
    val subscriber: TransactionSubscriber[Context, T] = new TransactionSubscriber(op, settings)
    val flow = Flow.fromSinkAndSource(Sink.fromSubscriber(subscriber), Source.fromFuture(subscriber.future))
    val complete = Sink.head[Int]

    flow.toMat(complete)(Keep.right)
  }

  /**
    * Creates a source from a streamable database operation
    * @param op operation
    * @param actorRefFactory actor factory for communication to the database actor
    * @param timeout ask timeout for the initial creation of the sink, not for each operation
    * @param ec context to use
    * @param actorName database actor name
    * @tparam Context context type (useful for table access)
    * @tparam T emitted type
    * @return source backed by the passed DB operation
    */

  def executeAsSource[Context <: DatabaseContext, T: Manifest](op: DBStreamOperation[Context, T, _])(implicit actorRefFactory: ActorRefFactory, timeout: Timeout, ec: ExecutionContext, actorName: DatabaseName) = {
    Source.fromFuture(executeDBOperation { ctx: Context with ExecutableDatabaseContext =>
      val actions = op(ctx)

      Future.successful(ctx.database.stream(actions))
    }) flatMapConcat (Source.fromPublisher(_))
  }
  /**
    * Syntactic sugar for access to the database. The block argument is scheduled as a [DBTransaction] to the implicit
    * database. Transactions may be attempted multiple times in case of failures, making them good candidates
    * for idiom such as optimistic locking.
    *
    *
    * {{{
    * executeDBTransaction { ctx: MyContext =>
    *   // ... generate actions, which will be wrapped with transactionally and executed
    *   DBIO.successful(42)
    * }
    * }}}
    *
    * @param op transaction to attempt
    * @param actorRefFactory most likely an actor system
    * @param timeout timeout for ask operation(s) used under the hood to implement the Future
    * @param ec execution context for ask operation(s) used under the hood to implement the Future
    * @param actorName name for the database actor
    * @tparam Context context type to pass to the transaction
    * @tparam T result type of the transaction
    * @return future with transaction result
    */
  def executeDBTransaction[Context <: DatabaseContext, T: Manifest](op: DBTransaction[Context, T])(implicit actorRefFactory: ActorRefFactory, timeout: Timeout, ec: ExecutionContext, actorName: DatabaseName): Future[T] = {
    (actorRefFactory.actorSelection(actorName.name) ? op).mapTo[T]
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy