diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1913f8..cd1adf65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. This change ### Changed - Remove unnecessary locks in read transaction - Improved error message and documentation for managing LMDB connection +### Added +- `core/get-conn` and `core/with-conn` ## 0.3.8 ### Fixed diff --git a/src/datalevin/core.cljc b/src/datalevin/core.cljc index ceed333b..4194b70b 100644 --- a/src/datalevin/core.cljc +++ b/src/datalevin/core.cljc @@ -370,13 +370,14 @@ :cljs (satisfies? cljs.core/IDeref conn)) (db/db? @conn))) +(defprotocol ConnClosable + (close [this])) (defn conn-from-db "Creates a mutable reference to a given database. See [[create-conn]]." [db] (atom db :meta { :listeners (atom {}) })) - (defn conn-from-datoms "Creates an empty DB and a mutable reference to it. See [[create-conn]]." ([datoms] (conn-from-db (init-db datoms))) @@ -389,7 +390,7 @@ Please note that the connection should be managed like a stateful resource. Application should hold on to the same connection rather than opening multiple connections to the same database in the same process. - Connections are lightweight in-memory structures (~atoms). See also [[transact!]], [[db]], [[close]], and [[lmdb/open-lmdb]]. + Connections are lightweight in-memory structures (~atoms). See also [[transact!]], [[db]], [[close]], [[get-conn]], and [[lmdb/open-lmdb]]. To access underlying DB, deref: `@conn`. @@ -637,6 +638,60 @@ :rschema (db/rschema s)))) (schema conn))) +(defonce ^:private connections (atom {})) + +(defn- add-conn [dir conn] (swap! connections assoc dir conn)) + +(defn- new-conn + [dir] + (let [conn (create-conn dir)] + (add-conn dir conn) + conn)) + +(defn get-conn + "Obtain an open connection to a database. Create the database if it does not + exist. Reuse the same connection if a connection to the same database already + exists. Open the database if it is closed. Return the connection. + + See also [[create-conn]] and [[with-conn]]" + ([dir] + (get-conn dir nil)) + ([dir schema] + (let [conn (if-let [c (get @connections dir)] + (if (closed? c) (new-conn dir) c) + (new-conn dir))] + (when schema (update-schema conn schema)) + conn))) + +(defmacro with-conn + "Evaluate body in the context of an connection to the database. + + If the database does not exist, this will create it. If it is closed, + this will open it. However, the connection will be closed in the end of + this call. If a database needs to be kept open, use `create-conn` and + hold onto the returned connection. See also [[create-conn]] and [[get-conn]] + + `spec` is a vector of an identifier of the database connection, a data path + string, and optionally a schema map. + + Example: + + (with-conn [conn \"my-data-path\"] + ...conn...) + + (with-conn [conn \"my-data-path\" {:likes {:db/cardinality :db.cardinality/many}}] + ...conn...) + " + [spec & body] + `(let [dir# ~(second spec) + schema# ~(second (rest spec)) + conn# (get-conn dir#)] + (when schema# (update-schema conn# schema#)) + (try + (let [~(first spec) conn#] ~@body) + (finally + (close conn#))))) + (defn transact "Same as [[transact!]], but returns an immediately realized future. diff --git a/test/datalevin/test/conn.cljc b/test/datalevin/test/conn.cljc index 669c5d33..9171225d 100644 --- a/test/datalevin/test/conn.cljc +++ b/test/datalevin/test/conn.cljc @@ -72,3 +72,10 @@ :name "Another name" :dt/updated-at (Date.)}]) (is (= 4 (count (d/datoms @conn2 :eavt))))))) + +(deftest test-with-conn + (d/with-conn [conn (u/tmp-dir (str "with-conn-test-" (UUID/randomUUID)))] + (d/transact! conn [{:db/id -1 + :name "something" + :updated-at (Date.)}]) + (is (= 2 (count (d/datoms @conn :eav))))))