tonsky / datascript

Immutable database and Datalog query engine for Clojure, ClojureScript and JS
Eclipse Public License 1.0
5.45k stars 304 forks source link

Recursive reverse lookups not working quite like it should #451

Closed chuckberrypi closed 1 year ago

chuckberrypi commented 1 year ago

With this setup:

 (let [conn (ds/create-conn {:code/label      {:db/cardinality :db.cardinality/one}
                                               :code/children {:db/cardinality :db.cardinality/many
                                                                         :db/valueType   :db.type/ref
                                                                         :db/isComponent true}})]
    (ds/transact! conn [{:db/id             1
                                       :code/label    "One"
                                       :code/children [2 3]}
                                      {:db/id      2
                                       :code/label "Two"}
                                      {:db/id         3
                                       :code/label    "Three"
                                       :code/children [4]}
                                      {:db/id      4
                                       :code/label "Four"}])
    [...]
    )

(ds/pull @conn '[{:code/_children ...}] 4) results in a nil, whereas I think it should pull all of the ancestors of 4. Thanks to a suggestion on slack, I tried (ds/pull @conn '[:db/id {:code/_children ...}] 4) and got the expected result: {:db/id 4, :code/_children {:db/id 3, :code/_children #:db{:id 1}}}. If I am reading the datomic docs correctly, the former should work.

tonsky commented 1 year ago

Ok, I think I know what’s going on. When you pull just '[{:code/_children ...}], you are not actually pulling any content. Like, it navigates references, but when it arrives to "One", it will see no more children, but also no content to fetch for "One" as well. Not a single attribute. So it reports “empty”, because it’s a convention to not return empty maps. Then its parent gets empty, and all up to the very beginning.

If you add at least one attribute that actually exist in a final entity, the whole tree is reported back. Because leaf is not empty.

Datomic has the same behaviour, actually. As I’m trying to be close to it API-wise, I think I’ll keep it, too. It might be counter-intuitive, but maybe you don’t usually need to fetch zero fields for your entities?

Here’s your test case reproduced in Datomic:

Screenshot 2023-07-14 at 20 14 22