datomic

souenzzo 2025-05-03T02:56:35.345009Z

Q: How to "get in or default" in datomic? Full example in thread

souenzzo 2025-05-03T02:57:35.492759Z

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

favila 2025-05-03T03:01:51.095899Z

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

favila 2025-05-03T03:03:32.073819Z

If you want to use or-join you need to make them mutually exclusive, eg by (not [?msg :msg/text]) in your ground case

favila 2025-05-03T03:08:46.189069Z

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]

favila 2025-05-03T03:17:54.623229Z

If the :msg/text of the reply is optional too, you may need to adjust these

favila 2025-05-03T03:18:10.149989Z

(I'd just use simple pull)

souenzzo 2025-05-03T03:38:15.406709Z

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