Skip to content

Commit 009c628

Browse files
committed
Fixed tempid in unique refs (closes #464)
1 parent 4cdb43c commit 009c628

File tree

3 files changed

+131
-83
lines changed

3 files changed

+131
-83
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- Implement “constant substitution” optimization for queries #462
44
- Fixed :max-eid for dangling entities during reader-based serialization #463
5+
- Fixed tempid in unique refs #464
56

67
# 1.6.3
78

src/datascript/db.cljc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1377,7 +1377,12 @@
13771377
[db entity]
13781378
(if-some [idents (not-empty (-attrs-by db :db.unique/identity))]
13791379
(let [resolve (fn [a v]
1380-
(:e (first (-datoms db :avet a v nil nil))))
1380+
(cond
1381+
(not (ref? db a))
1382+
(:e (first (-datoms db :avet a v nil nil)))
1383+
1384+
(not (tempid? v))
1385+
(:e (first (-datoms db :avet a (entid db v) nil nil)))))
13811386
split (fn [a vs]
13821387
(reduce
13831388
(fn [acc v]

test/datascript/test/upsert.cljc

Lines changed: 124 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -10,191 +10,233 @@
1010
(def Throwable js/Error))
1111

1212
(deftest test-upsert
13-
(let [db (d/db-with (d/empty-db {:name { :db/unique :db.unique/identity }
14-
:email { :db/unique :db.unique/identity }
15-
:slugs { :db/unique :db.unique/identity
16-
:db/cardinality :db.cardinality/many }})
17-
[{:db/id 1 :name "Ivan" :email "@1"}
18-
{:db/id 2 :name "Petr" :email "@2"}])
19-
touched (fn [tx e] (into {} (d/touch (d/entity (:db-after tx) e))))
20-
tempids (fn [tx] (dissoc (:tempids tx) :db/current-tx))]
13+
(let [ivan {:db/id 1 :name "Ivan" :email "@1"}
14+
petr {:db/id 2 :name "Petr" :email "@2" :ref 3}
15+
dima {:db/id 3 :name "Dima" :email "@3" :ref 4}
16+
olga {:db/id 4 :name "Olga" :email "@4" :ref 1}
17+
db (d/db-with (d/empty-db {:name {:db/unique :db.unique/identity}
18+
:email {:db/unique :db.unique/identity}
19+
:slugs {:db/unique :db.unique/identity
20+
:db/cardinality :db.cardinality/many}
21+
:ref {:db/unique :db.unique/identity
22+
:db/type :db.type/ref}})
23+
[ivan petr dima olga])
24+
pull (fn [tx e]
25+
(d/pull (:db-after tx) ['* {[:ref :xform #(:db/id %)] [:db/id]}] e))
26+
tempids (fn [tx]
27+
(dissoc (:tempids tx) :db/current-tx))]
2128
(testing "upsert, no tempid"
2229
(let [tx (d/with db [{:name "Ivan" :age 35}])]
23-
(is (= (touched tx 1)
24-
{:name "Ivan" :email "@1" :age 35}))
30+
(is (= {:db/id 1 :name "Ivan" :email "@1" :age 35}
31+
(pull tx 1)))
2532
(is (= (tempids tx)
26-
{}))))
33+
{}))))
2734

2835
(testing "upsert by 2 attrs, no tempid"
2936
(let [tx (d/with db [{:name "Ivan" :email "@1" :age 35}])]
30-
(is (= (touched tx 1)
31-
{:name "Ivan" :email "@1" :age 35}))
37+
(is (= {:db/id 1 :name "Ivan" :email "@1" :age 35}
38+
(pull tx 1)))
3239
(is (= (tempids tx)
33-
{}))))
40+
{}))))
3441

3542
(testing "upsert with tempid"
3643
(let [tx (d/with db [{:db/id -1 :name "Ivan" :age 35}])]
37-
(is (= (touched tx 1)
38-
{:name "Ivan" :email "@1" :age 35}))
44+
(is (= {:db/id 1 :name "Ivan" :email "@1" :age 35}
45+
(pull tx 1)))
3946
(is (= (tempids tx)
40-
{-1 1}))))
47+
{-1 1}))))
4148

4249
(testing "upsert with string tempid"
4350
(let [tx (d/with db [{:db/id "1" :name "Ivan" :age 35}
4451
[:db/add "2" :name "Oleg"]
4552
[:db/add "2" :email "@2"]])]
46-
(is (= (touched tx 1)
47-
{:name "Ivan" :email "@1" :age 35}))
48-
(is (= (touched tx 2)
49-
{:name "Oleg" :email "@2"}))
53+
(is (= {:db/id 1 :name "Ivan" :email "@1" :age 35}
54+
(pull tx 1)))
55+
(is (= {:db/id 2 :name "Oleg" :email "@2" :ref 3}
56+
(pull tx 2)))
5057
(is (= (tempids tx)
51-
{"1" 1
52-
"2" 2}))))
58+
{"1" 1
59+
"2" 2}))))
5360

5461
(testing "upsert by 2 attrs with tempid"
5562
(let [tx (d/with db [{:db/id -1 :name "Ivan" :email "@1" :age 35}])]
56-
(is (= (touched tx 1)
57-
{:name "Ivan" :email "@1" :age 35}))
63+
(is (= {:db/id 1 :name "Ivan" :email "@1" :age 35}
64+
(pull tx 1)))
5865
(is (= (tempids tx)
59-
{-1 1}))))
66+
{-1 1}))))
6067

6168
(testing "upsert to two entities, resolve to same tempid"
6269
(let [tx (d/with db [{:db/id -1 :name "Ivan" :age 35}
6370
{:db/id -1 :name "Ivan" :age 36}])]
64-
(is (= (touched tx 1)
65-
{:name "Ivan" :email "@1" :age 36}))
71+
(is (= {:db/id 1 :name "Ivan" :email "@1" :age 36}
72+
(pull tx 1)))
6673
(is (= (tempids tx)
67-
{-1 1}))))
74+
{-1 1}))))
6875

6976
(testing "upsert to two entities, two tempids"
7077
(let [tx (d/with db [{:db/id -1 :name "Ivan" :age 35}
7178
{:db/id -2 :name "Ivan" :age 36}])]
72-
(is (= (touched tx 1)
73-
{:name "Ivan" :email "@1" :age 36}))
79+
(is (= {:db/id 1 :name "Ivan" :email "@1" :age 36}
80+
(pull tx 1)))
7481
(is (= (tempids tx)
75-
{-1 1, -2 1}))))
82+
{-1 1, -2 1}))))
7683

7784
(testing "upsert with existing id"
7885
(let [tx (d/with db [{:db/id 1 :name "Ivan" :age 35}])]
79-
(is (= (touched tx 1)
80-
{:name "Ivan" :email "@1" :age 35}))
86+
(is (= {:db/id 1 :name "Ivan" :email "@1" :age 35}
87+
(pull tx 1)))
8188
(is (= (tempids tx)
82-
{}))))
89+
{}))))
8390

8491
(testing "upsert by 2 attrs with existing id"
8592
(let [tx (d/with db [{:db/id 1 :name "Ivan" :email "@1" :age 35}])]
86-
(is (= (touched tx 1)
87-
{:name "Ivan" :email "@1" :age 35}))
93+
(is (= {:db/id 1 :name "Ivan" :email "@1" :age 35}
94+
(pull tx 1)))
8895
(is (= (tempids tx)
89-
{}))))
96+
{}))))
9097

9198
(testing "upsert by 2 attrs with existing id as lookup ref"
9299
(let [tx (d/with db [{:db/id [:name "Ivan"] :name "Ivan" :email "@1" :age 35}])]
93-
(is (= (touched tx 1)
94-
{:name "Ivan" :email "@1" :age 35}))
100+
(is (= {:db/id 1 :name "Ivan" :email "@1" :age 35}
101+
(pull tx 1)))
95102
(is (= (tempids tx)
96-
{}))))
103+
{}))))
97104

98105
(testing "upsert conflicts with existing id"
99106
(is (thrown-with-msg? Throwable #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id 2"
100-
(d/with db [{:db/id 2 :name "Ivan" :age 36}]))))
107+
(d/with db [{:db/id 2 :name "Ivan" :age 36}]))))
101108

102109
(testing "upsert conflicts with non-existing id"
103-
(is (thrown-with-msg? Throwable #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id 3"
104-
(d/with db [{:db/id 3 :name "Ivan" :age 36}]))))
110+
(is (thrown-with-msg? Throwable #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id 5"
111+
(d/with db [{:db/id 5 :name "Ivan" :age 36}]))))
105112

106113
(testing "upsert by non-existing value resolves as update"
107-
(let [tx (d/with db [{:name "Ivan" :email "@3" :age 35}])]
108-
(is (= (touched tx 1)
109-
{:name "Ivan" :email "@3" :age 35}))
114+
(let [tx (d/with db [{:name "Ivan" :email "@5" :age 35}])]
115+
(is (= {:db/id 1 :name "Ivan" :email "@5" :age 35}
116+
(pull tx 1)))
110117
(is (= (tempids tx)
111-
{}))))
118+
{}))))
112119

113120
(testing "upsert by 2 conflicting fields"
114121
(is (thrown-with-msg? Throwable #"Conflicting upserts: \[:name \"Ivan\"\] resolves to 1, but \[:email \"@2\"\] resolves to 2"
115-
(d/with db [{:name "Ivan" :email "@2" :age 35}]))))
122+
(d/with db [{:name "Ivan" :email "@2" :age 35}]))))
116123

117124
(testing "upsert over intermediate db"
118125
(let [tx (d/with db [{:name "Igor" :age 35}
119126
{:name "Igor" :age 36}])]
120-
(is (= (touched tx 3)
121-
{:name "Igor" :age 36}))
127+
(is (= {:db/id 5 :name "Igor" :age 36}
128+
(pull tx 5)))
122129
(is (= (tempids tx)
123-
{3 3}))))
130+
{5 5}))))
124131

125132
(testing "upsert over intermediate db, tempids"
126133
(let [tx (d/with db [{:db/id -1 :name "Igor" :age 35}
127134
{:db/id -1 :name "Igor" :age 36}])]
128-
(is (= (touched tx 3)
129-
{:name "Igor" :age 36}))
135+
(is (= {:db/id 5 :name "Igor" :age 36}
136+
(pull tx 5)))
130137
(is (= (tempids tx)
131-
{-1 3}))))
138+
{-1 5}))))
132139

133140
(testing "upsert over intermediate db, different tempids"
134141
(let [tx (d/with db [{:db/id -1 :name "Igor" :age 35}
135142
{:db/id -2 :name "Igor" :age 36}])]
136-
(is (= (touched tx 3)
137-
{:name "Igor" :age 36}))
143+
(is (= {:db/id 5 :name "Igor" :age 36}
144+
(pull tx 5)))
138145
(is (= (tempids tx)
139-
{-1 3, -2 3}))))
146+
{-1 5, -2 5}))))
140147

141148
(testing "upsert and :current-tx conflict"
142149
(is (thrown-with-msg? Throwable #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id \d+"
143-
(d/with db [{:db/id :db/current-tx :name "Ivan" :age 35}]))))
150+
(d/with db [{:db/id :db/current-tx :name "Ivan" :age 35}]))))
144151

145152
(testing "upsert of unique, cardinality-many values"
146153
(let [tx (d/with db [{:name "Ivan" :slugs "ivan1"}
147154
{:name "Petr" :slugs "petr1"}])
148155
tx2 (d/with (:db-after tx) [{:name "Ivan" :slugs ["ivan1" "ivan2"]}])]
149-
(is (= (touched tx 1)
150-
{:name "Ivan" :email "@1" :slugs #{"ivan1"}}))
151-
(is (= (touched tx2 1)
152-
{:name "Ivan" :email "@1" :slugs #{"ivan1" "ivan2"}}))
156+
(is (= {:db/id 1 :name "Ivan" :email "@1" :slugs ["ivan1"]}
157+
(pull tx 1)))
158+
(is (= {:db/id 1 :name "Ivan" :email "@1" :slugs ["ivan1" "ivan2"]}
159+
(pull tx2 1)))
153160
(is (thrown-with-msg? Throwable #"Conflicting upserts:"
154161
(d/with (:db-after tx) [{:slugs ["ivan1" "petr1"]}])))))
162+
163+
(testing "upsert by ref"
164+
(let [tx (d/with db [{:ref 3 :age 36}])]
165+
(is (= {:db/id 2 :name "Petr" :email "@2" :ref 3 :age 36}
166+
(pull tx 2))))
167+
(let [tx (d/with db [{:ref 4 :age 37}])]
168+
(is (= {:db/id 3 :name "Dima" :email "@3" :ref 4 :age 37}
169+
(pull tx 3))))
170+
(let [tx (d/with db [{:ref 1 :age 38}])]
171+
(is (= {:db/id 4 :name "Olga" :email "@4" :ref 1 :age 38}
172+
(pull tx 4)))))
173+
174+
(testing "upsert by lookup ref"
175+
(let [tx (d/with db [{:ref [:name "Dima"] :age 36}])]
176+
(is (= {:db/id 2 :name "Petr" :email "@2" :ref 3 :age 36}
177+
(pull tx 2))))
178+
(let [tx (d/with db [{:ref [:name "Olga"] :age 37}])]
179+
(is (= {:db/id 3 :name "Dima" :email "@3" :ref 4 :age 37}
180+
(pull tx 3))))
181+
(let [tx (d/with db [{:ref [:name "Ivan"] :age 38}])]
182+
(is (= {:db/id 4 :name "Olga" :email "@4" :ref 1 :age 38}
183+
(pull tx 4)))))
184+
185+
;; https://github.com/tonsky/datascript/issues/464
186+
(testing "not upsert by ref"
187+
(let [tx (d/with db [{:db/id -1 :name "Igor"}
188+
{:db/id -2 :name "Anna" :ref -1}])]
189+
(is (= {:db/id 5 :name "Igor"} (pull tx 5)))
190+
(is (= {:db/id 6 :name "Anna" :ref 5} (pull tx 6))))
191+
192+
(let [tx (d/with db [{:db/id "A" :name "Igor"}
193+
{:db/id "B" :name "Anna" :ref "A"}])]
194+
(is (= {:db/id 5 :name "Igor"} (pull tx 5)))
195+
(is (= {:db/id 6 :name "Anna" :ref 5} (pull tx 6)))))
196+
155197
))
156198

157199

158200
(deftest test-redefining-ids
159201
(let [db (-> (d/empty-db {:name { :db/unique :db.unique/identity }})
160-
(d/db-with [{:db/id -1 :name "Ivan"}]))]
202+
(d/db-with [{:db/id -1 :name "Ivan"}]))]
161203
(let [tx (d/with db [{:db/id -1 :age 35}
162204
{:db/id -1 :name "Ivan" :age 36}])]
163205
(is (= #{[1 :age 36] [1 :name "Ivan"]}
164-
(tdc/all-datoms (:db-after tx))))
206+
(tdc/all-datoms (:db-after tx))))
165207
(is (= {-1 1, :db/current-tx (+ d/tx0 2)}
166-
(:tempids tx)))))
208+
(:tempids tx)))))
167209

168210
(let [db (-> (d/empty-db {:name { :db/unique :db.unique/identity }})
169-
(d/db-with [{:db/id -1 :name "Ivan"}
170-
{:db/id -2 :name "Oleg"}]))]
211+
(d/db-with [{:db/id -1 :name "Ivan"}
212+
{:db/id -2 :name "Oleg"}]))]
171213
(is (thrown-with-msg? Throwable #"Conflicting upsert: -1 resolves both to 1 and 2"
172214
(d/with db [{:db/id -1 :name "Ivan" :age 35}
173215
{:db/id -1 :name "Oleg" :age 36}])))))
174216

175217
;; https://github.com/tonsky/datascript/issues/285
176218
(deftest test-retries-order
177219
(let [db (-> (d/empty-db {:name {:db/unique :db.unique/identity}})
178-
(d/db-with [[:db/add -1 :age 42]
179-
[:db/add -2 :likes "Pizza"]
180-
[:db/add -1 :name "Bob"]
181-
[:db/add -2 :name "Bob"]]))]
220+
(d/db-with [[:db/add -1 :age 42]
221+
[:db/add -2 :likes "Pizza"]
222+
[:db/add -1 :name "Bob"]
223+
[:db/add -2 :name "Bob"]]))]
182224
(is (= {:db/id 1, :name "Bob", :likes "Pizza", :age 42}
183-
(tdc/entity-map db 1))))
225+
(tdc/entity-map db 1))))
184226

185227
(let [db (-> (d/empty-db {:name {:db/unique :db.unique/identity}})
186-
(d/db-with [[:db/add -1 :age 42]
187-
[:db/add -2 :likes "Pizza"]
188-
[:db/add -2 :name "Bob"]
189-
[:db/add -1 :name "Bob"]]))]
228+
(d/db-with [[:db/add -1 :age 42]
229+
[:db/add -2 :likes "Pizza"]
230+
[:db/add -2 :name "Bob"]
231+
[:db/add -1 :name "Bob"]]))]
190232
(is (= {:db/id 2, :name "Bob", :likes "Pizza", :age 42}
191-
(tdc/entity-map db 2)))))
233+
(tdc/entity-map db 2)))))
192234

193235
;; https://github.com/tonsky/datascript/issues/403
194236
(deftest test-upsert-string-tempid-ref
195237
(let [db (-> (d/empty-db {:name {:db/unique :db.unique/identity}
196238
:ref {:db/valueType :db.type/ref}})
197-
(d/db-with [{:name "Alice"}]))
239+
(d/db-with [{:name "Alice"}]))
198240
expected #{[1 :name "Alice"]
199241
[2 :age 36]
200242
[2 :ref 1]}]
@@ -213,7 +255,7 @@
213255

214256
(deftest test-vector-upsert
215257
(let [db (-> (d/empty-db {:name {:db/unique :db.unique/identity}})
216-
(d/db-with [{:db/id -1, :name "Ivan"}]))]
258+
(d/db-with [{:db/id -1, :name "Ivan"}]))]
217259
(are [tx res] (= res (tdc/all-datoms (d/db-with db tx)))
218260
[[:db/add -1 :name "Ivan"]
219261
[:db/add -1 :age 12]]
@@ -224,8 +266,8 @@
224266
#{[1 :age 12] [1 :name "Ivan"]}))
225267

226268
(let [db (-> (d/empty-db {:name { :db/unique :db.unique/identity }})
227-
(d/db-with [[:db/add -1 :name "Ivan"]
228-
[:db/add -2 :name "Oleg"]]))]
269+
(d/db-with [[:db/add -1 :name "Ivan"]
270+
[:db/add -2 :name "Oleg"]]))]
229271
(is (thrown-with-msg? Throwable #"Conflicting upsert: -1 resolves both to 1 and 2"
230272
(d/with db [[:db/add -1 :name "Ivan"]
231273
[:db/add -1 :age 35]

0 commit comments

Comments
 (0)