continuing deferred thought experiment
Signed-off-by: Sean Corfield <sean@corfield.org>
This commit is contained in:
parent
eb7dafbe58
commit
87ea23271e
1 changed files with 85 additions and 27 deletions
|
|
@ -10,23 +10,31 @@
|
||||||
and next.jdbc.sql API that produces a data structure that
|
and next.jdbc.sql API that produces a data structure that
|
||||||
describes a series of SQL operations to be performed, that
|
describes a series of SQL operations to be performed, that
|
||||||
are held in a dynamic var, and that can be executed at a
|
are held in a dynamic var, and that can be executed at a
|
||||||
later time, in a transaction.
|
later time, in a transaction."
|
||||||
|
(:require [clojure.test :refer [deftest is testing use-fixtures]]
|
||||||
(with-deferred
|
|
||||||
(insert! :foo {:name \"Sean\"})
|
|
||||||
(insert! :bar {:foo_id (deferred-key :foo) :value 42})
|
|
||||||
(insert! :baz {:bar_id (deferred-key :bar) :value 99})
|
|
||||||
(update! :foo {:id (deferred-key :foo)} {:name \"Sean Corfield\"})
|
|
||||||
(delete! :foo {:id (deferred-key :foo)})
|
|
||||||
(delete! :bar {:foo_id (deferred-key :foo)})
|
|
||||||
(delete! :baz {:bar_id (deferred-key :bar)}))
|
|
||||||
"
|
|
||||||
(:require [clojure.test :refer [deftest is testing]]
|
|
||||||
[next.jdbc :as jdbc]
|
[next.jdbc :as jdbc]
|
||||||
[next.jdbc.sql.builder :refer [for-insert]]))
|
[next.jdbc.sql.builder :refer [for-delete for-insert for-update]]
|
||||||
|
[next.jdbc.test-fixtures
|
||||||
|
:refer [ds with-test-db]]))
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(use-fixtures :once with-test-db)
|
||||||
|
|
||||||
(def ^:private ^:dynamic *deferred* nil)
|
(def ^:private ^:dynamic *deferred* nil)
|
||||||
|
|
||||||
|
(defn execute-one!
|
||||||
|
"Given a vector containing a SQL statement and parameters, defer
|
||||||
|
execution of that statement."
|
||||||
|
([sql-p]
|
||||||
|
(execute-one! sql-p {}))
|
||||||
|
([sql-p opts]
|
||||||
|
(swap! *deferred* conj
|
||||||
|
{:sql-p sql-p
|
||||||
|
:key-fn (or (:key-fn opts) (comp first vals))
|
||||||
|
:key (:key opts)
|
||||||
|
:opts opts})))
|
||||||
|
|
||||||
(defn insert!
|
(defn insert!
|
||||||
"Given a table name, and a data hash map, defer an insertion of the
|
"Given a table name, and a data hash map, defer an insertion of the
|
||||||
data as a single row in the database."
|
data as a single row in the database."
|
||||||
|
|
@ -39,10 +47,32 @@
|
||||||
:key (:key opts)
|
:key (:key opts)
|
||||||
:opts opts})))
|
:opts opts})))
|
||||||
|
|
||||||
(defn deferable [transactable stmts]
|
(defn update!
|
||||||
|
"Given a table name, a hash map of columns and values to set, and
|
||||||
|
either a hash map of columns and values to search on or a vector
|
||||||
|
of a SQL where clause and parameters, defer an update on the table."
|
||||||
|
([table key-map where-params]
|
||||||
|
(update! table key-map where-params {}))
|
||||||
|
([table key-map where-params opts]
|
||||||
|
(swap! *deferred* conj
|
||||||
|
{:sql-p (for-update table key-map where-params opts)
|
||||||
|
:opts opts})))
|
||||||
|
|
||||||
|
(defn delete!
|
||||||
|
"Given a table name, and either a hash map of columns and values
|
||||||
|
to search on or a vector of a SQL where clause and parameters,
|
||||||
|
defer a delete on the table."
|
||||||
|
([table where-params]
|
||||||
|
(delete! table where-params {}))
|
||||||
|
([table where-params opts]
|
||||||
|
(swap! *deferred* conj
|
||||||
|
{:sql-p (for-delete table where-params opts)
|
||||||
|
:opts opts})))
|
||||||
|
|
||||||
|
(defn deferrable [transactable stmts]
|
||||||
(reify clojure.lang.IDeref
|
(reify clojure.lang.IDeref
|
||||||
(deref [_]
|
(deref [_]
|
||||||
(let [keys (atom {})]
|
(let [keys (atom {})]
|
||||||
(jdbc/with-transaction [conn transactable]
|
(jdbc/with-transaction [conn transactable]
|
||||||
(doseq [{:keys [sql-p key-fn key opts]} @stmts]
|
(doseq [{:keys [sql-p key-fn key opts]} @stmts]
|
||||||
(let [sql-p
|
(let [sql-p
|
||||||
|
|
@ -54,19 +84,47 @@
|
||||||
{:key v})))
|
{:key v})))
|
||||||
v))
|
v))
|
||||||
sql-p)
|
sql-p)
|
||||||
result (jdbc/execute! conn sql-p opts)]
|
result (jdbc/execute-one! conn sql-p opts)]
|
||||||
(when key
|
(when key
|
||||||
(swap! keys assoc key (key-fn result))))))))))
|
(swap! keys assoc key (key-fn result))))))
|
||||||
|
@keys))))
|
||||||
|
|
||||||
(defmacro with-deferred [[conn connectable] & body]
|
(defmacro defer-ops [& body]
|
||||||
`(let [~conn ~connectable]
|
`(binding [*deferred* (atom [])]
|
||||||
(deferrable ~conn (binding [*deferred* (atom [])] (do ~@body) *deferred*))))
|
(do ~@body)
|
||||||
|
*deferred*))
|
||||||
|
|
||||||
|
(defmacro with-deferred [connectable & body]
|
||||||
|
`(let [conn# ~connectable]
|
||||||
|
(deferrable conn# (defer-ops ~@body))))
|
||||||
|
|
||||||
(deftest basic-test
|
(deftest basic-test
|
||||||
(is (= [{:sql-p ["INSERT INTO foo (name) VALUES (?)" "Sean"]
|
(testing "data structures"
|
||||||
:key-fn :GENERATED_KEY
|
(is (= [{:sql-p ["INSERT INTO foo (name) VALUES (?)" "Sean"]
|
||||||
:key :id
|
:key-fn :GENERATED_KEY
|
||||||
:opts {:key-fn :GENERATED_KEY :key :id}}]
|
:key :id
|
||||||
(binding [*deferred* (atom [])]
|
:opts {:key-fn :GENERATED_KEY :key :id}}]
|
||||||
(insert! :foo {:name "Sean"} {:key-fn :GENERATED_KEY :key :id})
|
@(defer-ops
|
||||||
@*deferred*))))
|
(insert! :foo {:name "Sean"} {:key-fn :GENERATED_KEY :key :id})))))
|
||||||
|
(testing "execution"
|
||||||
|
(let [effects (with-deferred (ds)
|
||||||
|
(insert! :fruit {:name "Mango"} {:key :test}))]
|
||||||
|
(is (= {:test 1} @effects))
|
||||||
|
(is (= 1 (count (jdbc/execute! (ds)
|
||||||
|
["select * from fruit where name = ?"
|
||||||
|
"Mango"])))))
|
||||||
|
(let [effects (with-deferred (ds)
|
||||||
|
(insert! :fruit {:name "Dragonfruit"} {:key :test})
|
||||||
|
(update! :fruit {:cost 123} {:name "Dragonfruit"})
|
||||||
|
(delete! :fruit {:name "Dragonfruit"}))]
|
||||||
|
(is (= {:test 1} @effects))
|
||||||
|
(is (= 0 (count (jdbc/execute! (ds)
|
||||||
|
["select * from fruit where name = ?"
|
||||||
|
"Dragonfruit"])))))
|
||||||
|
(let [effects (with-deferred (ds)
|
||||||
|
(insert! :fruit {:name "Grapefruit" :bad_column 0} {:key :test}))]
|
||||||
|
(is (= :failed (try @effects
|
||||||
|
(catch Exception _ :failed))))
|
||||||
|
(is (= 0 (count (jdbc/execute! (ds)
|
||||||
|
["select * from fruit where name = ?"
|
||||||
|
"Grapefruit"])))))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue