Exactly One (This) Ref

Writing a messaging system, we have an entity with a “from” ref and a “to” many-cardinality ref. In classic messaging style, we want to be able to select all the messages between two individuals where no one else is in the loop. So,

Bob (to Ted): Hello
Ted (to Bob): Hello
Bob (to Ted and Carol and Alice): Did you get that thing I sent you?

So, if we’re trying to display conversations between Bob & Ted, we want to be able to say just those two, and not the messages that also pull in Carol and Alice.

I saw this which seems slightly off and a little overkill.

Might you model the conversation as a linked list?

For set logic in just datalog I have seen https://gist.github.com/eraserhd/c918cd1fa8cf06694b071c135e532125

I’m not at the point where I can really get your gist. I would have hoped there were standard ways of dealing with “many” refs. You know, select the entities with references that are exactly this, any combination of that, etc.

Do I pull them all out and run counts on the result set?

Hi Blake,

What you need is a not-join clause to filter out the messages to other users. Here’s an example (schema, sample data according to your scenario, and a query):

(note: this is a scrollable div below and some of the syntax highlight is wrong):

(d/transact conn {:tx-data [{:db/ident       :person/name
                               :db/valueType   :db.type/string
                               :db/unique      :db.unique/identity
                               :db/cardinality :db.cardinality/one}
                              {:db/ident :message/id
                               :db/valueType :db.type/long
                               :db/cardinality :db.cardinality/one}
                              {:db/ident       :message/to
                               :db/valueType   :db.type/ref
                               :db/cardinality :db.cardinality/many}
                              {:db/ident       :message/from
                               :db/valueType   :db.type/ref
                               :db/cardinality :db.cardinality/one}
                              {:db/ident       :message/text
                               :db/valueType   :db.type/string
                               :db/cardinality :db.cardinality/one}]})

  (d/transact conn {:tx-data [{:person/name "Bob"}
                              {:person/name "Ted"}
                              {:person/name "Carol"}
                              {:person/name "Alice"}]})

  (d/transact conn {:tx-data [{:message/id 1
                               :message/text "Hello"
                               :message/from [:person/name "Bob"]
                               :message/to #{[:person/name "Ted"]}}
                              {:message/id 2
                               :message/text "Hello"
                               :message/from [:person/name "Ted"]
                               :message/to #{[:person/name "Bob"]}}
                              {:message/id 3
                               :message/text " Did you get that thing I sent you?"
                               :message/from [:person/name "Bob"]
                               :message/to #{[:person/name "Ted"]
                                             [:person/name "Carol"]
                                             [:person/name "Alice"]}}]})

  (d/q '[:find (pull ?message [:message/id])
         :in $ ?p1 ?p2
         [?message :message/from ?p1]
         [?message :message/to ?p2]
         (not-join [?message]
           [?message :message/to ?p2]
           [?message :message/to ?p3]
           [(not= ?p2 ?p3)])]
       db [:person/name "Bob"] [:person/name "Ted"])
;; returns
[[{:message/:id 1}]]

The query selects all messages from Bob, ?p1, and to Ted, ?p2 and then removes from selection messages that are to ?p2 and ?p3, where ?p3 is not ?p2.

Thank you very much. That worked well.

1 Like