From e9c1bda637efaa1a04bd081b519ed38365fef44f Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 28 Jun 2020 15:10:41 -0700 Subject: [PATCH] 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]