From 7c0cee09bf4a992216590f9ecd50b3a975f042f8 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 4 Jun 2020 22:30:38 -0700 Subject: [PATCH 1/5] Issue #117 sketch of dynamic nested transaction behavior --- src/next/jdbc/transaction.clj | 36 ++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/next/jdbc/transaction.clj b/src/next/jdbc/transaction.clj index e538149..a6534f9 100644 --- a/src/next/jdbc/transaction.clj +++ b/src/next/jdbc/transaction.clj @@ -79,15 +79,41 @@ (.setReadOnly con old-readonly) (catch Exception _)))))))) +(defonce ^:dynamic ^{:doc ""} *nested-tx* :allow) +(defonce ^:private ^:dynamic ^{:doc ""} *active-tx* false) + (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))) From 751c967741d68b38da10b5c512fd4c2b621e4c52 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 6 Jun 2020 17:28:19 -0700 Subject: [PATCH 2/5] Merge jTDS, updated PostgreSQL to nested-tx --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d87b06b..49ff5e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Only accretive/fixative changes will be made from now on. Changes made on master since 1.0.462: +* WIP: nested transaction support! * Add tests for `"jtds"` database driver (against MS SQL Server), making it officially supported. * Switch from OpenTable Embedded PostgreSQL to Zonky's version, so that testing can move forward from PostgreSQL 10.11 to 12.2.0. * Add log4j2 as a test dependency so that I have better control over logging (which makes debugging easier!). From decabae0ab4026dcbca0e77ee2083e467202e024 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 22 Jun 2020 17:04:33 -0700 Subject: [PATCH 3/5] Restore WIP CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 214dc9c..d865e7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ Only accretive/fixative changes will be made from now on. +Changes made on master since 1.0.475: +* WIP: nested transaction support! + ## Stable Builds * 2020-06-22 -- 1.0.475 From 71283ce43734b0445d422b5678bfbdd166b48b5d Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 22 Jun 2020 23:24:56 -0700 Subject: [PATCH 4/5] Baseline to 1.0.476 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0df7ade..cf9b6b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Only accretive/fixative changes will be made from now on. -Changes made on master since 1.0.475: +Changes made on master since 1.0.476: * WIP: nested transaction support! ## Stable Builds From e9c1bda637efaa1a04bd081b519ed38365fef44f Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 28 Jun 2020 15:10:41 -0700 Subject: [PATCH 5/5] Fixes #117 by providing control over nested transactions --- CHANGELOG.md | 3 +-- src/next/jdbc/transaction.clj | 40 ++++++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5c3d61..2c5f4e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,10 @@ Only accretive/fixative changes will be made from now on. -* WIP: nested transaction support! - 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. diff --git a/src/next/jdbc/transaction.clj b/src/next/jdbc/transaction.clj index a6534f9..b1aea8a 100644 --- a/src/next/jdbc/transaction.clj +++ b/src/next/jdbc/transaction.clj @@ -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 @@ -79,9 +112,6 @@ (.setReadOnly con old-readonly) (catch Exception _)))))))) -(defonce ^:dynamic ^{:doc ""} *nested-tx* :allow) -(defonce ^:private ^:dynamic ^{:doc ""} *active-tx* false) - (extend-protocol p/Transactable java.sql.Connection (-transact [this body-fn opts]