Pre: Use deftest-db in tests.

This commit is contained in:
Nick Alexander 2016-09-02 13:51:51 -07:00
parent 8e8dd21164
commit cfdce12c1a
2 changed files with 400 additions and 501 deletions

View file

@ -97,7 +97,6 @@
new))] new))]
(let [exec (partial s/execute! (:sqlite-connection db)) (let [exec (partial s/execute! (:sqlite-connection db))
part->vector (fn [[part {:keys [start idx]}]] part->vector (fn [[part {:keys [start idx]}]]
(println "part->vector" part start idx)
[(sqlite-schema/->SQLite part) start idx])] [(sqlite-schema/->SQLite part) start idx])]
;; TODO: allow inserting new parts. ;; TODO: allow inserting new parts.
;; TODO: think more carefully about allocating new parts and bitmasking part ranges. ;; TODO: think more carefully about allocating new parts and bitmasking part ranges.

View file

@ -19,12 +19,12 @@
#?@(:clj [[datomish.jdbc-sqlite] #?@(:clj [[datomish.jdbc-sqlite]
[datomish.pair-chan :refer [go-pair <?]] [datomish.pair-chan :refer [go-pair <?]]
[tempfile.core :refer [tempfile with-tempfile]] [tempfile.core :refer [tempfile with-tempfile]]
[datomish.test-macros :refer [deftest-async]] [datomish.test-macros :refer [deftest-async deftest-db]]
[clojure.test :as t :refer [is are deftest testing]] [clojure.test :as t :refer [is are deftest testing]]
[clojure.core.async :refer [go <! >!]]]) [clojure.core.async :refer [go <! >!]]])
#?@(:cljs [[datomish.promise-sqlite] #?@(:cljs [[datomish.promise-sqlite]
[datomish.pair-chan] [datomish.pair-chan]
[datomish.test-macros :refer-macros [deftest-async]] [datomish.test-macros :refer-macros [deftest-async deftest-db]]
[datomish.node-tempfile :refer [tempfile]] [datomish.node-tempfile :refer [tempfile]]
[cljs.test :as t :refer-macros [is are deftest testing async]] [cljs.test :as t :refer-macros [is are deftest testing async]]
[cljs.core.async :as a :refer [<! >!]]])) [cljs.core.async :as a :refer [<! >!]]]))
@ -81,541 +81,445 @@
:db.install/_attribute :db.part/db} :db.install/_attribute :db.part/db}
]) ])
(deftest-async test-add-one (deftest-db test-add-one conn
(with-tempfile [t (tempfile)] (let [{tx0 :tx} (<? (d/<transact! conn test-schema))]
(let [conn (<? (d/<connect t))] (let [{:keys [tx txInstant]} (<? (d/<transact! conn [[:db/add 0 :name "valuex"]]))]
(try (is (= (<? (<datoms-after (d/db conn) tx0))
(let [{tx0 :tx} (<? (d/<transact! conn test-schema))] #{[0 :name "valuex"]}))
(let [{:keys [tx txInstant]} (<? (d/<transact! conn [[:db/add 0 :name "valuex"]]))] (is (= (<? (<transactions-after (d/db conn) tx0))
(is (= (<? (<datoms-after (d/db conn) tx0)) [[0 :name "valuex" tx 1] ;; TODO: true, not 1.
#{[0 :name "valuex"]})) [tx :db/txInstant txInstant tx 1]])))))
(is (= (<? (<transactions-after (d/db conn) tx0))
[[0 :name "valuex" tx 1] ;; TODO: true, not 1.
[tx :db/txInstant txInstant tx 1]]))))
(finally
(<? (d/<close conn)))))))
(deftest-async test-add-two (deftest-db test-add-two conn
(with-tempfile [t (tempfile)] (let [{tx0 :tx} (<? (d/<transact! conn test-schema))
(let [conn (<? (d/<connect t))] {tx1 :tx txInstant1 :txInstant} (<? (d/<transact! conn [[:db/add 1 :name "Ivan"]]))
(try {tx2 :tx txInstant2 :txInstant} (<? (d/<transact! conn [[:db/add 1 :name "Petr"]]))
(let [{tx0 :tx} (<? (d/<transact! conn test-schema)) {tx3 :tx txInstant3 :txInstant} (<? (d/<transact! conn [[:db/add 1 :aka "Tupen"]]))
{tx1 :tx txInstant1 :txInstant} (<? (d/<transact! conn [[:db/add 1 :name "Ivan"]])) {tx4 :tx txInstant4 :txInstant} (<? (d/<transact! conn [[:db/add 1 :aka "Devil"]]))]
{tx2 :tx txInstant2 :txInstant} (<? (d/<transact! conn [[:db/add 1 :name "Petr"]])) (is (= (<? (<datoms-after (d/db conn) tx0))
{tx3 :tx txInstant3 :txInstant} (<? (d/<transact! conn [[:db/add 1 :aka "Tupen"]])) #{[1 :name "Petr"]
{tx4 :tx txInstant4 :txInstant} (<? (d/<transact! conn [[:db/add 1 :aka "Devil"]]))] [1 :aka "Tupen"]
(is (= (<? (<datoms-after (d/db conn) tx0)) [1 :aka "Devil"]}))
#{[1 :name "Petr"]
[1 :aka "Tupen"]
[1 :aka "Devil"]}))
(is (= (<? (<transactions-after (d/db conn) tx0)) (is (= (<? (<transactions-after (d/db conn) tx0))
[[1 :name "Ivan" tx1 1] ;; TODO: true, not 1. [[1 :name "Ivan" tx1 1] ;; TODO: true, not 1.
[tx1 :db/txInstant txInstant1 tx1 1] [tx1 :db/txInstant txInstant1 tx1 1]
[1 :name "Ivan" tx2 0] [1 :name "Ivan" tx2 0]
[1 :name "Petr" tx2 1] [1 :name "Petr" tx2 1]
[tx2 :db/txInstant txInstant2 tx2 1] [tx2 :db/txInstant txInstant2 tx2 1]
[1 :aka "Tupen" tx3 1] [1 :aka "Tupen" tx3 1]
[tx3 :db/txInstant txInstant3 tx3 1] [tx3 :db/txInstant txInstant3 tx3 1]
[1 :aka "Devil" tx4 1] [1 :aka "Devil" tx4 1]
[tx4 :db/txInstant txInstant4 tx4 1]]))) [tx4 :db/txInstant txInstant4 tx4 1]]))))
(finally (deftest-db test-retract conn
(<? (d/<close conn))))))) (let [{tx0 :tx} (<? (d/<transact! conn test-schema))
{tx1 :tx txInstant1 :txInstant} (<? (d/<transact! conn [[:db/add 0 :x 123]]))
{tx2 :tx txInstant2 :txInstant} (<? (d/<transact! conn [[:db/retract 0 :x 123]]))]
(is (= (<? (<datoms-after (d/db conn) tx0))
#{}))
(is (= (<? (<transactions-after (d/db conn) tx0))
[[0 :x 123 tx1 1]
[tx1 :db/txInstant txInstant1 tx1 1]
[0 :x 123 tx2 0]
[tx2 :db/txInstant txInstant2 tx2 1]]))))
(deftest-async test-retract (deftest-db test-id-literal-1 conn
(with-tempfile [t (tempfile)] (let [tx0 (:tx (<? (d/<transact! conn test-schema)))
(let [conn (<? (d/<connect t))] report (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :x 0]
(try [:db/add (d/id-literal :db.part/user -1) :y 1]
(let [{tx0 :tx} (<? (d/<transact! conn test-schema)) [:db/add (d/id-literal :db.part/user -2) :y 2]
{tx1 :tx txInstant1 :txInstant} (<? (d/<transact! conn [[:db/add 0 :x 123]])) [:db/add (d/id-literal :db.part/user -2) :y 3]]))]
{tx2 :tx txInstant2 :txInstant} (<? (d/<transact! conn [[:db/retract 0 :x 123]]))] (is (= (keys (:tempids report)) ;; TODO: include values.
(is (= (<? (<datoms-after (d/db conn) tx0)) [(d/id-literal :db.part/user -1)
#{})) (d/id-literal :db.part/user -2)]))
(is (= (<? (<transactions-after (d/db conn) tx0))
[[0 :x 123 tx1 1]
[tx1 :db/txInstant txInstant1 tx1 1]
[0 :x 123 tx2 0]
[tx2 :db/txInstant txInstant2 tx2 1]])))
(finally
(<? (d/<close conn)))))))
(deftest-async test-id-literal-1 (let [eid1 (get-in report [:tempids (d/id-literal :db.part/user -1)])
(with-tempfile [t (tempfile)] eid2 (get-in report [:tempids (d/id-literal :db.part/user -2)])]
(let [conn (<? (d/<connect t))] (is (= (<? (<datoms-after (d/db conn) tx0))
(try #{[eid1 :x 0]
(let [tx0 (:tx (<? (d/<transact! conn test-schema))) [eid1 :y 1]
report (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :x 0] [eid2 :y 2]
[:db/add (d/id-literal :db.part/user -1) :y 1] [eid2 :y 3]})))))
[:db/add (d/id-literal :db.part/user -2) :y 2]
[:db/add (d/id-literal :db.part/user -2) :y 3]]))]
(is (= (keys (:tempids report)) ;; TODO: include values.
[(d/id-literal :db.part/user -1)
(d/id-literal :db.part/user -2)]))
(let [eid1 (get-in report [:tempids (d/id-literal :db.part/user -1)]) (deftest-db test-unique conn
eid2 (get-in report [:tempids (d/id-literal :db.part/user -2)])] (let [tx0 (:tx (<? (d/<transact! conn test-schema)))]
(is (= (<? (<datoms-after (d/db conn) tx0)) (testing "Multiple :db/unique values in tx-data violate unique constraint, no tempid"
#{[eid1 :x 0] (is (thrown-with-msg?
[eid1 :y 1] ExceptionInfo #"unique constraint"
[eid2 :y 2] (<? (d/<transact! conn [[:db/add 1 :x 0]
[eid2 :y 3]})))) [:db/add 2 :x 0]])))))
(finally (testing "Multiple :db/unique values in tx-data violate unique constraint, tempid"
(<? (d/<close conn))))))) (is (thrown-with-msg?
ExceptionInfo #"unique constraint"
(<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :spouse "Dana"]
[:db/add (d/id-literal :db.part/user -2) :spouse "Dana"]])))))))
(deftest-async test-unique (deftest-db test-valueType-keyword conn
(with-tempfile [t (tempfile)] (let [tx0 (:tx (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1)
(let [conn (<? (d/<connect t))] :db/ident :test/kw
(try :db/unique :db.unique/identity
(let [tx0 (:tx (<? (d/<transact! conn test-schema)))] :db/valueType :db.type/keyword}
(testing "Multiple :db/unique values in tx-data violate unique constraint, no tempid" {:db/id :db.part/db :db.install/attribute (d/id-literal :db.part/user -1)}])))]
(is (thrown-with-msg?
ExceptionInfo #"unique constraint"
(<? (d/<transact! conn [[:db/add 1 :x 0]
[:db/add 2 :x 0]])))))
(testing "Multiple :db/unique values in tx-data violate unique constraint, tempid" (let [report (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :test/kw :test/kw1]]))
(is (thrown-with-msg? eid (get-in report [:tempids (d/id-literal :db.part/user -1)])]
ExceptionInfo #"unique constraint" (is (= (<? (<datoms-after (d/db conn) tx0))
(<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :spouse "Dana"] #{[eid :test/kw ":test/kw1"]})) ;; Value is raw.
[:db/add (d/id-literal :db.part/user -2) :spouse "Dana"]]))))))
(finally (testing "Adding the same value compares existing values correctly."
(<? (d/<close conn))))))) (<? (d/<transact! conn [[:db/add eid :test/kw :test/kw1]]))
(is (= (<? (<datoms-after (d/db conn) tx0))
#{[eid :test/kw ":test/kw1"]}))) ;; Value is raw.
(deftest-async test-valueType-keyword (testing "Upserting retracts existing value correctly."
(with-tempfile [t (tempfile)] (<? (d/<transact! conn [[:db/add eid :test/kw :test/kw2]]))
(let [conn (<? (d/<connect t))] (is (= (<? (<datoms-after (d/db conn) tx0))
(try #{[eid :test/kw ":test/kw2"]}))) ;; Value is raw.
(let [tx0 (:tx (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1)
:db/ident :test/kw
:db/unique :db.unique/identity
:db/valueType :db.type/keyword}
{:db/id :db.part/db :db.install/attribute (d/id-literal :db.part/user -1)}])))]
(let [report (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :test/kw :test/kw1]])) (testing "Retracting compares values correctly."
eid (get-in report [:tempids (d/id-literal :db.part/user -1)])] (<? (d/<transact! conn [[:db/retract eid :test/kw :test/kw2]]))
(is (= (<? (<datoms-after (d/db conn) tx0)) (is (= (<? (<datoms-after (d/db conn) tx0))
#{[eid :test/kw ":test/kw1"]})) ;; Value is raw. #{}))))))
(testing "Adding the same value compares existing values correctly." (deftest-db test-vector-upsert conn
(<? (d/<transact! conn [[:db/add eid :test/kw :test/kw1]])) ;; Not having DB-as-value really hurts us here. This test only works because all upserts
(is (= (<? (<datoms-after (d/db conn) tx0)) ;; succeed on top of each other, so we never need to reset the underlying store.
#{[eid :test/kw ":test/kw1"]}))) ;; Value is raw. (<? (d/<transact! conn test-schema))
(let [tx0 (:tx (<? (d/<transact! conn [{:db/id 101 :name "Ivan" :email "@1"}
{:db/id 102 :name "Petr" :email "@2"}])))]
(testing "Upserting retracts existing value correctly." (testing "upsert with tempid"
(<? (d/<transact! conn [[:db/add eid :test/kw :test/kw2]])) (let [report (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :name "Ivan"]
(is (= (<? (<datoms-after (d/db conn) tx0)) [:db/add (d/id-literal :db.part/user -1) :age 12]]))]
#{[eid :test/kw ":test/kw2"]}))) ;; Value is raw. (is (= (<? (<shallow-entity (d/db conn) 101))
{:name "Ivan" :age 12 :email "@1"}))
(is (= (tempids report)
{-1 101}))))
(testing "Retracting compares values correctly." (testing "upsert with tempid, order does not matter"
(<? (d/<transact! conn [[:db/retract eid :test/kw :test/kw2]])) (let [report (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :age 13]
(is (= (<? (<datoms-after (d/db conn) tx0)) [:db/add (d/id-literal :db.part/user -1) :name "Petr"]]))]
#{}))))) (is (= (<? (<shallow-entity (d/db conn) 102))
{:name "Petr" :age 13 :email "@2"}))
(is (= (tempids report)
{-1 102}))))
(finally (testing "Conflicting upserts fail"
(<? (d/<close conn))))))) (is (thrown-with-msg? Throwable #"Conflicting upsert"
(<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :name "Ivan"]
[:db/add (d/id-literal :db.part/user -1) :age 35]
[:db/add (d/id-literal :db.part/user -1) :name "Petr"]
[:db/add (d/id-literal :db.part/user -1) :age 36]])))))))
(deftest-async test-vector-upsert (deftest-db test-map-upsert conn
(with-tempfile [t (tempfile)] ;; Not having DB-as-value really hurts us here. This test only works because all upserts
(let [conn (<? (d/<connect t))] ;; succeed on top of each other, so we never need to reset the underlying store.
(try (<? (d/<transact! conn test-schema))
;; Not having DB-as-value really hurts us here. This test only works because all upserts (let [tx0 (:tx (<? (d/<transact! conn [{:db/id 101 :name "Ivan" :email "@1"}
;; succeed on top of each other, so we never need to reset the underlying store. {:db/id 102 :name "Petr" :email "@2"}])))]
(<? (d/<transact! conn test-schema))
(let [tx0 (:tx (<? (d/<transact! conn [{:db/id 101 :name "Ivan" :email "@1"}
{:db/id 102 :name "Petr" :email "@2"}])))]
(testing "upsert with tempid" (testing "upsert with tempid"
(let [report (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :name "Ivan"] (let [tx (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :age 35}]))]
[:db/add (d/id-literal :db.part/user -1) :age 12]]))] (is (= (<? (<shallow-entity (d/db conn) 101))
(is (= (<? (<shallow-entity (d/db conn) 101)) {:name "Ivan" :email "@1" :age 35}))
{:name "Ivan" :age 12 :email "@1"})) (is (= (tempids tx)
(is (= (tempids report) {-1 101}))))
{-1 101}))))
(testing "upsert with tempid, order does not matter" (testing "upsert by 2 attrs with tempid"
(let [report (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :age 13] (let [tx (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :email "@1" :age 35}]))]
[:db/add (d/id-literal :db.part/user -1) :name "Petr"]]))] (is (= (<? (<shallow-entity (d/db conn) 101))
(is (= (<? (<shallow-entity (d/db conn) 102)) {:name "Ivan" :email "@1" :age 35}))
{:name "Petr" :age 13 :email "@2"})) (is (= (tempids tx)
(is (= (tempids report) {-1 101}))))
{-1 102}))))
(testing "Conflicting upserts fail" (testing "upsert with existing id"
(is (thrown-with-msg? Throwable #"Conflicting upsert" (let [tx (<? (d/<transact! conn [{:db/id 101 :name "Ivan" :age 36}]))]
(<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :name "Ivan"] (is (= (<? (<shallow-entity (d/db conn) 101))
[:db/add (d/id-literal :db.part/user -1) :age 35] {:name "Ivan" :email "@1" :age 36}))
[:db/add (d/id-literal :db.part/user -1) :name "Petr"] (is (= (tempids tx)
[:db/add (d/id-literal :db.part/user -1) :age 36]])))))) {}))))
(finally
(<? (d/<close conn)))))))
(deftest-async test-map-upsert (testing "upsert by 2 attrs with existing id"
(with-tempfile [t (tempfile)] (let [tx (<? (d/<transact! conn [{:db/id 101 :name "Ivan" :email "@1" :age 37}]))]
(let [conn (<? (d/<connect t))] (is (= (<? (<shallow-entity (d/db conn) 101))
(try {:name "Ivan" :email "@1" :age 37}))
;; Not having DB-as-value really hurts us here. This test only works because all upserts (is (= (tempids tx)
;; succeed on top of each other, so we never need to reset the underlying store. {}))))
(<? (d/<transact! conn test-schema))
(let [tx0 (:tx (<? (d/<transact! conn [{:db/id 101 :name "Ivan" :email "@1"}
{:db/id 102 :name "Petr" :email "@2"}])))]
(testing "upsert with tempid" (testing "upsert to two entities, resolve to same tempid, fails due to overlapping writes"
(let [tx (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :age 35}]))] (is (thrown-with-msg? Throwable #"cardinality constraint"
(is (= (<? (<shallow-entity (d/db conn) 101)) (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :age 35}
{:name "Ivan" :email "@1" :age 35})) {:db/id (d/id-literal :db.part/user -1) :name "Ivan" :age 36}])))))
(is (= (tempids tx)
{-1 101}))))
(testing "upsert by 2 attrs with tempid" (testing "upsert to two entities, two tempids, fails due to overlapping writes"
(let [tx (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :email "@1" :age 35}]))] (is (thrown-with-msg? Throwable #"cardinality constraint"
(is (= (<? (<shallow-entity (d/db conn) 101)) (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :age 35}
{:name "Ivan" :email "@1" :age 35})) {:db/id (d/id-literal :db.part/user -2) :name "Ivan" :age 36}])))))))
(is (= (tempids tx)
{-1 101}))))
(testing "upsert with existing id" (deftest-db test-map-upsert-conflicts conn
(let [tx (<? (d/<transact! conn [{:db/id 101 :name "Ivan" :age 36}]))] ;; Not having DB-as-value really hurts us here. This test only works because all upserts
(is (= (<? (<shallow-entity (d/db conn) 101)) ;; fail until the final one, so we never need to reset the underlying store.
{:name "Ivan" :email "@1" :age 36})) (<? (d/<transact! conn test-schema))
(is (= (tempids tx) (let [tx0 (:tx (<? (d/<transact! conn [{:db/id 101 :name "Ivan" :email "@1"}
{})))) {:db/id 102 :name "Petr" :email "@2"}])))]
(testing "upsert by 2 attrs with existing id" ;; TODO: improve error message to refer to upsert inputs.
(let [tx (<? (d/<transact! conn [{:db/id 101 :name "Ivan" :email "@1" :age 37}]))] (testing "upsert conficts with existing id"
(is (= (<? (<shallow-entity (d/db conn) 101)) (is (thrown-with-msg? Throwable #"unique constraint"
{:name "Ivan" :email "@1" :age 37})) (<? (d/<transact! conn [{:db/id 102 :name "Ivan" :age 36}])))))
(is (= (tempids tx)
{}))))
(testing "upsert to two entities, resolve to same tempid, fails due to overlapping writes" ;; TODO: improve error message to refer to upsert inputs.
(is (thrown-with-msg? Throwable #"cardinality constraint" (testing "upsert conficts with non-existing id"
(<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :age 35} (is (thrown-with-msg? Throwable #"unique constraint"
{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :age 36}]))))) (<? (d/<transact! conn [{:db/id 103 :name "Ivan" :age 36}])))))
(testing "upsert to two entities, two tempids, fails due to overlapping writes" ;; TODO: improve error message to refer to upsert inputs.
(is (thrown-with-msg? Throwable #"cardinality constraint" (testing "upsert by 2 conflicting fields"
(<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :age 35} (is (thrown-with-msg? Throwable #"Conflicting upsert"
{:db/id (d/id-literal :db.part/user -2) :name "Ivan" :age 36}])))))) (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :email "@2" :age 35}])))))
(finally (testing "upsert by non-existing value resolves as update"
(<? (d/<close conn))))))) (let [report (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :email "@3" :age 35}]))]
(is (= (<? (<shallow-entity (d/db conn) 101))
{:name "Ivan" :email "@3" :age 35}))
(is (= (tempids report)
{-1 101}))))))
(deftest-async test-map-upsert-conflicts (deftest-db test-add-ident conn
(with-tempfile [t (tempfile)] (is (= :test/ident (d/entid (d/db conn) :test/ident)))
(let [conn (<? (d/<connect t))]
(try
;; Not having DB-as-value really hurts us here. This test only works because all upserts
;; fail until the final one, so we never need to reset the underlying store.
(<? (d/<transact! conn test-schema))
(let [tx0 (:tx (<? (d/<transact! conn [{:db/id 101 :name "Ivan" :email "@1"}
{:db/id 102 :name "Petr" :email "@2"}])))]
;; TODO: improve error message to refer to upsert inputs. (let [report (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/db -1) :db/ident :test/ident]]))
(testing "upsert conficts with existing id" eid (get-in report [:tempids (d/id-literal :db.part/db -1)])]
(is (thrown-with-msg? Throwable #"unique constraint" (is (= eid (d/entid (d/db conn) :test/ident)))
(<? (d/<transact! conn [{:db/id 102 :name "Ivan" :age 36}]))))) (is (= :test/ident (d/ident (d/db conn) eid))))
;; TODO: improve error message to refer to upsert inputs. ;; TODO: This should fail, but doesn't, due to stringification of :test/ident.
(testing "upsert conficts with non-existing id" ;; (is (thrown-with-msg?
(is (thrown-with-msg? Throwable #"unique constraint" ;; ExceptionInfo #"Retracting a :db/ident is not yet supported, got "
(<? (d/<transact! conn [{:db/id 103 :name "Ivan" :age 36}]))))) ;; (<? (d/<transact! conn [[:db/retract 44 :db/ident :test/ident]]))))
;; TODO: improve error message to refer to upsert inputs. ;; ;; Renaming looks like retraction and then assertion.
(testing "upsert by 2 conflicting fields" ;; (is (thrown-with-msg?
(is (thrown-with-msg? Throwable #"Conflicting upsert" ;; ExceptionInfo #"Retracting a :db/ident is not yet supported, got"
(<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :email "@2" :age 35}]))))) ;; (<? (d/<transact! conn [[:db/add 44 :db/ident :other-name]]))))
(testing "upsert by non-existing value resolves as update" ;; (is (thrown-with-msg?
(let [report (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :name "Ivan" :email "@3" :age 35}]))] ;; ExceptionInfo #"Re-asserting a :db/ident is not yet supported, got"
(is (= (<? (<shallow-entity (d/db conn) 101)) ;; (<? (d/<transact! conn [[:db/add 55 :db/ident :test/ident]]))))
{:name "Ivan" :email "@3" :age 35})) )
(is (= (tempids report)
{-1 101})))))
(finally (deftest-db test-add-schema conn
(<? (d/<close conn))))))) (let [es [[:db/add :db.part/db :db.install/attribute (d/id-literal :db.part/db -1)]
{:db/id (d/id-literal :db.part/db -1)
(deftest-async test-add-ident :db/ident :test/attr
(with-tempfile [t (tempfile)]
(let [conn (<? (d/<connect t))]
(try
(is (= :test/ident (d/entid (d/db conn) :test/ident)))
(let [report (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/db -1) :db/ident :test/ident]]))
eid (get-in report [:tempids (d/id-literal :db.part/db -1)])]
(is (= eid (d/entid (d/db conn) :test/ident)))
(is (= :test/ident (d/ident (d/db conn) eid))))
;; TODO: This should fail, but doesn't, due to stringification of :test/ident.
;; (is (thrown-with-msg?
;; ExceptionInfo #"Retracting a :db/ident is not yet supported, got "
;; (<? (d/<transact! conn [[:db/retract 44 :db/ident :test/ident]]))))
;; ;; Renaming looks like retraction and then assertion.
;; (is (thrown-with-msg?
;; ExceptionInfo #"Retracting a :db/ident is not yet supported, got"
;; (<? (d/<transact! conn [[:db/add 44 :db/ident :other-name]]))))
;; (is (thrown-with-msg?
;; ExceptionInfo #"Re-asserting a :db/ident is not yet supported, got"
;; (<? (d/<transact! conn [[:db/add 55 :db/ident :test/ident]]))))
(finally
(<? (d/<close conn)))))))
(deftest-async test-add-schema
(with-tempfile [t (tempfile)]
(let [conn (<? (d/<connect t))]
(try
(let [es [[:db/add :db.part/db :db.install/attribute (d/id-literal :db.part/db -1)]
{:db/id (d/id-literal :db.part/db -1)
:db/ident :test/attr
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}]
report (<? (d/<transact! conn es))
db-after (:db-after report)
tx (:tx db-after)]
(testing "New ident is allocated"
(is (some? (d/entid db-after :test/attr))))
(testing "Schema is modified"
(is (= (get-in db-after [:symbolic-schema :test/attr])
{:db/valueType :db.type/string,
:db/cardinality :db.cardinality/one})))
(testing "Schema is used in subsequent transaction"
(<? (d/<transact! conn [{:db/id 100 :test/attr "value 1"}]))
(<? (d/<transact! conn [{:db/id 100 :test/attr "value 2"}]))
(is (= (<? (<shallow-entity (d/db conn) 100))
{:test/attr "value 2"}))))
(finally
(<? (d/<close conn)))))))
(deftest-async test-fulltext
(with-tempfile [t (tempfile)]
(let [conn (<? (d/<connect t))
schema [{:db/id (d/id-literal :db.part/db -1)
:db/ident :test/fulltext
:db/valueType :db.type/string :db/valueType :db.type/string
:db/fulltext true :db/cardinality :db.cardinality/one}]
:db/unique :db.unique/identity} report (<? (d/<transact! conn es))
{:db/id :db.part/db :db.install/attribute (d/id-literal :db.part/db -1)} db-after (:db-after report)
{:db/id (d/id-literal :db.part/db -2) tx (:tx db-after)]
:db/ident :test/other
:db/valueType :db.type/string (testing "New ident is allocated"
:db/fulltext true (is (some? (d/entid db-after :test/attr))))
:db/cardinality :db.cardinality/one}
{:db/id :db.part/db :db.install/attribute (d/id-literal :db.part/db -2)} (testing "Schema is modified"
] (is (= (get-in db-after [:symbolic-schema :test/attr])
tx0 (:tx (<? (d/<transact! conn schema)))] {:db/valueType :db.type/string,
(testing "Schema checks" :db/cardinality :db.cardinality/one})))
(is (ds/fulltext? (d/schema (d/db conn))
(d/entid (d/db conn) :test/fulltext)))) (testing "Schema is used in subsequent transaction"
(try (<? (d/<transact! conn [{:db/id 100 :test/attr "value 1"}]))
(testing "Can add fulltext indexed datoms" (<? (d/<transact! conn [{:db/id 100 :test/attr "value 2"}]))
(let [{tx1 :tx txInstant1 :txInstant} (is (= (<? (<shallow-entity (d/db conn) 100))
(<? (d/<transact! conn [[:db/add 101 :test/fulltext "test this"]]))] {:test/attr "value 2"})))))
(deftest-db test-fulltext conn
(let [schema [{:db/id (d/id-literal :db.part/db -1)
:db/ident :test/fulltext
:db/valueType :db.type/string
:db/fulltext true
:db/unique :db.unique/identity}
{:db/id :db.part/db :db.install/attribute (d/id-literal :db.part/db -1)}
{:db/id (d/id-literal :db.part/db -2)
:db/ident :test/other
:db/valueType :db.type/string
:db/fulltext true
:db/cardinality :db.cardinality/one}
{:db/id :db.part/db :db.install/attribute (d/id-literal :db.part/db -2)}
]
tx0 (:tx (<? (d/<transact! conn schema)))]
(testing "Schema checks"
(is (ds/fulltext? (d/schema (d/db conn))
(d/entid (d/db conn) :test/fulltext))))
(testing "Can add fulltext indexed datoms"
(let [{tx1 :tx txInstant1 :txInstant}
(<? (d/<transact! conn [[:db/add 101 :test/fulltext "test this"]]))]
(is (= (<? (<fulltext-values (d/db conn)))
[[1 "test this"]]))
(is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :test/fulltext 1]})) ;; Values are raw; 1 is the rowid into fulltext_values.
(is (= (<? (<transactions-after (d/db conn) tx0))
[[101 :test/fulltext 1 tx1 1] ;; Values are raw; 1 is the rowid into fulltext_values.
[tx1 :db/txInstant txInstant1 tx1 1]]))
(testing "Can replace fulltext indexed datoms"
(let [{tx2 :tx txInstant2 :txInstant} (<? (d/<transact! conn [[:db/add 101 :test/fulltext "alternate thing"]]))]
(is (= (<? (<fulltext-values (d/db conn))) (is (= (<? (<fulltext-values (d/db conn)))
[[1 "test this"]])) [[1 "test this"]
[2 "alternate thing"]]))
(is (= (<? (<datoms-after (d/db conn) tx0)) (is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :test/fulltext 1]})) ;; Values are raw; 1 is the rowid into fulltext_values. #{[101 :test/fulltext 2]})) ;; Values are raw; 2 is the rowid into fulltext_values.
(is (= (<? (<transactions-after (d/db conn) tx0)) (is (= (<? (<transactions-after (d/db conn) tx0))
[[101 :test/fulltext 1 tx1 1] ;; Values are raw; 1 is the rowid into fulltext_values. [[101 :test/fulltext 1 tx1 1] ;; Values are raw; 1 is the rowid into fulltext_values.
[tx1 :db/txInstant txInstant1 tx1 1]])) [tx1 :db/txInstant txInstant1 tx1 1]
[101 :test/fulltext 1 tx2 0] ;; Values are raw; 1 is the rowid into fulltext_values.
[101 :test/fulltext 2 tx2 1] ;; Values are raw; 2 is the rowid into fulltext_values.
[tx2 :db/txInstant txInstant2 tx2 1]]))
(testing "Can replace fulltext indexed datoms" (testing "Can upsert keyed by fulltext indexed datoms"
(let [{tx2 :tx txInstant2 :txInstant} (<? (d/<transact! conn [[:db/add 101 :test/fulltext "alternate thing"]]))] (let [{tx3 :tx txInstant3 :txInstant} (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user) :test/fulltext "alternate thing" :test/other "other"}]))]
(is (= (<? (<fulltext-values (d/db conn))) (is (= (<? (<fulltext-values (d/db conn)))
[[1 "test this"] [[1 "test this"]
[2 "alternate thing"]])) [2 "alternate thing"]
[3 "other"]]))
(is (= (<? (<datoms-after (d/db conn) tx0)) (is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :test/fulltext 2]})) ;; Values are raw; 2 is the rowid into fulltext_values. #{[101 :test/fulltext 2] ;; Values are raw; 2, 3 are the rowids into fulltext_values.
[101 :test/other 3]}))
(is (= (<? (<transactions-after (d/db conn) tx0)) (is (= (<? (<transactions-after (d/db conn) tx0))
[[101 :test/fulltext 1 tx1 1] ;; Values are raw; 1 is the rowid into fulltext_values. [[101 :test/fulltext 1 tx1 1] ;; Values are raw; 1 is the rowid into fulltext_values.
[tx1 :db/txInstant txInstant1 tx1 1] [tx1 :db/txInstant txInstant1 tx1 1]
[101 :test/fulltext 1 tx2 0] ;; Values are raw; 1 is the rowid into fulltext_values. [101 :test/fulltext 1 tx2 0] ;; Values are raw; 1 is the rowid into fulltext_values.
[101 :test/fulltext 2 tx2 1] ;; Values are raw; 2 is the rowid into fulltext_values. [101 :test/fulltext 2 tx2 1] ;; Values are raw; 2 is the rowid into fulltext_values.
[tx2 :db/txInstant txInstant2 tx2 1]])) [tx2 :db/txInstant txInstant2 tx2 1]
[101 :test/other 3 tx3 1] ;; Values are raw; 3 is the rowid into fulltext_values.
[tx3 :db/txInstant txInstant3 tx3 1]]))
(testing "Can upsert keyed by fulltext indexed datoms" ))))))
(let [{tx3 :tx txInstant3 :txInstant} (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user) :test/fulltext "alternate thing" :test/other "other"}]))]
(is (= (<? (<fulltext-values (d/db conn)))
[[1 "test this"]
[2 "alternate thing"]
[3 "other"]]))
(is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :test/fulltext 2] ;; Values are raw; 2, 3 are the rowids into fulltext_values.
[101 :test/other 3]}))
(is (= (<? (<transactions-after (d/db conn) tx0))
[[101 :test/fulltext 1 tx1 1] ;; Values are raw; 1 is the rowid into fulltext_values.
[tx1 :db/txInstant txInstant1 tx1 1]
[101 :test/fulltext 1 tx2 0] ;; Values are raw; 1 is the rowid into fulltext_values.
[101 :test/fulltext 2 tx2 1] ;; Values are raw; 2 is the rowid into fulltext_values.
[tx2 :db/txInstant txInstant2 tx2 1]
[101 :test/other 3 tx3 1] ;; Values are raw; 3 is the rowid into fulltext_values.
[tx3 :db/txInstant txInstant3 tx3 1]]))
)))))) (testing "Can re-use fulltext indexed datoms"
(let [r (<? (d/<transact! conn [[:db/add 102 :test/other "test this"]]))]
(is (= (<? (<fulltext-values (d/db conn)))
[[1 "test this"]
[2 "alternate thing"]
[3 "other"]]))
(is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :test/fulltext 2]
[101 :test/other 3]
[102 :test/other 1]})) ;; Values are raw; 1, 2, 3 are the rowids into fulltext_values.
))
(testing "Can re-use fulltext indexed datoms" (testing "Can retract fulltext indexed datoms"
(let [r (<? (d/<transact! conn [[:db/add 102 :test/other "test this"]]))] (let [r (<? (d/<transact! conn [[:db/retract 101 :test/fulltext "alternate thing"]]))]
(is (= (<? (<fulltext-values (d/db conn))) (is (= (<? (<fulltext-values (d/db conn)))
[[1 "test this"] [[1 "test this"]
[2 "alternate thing"] [2 "alternate thing"]
[3 "other"]])) [3 "other"]]))
(is (= (<? (<datoms-after (d/db conn) tx0)) (is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :test/fulltext 2] #{[101 :test/other 3]
[101 :test/other 3] [102 :test/other 1]})) ;; Values are raw; 1, 3 are the rowids into fulltext_values.
[102 :test/other 1]})) ;; Values are raw; 1, 2, 3 are the rowids into fulltext_values. ))))
))
(testing "Can retract fulltext indexed datoms" (deftest-db test-txInstant conn
(let [r (<? (d/<transact! conn [[:db/retract 101 :test/fulltext "alternate thing"]]))] (let [{tx0 :tx} (<? (d/<transact! conn test-schema))
(is (= (<? (<fulltext-values (d/db conn))) {txa :tx txInstantA :txInstant} (<? (d/<transact! conn []))]
[[1 "test this"] (testing ":db/txInstant is set by default"
[2 "alternate thing"] (is (= (<? (<transactions-after (d/db conn) tx0))
[3 "other"]])) [[txa :db/txInstant txInstantA txa 1]])))
(is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :test/other 3]
[102 :test/other 1]})) ;; Values are raw; 1, 3 are the rowids into fulltext_values.
))
(finally ;; TODO: range check txInstant values against DB clock.
(<? (d/<close conn))))))) (testing ":db/txInstant can be set explicitly"
(let [{txb :tx txInstantB :txInstant} (<? (d/<transact! conn [[:db/add :db/tx :db/txInstant (+ txInstantA 1)]]))]
(is (= txInstantB (+ txInstantA 1)))
(is (= (<? (<transactions-after (d/db conn) txa))
[[txb :db/txInstant txInstantB txb 1]]))
(deftest-async test-txInstant (testing ":db/txInstant can be set explicitly, with additional datoms"
(with-tempfile [t (tempfile)] (let [{txc :tx txInstantC :txInstant} (<? (d/<transact! conn [[:db/add :db/tx :db/txInstant (+ txInstantB 2)]
(let [conn (<? (d/<connect t)) [:db/add :db/tx :x 123]]))]
{tx0 :tx} (<? (d/<transact! conn test-schema))] (is (= txInstantC (+ txInstantB 2)))
(try (is (= (<? (<transactions-after (d/db conn) txb))
(let [{txa :tx txInstantA :txInstant} (<? (d/<transact! conn []))] [[txc :db/txInstant txInstantC txc 1]
(testing ":db/txInstant is set by default" [txc :x 123 txc 1]]))
(is (= (<? (<transactions-after (d/db conn) tx0))
[[txa :db/txInstant txInstantA txa 1]])))
;; TODO: range check txInstant values against DB clock. (testing "additional datoms can be added, without :db/txInstant explicitly"
(testing ":db/txInstant can be set explicitly" (let [{txd :tx txInstantD :txInstant} (<? (d/<transact! conn [[:db/add :db/tx :x 456]]))]
(let [{txb :tx txInstantB :txInstant} (<? (d/<transact! conn [[:db/add :db/tx :db/txInstant (+ txInstantA 1)]]))] (is (= (<? (<transactions-after (d/db conn) txc))
(is (= txInstantB (+ txInstantA 1))) [[txd :db/txInstant txInstantD txd 1]
(is (= (<? (<transactions-after (d/db conn) txa)) [txd :x 456 txd 1]]))))))))))
[[txb :db/txInstant txInstantB txb 1]]))
(testing ":db/txInstant can be set explicitly, with additional datoms" (deftest-db test-no-tx conn
(let [{txc :tx txInstantC :txInstant} (<? (d/<transact! conn [[:db/add :db/tx :db/txInstant (+ txInstantB 2)] (let [{tx0 :tx} (<? (d/<transact! conn test-schema))]
[:db/add :db/tx :x 123]]))] (testing "Cannot specificy an explicit tx"
(is (= txInstantC (+ txInstantB 2))) (is (thrown-with-msg?
(is (= (<? (<transactions-after (d/db conn) txb)) ExceptionInfo #"Bad entity: too long"
[[txc :db/txInstant txInstantC txc 1] (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user) :x 0 10101]])))))))
[txc :x 123 txc 1]]))
(testing "additional datoms can be added, without :db/txInstant explicitly" (deftest-db test-explode-sequences conn
(let [{txd :tx txInstantD :txInstant} (<? (d/<transact! conn [[:db/add :db/tx :x 456]]))] (let [{tx0 :tx} (<? (d/<transact! conn test-schema))]
(is (= (<? (<transactions-after (d/db conn) txc)) (testing ":db.cardinality/many sequences are accepted"
[[txd :db/txInstant txInstantD txd 1] (<? (d/<transact! conn [{:db/id 101 :aka ["first" "second"]}]))
[txd :x 456 txd 1]]))))))))) (is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :aka "first"]
[101 :aka "second"]})))
(finally (testing ":db.cardinality/many sequences are recursively applied, allowing unexpected sequence nesting"
(<? (d/<close conn))))))) (<? (d/<transact! conn [{:db/id 102 :aka [[["first"]] ["second"]]}]))
(is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :aka "first"]
[101 :aka "second"]
[102 :aka "first"]
[102 :aka "second"]})))
(deftest-async test-no-tx (testing ":db.cardinality/one sequences fail"
(with-tempfile [t (tempfile)] (is (thrown-with-msg?
(let [conn (<? (d/<connect t)) ExceptionInfo #"Sequential values"
{tx0 :tx} (<? (d/<transact! conn test-schema))] (<? (d/<transact! conn [{:db/id 101 :email ["@1" "@2"]}])))))))
(try
(testing "Cannot specificy an explicit tx"
(is (thrown-with-msg?
ExceptionInfo #"Bad entity: too long"
(<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user) :x 0 10101]])))))
(finally (deftest-db test-explode-maps conn
(<? (d/<close conn))))))) (let [{tx0 :tx} (<? (d/<transact! conn test-schema))]
(testing "nested maps are accepted"
(<? (d/<transact! conn [{:db/id 101 :friends {:name "Petr"}}]))
;; TODO: this works only because we have a single friend.
(let [{petr :friends} (<? (<shallow-entity (d/db conn) 101))]
(is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :friends petr]
[petr :name "Petr"]}))))
(deftest-async test-explode-sequences (testing "recursively nested maps are accepted"
(with-tempfile [t (tempfile)] (<? (d/<transact! conn [{:db/id 102 :friends {:name "Ivan" :friends {:name "Petr"}}}]))
(let [conn (<? (d/<connect t)) ;; This would be much easier with `entity` and lookup refs.
{tx0 :tx} (<? (d/<transact! conn test-schema))] (let [{ivan :friends} (<? (<shallow-entity (d/db conn) 102))
(try {petr :friends} (<? (<shallow-entity (d/db conn) ivan))]
(testing ":db.cardinality/many sequences are accepted" (is (= (<? (<datoms-after (d/db conn) tx0))
(<? (d/<transact! conn [{:db/id 101 :aka ["first" "second"]}])) #{[101 :friends petr]
(is (= (<? (<datoms-after (d/db conn) tx0)) [petr :name "Petr"]
#{[101 :aka "first"] [102 :friends ivan]
[101 :aka "second"]}))) [ivan :name "Ivan"]
[ivan :friends petr]}))))
(testing ":db.cardinality/many sequences are recursively applied, allowing unexpected sequence nesting" (testing "nested maps without :db.type/ref fail"
(<? (d/<transact! conn [{:db/id 102 :aka [[["first"]] ["second"]]}])) (is (thrown-with-msg?
(is (= (<? (<datoms-after (d/db conn) tx0)) ExceptionInfo #"\{:db/valueType :db.type/ref\}"
#{[101 :aka "first"] (<? (d/<transact! conn [{:db/id 101 :aka {:name "Petr"}}])))))))
[101 :aka "second"]
[102 :aka "first"]
[102 :aka "second"]})))
(testing ":db.cardinality/one sequences fail" (deftest-db test-explode-reverse-refs conn
(is (thrown-with-msg? (let [{tx0 :tx} (<? (d/<transact! conn test-schema))]
ExceptionInfo #"Sequential values" (testing "reverse refs are accepted"
(<? (d/<transact! conn [{:db/id 101 :email ["@1" "@2"]}]))))) (<? (d/<transact! conn [{:db/id 101 :name "Igor"}]))
(<? (d/<transact! conn [{:db/id 102 :name "Oleg" :_friends 101}]))
(is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :name "Igor"]
[102 :name "Oleg"]
[101 :friends 102]})))
(finally (testing "reverse refs without :db.type/ref fail"
(<? (d/<close conn))))))) (is (thrown-with-msg?
ExceptionInfo #"\{:db/valueType :db.type/ref\}"
(deftest-async test-explode-maps (<? (d/<transact! conn [{:db/id 101 :_aka 102}])))))))
(with-tempfile [t (tempfile)]
(let [conn (<? (d/<connect t))
{tx0 :tx} (<? (d/<transact! conn test-schema))]
(try
(testing "nested maps are accepted"
(<? (d/<transact! conn [{:db/id 101 :friends {:name "Petr"}}]))
;; TODO: this works only because we have a single friend.
(let [{petr :friends} (<? (<shallow-entity (d/db conn) 101))]
(is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :friends petr]
[petr :name "Petr"]}))))
(testing "recursively nested maps are accepted"
(<? (d/<transact! conn [{:db/id 102 :friends {:name "Ivan" :friends {:name "Petr"}}}]))
;; This would be much easier with `entity` and lookup refs.
(let [{ivan :friends} (<? (<shallow-entity (d/db conn) 102))
{petr :friends} (<? (<shallow-entity (d/db conn) ivan))]
(is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :friends petr]
[petr :name "Petr"]
[102 :friends ivan]
[ivan :name "Ivan"]
[ivan :friends petr]}))))
(testing "nested maps without :db.type/ref fail"
(is (thrown-with-msg?
ExceptionInfo #"\{:db/valueType :db.type/ref\}"
(<? (d/<transact! conn [{:db/id 101 :aka {:name "Petr"}}])))))
(finally
(<? (d/<close conn)))))))
(deftest-async test-explode-reverse-refs
(with-tempfile [t (tempfile)]
(let [conn (<? (d/<connect t))
{tx0 :tx} (<? (d/<transact! conn test-schema))]
(try
(testing "reverse refs are accepted"
(<? (d/<transact! conn [{:db/id 101 :name "Igor"}]))
(<? (d/<transact! conn [{:db/id 102 :name "Oleg" :_friends 101}]))
(is (= (<? (<datoms-after (d/db conn) tx0))
#{[101 :name "Igor"]
[102 :name "Oleg"]
[101 :friends 102]})))
(testing "reverse refs without :db.type/ref fail"
(is (thrown-with-msg?
ExceptionInfo #"\{:db/valueType :db.type/ref\}"
(<? (d/<transact! conn [{:db/id 101 :_aka 102}])))))
(finally
(<? (d/<close conn)))))))
;; We don't use deftest-db in order to be able to re-open an on disk file.
(deftest-async test-next-eid (deftest-async test-next-eid
(with-tempfile [t (tempfile)] (with-tempfile [t (tempfile)]
(let [conn (<? (d/<connect t)) (let [conn (<? (d/<connect t))
@ -644,34 +548,30 @@
(finally (finally
(<? (d/<close conn)))))))))) (<? (d/<close conn))))))))))
(deftest-async test-unique-value (deftest-db test-unique-value conn
(with-tempfile [t (tempfile)] (let [tx0 (:tx (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1)
(let [conn (<? (d/<connect t))] :db/ident :test/x
(try :db/unique :db.unique/value
(let [tx0 (:tx (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :db/valueType :db.type/long
:db/ident :test/x :db.install/_attribute :db.part/db}
:db/unique :db.unique/value {:db/id (d/id-literal :db.part/user -2)
:db/valueType :db.type/long :db/ident :test/y
:db.install/_attribute :db.part/db} :db/unique :db.unique/value
{:db/id (d/id-literal :db.part/user -2) :db/valueType :db.type/long
:db/ident :test/y :db.install/_attribute :db.part/db}])))]
:db/unique :db.unique/value
:db/valueType :db.type/long
:db.install/_attribute :db.part/db}])))]
(testing "can insert different :db.unique/value attributes with the same value" (testing "can insert different :db.unique/value attributes with the same value"
(let [report1 (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :test/x 12345]])) (let [report1 (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :test/x 12345]]))
eid1 (get-in report1 [:tempids (d/id-literal :db.part/user -1)]) eid1 (get-in report1 [:tempids (d/id-literal :db.part/user -1)])
report2 (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -2) :test/y 12345]])) report2 (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -2) :test/y 12345]]))
eid2 (get-in report2 [:tempids (d/id-literal :db.part/user -2)])] eid2 (get-in report2 [:tempids (d/id-literal :db.part/user -2)])]
(is (= (<? (<datoms-after (d/db conn) tx0)) (is (= (<? (<datoms-after (d/db conn) tx0))
#{[eid1 :test/x 12345] #{[eid1 :test/x 12345]
[eid2 :test/y 12345]})))) [eid2 :test/y 12345]}))))
(testing "can't upsert a :db.unique/value field" (testing "can't upsert a :db.unique/value field"
(is (thrown-with-msg? (is (thrown-with-msg?
ExceptionInfo #"unique constraint" ExceptionInfo #"unique constraint"
(<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :test/x 12345 :test/y 99999}])))))) (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :test/x 12345 :test/y 99999}])))))))
(finally #_ (time (t/run-tests))
(<? (d/<close conn)))))))