clojurehugsql

Building WHEN clause with a clojure expression in hugsql


I have a database with a status entity that I'd like to be able to fetch in many different ways. As a result, I'd like to build the WHEN clause of my query based on the content of a map.

For instance, like this:

(get-status *db* {:message_id 2 :user_id 1 :status "sent"})
;; or
(get-status *db* {:message_id 2})
;; or
(get-status *db* {:user_id 1})
;; etc.

I'm struggling using hubsql's clojure expressions. I am doing the following:

-- :name get-status
-- :doc fetch the status of a specific message
-- :command :query
-- :return :many
/* :require [clojure.string :as s] */
SELECT
    *
FROM
    message_status
/*~
(let [params (filter (comp some? val) params)]
  (when (not (empty? params))
    (str "WHERE "
      (s/join ","
              (for [[field value] params]
                (str field " = " (keyword field)))))))
~*/

However, here is how the request is prepared by hugsql:

=> (get-status-sqlvec {:message_id 2 :user_id 1})
["SELECT\n    *\nFROM\n    message_status\nWHERE ? = ?,? = ?" 2 2 1 1]

Whereas I want something like:

=> (get-status-sqlvec {:message_id 2 :user_id 1})
["SELECT\n    *\nFROM\n    message_status\nWHERE message_id = 2, user_id = 1"]

Solution

  • I finally managed to get this working. The above code had two issues.

    First, we have

    (s/join ","
      (for [[field value] params]
        (str field " = " (keyword field)))
    

    Since field is a keyword, this actually generates this kind of string: :foo = :foo, :bar = :bar. The keywords are then replaced by ? by hugsql. What we want instead is build this kind of string foo = :foo, bar = :bar, which we can do with this code:

    (s/join ", "
      (for [[field _] params]
        (str (name field) " = " field))))))
    

    The second problem is that the WHEN clause is not even valid SQL. The above code ends up generating requests such as:

    SELECT * FROM message_status WHERE foo = 2, bar = 1
    

    The commas in the WHERE clause should be AND, so the final (working) code is:

    -- :name get-status
    -- :doc fetch the status of a specific message
    -- :command :query
    -- :return :many
    /* :require [clojure.string :as s] */
    SELECT
        *
    FROM
        message_status
    /*~
    (let [params (filter (comp some? val) params)]
      (when (not (empty? params))
        (str "WHERE "
          (s/join " AND "
                  (for [[field _] params]
                    (str (name field) " = " field))))))
    ~*/