datomic

enn 2026-04-17T18:29:14.239129Z

I'm going through my application and wrapping all of my queries in a linter function that (in development) sets :query-stats to true, and flags when it warns about unbound variables. I'm noticing that Datomic will warn about queries where the query matches only on a literal value in-line in the :where clause:

(d/q '[:find [?foo ...] :in $ :where [?foo :foo/tag "some-tag"]] db)
but not if the value is passed as an argument:
(d/q '[:find [?foo ...] :in $ ?tag :where [?foo :foo/tag ?tag]] db "some-tag")
I know the second style is more correct, but my intuition is that the performance should be the same for both queries. But maybe my intuition is wrong? I'm asking because I'm trying to decide whether to address this by fixing the queries (some are dynamic so it's nontrivial to fix) or the linter. If the more-correct form is genuinely faster, there is a stronger case for fixing the queries.

favila 2026-04-17T19:33:22.090509Z

What is the warning you're looking for?

favila 2026-04-17T19:34:57.019449Z

In general, you want to keep the number of distinct query forms in a process as low as possible. query form compilation is cached, and the cache has limited size. In addition, function expression forms generate eval calls during compilation, thus create distinct classes, thus can use a lot of metaspace over time.

enn 2026-04-17T20:05:30.049189Z

What is the warning you're looking for?The :unbound-vars warning (I believe this is the only warning :query-stats currently emits? at least, it's the only one documented at https://docs.datomic.com/reference/query-stats.html#warnings and the only one I have encountered in practice.) I had not considered the query form compilation aspect, thank you.

favila 2026-04-17T20:05:59.086959Z

but you want this warning, or don't?

favila 2026-04-17T20:07:13.180429Z

It sounds like this warning isn't aware of all binds from :in ?

enn 2026-04-17T21:13:19.163879Z

I think I'm arguing that it should not warn for (d/q '[:find [?foo ...] :in $ :where [?foo :foo/tag "some-tag"]] db) because the "some-tag" literal in that where expression is functionally equivalent to a bound lvar in the same position.

favila 2026-04-17T21:14:32.338459Z

Could you paste the query-stats you are seeing?

favila 2026-04-17T21:17:47.174289Z

I think I see what you mean:

(datomic.api/query {:query '[:find [?foo ...] :in $ :where [?foo :foo/tag "some-tag"]]
                    :args [[]]
                    :query-stats true})
=>
{:ret [],
 :query-stats {:query [:find [?foo ...] :in $ :where [?foo :foo/tag "some-tag"]],
               :phases [{:sched (([?foo :foo/tag "some-tag"])),
                         :clauses [{:clause [?foo :foo/tag "some-tag"],
                                    :rows-in 0,
                                    :rows-out 0,
                                    :binds-in (),
                                    :binds-out [?foo],
                                    :warnings {:unbound-vars #{?foo}}}]}]}}
(datomic.api/query {:query '[:find [?foo ...] :in $ ?tag :where [?foo :foo/tag ?tag]]
                    :args [[] "some-tag"]
                    :query-stats true})
=>
{:ret [],
 :query-stats {:query [:find [?foo ...] :in $ ?tag :where [?foo :foo/tag ?tag]],
               :phases [{:sched (([(ground $__in__2) ?tag] [?foo :foo/tag ?tag])),
                         :clauses [{:clause [(ground $__in__2) ?tag],
                                    :rows-in 0,
                                    :rows-out 1,
                                    :binds-in (),
                                    :binds-out [?tag],
                                    :expansion 1}
                                   {:clause [?foo :foo/tag ?tag],
                                    :rows-in 1,
                                    :rows-out 0,
                                    :binds-in [?tag],
                                    :binds-out [?foo]}]}]}}
The warning for "unbound vars" is about unbound vars on input to the row, not output