fixes #282 by tracking raw Connection objects for TXs.

this no longer checks TX nesting for DataSource-based TXs, but instead uses the Connection-based implementation directly.

raw Connection objects are tracked in a dynamic set.

thanks to [mbezjak](https://github.com/mbezjak) for the core of the implementation.

Signed-off-by: Sean Corfield <sean@corfield.org>
This commit is contained in:
Sean Corfield 2024-11-20 16:49:39 -08:00
parent a75468105f
commit b0a640a101
No known key found for this signature in database
4 changed files with 43 additions and 35 deletions

View file

@ -4,6 +4,7 @@ Only accretive/fixative changes will be made from now on.
* 1.3.next in progress * 1.3.next in progress
* Fix [#287](https://github.com/seancorfield/next-jdbc/issues/287) by merging user-supplied options over `:return-keys true`. * Fix [#287](https://github.com/seancorfield/next-jdbc/issues/287) by merging user-supplied options over `:return-keys true`.
* Fix [#282](https://github.com/seancorfield/next-jdbc/issues/282) by tracking raw `Connection` objects for active TXs, which relaxes several of the conditions around nested transactions.
* 1.3.955 -- 2024-10-06 * 1.3.955 -- 2024-10-06
* Address [#285](https://github.com/seancorfield/next-jdbc/issues/285) by setting the default Clojure version to the earliest supported (1.10.3) to give a better hint to users. * Address [#285](https://github.com/seancorfield/next-jdbc/issues/285) by setting the default Clojure version to the earliest supported (1.10.3) to give a better hint to users.

View file

@ -447,12 +447,19 @@
"Returns true if `next.jdbc` has a currently active transaction in the "Returns true if `next.jdbc` has a currently active transaction in the
current thread, else false. current thread, else false.
With no arguments, tells you if any transaction is currently active.
With a `Connection` argument, tells you if a transaction is currently
active on that specific connection.
Note: transactions are a convention of operations on a `Connection` so Note: transactions are a convention of operations on a `Connection` so
this predicate only reflects `next.jdbc/transact` and `next.jdbc/with-transaction` this predicate only reflects `next.jdbc/transact` and `next.jdbc/with-transaction`
operations -- it does not reflect any other operations on a `Connection`, operations -- it does not reflect any other operations on a `Connection`,
performed via JDBC interop directly." performed via JDBC interop directly."
[] ([]
@#'tx/*active-tx*) (boolean (seq @#'tx/*active-tx*)))
([con]
(contains? @#'tx/*active-tx* con)))
(defn with-options (defn with-options
"Given a connectable/transactable object and a set of (default) options "Given a connectable/transactable object and a set of (default) options

View file

@ -38,7 +38,7 @@
:allow) :allow)
(defonce ^:private ^:dynamic ^{:doc "Used to detect nested transactions."} (defonce ^:private ^:dynamic ^{:doc "Used to detect nested transactions."}
*active-tx* false) *active-tx* #{})
(def ^:private isolation-levels (def ^:private isolation-levels
"Transaction isolation levels." "Transaction isolation levels."
@ -112,43 +112,37 @@
(.setReadOnly con old-readonly) (.setReadOnly con old-readonly)
(catch Exception _)))))))) (catch Exception _))))))))
(defn- raw-connection ^Connection [^Connection con]
(if (.isWrapperFor con Connection)
(.unwrap con Connection)
con))
(extend-protocol p/Transactable (extend-protocol p/Transactable
java.sql.Connection java.sql.Connection
(-transact [this body-fn opts] (-transact [this body-fn opts]
(cond (let [raw (raw-connection this)]
(and (not *active-tx*) (= :ignore *nested-tx*)) (cond
;; #245 do not lock when in c.j.j compatibility mode: (and (not (contains? *active-tx* raw)) (= :ignore *nested-tx*))
(binding [*active-tx* true] ;; #245 do not lock when in c.j.j compatibility mode:
(transact* this body-fn opts)) (binding [*active-tx* (conj *active-tx* raw)]
(or (not *active-tx*) (= :allow *nested-tx*)) (transact* this body-fn opts))
(locking this (or (not (contains? *active-tx* raw)) (= :allow *nested-tx*))
(binding [*active-tx* true] (locking this
(transact* this body-fn opts))) (binding [*active-tx* (conj *active-tx* raw)]
(= :ignore *nested-tx*) (transact* this body-fn opts)))
(body-fn this) (= :ignore *nested-tx*)
(= :prohibit *nested-tx*) (body-fn this)
(throw (IllegalStateException. "Nested transactions are prohibited")) (= :prohibit *nested-tx*)
:else (throw (IllegalStateException. "Nested transactions are prohibited"))
(throw (IllegalArgumentException. :else
(str "*nested-tx* (" (throw (IllegalArgumentException.
*nested-tx* (str "*nested-tx* ("
") was not :allow, :ignore, or :prohibit"))))) *nested-tx*
") was not :allow, :ignore, or :prohibit"))))))
javax.sql.DataSource javax.sql.DataSource
(-transact [this body-fn opts] (-transact [this body-fn opts]
(cond (or (not *active-tx*) (= :allow *nested-tx*)) (with-open [con (p/get-connection this opts)]
(binding [*active-tx* true] (p/-transact con body-fn opts)))
(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 Object
(-transact [this body-fn opts] (-transact [this body-fn opts]
(p/-transact (p/get-datasource this) body-fn opts))) (p/-transact (p/get-datasource this) body-fn opts)))

View file

@ -233,6 +233,7 @@ VALUES ('Pear', 'green', 49, 47)
(is (= [{:next.jdbc/update-count 1}] (is (= [{:next.jdbc/update-count 1}]
(jdbc/with-transaction [t (ds) {:rollback-only true}] (jdbc/with-transaction [t (ds) {:rollback-only true}]
(is (jdbc/active-tx?) "should be in a transaction") (is (jdbc/active-tx?) "should be in a transaction")
(is (jdbc/active-tx? t) "connection should be in a transaction")
(jdbc/execute! t [" (jdbc/execute! t ["
INSERT INTO fruit (name, appearance, cost, grade) INSERT INTO fruit (name, appearance, cost, grade)
VALUES ('Pear', 'green', 49, 47) VALUES ('Pear', 'green', 49, 47)
@ -244,6 +245,7 @@ VALUES ('Pear', 'green', 49, 47)
(is (= [{:next.jdbc/update-count 1}] (is (= [{:next.jdbc/update-count 1}]
(jdbc/with-transaction [t con {:rollback-only true}] (jdbc/with-transaction [t con {:rollback-only true}]
(is (jdbc/active-tx?) "should be in a transaction") (is (jdbc/active-tx?) "should be in a transaction")
(is (jdbc/active-tx? t) "connection should be in a transaction")
(jdbc/execute! t [" (jdbc/execute! t ["
INSERT INTO fruit (name, appearance, cost, grade) INSERT INTO fruit (name, appearance, cost, grade)
VALUES ('Pear', 'green', 49, 47) VALUES ('Pear', 'green', 49, 47)
@ -258,6 +260,7 @@ INSERT INTO fruit (name, appearance, cost, grade)
VALUES ('Pear', 'green', 49, 47) VALUES ('Pear', 'green', 49, 47)
"]) "])
(is (jdbc/active-tx?) "should be in a transaction") (is (jdbc/active-tx?) "should be in a transaction")
(is (jdbc/active-tx? t) "connection should be in a transaction")
(throw (ex-info "abort" {}))))) (throw (ex-info "abort" {})))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))
(is (not (jdbc/active-tx?)) "should not be in a transaction") (is (not (jdbc/active-tx?)) "should not be in a transaction")
@ -270,6 +273,7 @@ INSERT INTO fruit (name, appearance, cost, grade)
VALUES ('Pear', 'green', 49, 47) VALUES ('Pear', 'green', 49, 47)
"]) "])
(is (jdbc/active-tx?) "should be in a transaction") (is (jdbc/active-tx?) "should be in a transaction")
(is (jdbc/active-tx? t) "connection should be in a transaction")
(throw (ex-info "abort" {}))))) (throw (ex-info "abort" {})))))
(is (= 4 (count (jdbc/execute! con ["select * from fruit"])))) (is (= 4 (count (jdbc/execute! con ["select * from fruit"]))))
(is (= ac (.getAutoCommit con)))))) (is (= ac (.getAutoCommit con))))))
@ -283,6 +287,7 @@ VALUES ('Pear', 'green', 49, 47)
(.rollback t) (.rollback t)
;; still in a next.jdbc TX even tho' we rolled back! ;; still in a next.jdbc TX even tho' we rolled back!
(is (jdbc/active-tx?) "should be in a transaction") (is (jdbc/active-tx?) "should be in a transaction")
(is (jdbc/active-tx? t) "connection should be in a transaction")
result)))) result))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))
(is (not (jdbc/active-tx?)) "should not be in a transaction") (is (not (jdbc/active-tx?)) "should not be in a transaction")
@ -309,6 +314,7 @@ VALUES ('Pear', 'green', 49, 47)
(.rollback t save-point) (.rollback t save-point)
;; still in a next.jdbc TX even tho' we rolled back to a save point! ;; still in a next.jdbc TX even tho' we rolled back to a save point!
(is (jdbc/active-tx?) "should be in a transaction") (is (jdbc/active-tx?) "should be in a transaction")
(is (jdbc/active-tx? t) "connection should be in a transaction")
result)))) result))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))
(is (not (jdbc/active-tx?)) "should not be in a transaction") (is (not (jdbc/active-tx?)) "should not be in a transaction")