datascript icon indicating copy to clipboard operation
datascript copied to clipboard

Reflexive tuples block certain rule-driven inferences

Open DalekBaldwin opened this issue 7 years ago • 2 comments

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]}

DalekBaldwin avatar May 30 '17 05:05 DalekBaldwin

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]}.

DalekBaldwin avatar May 30 '17 07:05 DalekBaldwin

Can now confirm: I see the same behavior in CLJS.

DalekBaldwin avatar Jul 04 '17 22:07 DalekBaldwin