Further evolution of middleware/hooks
Removed the `IDeref` approach and plumbed `:post-execute-fn` directly into a new `WrappedExecutable` protocol that re-implements the `Executable` protocol but with hooks in place.
This commit is contained in:
parent
bb6eb02cbc
commit
8a66baab09
5 changed files with 255 additions and 75 deletions
|
|
@ -7,7 +7,6 @@ Only accretive/fixative changes will be made from now on.
|
||||||
The following changes have been committed to the **master** branch since the 1.0.10 release:
|
The following changes have been committed to the **master** branch since the 1.0.10 release:
|
||||||
|
|
||||||
* Add `next.jdbc.middleware` containing a `wrapper` for connectable objects that can offer default options, as well as four "hooks" for pre- and post-processing functions that make it easier to add logging and timing code to your `next.jdbc`-based application.
|
* Add `next.jdbc.middleware` containing a `wrapper` for connectable objects that can offer default options, as well as four "hooks" for pre- and post-processing functions that make it easier to add logging and timing code to your `next.jdbc`-based application.
|
||||||
* Make the "mapified" result set object implement `clojure.lang.IDeref` so you can "force" the result set builder to be constructed so that the `:post-execute-fn` hook will run in middleware.
|
|
||||||
* Add testing against Microsoft SQL Server (run tests with environment variables `NEXT_JDBC_TEST_MSSQL=yes` and `MSSQL_SA_PASSWORD` set to your local -- `127.0.0.1:1433` -- SQL Server `sa` user password; assumes that it can create and drop `fruit` and `fruit_time` tables in the `model` database).
|
* Add testing against Microsoft SQL Server (run tests with environment variables `NEXT_JDBC_TEST_MSSQL=yes` and `MSSQL_SA_PASSWORD` set to your local -- `127.0.0.1:1433` -- SQL Server `sa` user password; assumes that it can create and drop `fruit` and `fruit_time` tables in the `model` database).
|
||||||
* Add testing against MySQL (run tests with environment variables `NEXT_JDBC_TEST_MYSQL=yes` and `MYSQL_ROOT_PASSWORD` set to your local -- `127.0.0.1:3306` -- MySQL `root` user password; assumes you have already created an empty database called `clojure_test`).
|
* Add testing against MySQL (run tests with environment variables `NEXT_JDBC_TEST_MYSQL=yes` and `MYSQL_ROOT_PASSWORD` set to your local -- `127.0.0.1:3306` -- MySQL `root` user password; assumes you have already created an empty database called `clojure_test`).
|
||||||
* Bump several JDBC driver versions for up-to-date testing.
|
* Bump several JDBC driver versions for up-to-date testing.
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,10 @@ The `next.jdbc.middleware/wrapper` function accepts a connectable and an optiona
|
||||||
In addition to providing default options, the middleware wrapper also provides a number of "hooks" around SQL execution and result set building that you can tap into by providing any of the following options:
|
In addition to providing default options, the middleware wrapper also provides a number of "hooks" around SQL execution and result set building that you can tap into by providing any of the following options:
|
||||||
|
|
||||||
* `:pre-process-fn` -- `(fn [sql-params opts] ,,, [sql-params' opts'])` -- this function is called on the SQL & parameters and the options hash map, prior to executing the SQL, and can pre-process them, returning a vector pair of (possibly updated) SQL & parameters and options,
|
* `:pre-process-fn` -- `(fn [sql-params opts] ,,, [sql-params' opts'])` -- this function is called on the SQL & parameters and the options hash map, prior to executing the SQL, and can pre-process them, returning a vector pair of (possibly updated) SQL & parameters and options,
|
||||||
* `:post-process-fn` -- `(fn [rs opts] ,,, [rs' opts'])` -- this function is called on the `ResultSet` object and the options hash map, after executing the SQL, and can post-process them, returning a vector pair of (possibly updated) `ResultSet` object and options,
|
* `:post-process-fn` -- `(fn [rs opts] ,,, [rs' opts'])` -- this function is called on the `ResultSet` object and the options hash map, after executing the SQL, and can post-process them, returning a vector pair of (possibly updated) `ResultSet` object and options;
|
||||||
|
if the SQL operation does not return a `ResultSet` then this function is called on the update count and the options hash map, and should return a vector pair of the update count and options (unchanged),
|
||||||
* `:row!-fn` -- `(fn [row opts] ,,, row')` -- this function is called on each row as it is realized (and also passed the options hash map) and can post-process the row, returning a (possibly updated) row; it is named for the `row!` function in the result set builder that it wraps,
|
* `:row!-fn` -- `(fn [row opts] ,,, row')` -- this function is called on each row as it is realized (and also passed the options hash map) and can post-process the row, returning a (possibly updated) row; it is named for the `row!` function in the result set builder that it wraps,
|
||||||
* `:rs!-fn` -- `(fn [sql-params opts] ,,, [sql-params' opts'])` -- this function is called on the result set once it is realized (and also passed the options hash map) and can post-process the result set, returning a (possibly updated) result set; it is named for the `rs!` function in the result set builder that it wraps.
|
* `:rs!-fn` -- `(fn [sql-params opts] ,,, [sql-params' opts'])` -- this function is called, for `execute!` only, on the full result set once it is realized (and also passed the options hash map) and can post-process the result set, returning a (possibly updated) result set; it is named for the `rs!` function in the result set builder that it wraps.
|
||||||
|
|
||||||
Here's the data flow of middleware:
|
Here's the data flow of middleware:
|
||||||
|
|
||||||
|
|
@ -48,9 +49,11 @@ Here's the data flow of middleware:
|
||||||
;; 2. pre-process the SQL, parameters, and options:
|
;; 2. pre-process the SQL, parameters, and options:
|
||||||
;; [sql-params' opts''] <- (A ["select..." 4] opts')
|
;; [sql-params' opts''] <- (A ["select..." 4] opts')
|
||||||
;; 3. execute sql-params' with the opts'' hash map
|
;; 3. execute sql-params' with the opts'' hash map
|
||||||
;; 4. create the result set builder from the ResultSet rs and options opts''
|
;; 4. post-process the ResultSet (or update count) and options:
|
||||||
;; 5. inside that builder, post-process the ResultSet and options:
|
;; [rs' opts'''] <- (B rs opts'') or
|
||||||
;; [rs' opts'''] <- (B rs opts'')
|
;; [count' opts'''] <- (B count opts'')
|
||||||
|
;; if a result set was produced then:
|
||||||
|
;; 5. create the result set builder from the ResultSet rs' and options opts'''
|
||||||
;; 6. post-process each row as row! is called:
|
;; 6. post-process each row as row! is called:
|
||||||
;; row' <- (C (row! builder row) opts''')
|
;; row' <- (C (row! builder row) opts''')
|
||||||
;; 7. add row' into the result set being built
|
;; 7. add row' into the result set being built
|
||||||
|
|
@ -63,27 +66,24 @@ As you can see, both `:pre-process-fn` and `:post-process-fn` can return updated
|
||||||
|
|
||||||
Any of the hook functions may execute side-effects (such as logging) but must still return the expected data.
|
Any of the hook functions may execute side-effects (such as logging) but must still return the expected data.
|
||||||
|
|
||||||
## Middleware and `plan`
|
### Middleware and `plan`
|
||||||
|
|
||||||
Because `next.jdbc/plan` tries to avoid realizing a result set, it is possible to perform reductions that do not even cause the result set builder to be constructed -- the `:post-execute-fn` hook will not executed in such cases. For example:
|
Because `plan` tries to avoid realizing a result set, it is possible to perform reductions that do not even cause the result set builder to be constructed -- the `:post-execute-fn` hook will still be executed in such cases, but the `:row!-fn` hook may not be executed and the `:rs!-fn` hook will definitely not be executed. For example:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
user=> (into [] (map :name) ; does not construct the builder!
|
user=> (into [] (map :name) ; does not construct the builder!
|
||||||
(jdbc/plan db-spec ["select * from fruit"]))
|
(jdbc/plan db-spec ["select id, name from fruit"]))
|
||||||
["Apple" "Banana" "Peach" "Orange"]
|
["Apple" "Banana" "Peach" "Orange"]
|
||||||
|
user=> (into [] (map #(dissoc % :id)) ; constructs builder, calls row!-fn
|
||||||
|
(jdbc/plan db-spec ["select id, name from fruit"]))
|
||||||
|
[{:name "Apple"} {:name "Banana"} {:name "Peach"} {:name "Orange"}]
|
||||||
```
|
```
|
||||||
|
|
||||||
You can force the result set builder to be constructed by calling `deref` on any row:
|
### Middleware and `execute-one!`
|
||||||
|
|
||||||
```clojure
|
Because `execute-one!` only realizes at most one row from a result set, it never calls `rs!-fn`. It will call `row!-fn` at most once (zero times if no rows are returned from the SQL operation, exactly once if any rows are returned).
|
||||||
user=> (into [] (map (comp :name deref))
|
|
||||||
(jdbc/plan db-spec ["select * from fruit"]))
|
|
||||||
["Apple" "Banana" "Peach" "Orange"]
|
|
||||||
```
|
|
||||||
|
|
||||||
That will ensure that the result set builder _is_ constructed and it will execute the `:post-execute-fn` hook, but it will not cause rows (or the overall result set) to be realized. Thus, the only overhead of calling `deref` on each row is the one-off cost of constructing the result set builder for the first row, and the cost of derefencing a realized delay object for each row (and throwing that value away).
|
Both of the `:pre-execute-fn` and `:post-execute-fn` hooks are always called for `execute-one!`.
|
||||||
|
|
||||||
*Note: If your SQL operation produces no rows, or no `ResultSet` at all (only an update count), then there is no way to force the result set builder to be constructed and no way for the `:post-execute-fn` hook to be executed.*
|
|
||||||
|
|
||||||
## Examples of Middleware Usage
|
## Examples of Middleware Usage
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,11 @@
|
||||||
For `:rs!-fn`, that means returning the result set data unchanged (and
|
For `:rs!-fn`, that means returning the result set data unchanged (and
|
||||||
ignoring the options).
|
ignoring the options).
|
||||||
|
|
||||||
|
For SQL operations that do not produce a `ResultSet`, the post-process
|
||||||
|
hook (`:post-execute-fn`) is called with the update count and options
|
||||||
|
instead of the result set (and options) and should return a pair of the
|
||||||
|
update count and the options (unchanged).
|
||||||
|
|
||||||
For timing middleware, you can pass per-operation timing data through the
|
For timing middleware, you can pass per-operation timing data through the
|
||||||
options hash map, so you can measure the timing for the SQL execution, and
|
options hash map, so you can measure the timing for the SQL execution, and
|
||||||
also the time taken to build the full result set (if it is built).
|
also the time taken to build the full result set (if it is built).
|
||||||
|
|
@ -38,8 +43,10 @@
|
||||||
|
|
||||||
You can also transform the SQL & parameters prior to execution and transform
|
You can also transform the SQL & parameters prior to execution and transform
|
||||||
the rows and/or result set after each is built."
|
the rows and/or result set after each is built."
|
||||||
(:require [next.jdbc.protocols :as p]
|
(:require [next.jdbc.prepare :as prepare]
|
||||||
[next.jdbc.result-set :as rs]))
|
[next.jdbc.protocols :as p]
|
||||||
|
[next.jdbc.result-set :as rs])
|
||||||
|
(:import (java.sql PreparedStatement Statement)))
|
||||||
|
|
||||||
(defn post-processing-adapter
|
(defn post-processing-adapter
|
||||||
"Given a builder function (e.g., `as-lower-maps`), return a new builder
|
"Given a builder function (e.g., `as-lower-maps`), return a new builder
|
||||||
|
|
@ -49,10 +56,6 @@
|
||||||
functions, which should include `:next.jdbc/sql-params` (the vector of SQL
|
functions, which should include `:next.jdbc/sql-params` (the vector of SQL
|
||||||
and parameters, in case post-processing needs it):
|
and parameters, in case post-processing needs it):
|
||||||
|
|
||||||
* `:post-execute-fn` -- called on the `ResultSet` object and the options
|
|
||||||
immediately after the SQL operation completes
|
|
||||||
returns a pair of a (possibly updated) `ResultSet`
|
|
||||||
object and (possibly updated) options
|
|
||||||
* `:row!-fn` -- called on each row and the options, as the row is
|
* `:row!-fn` -- called on each row and the options, as the row is
|
||||||
fully-realized and returns the (possiblly updated)
|
fully-realized and returns the (possiblly updated)
|
||||||
row data
|
row data
|
||||||
|
|
@ -63,10 +66,7 @@
|
||||||
The results of these functions are returned as the rows/result set."
|
The results of these functions are returned as the rows/result set."
|
||||||
[builder-fn]
|
[builder-fn]
|
||||||
(fn [rs opts]
|
(fn [rs opts]
|
||||||
(let [exec-fn (get opts :post-execute-fn vector)
|
(let [mrsb (builder-fn rs opts)
|
||||||
;; rebind both the ResultSet object and the options
|
|
||||||
[rs opts] (exec-fn rs opts)
|
|
||||||
mrsb (builder-fn rs opts)
|
|
||||||
row!-fn (get opts :row!-fn (comp first vector))
|
row!-fn (get opts :row!-fn (comp first vector))
|
||||||
rs!-fn (get opts :rs!-fn (comp first vector))]
|
rs!-fn (get opts :rs!-fn (comp first vector))]
|
||||||
(reify
|
(reify
|
||||||
|
|
@ -80,43 +80,186 @@
|
||||||
(with-row [this mrs row] (rs/with-row mrsb mrs row))
|
(with-row [this mrs row] (rs/with-row mrsb mrs row))
|
||||||
(rs! [this mrs] (rs!-fn (rs/rs! mrsb mrs) opts))))))
|
(rs! [this mrs] (rs!-fn (rs/rs! mrsb mrs) opts))))))
|
||||||
|
|
||||||
|
(defprotocol WrappedExecutable
|
||||||
|
"This is an implementation detail for the middleware wrapper."
|
||||||
|
(wrapped-execute ^clojure.lang.IReduceInit [this sql-params opts])
|
||||||
|
(wrapped-execute-one [this sql-params opts])
|
||||||
|
(wrapped-execute-all [this sql-params opts]))
|
||||||
|
|
||||||
|
(defn- reduce-stmt
|
||||||
|
"Variant of `next.jdbc.result-set/reduce-stmt` that calls the
|
||||||
|
`:post-execute-fn` hook on results sets and update counts."
|
||||||
|
[^PreparedStatement stmt f init opts]
|
||||||
|
(if-let [rs (#'rs/stmt->result-set stmt opts)]
|
||||||
|
(let [[rs opts] ((:post-execute-fn opts) rs opts)
|
||||||
|
rs-map (#'rs/mapify-result-set rs opts)]
|
||||||
|
(loop [init' init]
|
||||||
|
(if (.next rs)
|
||||||
|
(let [result (f init' rs-map)]
|
||||||
|
(if (reduced? result)
|
||||||
|
@result
|
||||||
|
(recur result)))
|
||||||
|
init')))
|
||||||
|
(let [[n _] ((:post-execute-fn opts) (.getUpdateCount stmt) opts)]
|
||||||
|
(f init {:next.jdbc/update-count n}))))
|
||||||
|
|
||||||
|
(defn- reduce-stmt-sql
|
||||||
|
"Variant of `next.jdbc.result-set/reduce-stmt-sql` that calls the
|
||||||
|
`:post-execute-fn` hook on results sets and update counts."
|
||||||
|
[^Statement stmt sql f init opts]
|
||||||
|
(if-let [rs (#'rs/stmt-sql->result-set stmt sql opts)]
|
||||||
|
(let [[rs opts] ((:post-execute-fn opts) rs opts)
|
||||||
|
rs-map (#'rs/mapify-result-set rs opts)]
|
||||||
|
(loop [init' init]
|
||||||
|
(if (.next rs)
|
||||||
|
(let [result (f init' rs-map)]
|
||||||
|
(if (reduced? result)
|
||||||
|
@result
|
||||||
|
(recur result)))
|
||||||
|
init')))
|
||||||
|
(let [[n _] ((:post-execute-fn opts) (.getUpdateCount stmt) opts)]
|
||||||
|
(f init {:next.jdbc/update-count n}))))
|
||||||
|
|
||||||
|
;; this duplicates the Executable implementations from next.jdbc.result-set
|
||||||
|
;; but with hooks for calling :post-execute-fn and being able to rely on
|
||||||
|
;; :builder-fn always being present
|
||||||
|
(extend-protocol WrappedExecutable
|
||||||
|
java.sql.Connection
|
||||||
|
(wrapped-execute [this sql-params opts]
|
||||||
|
(reify clojure.lang.IReduceInit
|
||||||
|
(reduce [_ f init]
|
||||||
|
(with-open [stmt (prepare/create this
|
||||||
|
(first sql-params)
|
||||||
|
(rest sql-params)
|
||||||
|
opts)]
|
||||||
|
(reduce-stmt stmt f init opts)))
|
||||||
|
(toString [_] "`IReduceInit` from `plan` -- missing reduction?")))
|
||||||
|
(wrapped-execute-one [this sql-params opts]
|
||||||
|
(with-open [stmt (prepare/create this
|
||||||
|
(first sql-params)
|
||||||
|
(rest sql-params)
|
||||||
|
opts)]
|
||||||
|
(wrapped-execute-one stmt nil opts)))
|
||||||
|
(wrapped-execute-all [this sql-params opts]
|
||||||
|
(with-open [stmt (prepare/create this
|
||||||
|
(first sql-params)
|
||||||
|
(rest sql-params)
|
||||||
|
opts)]
|
||||||
|
(wrapped-execute-all stmt nil opts)))
|
||||||
|
|
||||||
|
javax.sql.DataSource
|
||||||
|
(wrapped-execute [this sql-params opts]
|
||||||
|
(reify clojure.lang.IReduceInit
|
||||||
|
(reduce [_ f init]
|
||||||
|
(with-open [con (p/get-connection this opts)
|
||||||
|
stmt (prepare/create con
|
||||||
|
(first sql-params)
|
||||||
|
(rest sql-params)
|
||||||
|
opts)]
|
||||||
|
(reduce-stmt stmt f init opts)))
|
||||||
|
(toString [_] "`IReduceInit` from `plan` -- missing reduction?")))
|
||||||
|
(wrapped-execute-one [this sql-params opts]
|
||||||
|
(with-open [con (p/get-connection this opts)
|
||||||
|
stmt (prepare/create con
|
||||||
|
(first sql-params)
|
||||||
|
(rest sql-params)
|
||||||
|
opts)]
|
||||||
|
(wrapped-execute-one stmt nil opts)))
|
||||||
|
(wrapped-execute-all [this sql-params opts]
|
||||||
|
(with-open [con (p/get-connection this opts)
|
||||||
|
stmt (prepare/create con
|
||||||
|
(first sql-params)
|
||||||
|
(rest sql-params)
|
||||||
|
opts)]
|
||||||
|
(wrapped-execute-all stmt nil opts)))
|
||||||
|
|
||||||
|
java.sql.PreparedStatement
|
||||||
|
;; we can't tell if this PreparedStatement will return generated
|
||||||
|
;; keys so we pass a truthy value to at least attempt it if we
|
||||||
|
;; do not get a ResultSet back from the execute call
|
||||||
|
(wrapped-execute [this _ opts]
|
||||||
|
(reify clojure.lang.IReduceInit
|
||||||
|
(reduce [_ f init]
|
||||||
|
(reduce-stmt this f init (assoc opts :return-keys true)))
|
||||||
|
(toString [_] "`IReduceInit` from `plan` -- missing reduction?")))
|
||||||
|
(wrapped-execute-one [this _ opts]
|
||||||
|
(if-let [rs (#'rs/stmt->result-set this (assoc opts :return-keys true))]
|
||||||
|
(let [[rs opts] ((:post-execute-fn opts) rs opts)
|
||||||
|
builder ((:builder-fn opts) rs opts)]
|
||||||
|
(when (.next rs)
|
||||||
|
(rs/datafiable-row (#'rs/row-builder builder)
|
||||||
|
(.getConnection this) opts)))
|
||||||
|
(let [[n _] ((:post-execute-fn opts) (.getUpdateCount this) opts)]
|
||||||
|
{:next.jdbc/update-count n})))
|
||||||
|
(wrapped-execute-all [this _ opts]
|
||||||
|
(if-let [rs (#'rs/stmt->result-set this opts)]
|
||||||
|
(let [[rs opts] ((:post-execute-fn opts) rs opts)]
|
||||||
|
(rs/datafiable-result-set rs (.getConnection this) opts))
|
||||||
|
(let [[n _] ((:post-execute-fn opts) (.getUpdateCount this) opts)]
|
||||||
|
[{:next.jdbc/update-count n}])))
|
||||||
|
|
||||||
|
java.sql.Statement
|
||||||
|
;; we can't tell if this Statement will return generated
|
||||||
|
;; keys so we pass a truthy value to at least attempt it if we
|
||||||
|
;; do not get a ResultSet back from the execute call
|
||||||
|
(wrapped-execute [this sql-params opts]
|
||||||
|
(assert (= 1 (count sql-params))
|
||||||
|
"Parameters cannot be provided when executing a non-prepared Statement")
|
||||||
|
(reify clojure.lang.IReduceInit
|
||||||
|
(reduce [_ f init]
|
||||||
|
(reduce-stmt-sql this (first sql-params) f init (assoc opts :return-keys true)))
|
||||||
|
(toString [_] "`IReduceInit` from `plan` -- missing reduction?")))
|
||||||
|
(wrapped-execute-one [this sql-params opts]
|
||||||
|
(assert (= 1 (count sql-params))
|
||||||
|
"Parameters cannot be provided when executing a non-prepared Statement")
|
||||||
|
(if-let [rs (#'rs/stmt-sql->result-set this (first sql-params) (assoc opts :return-keys true))]
|
||||||
|
(let [[rs opts] ((:post-execute-fn opts) rs opts)
|
||||||
|
builder ((:builder-fn opts) rs opts)]
|
||||||
|
(when (.next rs)
|
||||||
|
(rs/datafiable-row (#'rs/row-builder builder)
|
||||||
|
(.getConnection this) opts)))
|
||||||
|
(let [[n _] ((:post-execute-fn opts) (.getUpdateCount this) opts)]
|
||||||
|
{:next.jdbc/update-count n})))
|
||||||
|
(wrapped-execute-all [this sql-params opts]
|
||||||
|
(assert (= 1 (count sql-params))
|
||||||
|
"Parameters cannot be provided when executing a non-prepared Statement")
|
||||||
|
(if-let [rs (#'rs/stmt-sql->result-set this (first sql-params) opts)]
|
||||||
|
(let [[rs opts] ((:post-execute-fn opts) rs opts)]
|
||||||
|
(rs/datafiable-result-set rs (.getConnection this) opts))
|
||||||
|
(let [[n _] ((:post-execute-fn opts) (.getUpdateCount this) opts)]
|
||||||
|
[{:next.jdbc/update-count n}])))
|
||||||
|
|
||||||
|
Object
|
||||||
|
(wrapped-execute [this sql-params opts]
|
||||||
|
(wrapped-execute (p/get-datasource this) sql-params opts))
|
||||||
|
(wrapped-execute-one [this sql-params opts]
|
||||||
|
(wrapped-execute-one (p/get-datasource this) sql-params opts))
|
||||||
|
(wrapped-execute-all [this sql-params opts]
|
||||||
|
(wrapped-execute-all (p/get-datasource this) sql-params opts)))
|
||||||
|
|
||||||
|
(defn- execute-wrapper
|
||||||
|
[f db global-opts sql-params opts]
|
||||||
|
(let [opts (merge {:pre-execute-fn vector :post-execute-fn vector
|
||||||
|
:builder-fn rs/as-maps}
|
||||||
|
global-opts opts)
|
||||||
|
;; rebind both the SQL & parameters and the options
|
||||||
|
[sql-params opts] ((:pre-execute-fn opts) sql-params opts)]
|
||||||
|
(f db sql-params
|
||||||
|
(assoc opts
|
||||||
|
:builder-fn (post-processing-adapter (:builder-fn opts))
|
||||||
|
:next.jdbc/sql-params sql-params))))
|
||||||
|
|
||||||
(defrecord JdbcMiddleware [db global-opts]
|
(defrecord JdbcMiddleware [db global-opts]
|
||||||
p/Executable
|
p/Executable
|
||||||
(-execute [this sql-params opts]
|
(-execute [this sql-params opts]
|
||||||
(let [opts (merge global-opts opts)
|
(execute-wrapper wrapped-execute db global-opts sql-params opts))
|
||||||
pre-execute-fn (get opts :pre-execute-fn vector)
|
|
||||||
;; rebind both the SQL & parameters and the options
|
|
||||||
[sql-params opts] (pre-execute-fn sql-params opts)
|
|
||||||
builder-fn (get opts :builder-fn rs/as-maps)]
|
|
||||||
(p/-execute db sql-params
|
|
||||||
(assoc opts
|
|
||||||
:builder-fn (post-processing-adapter builder-fn)
|
|
||||||
:next.jdbc/sql-params sql-params))))
|
|
||||||
(-execute-one [this sql-params opts]
|
(-execute-one [this sql-params opts]
|
||||||
(let [opts (merge global-opts opts)
|
(execute-wrapper wrapped-execute-one db global-opts sql-params opts))
|
||||||
pre-execute-fn (get opts :pre-execute-fn vector)
|
|
||||||
;; rebind both the SQL & parameters and the options
|
|
||||||
[sql-params opts] (pre-execute-fn sql-params opts)
|
|
||||||
builder-fn (get opts :builder-fn rs/as-maps)]
|
|
||||||
(p/-execute-one db sql-params
|
|
||||||
(assoc opts
|
|
||||||
:builder-fn (post-processing-adapter builder-fn)
|
|
||||||
:next.jdbc/sql-params sql-params))))
|
|
||||||
(-execute-all [this sql-params opts]
|
(-execute-all [this sql-params opts]
|
||||||
(let [opts (merge global-opts opts)
|
(execute-wrapper wrapped-execute-all db global-opts sql-params opts)))
|
||||||
pre-execute-fn (get opts :pre-execute-fn vector)
|
|
||||||
;; rebind both the SQL & parameters and the options
|
|
||||||
[sql-params opts] (pre-execute-fn sql-params opts)
|
|
||||||
builder-fn (get opts :builder-fn rs/as-maps)]
|
|
||||||
(p/-execute-all db sql-params
|
|
||||||
(assoc opts
|
|
||||||
:builder-fn (post-processing-adapter builder-fn)
|
|
||||||
:next.jdbc/sql-params sql-params)))))
|
|
||||||
|
|
||||||
(defn wrapper
|
(defn wrapper
|
||||||
"Given a connectable, return a wrapped connectable that will run hooks.
|
"Given a connectable and a hash map of options, return a wrapped connectable
|
||||||
|
|
||||||
Given a connectable and a hash map of options, return a wrapped connectable
|
|
||||||
that will use those options as defaults for any SQL operations and will
|
that will use those options as defaults for any SQL operations and will
|
||||||
run hooks.
|
run hooks.
|
||||||
|
|
||||||
|
|
@ -133,7 +276,12 @@
|
||||||
passed options)
|
passed options)
|
||||||
returns (possibly updated) result set data
|
returns (possibly updated) result set data
|
||||||
|
|
||||||
Uses `next.jdbc.middleware/post-processing-adapter for the last three,
|
For SQL operations that do not produce a `ResultSet`, the post-process
|
||||||
|
hook (`:post-execute-fn`) is called with the update count and options
|
||||||
|
instead of the result set (and options) and should return a pair of the
|
||||||
|
update count and the options (unchanged).
|
||||||
|
|
||||||
|
Uses `next.jdbc.middleware/post-processing-adapter for the last two,
|
||||||
wrapped around whatever `:builder-fn` you supply for each SQL operation."
|
wrapped around whatever `:builder-fn` you supply for each SQL operation."
|
||||||
([db] (JdbcMiddleware. db {}))
|
([db] (JdbcMiddleware. db {}))
|
||||||
([db opts] (JdbcMiddleware. db opts)))
|
([db opts] (JdbcMiddleware. db opts)))
|
||||||
|
|
|
||||||
|
|
@ -460,13 +460,6 @@
|
||||||
(row-builder @builder)
|
(row-builder @builder)
|
||||||
{`core-p/datafy (navize-row connectable opts)}))
|
{`core-p/datafy (navize-row connectable opts)}))
|
||||||
|
|
||||||
clojure.lang.IDeref
|
|
||||||
(deref [this]
|
|
||||||
;; force the builder to be created but return the row
|
|
||||||
;; without actually building anything
|
|
||||||
(deref builder)
|
|
||||||
this)
|
|
||||||
|
|
||||||
;; from java.lang.Object:
|
;; from java.lang.Object:
|
||||||
(toString [_]
|
(toString [_]
|
||||||
(try
|
(try
|
||||||
|
|
@ -573,6 +566,11 @@
|
||||||
init')))
|
init')))
|
||||||
(f init {:next.jdbc/update-count (.getUpdateCount stmt)})))
|
(f init {:next.jdbc/update-count (.getUpdateCount stmt)})))
|
||||||
|
|
||||||
|
;; the Connection and DataSource implementations could simply delegate to
|
||||||
|
;; the PreparedStatement implementation which would reduce the amount of
|
||||||
|
;; code here -- but they are unrolled for performance, to avoid the extra
|
||||||
|
;; function call through the protocol that would be involved in reuse
|
||||||
|
;; note that middleware duplicates this code but does delegate and reuse
|
||||||
(extend-protocol p/Executable
|
(extend-protocol p/Executable
|
||||||
java.sql.Connection
|
java.sql.Connection
|
||||||
(-execute [this sql-params opts]
|
(-execute [this sql-params opts]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
default-options]]
|
default-options]]
|
||||||
[next.jdbc.result-set :as rs]
|
[next.jdbc.result-set :as rs]
|
||||||
[next.jdbc.specs :as specs])
|
[next.jdbc.specs :as specs])
|
||||||
(:import (java.sql ResultSet ResultSetMetaData)))
|
(:import (java.sql ResultSet)))
|
||||||
|
|
||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
|
@ -64,14 +64,49 @@
|
||||||
(swap! (::timing opts) update ::total + (- end (::start opts)))
|
(swap! (::timing opts) update ::total + (- end (::start opts)))
|
||||||
[rs opts]))
|
[rs opts]))
|
||||||
timing (atom {::calls 0 ::total 0.0})
|
timing (atom {::calls 0 ::total 0.0})
|
||||||
|
mw-ds (mw/wrapper (ds) {::timing timing
|
||||||
|
:pre-execute-fn start-fn
|
||||||
|
:post-execute-fn end-fn})
|
||||||
sql-p ["select * from fruit where id in (?,?) order by id desc" 1 4]]
|
sql-p ["select * from fruit where id in (?,?) order by id desc" 1 4]]
|
||||||
(jdbc/execute! (mw/wrapper (ds) {::timing timing
|
(jdbc/execute! mw-ds sql-p)
|
||||||
:pre-execute-fn start-fn
|
(jdbc/execute! mw-ds sql-p)
|
||||||
:post-execute-fn end-fn})
|
|
||||||
sql-p)
|
|
||||||
(jdbc/execute! (mw/wrapper (ds) {::timing timing
|
|
||||||
:pre-execute-fn start-fn
|
|
||||||
:post-execute-fn end-fn})
|
|
||||||
sql-p)
|
|
||||||
(printf "%20s - %d calls took %,10d nanoseconds\n"
|
(printf "%20s - %d calls took %,10d nanoseconds\n"
|
||||||
(:dbtype (db)) (::calls @timing) (long (::total @timing)))))
|
(:dbtype (db)) (::calls @timing) (long (::total @timing)))))
|
||||||
|
|
||||||
|
(deftest post-execute-tests
|
||||||
|
(let [calls (atom 0)
|
||||||
|
seen-rs (atom 0)
|
||||||
|
rows (atom 0)
|
||||||
|
rss (atom 0)
|
||||||
|
post-fn (fn [x opts]
|
||||||
|
(swap! calls inc)
|
||||||
|
(when (instance? ResultSet x)
|
||||||
|
(swap! seen-rs inc))
|
||||||
|
[x opts])
|
||||||
|
mw-ds (mw/wrapper (ds) {:post-execute-fn post-fn
|
||||||
|
:row!-fn (fn [row _] (swap! rows inc) row)
|
||||||
|
:rs!-fn (fn [rs _] (swap! rss inc) rs)})]
|
||||||
|
;; first call, four rows, one result set
|
||||||
|
(jdbc/execute! mw-ds ["select * from fruit"])
|
||||||
|
(is (= 1 @calls))
|
||||||
|
(is (= 1 @seen-rs))
|
||||||
|
(is (= 4 @rows))
|
||||||
|
(is (= 1 @rss))
|
||||||
|
;; second call, no rows, one more result set
|
||||||
|
(jdbc/execute! mw-ds ["select * from fruit where id < 0"])
|
||||||
|
(is (= 2 @calls))
|
||||||
|
(is (= 2 @seen-rs))
|
||||||
|
(is (= 4 @rows))
|
||||||
|
(is (= 2 @rss))
|
||||||
|
;; third call, no result set
|
||||||
|
(jdbc/execute! mw-ds ["update fruit set name = ? where id < 0" "Plum"])
|
||||||
|
(is (= 3 @calls))
|
||||||
|
(is (= 2 @seen-rs))
|
||||||
|
(is (= 4 @rows))
|
||||||
|
(is (= 2 @rss))
|
||||||
|
;; fourth call, one row, one result set (but no rs!-fn)
|
||||||
|
(jdbc/execute-one! mw-ds ["select * from fruit"])
|
||||||
|
(is (= 4 @calls))
|
||||||
|
(is (= 3 @seen-rs))
|
||||||
|
(is (= 5 @rows))
|
||||||
|
(is (= 2 @rss))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue