From 9ae1fd56f630c71358e233fc89d83fb5766405bd Mon Sep 17 00:00:00 2001 From: Nikita Prokopov Date: Tue, 18 Jun 2024 22:07:35 +0200 Subject: [PATCH] Allow lookup-refs inside tuples used for lookup-refs (closes #452) --- CHANGELOG.md | 1 + src/datascript/db.cljc | 37 ++++++++++++++++++++++++++------ test/datascript/test/tuples.cljc | 31 ++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21b67bb4..9b6dd444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Correctly restore `:max-tx` from storage - Fixed tempid/upsert resolution when multiple tempids are added first #472 - Allow upsert by implicit tuple when only tuple components are specified #473 +- Allow lookup-refs inside tuples used for lookup-refs #452 # 1.6.5 - May 3, 2024 diff --git a/src/datascript/db.cljc b/src/datascript/db.cljc index 14829296..dbf218b8 100644 --- a/src/datascript/db.cljc +++ b/src/datascript/db.cljc @@ -1238,6 +1238,14 @@ (raise "Bad attribute type: " attr ", expected keyword or string" {:error :transact/syntax, :attribute attr}))) +(defn resolve-tuple-refs [db a vs] + (mapv + (fn [a v] + (if (and (ref? db a) (sequential? v)) ;; lookup-ref + (entid-strict db v) + v)) + (-> db -schema (get a) :db/tupleAttrs) vs)) + (defn+ ^number entid [db eid] {:pre [(db? db)]} (cond @@ -1250,15 +1258,22 @@ (let [[attr value] eid] (cond (not= (count eid) 2) - (raise "Lookup ref should contain 2 elements: " eid - {:error :lookup-ref/syntax, :entity-id eid}) + (raise "Lookup ref should contain 2 elements: " eid + {:error :lookup-ref/syntax, :entity-id eid}) + (not (is-attr? db attr :db/unique)) - (raise "Lookup ref attribute should be marked as :db/unique: " eid - {:error :lookup-ref/unique, :entity-id eid}) + (raise "Lookup ref attribute should be marked as :db/unique: " eid + {:error :lookup-ref/unique, :entity-id eid}) + + (tuple? db attr) + (let [value' (resolve-tuple-refs db attr value)] + (-> (-datoms db :avet attr value' nil nil) first :e)) + (nil? value) - nil + nil + :else - (-> (-datoms db :avet attr value nil nil) first :e))) + (-> (-datoms db :avet attr value nil nil) first :e))) #?@(:cljs [(array? eid) (recur db (array-seq eid))]) @@ -1838,6 +1853,13 @@ (allocate-eid v resolved) (update ::value-tempids assoc resolved v))] (recur report' es))) + + (and + (or (= op :db/add) (= op :db/retract)) + (not (::internal (meta entity))) + (tuple? db a) + (not= v (resolve-tuple-refs db a v))) + (recur report (cons [op e a (resolve-tuple-refs db a v)] entities)) (tempid? e) (let [upserted-eid (when (is-attr? db a :db.unique/identity) @@ -1861,7 +1883,8 @@ (raise "Conflicting upsert: " e " resolves to " upserted-eid " via " entity {:error :transact/upsert}))) - (and (not (::internal (meta entity))) + (and + (not (::internal (meta entity))) (tuple? db a)) ;; allow transacting in tuples if they fully match already existing values (let [tuple-attrs (get-in db [:schema a :db/tupleAttrs])] diff --git a/test/datascript/test/tuples.cljc b/test/datascript/test/tuples.cljc index e47f4ee8..13fa6153 100644 --- a/test/datascript/test/tuples.cljc +++ b/test/datascript/test/tuples.cljc @@ -293,6 +293,37 @@ :c "c"} (d/pull (d/db conn) '[*] [:a+b ["a" "b"]]))))) +;; https://github.com/tonsky/datascript/issues/452 +(deftest lookup-refs-in-tuple + (let [schema {:ref {:db/valueType :db.type/ref} + :name {:db/unique :db.unique/identity} + :ref+name {:db/valueType :db.type/tuple + :db/tupleAttrs [:ref :name] + :db/unique :db.unique/identity}} + db (-> (d/empty-db schema) + (d/db-with + [{:db/id -1 :name "Ivan"} + {:db/id -2 :name "Oleg"} + {:db/id -3 :name "Petr" :ref -1} + {:db/id -4 :name "Yuri" :ref -2}]))] + (let [db' (d/db-with db [{:ref+name [1 "Petr"], :age 32}])] + (is (= {:age 32} (d/pull db' [:age] 3)))) + + (let [db' (d/db-with db [{:ref+name [[:name "Ivan"] "Petr"], :age 32}])] + (is (= {:age 32} (d/pull db' [:age] 3)))) + + (let [db' (d/db-with db [[:db/add -1 :ref+name [1 "Petr"]] + [:db/add -1 :age 32]])] + (is (= {:age 32} (d/pull db' [:age] 3)))) + + (let [db' (d/db-with db [[:db/add -1 :ref+name [[:name "Ivan"] "Petr"]] + [:db/add -1 :age 32]])] + (is (= {:age 32} (d/pull db' [:age] 3)))) + + (is (= 1 (:db/id (d/entity db [:name "Ivan"])))) + (is (= 3 (:db/id (d/entity db [:ref+name [1 "Petr"]])))) + (is (= 3 (:db/id (d/entity db [:ref+name [[:name "Ivan"] "Petr"]])))))) + (deftest test-validation (let [db (d/empty-db {:a+b {:db/tupleAttrs [:a :b]}}) db1 (d/db-with db [[:db/add 1 :a "a"]])]