address #256 by adding with-transaction+options

This commit is contained in:
Sean Corfield 2023-08-09 19:35:25 -07:00
parent 8ad1110fcb
commit 044de70b49
5 changed files with 76 additions and 4 deletions

View file

@ -2,6 +2,9 @@
Only accretive/fixative changes will be made from now on.
* 1.3.next in progress
* Address [#256](https://github.com/seancorfield/next-jdbc/issues/256) by adding `with-transaction+options`. Documentation TBD.
* 1.3.883 -- 2023-06-25
* Address [#254](https://github.com/seancorfield/next-jdbc/issues/254) by adding `next.jdbc/active-tx?` and adding more explanation to [**Transactions**](https://cljdoc.org/d/com.github.seancorfield/next.jdbc/CURRENT/doc/getting-started/transactions) about the conventions behind transactions and the limitations of thread-local tracking of active transactions in `next.jdbc`.
* Address [#251](https://github.com/seancorfield/next-jdbc/issues/251) by updating `next.jdbc/with-logging` docstring.

View file

@ -1,5 +1,7 @@
{:hooks
{:analyze-call
{next.jdbc/with-transaction
hooks.com.github.seancorfield.next-jdbc/with-transaction}}
hooks.com.github.seancorfield.next-jdbc/with-transaction
next.jdbc/with-transaction+options
hooks.com.github.seancorfield.next-jdbc/with-transaction+options}}
:lint-as {next.jdbc/on-connection clojure.core/with-open}}

View file

@ -16,3 +16,19 @@
opts
body))]
{:node new-node})))
(defn with-transaction+options
"Expands (with-transaction+options [tx expr opts] body)
to (let [tx expr] opts body) per clj-kondo examples."
[{:keys [:node]}]
(let [[binding-vec & body] (rest (:children node))
[sym val opts] (:children binding-vec)]
(when-not (and sym val)
(throw (ex-info "No sym and val provided" {})))
(let [new-node (api/list-node
(list*
(api/token-node 'let)
(api/vector-node [sym val])
opts
body))]
{:node new-node})))

View file

@ -389,6 +389,9 @@
Like `with-open`, if `with-transaction` creates a new `Connection` object,
it will automatically close it for you.
If you are working with default options via `with-options`, you might want
to use `with-transaction+options` instead.
The options map supports:
* `:isolation` -- `:none`, `:read-committed`, `:read-uncommitted`,
`:repeatable-read`, `:serializable`,
@ -419,9 +422,45 @@
return plain Java objects, so if you call any of those on this wrapped
object, you'll need to re-wrap the Java object `with-options` again. See
the Datasources, Connections & Transactions section of Getting Started for
more details, and some examples of use with these functions."
more details, and some examples of use with these functions.
`with-transaction+options` exists to automatically rewrap a `Connection`
with the options from a `with-options` wrapper."
[connectable opts]
(opts/->DefaultOptions connectable opts))
(let [c (:connectable connectable)
o (:options connectable)]
(if (and c o)
(opts/->DefaultOptions c (merge o opts))
(opts/->DefaultOptions connectable opts))))
(defmacro with-transaction+options
"Given a transactable object, assumed to be wrapped with options, gets a
connection, rewraps it with those options, and binds it to `sym`, then
executes the `body` in that context, committing any changes if the body
completes successfully, otherwise rolling back any changes made.
Like `with-open`, if `with-transaction+options` creates a new `Connection`
object, it will automatically close it for you.
Note: the bound `sym` will be a **wrapped** connectable and not a plain
Java object, so you cannot call JDBC methods directly on it like you can
with `with-transaction`.
The options map supports:
* `:isolation` -- `:none`, `:read-committed`, `:read-uncommitted`,
`:repeatable-read`, `:serializable`,
* `:read-only` -- `true` / `false` (`true` will make the `Connection` readonly),
* `:rollback-only` -- `true` / `false` (`true` will make the transaction
rollback, even if it would otherwise succeed)."
[[sym transactable opts] & body]
(let [con (vary-meta sym assoc :tag 'java.sql.Connection)]
`(let [tx# ~transactable]
(transact tx#
(^{:once true} fn*
[con#]
(let [~con (with-options con# (:options tx# {}))]
~@body))
~(or opts {})))))
(defn with-logging
"Given a connectable/transactable object and a sql/params logging

View file

@ -65,6 +65,18 @@
ds-opts
["select appearance as looks_like from fruit where id = ?" 1]
jdbc/snake-kebab-opts))))
(let [ds' (jdbc/with-options ds-opts jdbc/snake-kebab-opts)]
(is (= "red" (:fruit/looks-like
(jdbc/execute-one!
ds'
["select appearance as looks_like from fruit where id = ?" 1])))))
(jdbc/with-transaction+options [ds' (jdbc/with-options ds-opts jdbc/snake-kebab-opts)]
(is (= (merge (default-options) jdbc/snake-kebab-opts)
(:options ds')))
(is (= "red" (:fruit/looks-like
(jdbc/execute-one!
ds'
["select appearance as looks_like from fruit where id = ?" 1])))))
(is (= "red" (:looks-like
(jdbc/execute-one!
ds-opts