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

Transaction / Indexing bug #376

Open den1k opened 3 years ago

den1k commented 3 years ago

In a transaction where a map has refs and reverse-ref attributes, the ref attributes are thrown away.

This works:


(require '[datascript.core :as d])

(def db
  (d/empty-db
    {:foo/id        {:db/unique :db.unique/identity}
     :foo/relations {:db/valueType   :db.type/ref
                     :db/cardinality :db.cardinality/many}}))

(def db-with-foo
  (d/db-with
    db
    [{:foo/id        "foo1"
      :foo/relations [{:foo/id "foo2"}
                      {:foo/id "foo3"}]}]))

(comment
 (d/touch (d/entity db-with-foo [:foo/id "foo3"]))
; works!
; => {:foo/id "foo3", :db/id 3}
)

This fails for :foo/relations [{:foo/id "foo3"}] but works for the reverse :foo/_relations:


(def db-with-foo-backwards
  (d/db-with
    db
    [{:foo/id         "foo2"
      :foo/_relations [{:foo/id "foo1" :foo/relations [{:foo/id "foo3"}]}]}]))

(comment
 (d/touch (d/entity db-with-foo-backwards [:foo/id "foo3"]))
; errors!
; => #object[Error Error: Assert failed: (entity? e)] 

; relation between "foo1" and "foo2" exists
(d/touch (d/entity db-with-foo-backwards [:foo/id "foo1"])) 
 ; => {:foo/id "foo1", :foo/relations #{#:db{:id 1}}, :db/id 2}
)

Looking at the indexes of either DB we see that the relations and datoms are missing in db-with-foo-backwards:

(comment
 (:aevt db-with-foo)
; =>
;#{#datascript/Datom[1 :foo/id "foo1" 536870913 true]
;  #datascript/Datom[2 :foo/id "foo2" 536870913 true]
;  #datascript/Datom[3 :foo/id "foo3" 536870913 true] ; <-- foo3 exists!
;  #datascript/Datom[1 :foo/relations 2 536870913 true]
;  #datascript/Datom[1 :foo/relations 3 536870913 true]}
)
(comment
  (:aevt db-with-foo-backwards)
; =>
;#{#datascript/Datom[1 :foo/id "foo2" 536870913 true]
;  #datascript/Datom[2 :foo/id "foo1" 536870913 true] ; <-- foo3 and relations missing!
;  #datascript/Datom[2 :foo/relations 1 536870913 true]}
)
den1k commented 3 years ago

The common use case where this matters anytime one wants to run a pull query on one database and transact the result into another. In my case I have a backend and frontend DataScript DB. Based on the route the backend runs d/pull and sends the result to the frontend to be transacted. Since the pull result contains both forward and backward refs, this fails.

tonsky commented 3 years ago

Looks like a valid use-case, I’ll look into it, thank you!