ITADN

Support clojure.java.jdbc/Connectable created by clojure.java.jdbc/with-db-transaction

#301Closeddevurandom 创建于 2025-05-08
needs analysis
D
devurandomcommented
**Describe the bug** next-jdbc appears to not work well with the `clojure.java.jdbc/Connectable` created by `clojure.java.jdbc/with-db-transaction`. In particular I see `next.jdbc/execute-batch!` ignoring transactions and operating outside of them. **To Reproduce** Please see https://github.com/devurandom/next-jdbc-issue301 for a reproduction example. It takes the following steps: 1. Prepare a PostgreSQL database with `CREATE TABLE table_a (id SERIAL PRIMARY KEY)`, `CREATE TABLE table_b (fkey INTEGER REFERENCES table_a (id))` 2. Create `{:datasource ...}` where `...` is a `javax.sql.DataSource`. 3. Within `clojure.java.jdbc/with-db-transaction [conn {:datasource ...}]` execute the following steps 4. Using `(:connection conn)` execute `INSERT INTO table_a (...) VALUES (...)` inside the transaction and "remember" the ID of the row inserted 5. Using `next.jdbc/execute-batch! conn ...` try to `INSERT INTO table_b (fkey) VALUES (ID remembered in step 4)` 6. Observe SQL error: The ID you inserted in step 4 supposedly does not exist. The first step happens in `compose/docker-entrypoint-initdb.d/99-local.sh`, all others in `issue301-test/issue301-test`. `issue301-test/issue301-workaround-test` shows the workaround using `(:connection conn)`. **Expected behavior** next-jdbc notices that `conn` contains `:connection` and prefers that over `:datasource`. **Environment** - OS: Linux - Java Version: Temurin 11 - Clojure Version: 1.12.0 - Database: PostgreSQL 14.17 - Driver Library Version: `org.postgresql/postgresql {:mvn/version "42.7.5"}` **Additional context** Relevant effect of `clojure.java.jdbc/with-db-transaction`, via `clojure.java.jdbc/db-transaction*`: ```clj [...] (with-open [con (get-connection db (dissoc opts :read-only?))] (db-transaction* (add-connection db con) func opts)) [...] ``` `add-connection` is defined via extension of `clojure.java.jdbc/Connectable` in `clojure.java.jdbc`: ```clj (extend-protocol Connectable [...] clojure.lang.Associative (add-connection [m connection] (assoc m :connection connection)) (get-level [m] (or (:level m) 0)) [...]) ``` Given a Clojure map of the shape `{:datasource ...}` this produces a Clojure map with the shape `{:datasource ... :connection ...}`. If I read it correctly, next-jdbc uses this via `next.jdbc.protocols/Connectable` as extended in `next.jdbc.connection`: ```clj (extend-protocol p/Connectable [...] Object (get-connection [this opts] (p/get-connection (p/get-datasource this) opts))) ``` `next.jdbc.protocols/get-datasource` is defined as extension of `next.jdbc.protocols/Sourceable` in `next.jdbc.connection`: ```clj (extend-protocol p/Sourceable clojure.lang.Associative (get-datasource [this] ;; #207 c.j.j compatibility: (if-let [datasource (:datasource this)] datasource (url+etc->datasource (if-let [uri (:connection-uri this)] (string->url+etc uri) (spec->url+etc this))))) [...]) ``` I.e. if the map passed has a `:datasource` key, it will be used and the `:connection` key will be ignored. ---- To fix / workaround this, I naively tried: ```clj (extend-protocol p/Connectable clojure.lang.Associative (get-connection [this opts] (or (:connection this) (p/get-connection (p/get-datasource this) opts)))) ``` But that resulted in: ``` clojure.java.jdbc/db-transaction* jdbc.clj: 789 clojure.java.jdbc/db-transaction* jdbc.clj: 852 clojure.java.jdbc/db-transaction* jdbc.clj: 818 clojure.java.jdbc/db-transaction*/fn jdbc.clj: 822 com.mchange.v2.c3p0.impl.NewProxyConnection.rollback NewProxyConnection.java: 1033 java.lang.NullPointerException: java.sql.SQLException: You can't operate on a closed Connection!!! errorCode: 0 clojure.lang.ExceptionInfo: Rollback failed handling "You can't operate on a closed Connection!!!" ``` It appears that next-jdbc functions badly interact with `clojure.java.jdbc/with-db-transaction` in general. However, wrapping `conn` as `(some conn [:connection :datasource])` before passing it to `next.jdbc/execute-batch!` worked well. See-also: https://github.com/seancorfield/next-jdbc/issues/207
关闭于 2025-05-12 6 条评论