datascript
datascript copied to clipboard
Reflexive tuples block certain rule-driven inferences
This is a thorny bug that only seems to happen when an entity is directly related to itself via some attribute. I've only tried this on the JVM, not in cljs. Here is the most minimal example I can construct:
(let [conn
(ds/create-conn
{:a {:db/valueType :db.type/ref}
:b {:db/valueType :db.type/ref}})
[x y]
(map - (range))
_
(ds/transact! conn [{:db/id x
:a y}
{:db/id y
:a y
:b x}])]
(ds/q '{:find [?p ?q]
:in [$ %]
:where [(bar ?p ?q)]}
@conn
'[[(foo ?p ?q)
[?p :b ?q]]
[(bar ?p ?q)
(baz ?p ?r)
(baz ?q ?r)]
[(baz ?p ?q)
[?p :a ?r]
(foo ?r ?q)]]))
;; => #{}
Removing a level of indirection yields the correct result -- both entities are related to themselves and to one another through rule bar
:
(let [conn
(ds/create-conn
{:a {:db/valueType :db.type/ref}
:b {:db/valueType :db.type/ref}})
[x y]
(map - (range))
_
(ds/transact! conn [{:db/id x
:a y}
{:db/id y
:a y
:b x}])]
(ds/q '{:find [?p ?q]
:in [$ %]
:where [(bar ?p ?q)]}
@conn
'[[(bar ?p ?q)
(baz ?p ?r)
(baz ?q ?r)]
[(baz ?p ?q)
[?p :a ?r]
[?r :b ?q] ;; inlining rule `foo`
]]))
;; => #{[0 0] [1 0] [1 1] [0 1]}
Likewise through this equivalent simplification:
(let [conn
(ds/create-conn
{:a {:db/valueType :db.type/ref}
:b {:db/valueType :db.type/ref}})
[x y]
(map - (range))
_
(ds/transact! conn [{:db/id x
:a y}
{:db/id y
:a y
:b x}])]
(ds/q '{:find [?p ?q]
:in [$ %]
:where [;; inlining rule `bar`
(baz ?p ?r)
(baz ?q ?r)]}
@conn
'[[(foo ?p ?q)
[?p :b ?q]]
[(baz ?p ?q)
[?p :a ?r]
(foo ?r ?q)]]))
;; => #{[0 0] [1 0] [1 1] [0 1]}
Compare the equivalent Datomic Datalog, which yields the correct result for the original query and rules (as well as for both simplified versions):
(let [uri
"datomic:mem://throwaway-db"
db
(do
(d/delete-database uri)
(d/create-database uri)
(-> uri d/connect d/db))
db1 ;; install schema first
(:db-after
(d/with db
[{:db/id (d/tempid :db.part/db)
:db/ident :a
:db/cardinality :db.cardinality/one
:db/valueType :db.type/ref}
{:db/id (d/tempid :db.part/db)
:db/ident :b
:db/cardinality :db.cardinality/one
:db/valueType :db.type/ref}]))
[x y]
(repeatedly (fn [] (d/tempid :db.part/user)))
db2
(:db-after
(d/with db1
[{:db/id x
:a y}
{:db/id y
:a y
:b x}]))]
(d/q '{:find [?p ?q]
:in [$ %]
:where [(bar ?p ?q)]}
db2
'[[(foo ?p ?q)
[?p :b ?q]]
[(bar ?p ?q)
(baz ?p ?r)
(baz ?q ?r)]
[(baz ?p ?q)
[?p :a ?r]
(foo ?r ?q)]]))
;; => #{[17592186045418 17592186045419] [17592186045419 17592186045418] [17592186045419 17592186045419] [17592186045418 17592186045418]}
Actually, it looks like this can be demonstrated with just one entity:
(let [conn
(ds/create-conn
{:a {:db/valueType :db.type/ref}
:b {:db/valueType :db.type/ref}})
_
(ds/transact! conn [{:db/id 0
:a 0
:b 0}])]
(ds/q '{:find [?p ?q]
:in [$ %]
:where [(bar ?p ?q)]}
@conn
'[[(foo ?p ?q)
[?p :b ?q]]
[(bar ?p ?q)
(baz ?p ?r)
(baz ?q ?r)]
[(baz ?p ?q)
[?p :a ?r]
(foo ?r ?q)]]))
;; => #{}
With the simplifications from above yielding the correct result #{[0 0]}
.
Can now confirm: I see the same behavior in CLJS.