Search code examples
macrosclojurekorma

Clojure Korma: Cannot run an aggregate count


I'm trying to run a simple query using Clojure+Korma to extract the number of records. This is what I'm trying to do:

(defmacro number-of [ref & filter]
  `(let [basetmp# (-> (kc/select* ~ref)
                      (kc/aggregate (count :*) :cnt))]
     (if ~filter
       (-> basetmp#
           (kc/where ~filter))
       basetmp#)))

However if I try to use this macro I get an error message saying: Wrong number of args (2) passed to: core$count

The query would perfectly work if executed in a function but there's something wrong/missing in the macro I cannot spot :(

Thanks, Nico


Solution

  • As noted by ponzao you are picking up the wrong count.

    Looking at the macroexpansion

    (number-of 'foo)  ;; expands to....
    
    (clojure.core/let [basetmp__9167__auto__ (clojure.core/->
                                              (korma.core/select* 'foo)
                                              (korma.core/aggregate
                                                (clojure.core/count :*)
                                                :cnt))]
      (if nil
        (clojure.core/-> basetmp__9167__auto__ (korma.core/where nil))
        basetmp__9167__auto__))    
    

    So you need to prevent the count in your macro being expanded as clojure.core/count, you can do this with a unquote/quote thus:

    (defmacro number-of [ref & filter]
      `(let [basetmp# (-> (kc/select* ~ref)
                          (kc/aggregate (~'count :*) :cnt))]
         (if ~filter
           (-> basetmp#
               (kc/where ~filter))
           basetmp#)))
    

    Which then expands as expected...

    (clojure.core/let [basetmp__9137__auto__ (clojure.core/->
                                              (korma.core/select* 'foo)
                                              (korma.core/aggregate
                                                (count :*)
                                                :cnt))]
      (if nil
        (clojure.core/-> basetmp__9137__auto__ (korma.core/where nil))
        basetmp__9137__auto__))
    

    The resultant SQL looks reasonable:

    (kc/as-sql (number-of 'foo))
    "SELECT COUNT(*) \"cnt\" FROM \"foo\""
    

    UPDATE:
    From the comments "What does the count actually represent?" - If you realize that kc/aggregate is also a macro and that the arguments are a 'SQL aggregate' DSL of sorts, then you can expand the kc/aggregate call too. You find there's a function, parse-aggregate in engine.clj where it eventually maps to korma.sql.fn/agg-count:

    (clojure.core/let [q__2640__auto__ (kc/select* 'foo)]
      (korma.sql.engine/bind-query
        q__2640__auto__
        (clojure.core/let [res__2641__auto__ (korma.core/fields
                                               q__2640__auto__
                                               [(clojure.core/->
                                                 q__2640__auto__
                                                 (korma.sql.fns/agg-count
                                                   :*))
                                                :cnt])]
          (if nil
            (korma.core/group res__2641__auto__ nil)
            res__2641__auto__))))