Q: How to "get in or default" in datomic? Full example in thread
My expectation: Get some result like
#{["With reply" "The reply"]
["No reply" "N/A"]}
my attempts:
(let [conn (-> "datomic:"
(doto d/delete-database d/create-database)
d/connect
(doto (-> (d/transact [{:db/ident :msg/text
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}
{:db/ident :msg/reply
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one}])
deref)))
{:keys [db-after]} @(d/transact conn
[{:msg/text "No reply"}
{:msg/text "With reply"
:msg/reply "reply"}
{:db/id "reply"
:msg/text "The reply"}])]
(d/q '[:find ?text ?reply
:in $
:where
[?msg :msg/text ?text]
(or-join [?msg ?reply]
(and [?msg :msg/reply ?r]
[?r :msg/text ?reply])
[(ground "N/A") ?reply])]
db-after))
=> #{["With reply" "The reply"] ["No reply" "N/A"] ["The reply" "N/A"] ["With reply" "N/A"]}
Pull with defaults is in general better for this. If it’s on same entity and cardinality one you can use get-else. If card many you can use a custom aggregate in the :find which removes your “none” sentinel, or a custom function which acts like get-else but works on card many
If you want to use or-join you need to make them mutually exclusive, eg by (not [?msg :msg/text]) in your ground case
Examples tailored to your case, where :msg/reply is a join. Use find to supply default, tabelize in clojure code
:find (pull ?msg [:msg/text {:msg/reply [(:msg/text :default "N/A")]}])
Make or-join branches mutually exclusive
(or-join [[?msg] ?reply]
(and [?msg :msg/reply ?r]
[?r :msg/text ?reply])
(and (not [?msg :msg/reply])
[(ground "N/A") ?reply]))
Different way, using get-else and impossible entity id as a sentinel
[?msg :msg/text ?text]
[(get-else $ ?msg :msg/reply -1) ?r]
(or-join [[?r] ?reply]
[?r :msg/text ?reply]
(and [(ground -1) ?r]
[(ground "N/A") ?reply]))
or
[?msg :msg/text ?text]
[(get-else $ ?msg :msg/reply -1) ?r]
[(get-else $ ?r :msg/text "N/A") ?reply]
If the :msg/text of the reply is optional too, you may need to adjust these
(I'd just use simple pull)
Managed to do with or-join + not
It seems simple to do with pull because I simplified a lot to make the example
But it is way more complicated
The query is 9+ lines long (just the where, without the or)
it would require to return some extra ids. do another query or fetch pull-many, join again with my data...