Fixes #37; Fixes #36

* Fix `with-transaction` spec and how it invokes `transact` when no 
options map is provided.
* Adds type hint to `with-transaction`.
* Document manual rollback of transactions.
This commit is contained in:
Sean Corfield 2019-07-02 16:45:48 -07:00
parent 85eba1bb2f
commit 1cec0a2643
7 changed files with 51 additions and 7 deletions

View file

@ -6,6 +6,8 @@ Only accretive/fixative changes will be made from now on.
The following changes have been committed to the **master** branch since the 1.0.0 release: The following changes have been committed to the **master** branch since the 1.0.0 release:
* Fix #37 by adjusting the spec for `with-transaction` to "require less" of the `:binding` vector.
* Fix #36 by adding type hint in `with-transaction` macro.
* Fix #35 by explaining the database-specific options needed to ensure `insert-multi!` performs a single, batched operation. * Fix #35 by explaining the database-specific options needed to ensure `insert-multi!` performs a single, batched operation.
## Stable Builds ## Stable Builds

View file

@ -8,7 +8,7 @@ The latest versions on Clojars and on cljdoc:
[![Clojars Project](https://clojars.org/seancorfield/next.jdbc/latest-version.svg)](https://clojars.org/seancorfield/next.jdbc) [![cljdoc badge](https://cljdoc.org/badge/seancorfield/next.jdbc)](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT) [![Clojars Project](https://clojars.org/seancorfield/next.jdbc/latest-version.svg)](https://clojars.org/seancorfield/next.jdbc) [![cljdoc badge](https://cljdoc.org/badge/seancorfield/next.jdbc)](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT)
This documentation is for the 1.0.0 release -- [see the CHANGELOG](CHANGELOG.md). This documentation is for **master**, beyond the 1.0.0 release -- [see the CHANGELOG](CHANGELOG.md).
* [Getting Started](/doc/getting-started.md) * [Getting Started](/doc/getting-started.md)
* [Migrating from `clojure.java.jdbc`](/doc/migration-from-clojure-java-jdbc.md) * [Migrating from `clojure.java.jdbc`](/doc/migration-from-clojure-java-jdbc.md)

View file

@ -2,17 +2,26 @@
The `transact` function and `with-transaction` macro were briefly mentioned in the [Getting Started](/doc/getting-started.md) section but we'll go into more detail here. The `transact` function and `with-transaction` macro were briefly mentioned in the [Getting Started](/doc/getting-started.md) section but we'll go into more detail here.
Although `(transact transactable thunk)` is available, it is expected that you will mostly use `(with-transaction [tx transactable] body...)` when you want to execute multiple SQL operations in the context of a single transaction so that is what this section focuses on. Although `(transact transactable f)` is available, it is expected that you will mostly use `(with-transaction [tx transactable] body...)` when you want to execute multiple SQL operations in the context of a single transaction so that is what this section focuses on.
## Connection-level Control
By default, all connections that `next.jdbc` creates are automatically committable, i.e., as each operation is performed, the effect is committed to the database directly before the next operation is performed. Any exceptions only cause the current operation to be aborted -- any prior operations have already been committed. By default, all connections that `next.jdbc` creates are automatically committable, i.e., as each operation is performed, the effect is committed to the database directly before the next operation is performed. Any exceptions only cause the current operation to be aborted -- any prior operations have already been committed.
It is possible to tell `next.jdbc` to create connections that do not automatically commit operations: pass `{:auto-commit false}` as part of the options map to anything that creates a connection (including `get-connection` itself). You can then decide when to commit or rollback by calling `.commit` or `.rollback` on the connection object itself. You can also create save points (`(.setSavePoint con)`, `(.setSavePoint con name)`) and rollback to them (`(.rollback con save-point)`). You can also change the auto-commit state of an open connection at any time (`(.setAutoCommit con on-off)`). It is possible to tell `next.jdbc` to create connections that do not automatically commit operations: pass `{:auto-commit false}` as part of the options map to anything that creates a connection (including `get-connection` itself). You can then decide when to commit or rollback by calling `.commit` or `.rollback` on the connection object itself. You can also create save points (`(.setSavePoint con)`, `(.setSavePoint con name)`) and rollback to them (`(.rollback con save-point)`). You can also change the auto-commit state of an open connection at any time (`(.setAutoCommit con on-off)`).
## Automatic Commit & Rollback
`next.jdbc`'s transaction handling provides a convenient baseline for either committing a group of operations if they all succeed or rolling them all back if any of them fails, by throwing an exception. You can either do this on an existing connection -- and `next.jdbc` will try to restore the state of the connection after the transaction completes -- or by providing a datasource and letting `with-transaction` create and manage its own connection: `next.jdbc`'s transaction handling provides a convenient baseline for either committing a group of operations if they all succeed or rolling them all back if any of them fails, by throwing an exception. You can either do this on an existing connection -- and `next.jdbc` will try to restore the state of the connection after the transaction completes -- or by providing a datasource and letting `with-transaction` create and manage its own connection:
```clojure ```clojure
(jdbc/with-transaction [tx my-datasource] (jdbc/with-transaction [tx my-datasource]
(jdbc/execute! tx ...) (jdbc/execute! tx ...)
(jdbc/execute! tx ...)) ; will commit, unless exception thrown
(jdbc/with-transaction [tx my-datasource]
(jdbc/execute! tx ...)
(when ... (throw ...)) ; will rollback
(jdbc/execute! tx ...)) (jdbc/execute! tx ...))
``` ```
@ -24,4 +33,18 @@ You can also provide an options map as the third element of the binding vector (
The latter can be particularly useful in tests, to run a series of SQL operations during a test and then roll them all back at the end. The latter can be particularly useful in tests, to run a series of SQL operations during a test and then roll them all back at the end.
## Manual Rollback Inside a Transaction
Instead of throwing an exception (which will propagate through `with-transaction` and therefore provide no result), you can also explicitly rollback if you want to return a result in that case:
```clojure
(jdbc/with-transaction [tx my-datasource]
(let [result (jdbc/execute! tx ...)]
(if ...
(do
(.rollback tx)
result)
(jdbc/execute! tx ...))))
```
[<: Prepared Statements](/doc/prepared-statements.md) | [All The Options :>](/doc/all-the-options.md) [<: Prepared Statements](/doc/prepared-statements.md) | [All The Options :>](/doc/all-the-options.md)

View file

@ -203,4 +203,5 @@
* `:read-only` -- `true` / `false`, * `:read-only` -- `true` / `false`,
* `:rollback-only` -- `true` / `false`." * `:rollback-only` -- `true` / `false`."
[[sym transactable opts] & body] [[sym transactable opts] & body]
`(transact ~transactable (^{:once true} fn* [~sym] ~@body) ~opts)) (let [con (vary-meta sym assoc :tag 'java.sql.Connection)]
`(transact ~transactable (^{:once true} fn* [~con] ~@body) (or ~opts {}))))

View file

@ -90,7 +90,7 @@
:args (s/cat :binding (s/and vector? :args (s/cat :binding (s/and vector?
(s/cat :sym simple-symbol? (s/cat :sym simple-symbol?
:transactable ::transactable :transactable ::transactable
:opts ::opts-map)) :opts (s/? ::opts-map)))
:body (s/* any?))) :body (s/* any?)))
(s/fdef sql/insert! (s/fdef sql/insert!

View file

@ -3,8 +3,7 @@
(ns next.jdbc.transaction (ns next.jdbc.transaction
"SQL Transaction logic." "SQL Transaction logic."
(:require [next.jdbc.protocols :as p]) (:require [next.jdbc.protocols :as p])
(:import (java.sql Connection (:import (java.sql Connection)))
SQLException)))
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)

View file

@ -110,13 +110,32 @@ VALUES ('Pear', 'green', 49, 47)
"])) "]))
{:rollback-only true}))) {:rollback-only true})))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
(testing "with-transaction" (testing "with-transaction rollback-only"
(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}]
(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)
"])))) "]))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
(testing "with-transaction exception"
(is (thrown? Throwable
(jdbc/with-transaction [t (ds)]
(jdbc/execute! t ["
INSERT INTO fruit (name, appearance, cost, grade)
VALUES ('Pear', 'green', 49, 47)
"])
(throw (ex-info "abort" {})))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
(testing "with-transaction call rollback"
(is (= [{:next.jdbc/update-count 1}]
(jdbc/with-transaction [t (ds)]
(let [result (jdbc/execute! t ["
INSERT INTO fruit (name, appearance, cost, grade)
VALUES ('Pear', 'green', 49, 47)
"])]
(.rollback t)
result))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))))
(deftest plan-misuse (deftest plan-misuse