Ensure middleware actually composes!

This commit is contained in:
Sean Corfield 2019-11-17 17:09:14 -08:00
parent d1ca139fa1
commit ef0f33f919
2 changed files with 58 additions and 37 deletions

View file

@ -67,8 +67,8 @@
[builder-fn]
(fn [rs opts]
(let [mrsb (builder-fn rs opts)
row!-fn (get opts :row!-fn (comp first vector))
rs!-fn (get opts :rs!-fn (comp first vector))]
row!-fn (:row!-fn opts (comp first vector))
rs!-fn (:rs!-fn opts (comp first vector))]
(reify
rs/RowBuilder
(->row [this] (rs/->row mrsb))
@ -86,12 +86,29 @@
(wrapped-execute-one [this sql-params opts])
(wrapped-execute-all [this sql-params opts]))
(defn- execute-wrapper
[f db global-opts sql-params opts]
(let [opts (merge global-opts opts)
;; rebind both the SQL & parameters and the options
[sql-params opts] ((:pre-execute-fn opts vector) sql-params opts)]
(f db sql-params (assoc opts :next.jdbc/sql-params sql-params))))
(defrecord JdbcMiddleware [db global-opts]
p/Executable
(-execute [this sql-params opts]
(execute-wrapper wrapped-execute db global-opts sql-params opts))
(-execute-one [this sql-params opts]
(execute-wrapper wrapped-execute-one db global-opts sql-params opts))
(-execute-all [this sql-params opts]
(execute-wrapper wrapped-execute-all db global-opts 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)
(let [opts (update opts :builder-fn (fnil post-processing-adapter rs/as-maps))
[rs opts] ((:post-execute-fn opts vector) rs opts)
rs-map (#'rs/mapify-result-set rs opts)]
(loop [init' init]
(if (.next rs)
@ -100,7 +117,7 @@
@result
(recur result)))
init')))
(let [[n _] ((:post-execute-fn opts) (.getUpdateCount stmt) opts)]
(let [[n _] ((:post-execute-fn opts vector) (.getUpdateCount stmt) opts)]
(f init {:next.jdbc/update-count n}))))
(defn- reduce-stmt-sql
@ -108,7 +125,8 @@
`: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)
(let [opts (update opts :builder-fn (fnil post-processing-adapter rs/as-maps))
[rs opts] ((:post-execute-fn opts vector) rs opts)
rs-map (#'rs/mapify-result-set rs opts)]
(loop [init' init]
(if (.next rs)
@ -117,12 +135,11 @@
@result
(recur result)))
init')))
(let [[n _] ((:post-execute-fn opts) (.getUpdateCount stmt) opts)]
(let [[n _] ((:post-execute-fn opts vector) (.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
;; but with hooks for calling :post-execute-fn
(extend-protocol WrappedExecutable
java.sql.Connection
(wrapped-execute [this sql-params opts]
@ -184,18 +201,20 @@
(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)
(let [opts (update opts :builder-fn (fnil post-processing-adapter rs/as-maps))
[rs opts] ((:post-execute-fn opts vector) 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)]
(let [[n _] ((:post-execute-fn opts vector) (.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)]
(let [opts (update opts :builder-fn (fnil post-processing-adapter rs/as-maps))
[rs opts] ((:post-execute-fn opts vector) rs opts)]
(rs/datafiable-result-set rs (.getConnection this) opts))
(let [[n _] ((:post-execute-fn opts) (.getUpdateCount this) opts)]
(let [[n _] ((:post-execute-fn opts vector) (.getUpdateCount this) opts)]
[{:next.jdbc/update-count n}])))
java.sql.Statement
@ -213,22 +232,32 @@
(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)
(let [opts (update opts :builder-fn (fnil post-processing-adapter rs/as-maps))
[rs opts] ((:post-execute-fn opts vector) 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)]
(let [[n _] ((:post-execute-fn opts vector) (.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)]
(let [opts (update opts :builder-fn (fnil post-processing-adapter rs/as-maps))
[rs opts] ((:post-execute-fn opts vector) rs opts)]
(rs/datafiable-result-set rs (.getConnection this) opts))
(let [[n _] ((:post-execute-fn opts) (.getUpdateCount this) opts)]
(let [[n _] ((:post-execute-fn opts vector) (.getUpdateCount this) opts)]
[{:next.jdbc/update-count n}])))
JdbcMiddleware ; this unrolls one layer of middleware, combining the options
(wrapped-execute [this sql-params opts]
(p/-execute this sql-params (merge (:global-opts this) opts)))
(wrapped-execute-one [this sql-params opts]
(p/-execute-one this sql-params (merge (:global-opts this) opts)))
(wrapped-execute-all [this sql-params opts]
(p/-execute-all this sql-params (merge (:global-opts this) opts)))
Object
(wrapped-execute [this sql-params opts]
(wrapped-execute (p/get-datasource this) sql-params opts))
@ -237,27 +266,6 @@
(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]
p/Executable
(-execute [this sql-params opts]
(execute-wrapper wrapped-execute db global-opts sql-params opts))
(-execute-one [this sql-params opts]
(execute-wrapper wrapped-execute-one db global-opts sql-params opts))
(-execute-all [this sql-params opts]
(execute-wrapper wrapped-execute-all db global-opts sql-params opts)))
(defn wrapper
"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

View file

@ -110,3 +110,16 @@
(is (= 3 @seen-rs))
(is (= 5 @rows))
(is (= 2 @rss))))
;; does middleware compose?
(deftest middleware-composition
(let [pre (atom 0)
post (atom 0)
rows (atom 0)
inner (mw/wrapper (ds) {:pre-execute-fn #(do (swap! pre inc) [%1 %2])})
mw-ds (mw/wrapper inner {:post-execute-fn #(do (swap! post inc) [%1 %2])})]
(jdbc/execute! mw-ds ["select * from fruit"]
{:row!-fn (fn [row _] (swap! rows inc) row)})
(is (= 1 @pre))
(is (= 1 @post))
(is (= 4 @rows))))