Fork me on GitHub
#datomic
<
2021-04-12
>
ayushka15:04:37

I'm new to datomic and datalog, but it seems this is what the top answer on stack suggests for counting and grouping aggregates. I'm not trying to criticize or anything but I can probably write this query in SQL in ~5 lines. What am I missing about datalog? I'm quite frustrated at this point...

(defn find-by-id
  [conn id]
  (let [db (d/db conn)]
    (first (d/q '[:find
                    ?eid ?id ?title ?content-type ?content-url (sum ?likes) (sum ?dislikes)
                   :keys
                    :eid :id :title :content-type :content-url :likes :dislikes
                   :with
                    ?data-point
                   :in
                    $ ?id
                   :where
                   [?eid :post/id ?id]
                   [?eid :post/title ?title]
                   [?eid :post/content-type ?cref]
                   [?cref :db/ident ?content-type]
                   [?eid :post/content-url ?content-url]
                   (or-join [?eid ?data-point ?likes ?dislikes]
                            (and [?like :interaction/interactable-id ?eid]
                                 [?like :interaction/type :interaction.type/like]
                                 [(identity ?like) ?data-point]
                                 [(ground 1) ?likes]
                                 [(ground 0) ?dislikes])
                            (and [?dislike :interaction/interactable-id ?eid]
                                 [?dislike :interaction/type :interaction.type/dislike]
                                 [(identity ?dislike) ?data-point]
                                 [(ground 1) ?dislikes]
                                 [(ground 0) ?likes])
                            (and [(identity ?eid) ?data-point]
                                 [(ground 0) ?likes]
                                 [(ground 0) ?dislikes]))]
            db id))))

em20:04:52

@U01GXF0SQ48 So this is a pretty common problem to solve, and in the very beginning of using Datomic I struggled with similar things. The biggest mistake was thinking of it as a traditional database, where you had to shove the entire query into one request, and compose a huge complicated hairy mess, just because the database was "over there". One big difference with Datomic is that in both Peer and Ions, your code literally runs in memory with the data, and you really shouldn't limit yourself to writing giant hairballs like your current solution. It's not very maintainable and kind of orthogonal to the ideas behind Datomic of composability, and generally I find if my query is longer than 10 lines there's something going horribly wrong. Here's a couple of solutions: 1.) If you had the ability to introduce a direct counts attribute you could simplify your life a lot and cache these lookups - these could be guaranteed with transaction functions. Would be pretty simple to implement. Then your query would literally be 3 lines, a simple pull expression.

em20:04:46

2.) Break the query down! Your solution may be technically correct, but there's no reason not to break the query down. The work done by the instance is almost the same, and if you break down the same logic into multiple queries, you get the additional benefit of more granular query caching. And reusability. And composability with more parts of your application. And readability. Consider a generic helper function that counts interactions:

(defn post-interaction-count 
  [interaction db post-id]
  (ffirst (d/q '[:find (sum ?likes)
                 :in $ ?post-id ?interaction
                 :where [?eid :post/id ?post-id]
                 [?likes :interaction/interactable-id ?eid]
                 [?likes :interaction/type ?interaction]])
          db post-id interaction))

em20:04:13

This is pretty generic for posts, and you could then have:

(def post-likes (partial post-interaction-count :interaction.type/like))
(def post-dislikes (partial post-interaction-count :interaction.type/dislike))

em20:04:10

And then your complete function is super simple, and very readable:

(defn post-by-id 
  [db post-id]
  (-> (d/q '[:find (pull ?eid [:post/id :post/title :post/content-url {:post/content-type [:db/ident]}])
             :in $ ?post-id
             :where [?eid :post/id ?post-id]]
           db post-id)
      first
      (merge {:post/likes (post-likes db post-id)
              :post/dislikes (post-dislikes db post-id)})))

em20:04:56

If :post/id is registered as an identity attribute you could simplify the pull expression even further with the pull API, shaving off another 2-3 lines. Obviously I didn't have access to your setup/database etc., so the code above may not run as-is, and notably I changed the semantics a bit, like passing around a db instead of a conn. (reason: a lot of times in the context of one web request, you actually want to keep the database value the same, and only request it once on the connection per request. Every post lookup on potentially different databases kinda defeats the purpose of Datomic's ability to give you the db as-of, which solves lots of application bugs and other unwanted issues/race conditions). Hope this helps!

🎯 3
ayushka13:04:18

@UNRDXKBNY wow that was a blog post level reply. Thanks so much for the pointers.

👍 2
souenzzo16:04:20

How to fix Error building classpath. Could not find artifact com.datomic:ion:jar:0.9.50 in central () ? - My ~/.clojure/deps.edn contains "datomic-cloud" {:url ""} - I can do aws s3 ls - it's a well-configured project (other developers are using it)

Joe Lane17:04:29

@souenzzo Use the --profile and --region flags to make sure the java process which gets the jar has those credentials.

3
Joe Lane17:04:40

I need more information about what program you're running that throws Error building classpath. .... I don't have nearly enough details to help.

futuro19:04:53

Do attributes predicates need to be explicitly allowed in the datomic/ion-config.edn file? Last week I didn't specify it and everything worked alright in dev-local, and this week I'm getting an anomaly returned saying I have to add the function to the :allow list in the ion config, though the docs for attribute predicates don't list this as a requirement.

futuro19:04:30

I'm fine either way, but the change in behavior from last week to this, plus the lack of a mention in the docs, leads me to wonder if something else is going on.

Joe Lane19:04:33

attribute predicates are ions

Joe Lane19:04:56

ions used in transactions must be in the :allow list

futuro19:04:30

That makes sense, thanks Joe!