Skip to content

Commit

Permalink
Support for nested maps in transact! (also fixes #38)
Browse files Browse the repository at this point in the history
  • Loading branch information
tonsky committed Jan 19, 2015
1 parent 2a96c2b commit 5ab2f99
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
29 changes: 19 additions & 10 deletions src/datascript/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/datascript/impl/entity.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 47 additions & 2 deletions test/test/datascript.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down

0 comments on commit 5ab2f99

Please sign in to comment.