-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Respect
nested-transaction-rule
in subsequent transactions (#146)
- Loading branch information
Showing
9 changed files
with
250 additions
and
209 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,38 @@ | ||
(ns toucan2.jdbc | ||
(:require | ||
[methodical.core :as m] | ||
[toucan2.jdbc.query :as jdbc.query] | ||
[toucan2.model :as model] | ||
[toucan2.pipeline :as pipeline] | ||
[toucan2.types :as types])) | ||
[toucan2.jdbc.connection :as jdbc.conn] | ||
[toucan2.jdbc.pipeline :as jdbc.pipeline] | ||
[toucan2.protocols :as protocols])) | ||
|
||
(set! *warn-on-reflection* true) | ||
|
||
(m/defmethod pipeline/transduce-execute-with-connection [#_connection java.sql.Connection | ||
#_query-type :default | ||
#_model :default] | ||
"Default impl for the JDBC query execution backend." | ||
[rf conn query-type model sql-args] | ||
{:pre [(sequential? sql-args) (string? (first sql-args))]} | ||
;; `:return-keys` is passed in this way instead of binding a dynamic var because we don't want any additional queries | ||
;; happening inside of the `rf` to return keys or whatever. | ||
(let [extra-options (when (isa? query-type :toucan.result-type/pks) | ||
{:return-keys true}) | ||
result (jdbc.query/reduce-jdbc-query rf (rf) conn model sql-args extra-options)] | ||
(rf result))) | ||
|
||
(m/defmethod pipeline/transduce-execute-with-connection [#_connection java.sql.Connection | ||
#_query-type :toucan.result-type/pks | ||
#_model :default] | ||
"JDBC query execution backend for executing queries that return PKs (`:toucan.result-type/pks`). | ||
Applies transducer to call [[toucan2.model/select-pks-fn]] on each result row." | ||
[rf conn query-type model sql-args] | ||
(let [xform (map (model/select-pks-fn model))] | ||
(next-method (xform rf) conn query-type model sql-args))) | ||
|
||
(defn- transduce-instances-from-pks | ||
[rf model columns pks] | ||
;; make sure [[toucan2.select]] is loaded so we get the impls for `:toucan.query-type/select.instances` | ||
(when-not (contains? (loaded-libs) 'toucan2.select) | ||
(locking clojure.lang.RT/REQUIRE_LOCK | ||
(require 'toucan2.select))) | ||
(if (empty? pks) | ||
[] | ||
(let [kv-args {:toucan/pk [:in pks]} | ||
parsed-args {:columns columns | ||
:kv-args kv-args}] | ||
(pipeline/transduce-query rf :toucan.query-type/select.instances-from-pks model parsed-args {})))) | ||
(comment jdbc.conn/keep-me | ||
jdbc.pipeline/keep-me) | ||
|
||
(derive ::DML-queries-returning-instances :toucan.result-type/instances) | ||
|
||
(doseq [query-type [:toucan.query-type/delete.instances | ||
:toucan.query-type/update.instances | ||
:toucan.query-type/insert.instances]] | ||
(derive query-type ::DML-queries-returning-instances)) | ||
|
||
(m/defmethod pipeline/transduce-execute-with-connection [#_connection java.sql.Connection | ||
#_query-type ::DML-queries-returning-instances | ||
#_model :default] | ||
"DML queries like `UPDATE` or `INSERT` don't usually support returning instances, at least not with JDBC. So for these | ||
situations we'll fake it by first running an equivalent query returning inserted/affected PKs, and then do a | ||
subsequent SELECT to get those rows. Then we'll reduce the rows with the original reducing function." | ||
[rf conn query-type model sql-args] | ||
;; We're using `conj` here instead of `rf` so no row-transform nonsense or whatever is done. We will pass the | ||
;; actual instances to the original `rf` once we get them. | ||
(let [pk-query-type (types/similar-query-type-returning query-type :toucan.result-type/pks) | ||
pks (pipeline/transduce-execute-with-connection conj conn pk-query-type model sql-args) | ||
;; this is sort of a hack but I don't know of any other way to pass along `:columns` information with the | ||
;; original parsed args | ||
columns (:columns pipeline/*parsed-args*)] | ||
;; once we have a sequence of PKs then get instances as with `select` and do our magic on them using the | ||
;; ORIGINAL `rf`. | ||
(transduce-instances-from-pks rf model columns pks))) | ||
(set! *warn-on-reflection* true) | ||
|
||
;;;; load the miscellaneous integrations | ||
|
||
(defn- class-exists? [^String class-name] | ||
(defn- class-for-name ^Class [^String class-name] | ||
(try | ||
(Class/forName class-name) | ||
(catch Throwable _))) | ||
|
||
(when (class-exists? "org.postgresql.jdbc.PgConnection") | ||
(when (class-for-name "org.postgresql.jdbc.PgConnection") | ||
(require 'toucan2.jdbc.postgres)) | ||
|
||
(when (some class-exists? ["org.mariadb.jdbc.Connection" | ||
(when (some class-for-name ["org.mariadb.jdbc.Connection" | ||
"org.mariadb.jdbc.MariaDbConnection" | ||
"com.mysql.cj.MysqlConnection"]) | ||
(require 'toucan2.jdbc.mysql-mariadb)) | ||
|
||
;;; c3p0 and Hikari integration: when we encounter a wrapped connection pool connection, dispatch off of the class of | ||
;;; connection it wraps | ||
(doseq [pool-connection-class-name ["com.mchange.v2.c3p0.impl.NewProxyConnection" | ||
"com.zaxxer.hikari.pool.HikariProxyConnection"]] | ||
(when-let [pool-connection-class (class-for-name pool-connection-class-name)] | ||
(extend pool-connection-class | ||
protocols/IDispatchValue | ||
{:dispatch-value (fn [^java.sql.Wrapper conn] | ||
(try | ||
(protocols/dispatch-value (.unwrap conn java.sql.Connection)) | ||
(catch Throwable _ | ||
pool-connection-class)))}))) | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
(ns toucan2.jdbc.connection | ||
(:require | ||
[methodical.core :as m] | ||
[next.jdbc] | ||
[next.jdbc.transaction] | ||
[toucan2.connection :as conn] | ||
[toucan2.log :as log])) | ||
|
||
(set! *warn-on-reflection* true) | ||
|
||
(m/defmethod conn/do-with-connection java.sql.Connection | ||
[conn f] | ||
(f conn)) | ||
|
||
(m/defmethod conn/do-with-connection javax.sql.DataSource | ||
[^javax.sql.DataSource data-source f] | ||
(with-open [conn (.getConnection data-source)] | ||
(f conn))) | ||
|
||
(m/defmethod conn/do-with-connection clojure.lang.IPersistentMap | ||
"Implementation for map connectables. Treats them as a `clojure.java.jdbc`-style connection spec map, converting them to | ||
a `java.sql.DataSource` with [[next.jdbc/get-datasource]]." | ||
[m f] | ||
(conn/do-with-connection (next.jdbc/get-datasource m) f)) | ||
|
||
;;; for record types that implement `DataSource`, prefer the `DataSource` impl over the map impl. | ||
(m/prefer-method! #'conn/do-with-connection javax.sql.DataSource clojure.lang.IPersistentMap) | ||
|
||
(m/defmethod conn/do-with-connection-string "jdbc" | ||
"Implementation of `do-with-connection-string` (and thus [[do-with-connection]]) for all strings starting with `jdbc:`. | ||
Calls `java.sql.DriverManager/getConnection` on the connection string." | ||
[^String connection-string f] | ||
(with-open [conn (java.sql.DriverManager/getConnection connection-string)] | ||
(f conn))) | ||
|
||
(m/defmethod conn/do-with-transaction java.sql.Connection | ||
[^java.sql.Connection conn options f] | ||
(let [nested-tx-rule (get options :nested-transaction-rule next.jdbc.transaction/*nested-tx*) | ||
options (dissoc options :nested-transaction-rule)] | ||
(log/debugf "do with JDBC transaction (nested rule: %s) with options %s" nested-tx-rule options) | ||
(binding [next.jdbc.transaction/*nested-tx* nested-tx-rule] | ||
(next.jdbc/with-transaction [t-conn conn options] | ||
(f t-conn))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
(ns toucan2.jdbc.pipeline | ||
(:require | ||
[methodical.core :as m] | ||
[toucan2.jdbc.query :as jdbc.query] | ||
[toucan2.model :as model] | ||
[toucan2.pipeline :as pipeline] | ||
[toucan2.types :as types])) | ||
|
||
(m/defmethod pipeline/transduce-execute-with-connection [#_connection java.sql.Connection | ||
#_query-type :default | ||
#_model :default] | ||
"Default impl for the JDBC query execution backend." | ||
[rf conn query-type model sql-args] | ||
{:pre [(sequential? sql-args) (string? (first sql-args))]} | ||
;; `:return-keys` is passed in this way instead of binding a dynamic var because we don't want any additional queries | ||
;; happening inside of the `rf` to return keys or whatever. | ||
(let [extra-options (when (isa? query-type :toucan.result-type/pks) | ||
{:return-keys true}) | ||
result (jdbc.query/reduce-jdbc-query rf (rf) conn model sql-args extra-options)] | ||
(rf result))) | ||
|
||
(m/defmethod pipeline/transduce-execute-with-connection [#_connection java.sql.Connection | ||
#_query-type :toucan.result-type/pks | ||
#_model :default] | ||
"JDBC query execution backend for executing queries that return PKs (`:toucan.result-type/pks`). | ||
Applies transducer to call [[toucan2.model/select-pks-fn]] on each result row." | ||
[rf conn query-type model sql-args] | ||
(let [xform (map (model/select-pks-fn model))] | ||
(next-method (xform rf) conn query-type model sql-args))) | ||
|
||
(defn- transduce-instances-from-pks | ||
[rf model columns pks] | ||
;; make sure [[toucan2.select]] is loaded so we get the impls for `:toucan.query-type/select.instances` | ||
(when-not (contains? (loaded-libs) 'toucan2.select) | ||
(locking clojure.lang.RT/REQUIRE_LOCK | ||
(require 'toucan2.select))) | ||
(if (empty? pks) | ||
[] | ||
(let [kv-args {:toucan/pk [:in pks]} | ||
parsed-args {:columns columns | ||
:kv-args kv-args}] | ||
(pipeline/transduce-query rf :toucan.query-type/select.instances-from-pks model parsed-args {})))) | ||
|
||
(derive ::DML-queries-returning-instances :toucan.result-type/instances) | ||
|
||
(doseq [query-type [:toucan.query-type/delete.instances | ||
:toucan.query-type/update.instances | ||
:toucan.query-type/insert.instances]] | ||
(derive query-type ::DML-queries-returning-instances)) | ||
|
||
(m/defmethod pipeline/transduce-execute-with-connection [#_connection java.sql.Connection | ||
#_query-type ::DML-queries-returning-instances | ||
#_model :default] | ||
"DML queries like `UPDATE` or `INSERT` don't usually support returning instances, at least not with JDBC. So for these | ||
situations we'll fake it by first running an equivalent query returning inserted/affected PKs, and then do a | ||
subsequent SELECT to get those rows. Then we'll reduce the rows with the original reducing function." | ||
[rf conn query-type model sql-args] | ||
;; We're using `conj` here instead of `rf` so no row-transform nonsense or whatever is done. We will pass the | ||
;; actual instances to the original `rf` once we get them. | ||
(let [pk-query-type (types/similar-query-type-returning query-type :toucan.result-type/pks) | ||
pks (pipeline/transduce-execute-with-connection conj conn pk-query-type model sql-args) | ||
;; this is sort of a hack but I don't know of any other way to pass along `:columns` information with the | ||
;; original parsed args | ||
columns (:columns pipeline/*parsed-args*)] | ||
;; once we have a sequence of PKs then get instances as with `select` and do our magic on them using the | ||
;; ORIGINAL `rf`. | ||
(transduce-instances-from-pks rf model columns pks))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.