From 5ab2f993e3842f902c00fd031556732c5c96a86f Mon Sep 17 00:00:00 2001 From: Nikita Prokopov Date: Mon, 19 Jan 2015 12:57:58 +0600 Subject: [PATCH] Support for nested maps in `transact!` (also fixes #38) --- CHANGELOG.md | 1 + src/datascript/core.cljs | 29 ++++++++++++------- src/datascript/impl/entity.cljs | 4 +-- test/test/datascript.cljs | 49 +++++++++++++++++++++++++++++++-- 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e9b60c6..bec8f359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Find specifications: collection `:find [?e ...]`, tuple `:find [?e ?v]`, and scalar `:find ?e .` - Support for `:db/isComponent` (issue #3) +- Support for nested maps in `transact!` (also fixes #38) - [ BREAKING ] Custom aggregate fns must be called via special syntax (`aggregate` keyword): `(q '[:find (aggregate ?myfn ?e) :in $ ?myfn ...])`. Built-in aggregates work as before: `(q '[:find (count ?e) ...]` - Return nil from `entity` when passed nil eid (issue #47) diff --git a/src/datascript/core.cljs b/src/datascript/core.cljs index 1f47ba03..7b7da85a 100644 --- a/src/datascript/core.cljs +++ b/src/datascript/core.cljs @@ -290,22 +290,31 @@ (update-in [:db-after] with-datom datom) (update-in [:tx-data] conj datom))) -(defn reverse-ref [attr] - (let [name (name attr)] - (when (= "_" (nth name 0)) - (keyword (namespace attr) (subs name 1))))) +(defn- reverse-ref? [attr] + (= "_" (nth (name attr) 0))) + +(defn- reverse-ref [attr] + (if (reverse-ref? attr) + (keyword (namespace attr) (subs (name attr) 1)) + (keyword (namespace attr) (str "_" (name attr))))) (defn- explode [db entity] (let [eid (:db/id entity)] - (for [[a vs] (dissoc entity :db/id) - :let [reverse-a (reverse-ref a)] + (for [[a vs] entity + :when (not= a :db/id) + :let [reverse? (reverse-ref? a) + straight-a (if reverse? (reverse-ref a) a) + _ (when (and reverse? (not (ref? db straight-a))) + (throw (js/Error. (str "Bad attribute " a ": reverse attribute name requires {:db/valueType :db.type/ref} in schema"))))] v (if (and (or (array? vs) (coll? vs)) (not (map? vs)) - (multival? db a)) + (or reverse? (multival? db a))) vs [vs])] - (if reverse-a - [:db/add v reverse-a eid] - [:db/add eid a v])))) + (if (and (ref? db straight-a) (map? v)) ;; another entity specified as nested map + (assoc v (reverse-ref a) eid) + (if reverse? + [:db/add v straight-a eid] + [:db/add eid straight-a v]))))) (defn- transact-add [report [_ e a v]] (let [tx (current-tx report) diff --git a/src/datascript/impl/entity.cljs b/src/datascript/impl/entity.cljs index 4603ce1b..af188536 100644 --- a/src/datascript/impl/entity.cljs +++ b/src/datascript/impl/entity.cljs @@ -125,8 +125,8 @@ (-lookup [_ attr not-found] (if (= attr :db/id) eid - (if-let [attr (dc/reverse-ref attr)] - (-lookup-backwards db eid attr not-found) + (if (dc/reverse-ref? attr) + (-lookup-backwards db eid (dc/reverse-ref attr) not-found) (or (cache attr) (if touched not-found diff --git a/test/test/datascript.cljs b/test/test/datascript.cljs index 0afa264a..f3dbdc40 100644 --- a/test/test/datascript.cljs +++ b/test/test/datascript.cljs @@ -414,8 +414,53 @@ (is (= (d/q '[:find ?n :where [_ :children ?e] [?e :name ?n]] db) - #{["Petr"] ["Evgeny"]}))))) - + #{["Petr"] ["Evgeny"]}))) + + (is (thrown-with-msg? js/Error #"Bad attribute :_parent" + (d/db-with db0 [{:name "Sergey" :_parent 1}]))))) + +(deftest test-explode-nested-maps + (let [schema { :profile { :db/valueType :db.type/ref }} + db (d/empty-db schema)] + (are [tx res] (= (d/q '[:find ?e ?a ?v + :where [?e ?a ?v]] + (d/db-with db tx)) res) + [ {:db/id 5 :name "Ivan" :profile {:db/id 7 :email "@2"}} ] + #{ [5 :name "Ivan"] [5 :profile 7] [7 :email "@2"] } + + [ {:name "Ivan" :profile {:email "@2"}} ] + #{ [1 :name "Ivan"] [1 :profile 2] [2 :email "@2"] } + + [ {:email "@2" :_profile {:name "Ivan"}} ] + #{ [1 :email "@2"] [2 :name "Ivan"] [2 :profile 1] } + )) + + (testing "multi-valued" + (let [schema { :profile { :db/valueType :db.type/ref + :db/cardinality :db.cardinality/many }} + db (d/empty-db schema)] + (are [tx res] (= (d/q '[:find ?e ?a ?v + :where [?e ?a ?v]] + (d/db-with db tx)) res) + [ {:db/id 5 :name "Ivan" :profile {:db/id 7 :email "@2"}} ] + #{ [5 :name "Ivan"] [5 :profile 7] [7 :email "@2"] } + + [ {:db/id 5 :name "Ivan" :profile [{:db/id 7 :email "@2"} {:db/id 8 :email "@3"}]} ] + #{ [5 :name "Ivan"] [5 :profile 7] [7 :email "@2"] [5 :profile 8] [8 :email "@3"] } + + [ {:name "Ivan" :profile {:email "@2"}} ] + #{ [1 :name "Ivan"] [1 :profile 2] [2 :email "@2"] } + + [ {:name "Ivan" :profile [{:email "@2"} {:email "@3"}]} ] + #{ [1 :name "Ivan"] [1 :profile 2] [2 :email "@2"] [1 :profile 3] [3 :email "@3"] } + + [ {:email "@2" :_profile {:name "Ivan"}} ] + #{ [1 :email "@2"] [2 :name "Ivan"] [2 :profile 1] } + + [ {:email "@2" :_profile [{:name "Ivan"} {:name "Petr"} ]} ] + #{ [1 :email "@2"] [2 :name "Ivan"] [2 :profile 1] [3 :name "Petr"] [3 :profile 1] } + )))) + (deftest test-joins (let [db (-> (d/empty-db) (d/db-with [ { :db/id 1, :name "Ivan", :age 15 }