Document current state of experiment

and my unhappiness with it!
This commit is contained in:
Sean Corfield 2019-11-10 17:53:23 -08:00
parent ce00025c3d
commit e9b7ee80ab
3 changed files with 62 additions and 32 deletions

View file

@ -1,6 +1,34 @@
;; copyright (c) 2019 world singles networks llc ;; copyright (c) 2019 Sean Corfield, all rights reserved
(ns next.jdbc.middleware (ns next.jdbc.middleware
"This is just an experimental sketch of what it might look like to be
able to provide middleware that can wrap SQL execution in a way that
behavior can be extended in interesting ways, to support logging, timing.
and other cross-cutting things.
Since it's just an experiment, there's no guarantee that this -- or
anything like it -- will actually end up in a next.jdbc release. You've
been warned!
So far these execution points can be hooked into:
* start -- pre-process the SQL & parameters and options
* (execute SQL)
* ????? -- process the options (and something else?)
* row -- post-process each row and options
* rs -- post-process the whole result set and options
For the rows and result set, it's 'obvious' that the functions should
take the values and return them (or updated versions). For the start
function with SQL & parameters, it also makes sense to take and return
that vector.
For timing middleware, you'd need to pass data through the call chain
somehow -- unless you control the whole middleware and this isn't sufficient
for that yet. Hence the decision to allow processing of the options and
passing data through those -- which leads to a rather odd call chain:
start can return the vector or a map of updated options (with a payload),
and the ????? point can process the options again (e.g., to update timing
data etc). And that's all kind of horrible."
(:require [next.jdbc.protocols :as p] (:require [next.jdbc.protocols :as p]
[next.jdbc.result-set :as rs])) [next.jdbc.result-set :as rs]))
@ -13,6 +41,8 @@
and parameters, in case post-processing needs it): and parameters, in case post-processing needs it):
* `:execute-fn` -- called immediately after the SQL operation completes * `:execute-fn` -- called immediately after the SQL operation completes
^ This is a horrible name and it needs to return the options which
is weird so I don't like this approach overall...
* `:row!-fn` -- called on each row as it is fully-realized * `:row!-fn` -- called on each row as it is fully-realized
* `:rs!-fn` -- called on the whole result set once it is fully-realized * `:rs!-fn` -- called on the whole result set once it is fully-realized
@ -20,8 +50,9 @@
[builder-fn] [builder-fn]
(fn [rs opts] (fn [rs opts]
(let [id2 (fn [x _] x) (let [id2 (fn [x _] x)
exec-fn (get opts :execute-fn id2) id2' (fn [_ x] x)
opts (exec-fn opts {}) exec-fn (get opts :execute-fn id2')
opts (exec-fn rs opts)
mrsb (builder-fn rs opts) mrsb (builder-fn rs opts)
row!-fn (get opts :row!-fn id2) row!-fn (get opts :row!-fn id2)
rs!-fn (get opts :rs!-fn id2)] rs!-fn (get opts :rs!-fn id2)]
@ -43,40 +74,40 @@
id2 (fn [x _] x) id2 (fn [x _] x)
builder-fn (get opts :builder-fn rs/as-maps) builder-fn (get opts :builder-fn rs/as-maps)
sql-params-fn (get opts :sql-params-fn id2) sql-params-fn (get opts :sql-params-fn id2)
result (sql-params-fn sql-params opts)] result (sql-params-fn sql-params opts)
(p/-execute db sql-params' (if (map? result)
(if (map? result) (or (:next.jdbc/sql-params result) sql-params)
(or (:next.jdbc/sql-params result) sql-params) result)]
result) (p/-execute db sql-params'
(assoc (if (map? result) result opts) (assoc (if (map? result) result opts)
:builder-fn :builder-fn (post-processing-adapter 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) (let [opts (merge global-opts opts)
id2 (fn [x _] x) id2 (fn [x _] x)
builder-fn (get opts :builder-fn rs/as-maps) builder-fn (get opts :builder-fn rs/as-maps)
sql-params-fn (get opts :sql-params-fn id2) sql-params-fn (get opts :sql-params-fn id2)
result (sql-params-fn sql-params opts)] result (sql-params-fn sql-params opts)
(p/-execute-one db sql-params' (if (map? result)
(if (map? result) (or (:next.jdbc/sql-params result) sql-params)
(or (:next.jdbc/sql-params result) sql-params) result)]
result) (p/-execute-one db sql-params'
(assoc (if (map? result) result opts) (assoc (if (map? result) result opts)
:builder-fn :builder-fn (post-processing-adapter 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) (let [opts (merge global-opts opts)
id2 (fn [x _] x) id2 (fn [x _] x)
builder-fn (get opts :builder-fn rs/as-maps) builder-fn (get opts :builder-fn rs/as-maps)
sql-params-fn (get opts :sql-params-fn id2) sql-params-fn (get opts :sql-params-fn id2)
result (sql-params-fn sql-params opts)] result (sql-params-fn sql-params opts)
(p/-execute-all db sql-params' (if (map? result)
(if (map? result) (or (:next.jdbc/sql-params result) sql-params)
(or (:next.jdbc/sql-params result) sql-params) result)]
result) (p/-execute-all db sql-params'
(assoc (if (map? result) result opts) (assoc (if (map? result) result opts)
:builder-fn :builder-fn (post-processing-adapter builder-fn)
(post-processing-adapter builder-fn)))))) :next.jdbc/sql-params sql-params')))))
(defn wrapper (defn wrapper
"" ""

View file

@ -1,4 +1,4 @@
;; copyright (c) 2019 world singles networks llc ;; copyright (c) 2019 Sean Corfield, all rights reserved
(ns next.jdbc.middleware-test (ns next.jdbc.middleware-test
(:require [clojure.string :as str] (:require [clojure.string :as str]
@ -22,6 +22,7 @@
(deftest logging-test (deftest logging-test
(let [logging (atom []) (let [logging (atom [])
logger (fn [data _] (swap! logging conj data) data) logger (fn [data _] (swap! logging conj data) data)
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)) (jdbc/execute! (mw/wrapper (ds))
sql-p sql-p
@ -60,7 +61,7 @@
start-fn (fn [sql-p opts] start-fn (fn [sql-p opts]
(swap! (:timing opts) update :calls inc) (swap! (:timing opts) update :calls inc)
(assoc opts :start (System/nanoTime))) (assoc opts :start (System/nanoTime)))
exec-fn (fn [opts _] exec-fn (fn [_ opts]
(let [end (System/nanoTime)] (let [end (System/nanoTime)]
(swap! (:timing opts) update :total + (- end (:start opts))) (swap! (:timing opts) update :total + (- end (:start opts)))
opts)) opts))

View file

@ -21,7 +21,8 @@
;; this is just a dummy db-spec -- it's handled in with-test-db below ;; this is just a dummy db-spec -- it's handled in with-test-db below
(def ^:private test-postgres {:dbtype "embedded-postgres"}) (def ^:private test-postgres {:dbtype "embedded-postgres"})
(defonce embedded-pg (atom nil)) ;; it takes a while to spin up so we kick it off at startup
(defonce embedded-pg (future (EmbeddedPostgres/start)))
(def ^:private test-db-specs (def ^:private test-db-specs
[test-derby test-h2-mem test-h2 test-hsql test-sqlite test-postgres]) [test-derby test-h2-mem test-h2 test-hsql test-sqlite test-postgres])
@ -56,11 +57,8 @@
(doseq [db test-db-specs] (doseq [db test-db-specs]
(reset! test-db-spec db) (reset! test-db-spec db)
(if (= "embedded-postgres" (:dbtype db)) (if (= "embedded-postgres" (:dbtype db))
(do (reset! test-datasource
(when-not @embedded-pg (.getPostgresDatabase ^EmbeddedPostgres @embedded-pg))
(reset! embedded-pg (EmbeddedPostgres/start)))
(reset! test-datasource
(.getPostgresDatabase ^EmbeddedPostgres @embedded-pg)))
(reset! test-datasource (jdbc/get-datasource db))) (reset! test-datasource (jdbc/get-datasource db)))
(let [auto-inc-pk (let [auto-inc-pk
(cond (or (derby?) (= "hsqldb" (:dbtype db))) (cond (or (derby?) (= "hsqldb" (:dbtype db)))