
doobie.util.transactor.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of doobie-core-cats_2.11 Show documentation
Show all versions of doobie-core-cats_2.11 Show documentation
Pure functional JDBC layer for Scala.
The newest version!
package doobie.util
import doobie.free.connection.{ ConnectionIO, ConnectionOp, setAutoCommit, commit, rollback, close, unit, delay }
import doobie.free.KleisliInterpreter
import doobie.util.lens._
import doobie.util.yolo.Yolo
import cats.{ Monad, ~> }
import cats.data.Kleisli
import cats.free.Free
import cats.implicits._
import fs2.{ Stream => Process }
import fs2.Stream.{ eval, eval_ }
import fs2.util.{ Catchable, Suspendable => Capture }
import fs2.interop.cats._
import fs2.interop.cats.reverse.functionKToUf1
import java.sql.{ Connection, DriverManager }
import javax.sql.DataSource
import scala.Predef.implicitly
object transactor {
private val syntax = new doobie.syntax.catchable.ToDoobieCatchableOps {}
import syntax._
/** @group Type Aliases */
type Interpreter[M[_]] = ConnectionOp ~> Kleisli[M, Connection, ?]
/**
* Data type representing the common setup, error-handling, and cleanup strategy associated with
* an SQL transaction. A `Transactor` uses a `Strategy` to wrap programs prior to execution.
* @param before a program to prepare the connection for use
* @param after a program to run on success
* @param oops a program to run on failure (catch)
* @param always a programe to run in all cases (finally)
* @group Data Types
*/
case class Strategy(
before: ConnectionIO[Unit],
after: ConnectionIO[Unit],
oops: ConnectionIO[Unit],
always: ConnectionIO[Unit]
) {
private implicit class VoidProcessOps(ma: ConnectionIO[Unit]) {
def p: Process[ConnectionIO, Nothing] = eval_(ma) // empty effectful process
}
/** Natural transformation that wraps a `ConnectionIO` program. */
val wrap = λ[ConnectionIO ~> ConnectionIO] { ma =>
(before *> ma <* after) onException oops ensuring always
}
/** Natural transformation that wraps a `ConnectionIO` stream. */
val wrapP = λ[Process[ConnectionIO, ?] ~> Process[ConnectionIO, ?]] { pa =>
(before.p ++ pa ++ after.p) onError { e => oops.p ++ eval_(delay(throw e)) } onFinalize always
}
/**
* Natural transformation that wraps a `Kleisli` program, using the provided interpreter to
* interpret the `before`/`after`/`oops`/`always` strategy.
*/
def wrapK[M[_]: Monad: Catchable](interp: Interpreter[M]) =
λ[Kleisli[M, Connection, ?] ~> Kleisli[M, Connection, ?]] { ka =>
val beforeʹ = before foldMap interp
val afterʹ = after foldMap interp
val oopsʹ = oops foldMap interp
val alwaysʹ = always foldMap interp
(beforeʹ *> ka <* afterʹ) onException oopsʹ ensuring alwaysʹ
}
}
object Strategy {
/** @group Lenses */ val before: Strategy @> ConnectionIO[Unit] = Lens(_.before, (a, b) => a.copy(before = b))
/** @group Lenses */ val after: Strategy @> ConnectionIO[Unit] = Lens(_.after, (a, b) => a.copy(after = b))
/** @group Lenses */ val oops: Strategy @> ConnectionIO[Unit] = Lens(_.oops, (a, b) => a.copy(oops = b))
/** @group Lenses */ val always: Strategy @> ConnectionIO[Unit] = Lens(_.always, (a, b) => a.copy(always = b))
/**
* A default `Strategy` with the following properties:
* - Auto-commit will be set to `false`;
* - the transaction will `commit` on success and `rollback` on failure;
* - and finally the connection will be closed in all cases.
* @group Constructors
*/
val default = Strategy(setAutoCommit(false), commit, rollback, close)
/**
* A no-op `Strategy`. All actions simply return `()`.
* @group Constructors
*/
val void = Strategy(unit, unit, unit, unit)
}
/**
* A thin wrapper around a source of database connections, an interpreter, and a strategy for
* running programs, parameterized over a target monad `M` and an arbitrary wrapped value `A`.
* Given a stream or program in `ConnectionIO` or a program in `Kleisli`, a `Transactor` can
* discharge the doobie machinery and yield an effectful stream or program in `M`.
* @tparam M a target effect type; typically `IO` or `Task`
* @group Data Types
*/
sealed abstract class Transactor[M[_]] { self =>
/** An arbitrary value that will be handed back to `connect` **/
type A
/** An arbitrary value, meaningful to the instance **/
def kernel: A
/** A program in `M` that can provide a database connection, given the kernel **/
def connect: A => M[Connection]
/** A natural transformation for interpreting `ConnectionIO` **/
def interpret: Interpreter[M]
/** A `Strategy` for running a program on a connection **/
def strategy: Strategy
/** Construct a [[Yolo]] for REPL experimentation. */
def yolo(implicit ev: Monad[M], ev1: Catchable[M], ev2: Capture[M]): Yolo[M] = new Yolo(this)
/**
* Construct a program to peform arbitrary configuration on the kernel value (changing the
* timeout on a connection pool, for example). This can be the basis for constructing a
* configuration language for a specific kernel type `A`, whose operations can be added to
* compatible `Transactor`s via implicit conversion.
* @group Configuration
*/
def configure[B](f: A => B)(implicit ev: Capture[M]): M[B] =
ev.delay(f(kernel))
/**
* Natural transformation equivalent to `exec` that does not use the provided `Strategy` and
* instead directly binds the `Connection` provided by `connect`. This can be useful in cases
* where transactional handling is unsupported or undesired.
* @group Natural Transformations
*/
def rawExec(implicit ev: Monad[M]): Kleisli[M, Connection, ?] ~> M =
λ[Kleisli[M, Connection, ?] ~> M](k => connect(kernel).flatMap(k.run))
/**
* Natural transformation that provides a connection and binds through a `Kleisli` program
* using the given `Strategy`, yielding an independent program in `M`.
* @group Natural Transformations
*/
def exec(implicit ev: Monad[M], ev2: Catchable[M]): Kleisli[M, Connection, ?] ~> M =
strategy.wrapK(interpret)(ev, ev2) andThen rawExec(ev)
/**
* Natural transformation equivalent to `trans` that does not use the provided `Strategy` and
* instead directly binds the `Connection` provided by `connect`. This can be useful in cases
* where transactional handling is unsupported or undesired.
* @group Natural Transformations
*/
def rawTrans(implicit ev: Monad[M]): ConnectionIO ~> M =
λ[ConnectionIO ~> M](f => connect(kernel).flatMap(f.foldMap(interpret).run))
/**
* Natural transformation that provides a connection and binds through a `ConnectionIO` program
* interpreted via the given interpreter, using the given `Strategy`, yielding an independent
* program in `M`. This is the most common way to run a doobie program.
* @group Natural Transformations
*/
def trans(implicit ev: Monad[M]): ConnectionIO ~> M =
strategy.wrap andThen rawTrans
def rawTransP(implicit ev: Monad[M]) = λ[Process[ConnectionIO, ?] ~> Process[M, ?]] { pa =>
// TODO: this can almost certainly be simplified
// Natural transformation by Kleisli application.
def applyKleisli[F[_], E](e: E) =
λ[Kleisli[F, E, ?] ~> F](_.run(e))
// Lift a natural translation over an functor to one over its free monad.
def liftF[F[_], G[_]: Monad](nat: F ~> G) =
λ[Free[F, ?] ~> G](_.foldMap(nat))
def nat(c: Connection): ConnectionIO ~> M = liftF(interpret andThen applyKleisli(c))
eval(connect(kernel)).flatMap(c => pa.translate[M](functionKToUf1(nat(c))))
}
def transP(implicit ev: Monad[M]): Process[ConnectionIO, ?] ~> Process[M, ?] =
strategy.wrapP andThen rawTransP
def copy(
kernel0: A = self.kernel,
connect0: A => M[Connection] = self.connect,
interpret0: Interpreter[M] = self.interpret,
strategy0: Strategy = self.strategy
): Transactor.Aux[M, A] = new Transactor[M] {
type A = self.A
val kernel = kernel0
val connect = connect0
val interpret = interpret0
val strategy = strategy0
}
}
object Transactor {
def apply[M[_], A0](
kernel0: A0,
connect0: A0 => M[Connection],
interpret0: Interpreter[M],
strategy0: Strategy
): Transactor.Aux[M, A0] = new Transactor[M] {
type A = A0
val kernel = kernel0
val connect = connect0
val interpret = interpret0
val strategy = strategy0
}
type Aux[M[_], A0] = Transactor[M] { type A = A0 }
/** @group Lenses */ def kernel [M[_], A]: Transactor.Aux[M, A] Lens A = Lens(_.kernel, (a, b) => a.copy(kernel0 = b))
/** @group Lenses */ def connect [M[_], A]: Transactor.Aux[M, A] Lens (A => M[Connection]) = Lens(_.connect, (a, b) => a.copy(connect0 = b))
/** @group Lenses */ def interpret[M[_]]: Transactor[M] Lens Interpreter[M] = Lens(_.interpret, (a, b) => a.copy(interpret0 = b))
/** @group Lenses */ def strategy [M[_]]: Transactor[M] Lens Strategy = Lens(_.strategy, (a, b) => a.copy(strategy0 = b))
/** @group Lenses */ def before [M[_]]: Transactor[M] Lens ConnectionIO[Unit] = strategy[M] >=> Strategy.before
/** @group Lenses */ def after [M[_]]: Transactor[M] Lens ConnectionIO[Unit] = strategy[M] >=> Strategy.after
/** @group Lenses */ def oops [M[_]]: Transactor[M] Lens ConnectionIO[Unit] = strategy[M] >=> Strategy.oops
/** @group Lenses */ def always [M[_]]: Transactor[M] Lens ConnectionIO[Unit] = strategy[M] >=> Strategy.always
/**
* Construct a constructor of `Transactor[M, D]` for some `D <: DataSource` by partial
* application of `M`, which cannot be inferred in general. This follows the pattern described
* [here](http://tpolecat.github.io/2015/07/30/infer.html).
* @group Constructors
*/
object fromDataSource {
def apply[M[_]] = new FromDataSourceUnapplied[M]
/**
* Constructor of `Transactor[M, D]` fixed for `M`; see the `apply` method for details.
* @group Constructors (Partially Applied)
*/
class FromDataSourceUnapplied[M[_]] {
def apply[A <: DataSource](a: A)(implicit ev0: Monad[M], ev1: Catchable[M], ev2: Capture[M]): Transactor.Aux[M, A] =
Transactor(a, a => ev2.delay(a.getConnection), KleisliInterpreter[M](ev0, implicitly, implicitly).ConnectionInterpreter, Strategy.default)
}
}
@deprecated("use Transactor.fromDataSource", "0.4.2")
val DataSourceTransactor: fromDataSource.type = fromDataSource
/** @group Constructors */
def fromConnection[M[_]: Catchable: Capture](a: Connection)(implicit M: Monad[M]): Transactor.Aux[M, Connection] =
Transactor(a, M.pure(_), KleisliInterpreter[M](M, implicitly, implicitly).ConnectionInterpreter, Strategy.default.copy(always = unit))
/**
* Construct a constructor of `Transactor[M, Unit]` backed by the JDBC DriverManager by partial
* application of `M`, which cannot be inferred in general. This follows the pattern described
* [here](http://tpolecat.github.io/2015/07/30/infer.html).
* @group Constructors
*/
object fromDriverManager {
private def create[M[_]: Capture: Catchable](driver: String, conn: => Connection): Transactor.Aux[M, Unit] =
Transactor((), u => Capture[M].delay { Class.forName(driver); conn }, KleisliInterpreter[M].ConnectionInterpreter, Strategy.default)
def apply[M[_]: Monad: Capture: Catchable](driver: String, url: String): Transactor.Aux[M, Unit] =
create(driver, DriverManager.getConnection(url))
def apply[M[_]: Monad: Capture: Catchable](driver: String, url: String, user: String, pass: String): Transactor.Aux[M, Unit] =
create(driver, DriverManager.getConnection(url, user, pass))
def apply[M[_]: Monad: Capture: Catchable](driver: String, url: String, info: java.util.Properties): Transactor.Aux[M, Unit] =
create(driver, DriverManager.getConnection(url, info))
}
@deprecated("use Transactor.fromDriverManager", "0.4.2")
val DriverManagerTransactor: fromDriverManager.type = fromDriverManager
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy