Fork me on GitHub
#datomic
<
2020-10-19
>
schmee09:10:54

can I write a shortest path query in Datomic, e.g can I determine if it is possible to navigate from Entity A to Entity B via some reference attribute?

favila17:10:40

Here is a trivial example:

favila17:10:44

'[[(path-exists? ?e1 ?a ?e2)
   [?e1 ?a ?e2]]
  [(path-exists? ?e1 ?a ?e2)
   [?e1 ?a ?e-mid]
   [(!= ?e2 ?e-mid)]
   [(path-exists? ?e-mid ?a ?e2)]]]

favila17:10:52

the general pattern with recursive rules is to define the rule multiple times, and have one that is terminal, and the rest recursive, and (generally but not required) the rule impls match disjoint sets.

favila17:10:57

unfortunately there’s no “cut” to stop evaluation early. I’m pretty sure this example will exhaustively discover every possible path, even though any one will do. However, it may discover them in parallel.

schmee17:10:33

thank you for the detailed example! 🙂

favila17:10:18

Note this example only searches refs in a forward direction. With two additional implementations, it could search backwards also

schmee09:10:21

I’ve looked at all the examples of recursive rules that I could find and they all “hardcode” the depth of the search (such as the MBrainz example: https://github.com/Datomic/mbrainz-sample/blob/master/src/clj/datomic/samples/mbrainz/rules.clj#L37)

kenny16:10:58

I would've expected the below query to return all txes where ?tx is not in ?ignore-tx. I actually get all txes, as if the not is completely ignore. ?ignore-tx is passed in as a set of tx ids. Why would this happen?

'[:find ?t ?status ?tx ?added
  :in $ [?ignore-tx ...]
  :where
  [?t ::task/status ?status ?tx ?added]
  (not [(identity ?ignore-tx) ?tx])]

favila16:10:05

datalog comparisons are not “type”-aware. are all ?ignore-tx actually tx longs and not some other representation?

kenny16:10:43

Yes

(type (first ignore-txes))
=> java.lang.Long

favila16:10:51

are they T or TX?

favila16:10:01

(both are longs, but TXs have partition bits)

favila16:10:43

does this behave differently? [(!= ?ignore-tx ?tx)]

favila16:10:52

(instead of (not …)

kenny16:10:34

Same result

favila16:10:23

print (first ignore-txes) ?

kenny16:10:45

(first ignore-txes)
=> 13194142112981

favila16:10:11

and you’re actually sure this is in the result set? You can test with `

'[:find ?t ?status ?tx ?added
  :in $ [?tx ...]
  :where
  [?t ::task/status ?status ?tx ?added]
  ]

kenny16:10:35

(d/q {:query '[:find ?t ?status ?tx ?added
                 :in $ [?ignore-tx ...]
                 :where
                 [?t ::task/status ?status ?tx ?added]
                 [(!= ?ignore-tx ?tx)]
                 [?tx :audit/user-id ?user]]
        :args  [(d/history (d/db conn))
                #{13194142035321 13194142112981}]
        :limit 10000})
=>
[[606930421025569 :cs.model.task/status-in-progress 13194142112981 false]
 [606930421025569 :cs.model.task/status-in-progress 13194142035321 true]
 [606930421025569 :cs.model.task/status-open 13194142112981 true]
 [606930421025569 :cs.model.task/status-open 13194142035321 false]]

kenny16:10:34

Identical result with (not [(identity ?ignore-tx) ?tx]).

favila16:10:41

That is really weird. I can’t reproduce with a toy example

favila16:10:43

(d/q '[:find ?e ?stat ?tx ?op
       :in $ [?ignore-tx ...]
       :where
       [?e :status ?stat ?tx ?op]
       [(!= ?ignore-tx ?tx)]
       ]
     [[1 :status :foo 100 true]
      [1 :status :bar 100 false]]
     #{100}
     )

kenny16:10:30

Yeah - that's what I would expect

favila16:10:05

what about using contains?

favila16:10:41

(d/q '[:find ?e ?stat ?tx ?op
       :in $ ?ignore-txs
       :where
       [?e :status ?stat ?tx ?op]
       (not [(contains? ?ignore-txs ?tx)])
       ]
     [[1 :status :foo 13194142112981 true]
      [1 :status :bar 13194142112981 false]
      [1 :status :baz 13194142112982 true]]
     #{13194142112981}
     )
=> #{[1 :baz 13194142112982 true]}

favila16:10:33

I’m just kinda probing to see if this is a problem with comparisons or something deeper

kenny16:10:55

(d/q {:query '[:find ?t ?status ?tx ?added
                 :in $ ?ignore-tx
                 :where
                 [?t ::task/status ?status ?tx ?added]
                 (not [(contains? ?ignore-tx ?tx)])
                 [?tx :audit/user-id ?user]]
        :args  [(d/history (d/db conn))
                #{13194142035321 13194142112981}]})
=> []

kenny16:10:44

That's the expected result. Still odd that the former didn't work.

kenny16:10:03

Even odder is that it worked in your toy example.

favila16:10:06

I think that points to something funky with the numeric comparisons done by the datalog engine, like it’s using object identity or something.

favila16:10:45

my toy example used on-prem, but should be able to replicate with cloud or peer-server

favila17:10:14

I was using 1.0.6165

kenny17:10:18

This is using the client api 0.8.102 and connecting to a system running in the cloud.

kenny17:10:38

Seems to work as expected with dev-local as well.

kenny17:10:46

Datomic Cloud includes :db-name and :database-id as get'able keys from a d/db. Are these part of the official API?

kenny17:10:23

e.g.,

(d/db conn)
=>
{:t 2580397,
 :next-t 2580398,
 :db-name "my-db",
 :database-id "74353541-feea-4ea2-afa6-f522a169856d",
 :type :datomic.client/db}

kenny17:10:23

If that is true, shouldn't dev-local support that? See below example using dev-local 0.9.203.

(def c2 (d/client {:server-type :dev-local,
                   :system "dev-local-bB7z07Io_A",
                   :storage-dir "/home/kenny/.datomic/data/dev-local-bB7z07Io_A"}))
(d/db (d/connect c2 {:db-name "cust-db__0535019e-79fe-44a1-a8d9-b19394abd958"}))


(:db-name *1)
=> nil

kenny17:10:23

Fairly certain this is a bug so I opened a support req: https://support.cognitect.com/hc/en-us/requests/2879