diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e03de4..f38dea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Only accretive/fixative changes will be made from now on. * 1.3.next in progress * Address [#295](https://github.com/seancorfield/next-jdbc/issues/295) by providing a way to tell `next.jdbc` that certain options should be passed "as-is" in the `Properties` object when creating a `Connection`. + * Fix [#181](https://github.com/seancorfield/next-jdbc/issues/181) (again!) by adding `Wrapped` protocol as a way for `DefaultOptions` and `SQLLogging` to consistently expose the underlying connectable, even when nested. * 1.3.994 -- 2025-01-28 * Fix [#293](https://github.com/seancorfield/next-jdbc/issues/293) by no longer `locking` on the `Connection` retrieved from a `DataSource`. diff --git a/src/next/jdbc.clj b/src/next/jdbc.clj index ab27c5f..b53015d 100644 --- a/src/next/jdbc.clj +++ b/src/next/jdbc.clj @@ -1,4 +1,4 @@ -;; copyright (c) 2018-2024 Sean Corfield, all rights reserved +;; copyright (c) 2018-2025 Sean Corfield, all rights reserved (ns next.jdbc "The public API of the next generation java.jdbc library. @@ -336,13 +336,14 @@ result)))) params))) ([connectable sql param-groups opts] - (if (or (instance? java.sql.Connection connectable) - (and (satisfies? p/Connectable connectable) - (instance? java.sql.Connection (:connectable connectable)))) - (with-open [ps (prepare connectable [sql] opts)] - (execute-batch! ps param-groups opts)) - (with-open [con (get-connection connectable)] - (execute-batch! con sql param-groups opts))))) + (let [conn (p/unwrap connectable)] + (if (instance? java.sql.Connection conn) + (with-open [ps (prepare conn [sql] (if-let [opts' (:options connectable)] + (merge opts' opts) + opts))] + (execute-batch! ps param-groups opts)) + (with-open [con (get-connection connectable)] + (execute-batch! con sql param-groups opts)))))) (defmacro on-connection "Given a connectable object, gets a connection and binds it to `sym`, @@ -365,15 +366,12 @@ executes the body, and automatically closes it for you." [[sym connectable] & body] (let [con-sym (vary-meta sym assoc :tag 'java.sql.Connection)] - `(let [con-obj# ~connectable] - (cond (instance? java.sql.Connection con-obj#) - ((^{:once true} fn* [~con-sym] ~@body) con-obj#) - (and (satisfies? p/Connectable con-obj#) - (instance? java.sql.Connection (:connectable con-obj#))) - ((^{:once true} fn* [~con-sym] ~@body) (:connectable con-obj#)) - :else - (with-open [con# (get-connection con-obj#)] - ((^{:once true} fn* [~con-sym] ~@body) con#)))))) + `(let [con-obj# ~connectable + bare-con# (p/unwrap con-obj#)] + (if (instance? java.sql.Connection bare-con#) + ((^{:once true} fn* [~con-sym] ~@body) bare-con#) + (with-open [con# (get-connection con-obj#)] + ((^{:once true} fn* [~con-sym] ~@body) con#)))))) (defmacro on-connection+options "Given a connectable object, assumed to be wrapped with options, gets @@ -403,15 +401,11 @@ with `on-connection`." [[sym connectable] & body] `(let [con-obj# ~connectable] - (cond (instance? java.sql.Connection con-obj#) - ((^{:once true} fn* [~sym] ~@body) con-obj#) - (and (satisfies? p/Connectable con-obj#) - (instance? java.sql.Connection (:connectable con-obj#))) - ((^{:once true} fn* [~sym] ~@body) con-obj#) - :else - (with-open [con# (get-connection con-obj#)] - ((^{:once true} fn* [~sym] ~@body) - (with-options con# (:options con-obj# {}))))))) + (if (instance? java.sql.Connection (p/unwrap con-obj#)) + ((^{:once true} fn* [~sym] ~@body) con-obj#) + (with-open [con# (get-connection con-obj#)] + ((^{:once true} fn* [~sym] ~@body) + (with-options con# (:options con-obj# {}))))))) (defn transact "Given a transactable object and a function (taking a `Connection`), diff --git a/src/next/jdbc/default_options.clj b/src/next/jdbc/default_options.clj index 5e02965..d995c4f 100644 --- a/src/next/jdbc/default_options.clj +++ b/src/next/jdbc/default_options.clj @@ -1,4 +1,4 @@ -;; copyright (c) 2020-2024 Sean Corfield, all rights reserved +;; copyright (c) 2020-2025 Sean Corfield, all rights reserved (ns ^:no-doc next.jdbc.default-options "Implementation of default options logic." @@ -8,6 +8,10 @@ (defrecord DefaultOptions [connectable options]) +(extend-protocol p/Wrapped + DefaultOptions + (unwrap [this] (p/unwrap (:connectable this)))) + (extend-protocol p/Sourceable DefaultOptions (get-datasource [this] diff --git a/src/next/jdbc/protocols.clj b/src/next/jdbc/protocols.clj index cb53472..5d066b2 100644 --- a/src/next/jdbc/protocols.clj +++ b/src/next/jdbc/protocols.clj @@ -1,4 +1,4 @@ -;; copyright (c) 2018-2024 Sean Corfield, all rights reserved +;; copyright (c) 2018-2025 Sean Corfield, all rights reserved (ns next.jdbc.protocols "This is the extensible core of the next generation java.jdbc library. @@ -63,3 +63,15 @@ :extend-via-metadata true (-transact [this body-fn opts] "Run the `body-fn` inside a transaction.")) + +(defprotocol Wrapped + "Protocol for (un)wrapping a `next.jdbc` connectable. + + Implementations are provided for `Object` (identity) and `DefaultOptions` + and SQLLogging." + (unwrap [this] + "Unwrap the connectable to get the underlying connectable.")) + +(extend-protocol Wrapped + Object + (unwrap [this] this)) diff --git a/src/next/jdbc/sql_logging.clj b/src/next/jdbc/sql_logging.clj index 4ae8067..a4abcc4 100644 --- a/src/next/jdbc/sql_logging.clj +++ b/src/next/jdbc/sql_logging.clj @@ -1,4 +1,4 @@ -;; copyright (c) 2021-2024 Sean Corfield, all rights reserved +;; copyright (c) 2021-2025 Sean Corfield, all rights reserved (ns ^:no-doc next.jdbc.sql-logging "Implementation of sql-logging logic." @@ -8,6 +8,10 @@ (defrecord SQLLogging [connectable sql-logger result-logger options]) +(extend-protocol p/Wrapped + SQLLogging + (unwrap [this] (p/unwrap (:connectable this)))) + (extend-protocol p/Sourceable SQLLogging (get-datasource [this] diff --git a/test/next/jdbc_test.clj b/test/next/jdbc_test.clj index ab9153a..2b04946 100644 --- a/test/next/jdbc_test.clj +++ b/test/next/jdbc_test.clj @@ -749,6 +749,44 @@ INSERT INTO fruit (name, appearance) VALUES (?,?) (conj result (count (jdbc/execute! (ds) ["select * from fruit"])))) (finally (jdbc/execute-one! (ds) [(str "delete from fruit where " (index) " > 4")]))))) + (is (= [1 1 1 1 1 1 1 1 1 13] + (try + (let [result (jdbc/execute-batch! (jdbc/with-options + (jdbc/with-logging (ds) println println) + {:ignore "me"}) + "INSERT INTO fruit (name, appearance) VALUES (?,?)" + [["fruit1" "one"] + ["fruit2" "two"] + ["fruit3" "three"] + ["fruit4" "four"] + ["fruit5" "five"] + ["fruit6" "six"] + ["fruit7" "seven"] + ["fruit8" "eight"] + ["fruit9" "nine"]] + {})] + (conj result (count (jdbc/execute! (ds) ["select * from fruit"])))) + (finally + (jdbc/execute-one! (ds) [(str "delete from fruit where " (index) " > 4")]))))) + (is (= [1 1 1 1 1 1 1 1 1 13] + (try + (let [result (jdbc/execute-batch! (jdbc/with-logging + (jdbc/with-options (ds) {:ignore "me"}) + println println) + "INSERT INTO fruit (name, appearance) VALUES (?,?)" + [["fruit1" "one"] + ["fruit2" "two"] + ["fruit3" "three"] + ["fruit4" "four"] + ["fruit5" "five"] + ["fruit6" "six"] + ["fruit7" "seven"] + ["fruit8" "eight"] + ["fruit9" "nine"]] + {})] + (conj result (count (jdbc/execute! (ds) ["select * from fruit"])))) + (finally + (jdbc/execute-one! (ds) [(str "delete from fruit where " (index) " > 4")]))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))) (testing "small batch insert" (is (= [1 1 1 1 1 1 1 1 1 13]