Fork me on GitHub
#datomic
<
2022-08-16
>
jumar05:08:37

@jarrodctaylor first, thanks for the amazing Max Datom tutorial. I'm working on Level 12 and got stuck on this error :

clojure.lang.ExceptionInfo: 'comment-count-str' needs to be listed under :xforms in datomic/ion-config.edn {:cognitect.anomalies/category :cognitect.anomalies/forbidden, :cognitect.anomalies/message "'comment-count-str' needs to be listed under :xforms in datomic/ion-config.edn"}
Reading :xform option docs, it seems that the function indeed needs to be in specified in the config file - I thought it's gonna be there, but maybe this restriction is something that was only added after the course was created?

jumar05:08:45

Here's my query:

(d/q '[:find  (pull ?posts [{:post/author [:user/first+last-name]}
                              [:post/comments :xform comment-count-str]])
         :where [?posts :post/author _]]
       db)

jumar05:08:54

I'm also facing the same problem when running on my machine: https://github.com/jumarko/datomic-starter-sample/pull/10

jumar05:08:35

Found the answer here: https://www.reddit.com/r/Clojure/comments/tw66df/comment/i3kldxb/ The function call must be fully qualified - that feels a bit odd, compared to normal clojure function calls...

Jarrod Taylor (Clojure team)14:08:43

Glad you got it sorted out. Thanks for working through the app 🙂

rende1110:08:31

Hi! What is the best way to make optional filtering in query? The Desired behavior is when vector with user-group/uids is empty or nil - all users are selected, otherwise select users in provided groups.

(d/q '[:find ?uid 
         :in $ [?ugi-uid ...] 
         :where
         [?u :user/uid ?uid]
         [?ug :user-group/users ?u]
         [?ug :user-group/uid ?ugi-uid]]
       (user/ctx-db) [#uuid "622f8a38-91b6-4907-b281-62db36568454"])

thumbnail12:08:01

Just have two paths, one with filtering and one without. Resulting in two clear queries

☝️ 1
Mitchell Harris19:08:51

We’ve used the fact that the query is data to construct the query in code based on desired filters.

robert-stuttaford07:08:18

i recommend simply writing a different function for each situation, and not trying to invent query-as-data composition logic about it. you'll thank me later 🙂

;; original
(d/q '[:find ?uid
       :in $ [?ugi-uid ...]
       :where
       [?u :user/uid ?uid]
       [?ug :user-group/uid ?ugi-uid]
       [?ug :user-group/users ?u]]
     (user/ctx-db) [#uuid "622f8a38-91b6-4907-b281-62db36568454"])

;; this is slow; it'll find ALL the users, then find all the matching groups, and then finally filter ALL users by those groups.
;; for performance; we should restrict the scope as early as possible to reduce the number of datoms that are considered;
;; first find the groups that match the ids, then find all the users that are in those groups, and then only return those users' ids.

(d/q '[:find ?uid
       :in $ [?ugi-uid ...]
       :where
       [?ug :user-group/uid ?ugi-uid]
       [?ug :user-group/users ?u]
       [?u :user/uid ?uid]]
     (user/ctx-db) [#uuid "622f8a38-91b6-4907-b281-62db36568454"])

;; when no groups are specified, the query is a lot simpler:
(d/q '[:find ?uid
       :in $
       :where
       [?u :user/uid ?uid]]
     (user/ctx-db))

;; or far more simply (i assume :user/uid is indexed):
(map :v (d/datoms (user/ctx-db) :avet :user/uid))

;; so the final code would be two functions:
(defn user-ids-by-groups [db group-ids]
  (d/q '[:find ?uid
         :in $ [?ugi-uid ...]
         :where
         [?ug :user-group/uid ?ugi-uid]
         [?ug :user-group/users ?u]
         [?u :user/uid ?uid]]
       db group-ids))

(defn all-user-ids [db]
  (map :v (d/datoms db :avet :user/uid)))

rende1109:08:27

@U0509NKGK That works in simple cases, if I have 2+ conditions I will struggling with it

Ivar Refsdal09:08:12

I've been using d/query which takes a map as input with parameters as :query :where And then I'm putting it together on the search input variables with cond->. I'm doing something like this essentially:

(defn add-query [org new]
  (merge-with into org new))

(comment
  (let [a 1
        b nil
        c 3]
    (into (sorted-map)
          (cond->
            '{:find  [[(pull ?e pattern) ...]]
              :in    [$ pattern]
              :where []
              :args []}
            (some? a) (add-query {:where ['[?e :e/a ?a]]
                                  :in    ['?a]
                                  :args  [a]})
            (some? b) (add-query {:where ['[?e :e/b ?b]]
                                  :in    ['?b]
                                  :args  [b]})
            (some? c) (add-query {:where ['[?e :e/c ?c]]
                                  :in    ['?c]
                                  :args  [c]})))))

Ivar Refsdal09:08:18

Note that the actual params for d/query is slightly different: https://docs.datomic.com/on-prem/clojure/index.html#datomic.api/query The above was just an example, but should be easily adaptable to real world datomic.api/query usage

Ivar Refsdal09:08:24

Hope that helps 🙂

Ivar Refsdal09:08:08

Be careful with your queries though. I've been OOMing in production too many times.

Ivar Refsdal14:08:36

Full working example here:

(ns backend.datomic-search-demo
  (:require [datomic.api :as d]))

(def conn
  (let [uri (str "datomic:")]
    (d/delete-database uri)
    (d/create-database uri)
    (d/connect uri)))

(def schema
  [#:db{:ident :e/a, :cardinality :db.cardinality/one, :valueType :db.type/long}
   #:db{:ident :e/b, :cardinality :db.cardinality/one, :valueType :db.type/long}
   #:db{:ident :e/c, :cardinality :db.cardinality/one, :valueType :db.type/long}
   #:db{:ident :e/name, :cardinality :db.cardinality/one, :valueType :db.type/string}])

@(d/transact conn schema)

(defn add-query [org new]
  (merge-with into org new))

@(d/transact conn [{:e/a 1
                    :e/b 2
                    :e/name "First"}
                   {:e/c 3
                    :e/b 2
                    :e/a 1
                    :e/name "Second"}])

(defn make-query [{:keys [a b c]}]
  (let [query (cond->
                '{:find  [[(pull ?e [:*]) ...]]
                  :in    [$]
                  :where []
                  :args  []}
                (some? a) (add-query {:where ['[?e :e/a ?a]]
                                      :in    ['?a]
                                      :args  [a]})
                (some? b) (add-query {:where ['[?e :e/b ?b]]
                                      :in    ['?b]
                                      :args  [b]})
                (some? c) (add-query {:where ['[?e :e/c ?c]]
                                      :in    ['?c]
                                      :args  [c]}))]
    {:args (:args query)
     :query (select-keys query [:find :in :where])}))

(comment
  (-> (make-query {:a 1})
      (update :args (partial into [(d/db conn)]))
      (d/query)))

(comment
  (-> (make-query {:a 1 :c 3})
      (update :args (partial into [(d/db conn)]))
      (d/query)))
Hope that helps @U4U68ADKR

rende1118:08:52

Thx @UGJE0MM0W! My current solution is similar - just build query map with cond-> . Nice tip - use add-query - I'll take it)

👍 1