continuing deferred thought experiment

Signed-off-by: Sean Corfield <sean@corfield.org>
This commit is contained in:
Sean Corfield 2024-05-09 00:44:34 -07:00
parent eb7dafbe58
commit 87ea23271e
No known key found for this signature in database

View file

@ -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,7 +47,29 @@
: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 {})]
@ -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
(testing "data structures"
(is (= [{:sql-p ["INSERT INTO foo (name) VALUES (?)" "Sean"] (is (= [{:sql-p ["INSERT INTO foo (name) VALUES (?)" "Sean"]
:key-fn :GENERATED_KEY :key-fn :GENERATED_KEY
:key :id :key :id
:opts {:key-fn :GENERATED_KEY :key :id}}] :opts {:key-fn :GENERATED_KEY :key :id}}]
(binding [*deferred* (atom [])] @(defer-ops
(insert! :foo {:name "Sean"} {:key-fn :GENERATED_KEY :key :id}) (insert! :foo {:name "Sean"} {:key-fn :GENERATED_KEY :key :id})))))
@*deferred*)))) (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"])))))))