How do I use a symbol, such as a function name, inside of a variable?

I want to use some “operators” that are used with MongoDB.

I’m using this library:

and I include this code:

(:require
[fourth.logging :as log]
[overtone.at-at :as at]
[clojure.java.io :as io]
[mongo-driver-3.client :as mcl]
[mongo-driver-3.collection :as mc]
[mongo-driver-3.operator :refer [$gt]]
[java-time :as jt])

Assume params is some JSON sent in from the outside world, via some API call. This JSON contains fields or sub documents that correspond to the main options that I need to work with to get what I need from MongoDB:

find – a map to match documents in the database

limit

skip

sort

projection – what fields to return

I use a multi method for this, in the default version I hardcode some values, and this works:

(mc/find db "ai" (:filter params) {
                                         :keywordize? true
                                         :realise-fn (partial into [])
                                         :projection {:_id 0}
                                         :sort {:created-at -1}
                                         }))

But I would like to use this operator:

$gt

which is for “greater than”.

To make my life easy I was going to allow the API to hardcode the field and value for this comparison, and only allow one use of $gt per API call.

So I tried to do this:

(defn query-format
  [params]
  (let [
        greater-than-field (:greater-than-field params)
        greater-than-value (:greater-than-value params)

        filter (if (and
                    greater-than-field
                    greater-than-value)
                 (merge filter {
                                greater-than-field { $gt greater-than-value }
                                })
                 filter)
        params (assoc params :filter filter)
        ]
    (query params)))

Which I was hoping would be easy.

It does compile, so the compiler recognizes the $gt symbol in this context.

But when I run this and then try to call this I get:

java.lang.ClassCastException: class clojure.core$filter cannot be cast to class org.bson.conversions.Bson

I’m not sure what this means, but I assume something is unhappy when it looks in the filter var and sees another var?

Is there a correct way to do this?

The documentation on this MongoDB driver repo says I could also do this:

“gt”

And I tried that but I got the same error. So it make not be about the symbol $gt. Instead it is something about filter not converting correctly. Not sure how to make this work.

It does compile, so the compiler recognizes the $gt symbol in this context.

What do you mean by “compile” here?

java.lang.ClassCastException: class clojure.core$filter cannot be cast to class org.bson.conversions.Bson

Take a close look at the filter binding. It’s value also uses filter, but it hasn’t been defined anywhere previously, at least not in the code that you provided. It means, that it actually uses clojure.core/filter - exactly as the error says (apparently, the condition in the filter binding is also false).
So, the error has nothing to do with $gt.

What the fix is depends on what you want to achieve - I have no idea what that filter is supposed to be on the RHS of the binding.

But it should’ve also failed on $gt, unless you have (def $gt ...) or something like that above this code.

Technically, :$gt is not a valid keyword in Clojure, but the reader doesn’t seem to mind. And you can also create it with (keyword "$gt") (maybe that’s what the $gt var is bound to already?).

1 Like

I did get this to work, where I hardcode the use of $gt. I didn’t figure out how to pass it inside of a variable, but this works:

(defn query-format
  ;; [filter sort return limit skip]
  [params]
  (try
    (let [
          db (nth dbs (rand-int (count dbs)))
          params (clojure.walk/keywordize-keys params)
          filter (:filter params)

          greater-than-field (:greater-than-field params)
          greater-than-value (:greater-than-value params)
          ]

      (log/log "in query-format greater-than-field greater-than-value : " greater-than-field " " greater-than-value)
      (mc/find db "ai" (merge filter {
                                      greater-than-field { $gt greater-than-value }
                                      }){
                                         :keywordize? true
                                         :realise-fn (partial into [])
                                         :projection {:_id 0}
                                         :sort {:created-at -1}
                                         }))
    (catch Exception e
      (log/elog e))))

Good point about me using “filter” and that conflicting with the function name. I’ll change my use of “filter”.

About this:

$gt

It is defined in the library I was using, and I import it here:

[mongo-driver-3.operator :refer [$gt]]

Ah, I didn’t notice that :require - I almost never use :refer myself. When you fix the filter issue, it should work just fine then.

These operators are just strings, in our internal usage of Mongo my team has decided to just not use them, you can use keywords rather than referring to these vars.
Bonus points: mongo-driver-3 is missing a couple of operators already, this approach allows you to use whatever you need.

So this works as expected:

(mcoll/find db coll {:foobar {:$gt 1 }})