Merge pull request #127 from seancorfield/nested-tx

Fixes #117 by adding control over the behavior of nested transactions
This commit is contained in:
Sean Corfield 2020-06-28 15:13:24 -07:00 committed by GitHub
commit 4a607cbdbb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 64 additions and 7 deletions

View file

@ -5,6 +5,7 @@ Only accretive/fixative changes will be made from now on.
Changes made since the 1.0.478 release:
* Address #125 by making the result of `plan` foldable (in the `clojure.core.reducers` sense).
* Address #124 by extending `next.jdbc.sql.builder/for-query` to support `:top` (SQL Server), `:limit` / `:offset` (MySQL/PostgreSQL), `:offset` / `:fetch` (SQL Standard).
* Address #117 by adding `next.jdbc.transaction/*nested-tx*` to provide control over how attempts to create nested transactions should be handled.
* Address #116 by adding a `:multi-rs` option to `execute!` to retrieve multiple result sets, for example from stored procedure calls or T-SQL scripts.
* Allow `:all` to be passed into `find-by-keys` instead of an example hash map or a where clause vector so all rows will be returned (expected to be used with `:offset` etc to support simple pagination of an entire table).
* Add `:columns` option to `find-by-keys` (and `get-by-id`) to specify a subset of columns to be returned in each row. This can also specify an alias for the column and allows for computed expressions to be selected with an alias.

View file

@ -1,12 +1,45 @@
;; copyright (c) 2018-2020 Sean Corfield, all rights reserved
(ns ^:no-doc next.jdbc.transaction
"Implementation of SQL transaction logic."
(ns next.jdbc.transaction
"Implementation of SQL transaction logic.
In general, you cannot nest transactions. `clojure.java.jdbc` would
ignore any attempt to create a nested transaction, even tho' some
databases do support it. `next.jdbc` allows you to call `with-transaction`
even while you are inside an active transaction, but the behavior may
vary across databases and the commit or rollback boundaries may not be
what you expect. In order to avoid two transactions constructed on the
same connection from interfering with each other, `next.jdbc` locks on
the `Connection` object (this prevents concurrent transactions on separate
threads from interfering but will cause deadlock on a single thread --
so beware).
Consequently, this namespace exposes a dynamic variable, `*nested-tx*`,
which can be used to vary the behavior when an attempt is made to start
a transaction when you are already inside a transaction."
(:require [next.jdbc.protocols :as p])
(:import (java.sql Connection)))
(set! *warn-on-reflection* true)
(defonce ^:dynamic
^{:doc "Controls the behavior when a nested transaction is attempted.
Possible values are:
* `:allow` -- the default: assumes you know what you are doing!
* `:ignore` -- the same behavior as `clojure.java.jdbc`: the nested
transaction is simply ignored and any SQL operations inside it are
executed in the context of the outer transaction.
* `:prohibit` -- any attempt to create a nested transaction will throw
an exception: this is the safest but most restrictive approach so
that you can make sure you don't accidentally have any attempts
to create nested transactions (since that might be a bug in your code)."}
*nested-tx*
:allow)
(defonce ^:private ^:dynamic ^{:doc "Used to detect nested transactions."}
*active-tx* false)
(def ^:private isolation-levels
"Transaction isolation levels."
{:none Connection/TRANSACTION_NONE
@ -82,12 +115,35 @@
(extend-protocol p/Transactable
java.sql.Connection
(-transact [this body-fn opts]
(locking this
(transact* this body-fn opts)))
(cond (or (not *active-tx*) (= :allow *nested-tx*))
(locking this
(binding [*active-tx* true]
(transact* this body-fn opts)))
(= :ignore *nested-tx*)
(body-fn this)
(= :prohibit *nested-tx*)
(throw (IllegalStateException. "Nested transactions are prohibited"))
:else
(throw (IllegalArgumentException.
(str "*nested-tx* ("
*nested-tx*
") was not :allow, :ignore, or :prohibit")))))
javax.sql.DataSource
(-transact [this body-fn opts]
(with-open [con (p/get-connection this opts)]
(transact* con body-fn opts)))
(cond (or (not *active-tx*) (= :allow *nested-tx*))
(binding [*active-tx* true]
(with-open [con (p/get-connection this opts)]
(transact* con body-fn opts)))
(= :ignore *nested-tx*)
(with-open [con (p/get-connection this opts)]
(body-fn con))
(= :prohibit *nested-tx*)
(throw (IllegalStateException. "Nested transactions are prohibited"))
:else
(throw (IllegalArgumentException.
(str "*nested-tx* ("
*nested-tx*
") was not :allow, :ignore, or :prohibit")))))
Object
(-transact [this body-fn opts]
(p/-transact (p/get-datasource this) body-fn opts)))
(p/-transact (p/get-datasource this) body-fn opts)))