Recast the adapter/processors as a middleware
Still not happy with this but it seems more "holistic".
This commit is contained in:
parent
b2b7696973
commit
a57011a998
4 changed files with 160 additions and 75 deletions
84
src/next/jdbc/middleware.clj
Normal file
84
src/next/jdbc/middleware.clj
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
;; copyright (c) 2019 world singles networks llc
|
||||||
|
|
||||||
|
(ns next.jdbc.middleware
|
||||||
|
(:require [next.jdbc.protocols :as p]
|
||||||
|
[next.jdbc.result-set :as rs]))
|
||||||
|
|
||||||
|
(defn post-processing-adapter
|
||||||
|
"Given a builder function (e.g., `as-lower-maps`), return a new builder
|
||||||
|
function that post-processes rows and the result set. The options may
|
||||||
|
contain post-processing functions that are called on each row and on the
|
||||||
|
the result set. The options map is provided as a second parameter to these
|
||||||
|
functions, which should include `:next.jdbc/sql-params` (the vector of SQL
|
||||||
|
and parameters, in case post-processing needs it):
|
||||||
|
|
||||||
|
* `:execute-fn` -- called immediately after the SQL operation completes
|
||||||
|
* `:row!-fn` -- called on each row as it is fully-realized
|
||||||
|
* `:rs!-fn` -- called on the whole result set once it is fully-realized
|
||||||
|
|
||||||
|
The results of these functions are returned as the rows/result set."
|
||||||
|
[builder-fn]
|
||||||
|
(fn [rs opts]
|
||||||
|
(let [id2 (fn [x _] x)
|
||||||
|
exec-fn (get opts :execute-fn id2)
|
||||||
|
opts (exec-fn opts {})
|
||||||
|
mrsb (builder-fn rs opts)
|
||||||
|
row!-fn (get opts :row!-fn id2)
|
||||||
|
rs!-fn (get opts :rs!-fn id2)]
|
||||||
|
(reify
|
||||||
|
rs/RowBuilder
|
||||||
|
(->row [this] (rs/->row mrsb))
|
||||||
|
(column-count [this] (rs/column-count mrsb))
|
||||||
|
(with-column [this row i] (rs/with-column mrsb row i))
|
||||||
|
(row! [this row] (row!-fn (rs/row! mrsb row) opts))
|
||||||
|
rs/ResultSetBuilder
|
||||||
|
(->rs [this] (rs/->rs mrsb))
|
||||||
|
(with-row [this mrs row] (rs/with-row mrsb mrs row))
|
||||||
|
(rs! [this mrs] (rs!-fn (rs/rs! mrsb mrs) opts))))))
|
||||||
|
|
||||||
|
(defrecord JdbcMiddleware [db global-opts]
|
||||||
|
p/Executable
|
||||||
|
(-execute [this sql-params opts]
|
||||||
|
(let [opts (merge global-opts opts)
|
||||||
|
id2 (fn [x _] x)
|
||||||
|
builder-fn (get opts :builder-fn rs/as-maps)
|
||||||
|
sql-params-fn (get opts :sql-params-fn id2)
|
||||||
|
result (sql-params-fn sql-params opts)]
|
||||||
|
(p/-execute db
|
||||||
|
(if (map? result)
|
||||||
|
(or (:next.jdbc/sql-params result) sql-params)
|
||||||
|
result)
|
||||||
|
(assoc (if (map? result) result opts)
|
||||||
|
:builder-fn
|
||||||
|
(post-processing-adapter builder-fn)))))
|
||||||
|
(-execute-one [this sql-params opts]
|
||||||
|
(let [opts (merge global-opts opts)
|
||||||
|
id2 (fn [x _] x)
|
||||||
|
builder-fn (get opts :builder-fn rs/as-maps)
|
||||||
|
sql-params-fn (get opts :sql-params-fn id2)
|
||||||
|
result (sql-params-fn sql-params opts)]
|
||||||
|
(p/-execute-one db
|
||||||
|
(if (map? result)
|
||||||
|
(or (:next.jdbc/sql-params result) sql-params)
|
||||||
|
result)
|
||||||
|
(assoc (if (map? result) result opts)
|
||||||
|
:builder-fn
|
||||||
|
(post-processing-adapter builder-fn)))))
|
||||||
|
(-execute-all [this sql-params opts]
|
||||||
|
(let [opts (merge global-opts opts)
|
||||||
|
id2 (fn [x _] x)
|
||||||
|
builder-fn (get opts :builder-fn rs/as-maps)
|
||||||
|
sql-params-fn (get opts :sql-params-fn id2)
|
||||||
|
result (sql-params-fn sql-params opts)]
|
||||||
|
(p/-execute-all db
|
||||||
|
(if (map? result)
|
||||||
|
(or (:next.jdbc/sql-params result) sql-params)
|
||||||
|
result)
|
||||||
|
(assoc (if (map? result) result opts)
|
||||||
|
:builder-fn
|
||||||
|
(post-processing-adapter builder-fn))))))
|
||||||
|
|
||||||
|
(defn wrapper
|
||||||
|
""
|
||||||
|
([db] (JdbcMiddleware. db {}))
|
||||||
|
([db opts] (JdbcMiddleware. db opts)))
|
||||||
|
|
@ -356,45 +356,6 @@
|
||||||
(with-row [this mrs row] (with-row arsb mrs row))
|
(with-row [this mrs row] (with-row arsb mrs row))
|
||||||
(rs! [this mrs] (rs! arsb mrs))))))
|
(rs! [this mrs] (rs! arsb mrs))))))
|
||||||
|
|
||||||
(defn builder-adapter
|
|
||||||
"Given a builder function (e.g., `as-lower-maps`) and a hash map of
|
|
||||||
(optional) processing functions, return a new builder function that
|
|
||||||
applies those functions as follows, _after_ the SQL operation completes:
|
|
||||||
|
|
||||||
* `:sql-params-fn` -- called on the value of `:next.jdbc/sql-params`
|
|
||||||
(which is the vector of SQL and parameters passed to `plan`, `execute!`,
|
|
||||||
or `execute-one!` that was just completed)
|
|
||||||
* `:row!-fn` -- called on each row once it is fully-realized
|
|
||||||
* `:rs!-fn` -- called on the whole result set once it is fully-realized
|
|
||||||
|
|
||||||
These functions are assumed to be side-effecting and their result is
|
|
||||||
ignored."
|
|
||||||
[builder-fn processors]
|
|
||||||
(fn [rs opts]
|
|
||||||
(when-let [f (:sql-params-fn processors)]
|
|
||||||
(when-let [sql-params (:next.jdbc/sql-params opts)]
|
|
||||||
(f sql-params)))
|
|
||||||
(let [mrsb (builder-fn rs opts)]
|
|
||||||
(reify
|
|
||||||
RowBuilder
|
|
||||||
(->row [this] (->row mrsb))
|
|
||||||
(column-count [this] (column-count mrsb))
|
|
||||||
(with-column [this row i] (with-column mrsb row i))
|
|
||||||
(row! [this row]
|
|
||||||
(let [r (row! mrsb row)]
|
|
||||||
(when-let [f (:row!-fn processors)]
|
|
||||||
(f r))
|
|
||||||
r))
|
|
||||||
ResultSetBuilder
|
|
||||||
(->rs [this] (->rs mrsb))
|
|
||||||
(with-row [this mrs row] (with-row mrsb mrs row))
|
|
||||||
(rs! [this mrs]
|
|
||||||
(let [r (rs! mrsb mrs)]
|
|
||||||
(when-let [f (:rs!-fn processors)]
|
|
||||||
(f r))
|
|
||||||
r))))))
|
|
||||||
|
|
||||||
|
|
||||||
(declare navize-row)
|
(declare navize-row)
|
||||||
|
|
||||||
(defprotocol DatafiableRow
|
(defprotocol DatafiableRow
|
||||||
|
|
|
||||||
76
test/next/jdbc/middleware_test.clj
Normal file
76
test/next/jdbc/middleware_test.clj
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
;; copyright (c) 2019 world singles networks llc
|
||||||
|
|
||||||
|
(ns next.jdbc.middleware-test
|
||||||
|
(:require [clojure.string :as str]
|
||||||
|
[clojure.test :refer [deftest is testing use-fixtures]]
|
||||||
|
[next.jdbc :as jdbc]
|
||||||
|
[next.jdbc.connection :as c]
|
||||||
|
[next.jdbc.middleware :as mw]
|
||||||
|
[next.jdbc.test-fixtures :refer [with-test-db db ds
|
||||||
|
derby? postgres?]]
|
||||||
|
[next.jdbc.prepare :as prep]
|
||||||
|
[next.jdbc.result-set :as rs]
|
||||||
|
[next.jdbc.specs :as specs])
|
||||||
|
(:import (java.sql ResultSet ResultSetMetaData)))
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(use-fixtures :once with-test-db)
|
||||||
|
|
||||||
|
(specs/instrument)
|
||||||
|
|
||||||
|
(deftest logging-test
|
||||||
|
(let [logging (atom [])
|
||||||
|
logger (fn [data _] (swap! logging conj data) data)
|
||||||
|
sql-p ["select * from fruit where id in (?,?) order by id desc" 1 4]]
|
||||||
|
(jdbc/execute! (mw/wrapper (ds))
|
||||||
|
sql-p
|
||||||
|
{:builder-fn rs/as-lower-maps
|
||||||
|
:sql-params-fn logger
|
||||||
|
:row!-fn logger
|
||||||
|
:rs!-fn logger})
|
||||||
|
;; should log four things
|
||||||
|
(is (= 4 (-> @logging count)))
|
||||||
|
;; :next.jdbc/sql-params value
|
||||||
|
(is (= sql-p (-> @logging (nth 0))))
|
||||||
|
;; first row (with PK 4)
|
||||||
|
(is (= 4 (-> @logging (nth 1) :fruit/id)))
|
||||||
|
;; second row (with PK 1)
|
||||||
|
(is (= 1 (-> @logging (nth 2) :fruit/id)))
|
||||||
|
;; full result set with two rows
|
||||||
|
(is (= 2 (-> @logging (nth 3) count)))
|
||||||
|
(is (= [4 1] (-> @logging (nth 3) (->> (map :fruit/id)))))
|
||||||
|
;; now repeat without the row logging
|
||||||
|
(reset! logging [])
|
||||||
|
(jdbc/execute! (mw/wrapper (ds)
|
||||||
|
{:builder-fn rs/as-lower-maps
|
||||||
|
:sql-params-fn logger
|
||||||
|
:rs!-fn logger})
|
||||||
|
sql-p)
|
||||||
|
;; should log two things
|
||||||
|
(is (= 2 (-> @logging count)))
|
||||||
|
;; :next.jdbc/sql-params value
|
||||||
|
(is (= sql-p (-> @logging (nth 0))))
|
||||||
|
;; full result set with two rows
|
||||||
|
(is (= 2 (-> @logging (nth 1) count)))
|
||||||
|
(is (= [4 1] (-> @logging (nth 1) (->> (map :fruit/id)))))))
|
||||||
|
|
||||||
|
(deftest timing-test
|
||||||
|
(let [timing (atom {:calls 0 :total 0.0})
|
||||||
|
start-fn (fn [sql-p opts]
|
||||||
|
(swap! (:timing opts) update :calls inc)
|
||||||
|
(assoc opts :start (System/nanoTime)))
|
||||||
|
exec-fn (fn [opts _]
|
||||||
|
(let [end (System/nanoTime)]
|
||||||
|
(swap! (:timing opts) update :total + (- end (:start opts)))
|
||||||
|
opts))
|
||||||
|
sql-p ["select * from fruit where id in (?,?) order by id desc" 1 4]]
|
||||||
|
(jdbc/execute! (mw/wrapper (ds) {:timing timing
|
||||||
|
:sql-params-fn start-fn
|
||||||
|
:execute-fn exec-fn})
|
||||||
|
sql-p)
|
||||||
|
(jdbc/execute! (mw/wrapper (ds) {:timing timing
|
||||||
|
:sql-params-fn start-fn
|
||||||
|
:execute-fn exec-fn})
|
||||||
|
sql-p)
|
||||||
|
(println (:calls @timing) (long (:total @timing)))))
|
||||||
|
|
@ -220,39 +220,3 @@ VALUES ('Pear', 'green', 49, 47)
|
||||||
(into [] (map pr-str) (jdbc/plan (ds) ["select * from fruit"]))))
|
(into [] (map pr-str) (jdbc/plan (ds) ["select * from fruit"]))))
|
||||||
(is (thrown? IllegalArgumentException
|
(is (thrown? IllegalArgumentException
|
||||||
(doall (take 3 (jdbc/plan (ds) ["select * from fruit"]))))))
|
(doall (take 3 (jdbc/plan (ds) ["select * from fruit"]))))))
|
||||||
|
|
||||||
(deftest adapter-side-effects
|
|
||||||
(let [logging (atom [])
|
|
||||||
logger (fn [data] (swap! logging conj data))
|
|
||||||
sql-p ["select * from fruit where id in (?,?) order by id desc" 1 4]]
|
|
||||||
(jdbc/execute! (ds) sql-p
|
|
||||||
{:builder-fn (rs/builder-adapter
|
|
||||||
rs/as-lower-maps
|
|
||||||
{:sql-params-fn logger
|
|
||||||
:row!-fn logger
|
|
||||||
:rs!-fn logger})})
|
|
||||||
;; should log four things
|
|
||||||
(is (= 4 (-> @logging count)))
|
|
||||||
;; :next.jdbc/sql-params value
|
|
||||||
(is (= sql-p (-> @logging (nth 0))))
|
|
||||||
;; first row (with PK 4)
|
|
||||||
(is (= 4 (-> @logging (nth 1) :fruit/id)))
|
|
||||||
;; second row (with PK 1)
|
|
||||||
(is (= 1 (-> @logging (nth 2) :fruit/id)))
|
|
||||||
;; full result set with two rows
|
|
||||||
(is (= 2 (-> @logging (nth 3) count)))
|
|
||||||
(is (= [4 1] (-> @logging (nth 3) (->> (map :fruit/id)))))
|
|
||||||
;; now repeat without the row logging
|
|
||||||
(reset! logging [])
|
|
||||||
(jdbc/execute! (ds) sql-p
|
|
||||||
{:builder-fn (rs/builder-adapter
|
|
||||||
rs/as-lower-maps
|
|
||||||
{:sql-params-fn logger
|
|
||||||
:rs!-fn logger})})
|
|
||||||
;; should log two things
|
|
||||||
(is (= 2 (-> @logging count)))
|
|
||||||
;; :next.jdbc/sql-params value
|
|
||||||
(is (= sql-p (-> @logging (nth 0))))
|
|
||||||
;; full result set with two rows
|
|
||||||
(is (= 2 (-> @logging (nth 1) count)))
|
|
||||||
(is (= [4 1] (-> @logging (nth 1) (->> (map :fruit/id)))))))
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue