Fork me on GitHub
#sql
<
2021-02-21
>
kulminaator10:02:09

hmm , i was reading over next-jdbc doc and somehow am failing to see a way how to construct queries with named placeholders

kulminaator10:02:21

in essence to write SELECT * FROM cities WHERE name = :town_name: and providing an extra param { :town_name "chicago" } .... is such functionality not present at all or am i just bad at reading the docs .... there is the ? placeholder support, but that gets horrid to use when you reach tables with 10 or 12 columns

seancorfield16:02:07

That sounds like a job for HoneySQL (which is mentioned in the next.jdbc docs).

emccue20:02:42

@kulminaator I have a macro for that

emccue20:02:01

;; ------------------------------------------------------------------------
(defn expand-named-parameters
  "Given a sql query like \"SELECT name FROM TABLE WHERE id = :id\"
  and a map of the form {:id ..., :other ...}, this will return
  a sqlvec representation of that query with the named parameters in
  the correct position"
  [sql-str params]
  (let [^Pattern replace-pattern  #":([a-zA-Z]|\-|[0-9])+"
        params-in-order (map first (re-seq replace-pattern sql-str))
        with-placeholders (string/replace sql-str replace-pattern "?")
        extract-param-value (fn [param]
                              (->> (keyword (.substring param 1 (count param)))
                                   (get params)))]
    (vec (cons with-placeholders
               (map extract-param-value params-in-order)))))

;; ------------------------------------------------------------------------
(defmacro expand-named-parameters-compile-time
  "Does the same work as [[expand-named-parameters]], but requires a
  string literal and a map literal in exchange for doing the parsing
  work at compile time."
  [sql-str params]
  (let [param-syms (map (fn [[name value]]
                          {:name name
                           :value value
                           :symbol (gensym)})
                        (seq params))
        param-map (into {} (map (juxt :name :symbol) param-syms))
        let-clause `(let ~(vec (mapcat (juxt :symbol :value) param-syms))
                      ~(expand-named-parameters sql-str param-map))]
    let-clause))

emccue20:02:33

(defn messages-since
  "returns a max `n` messages since the given instant."
  [{:keys [db]} {:keys [since n group-id]
                 :or {since (Instant/ofEpochMilli 0)
                      n Integer/MAX_VALUE}}]
  (jdbc/execute!
    db
    (db/expand-named-parameters-compile-time
      "SELECT * FROM \"group\"
       WHERE message.created_at > :since
       ORDER BY message.created_at ASC
       JOIN message ON message.to_group_id = :group-id
       LIMIT :n"
      {:since since
       :n n
       :group-id group-id})))

👍 3
emccue20:02:54

this is from a side project so uhh, you can have it MIT license or whatever

emccue20:02:09

so for your example it would be

emccue20:02:05

(jdbc/execute! 
  db
  (db/expand-named-parameters-compile-time 
    "SELECT * FROM cities WHERE name = :town_name"
    {:town_name "chicago"}))

seancorfield20:02:12

For comparison:

;; sql is honey.sql; select, from, where are from honey.sql.helpers:
(jdbc/execute! db (-> (select :*) (from :cities) (where [:= :name :?town_name]) (sql/format {:town_name "Chicago"}))

👍 3
kulminaator21:02:58

i dont mix sql with any language, not even clojure 🙂

kulminaator21:02:18

but ok, having a macro or a wrapping function around it indeed is not difficult to write

seancorfield21:02:29

We have a lot of very complex, conditionally composed queries in our reporting app -- a colleague gave a Clojure/West talk about it -- so HoneySQL is perfect for that 🙂 but I know some people lean more to things like HugSQL where you can keep your SQL and code completely separate (and which, I believe, supports named parameters).

kulminaator22:02:46

i often need to grab queries out from the programming langauge and launch at the database, if i have to start reverse engineering my own code at that point or add full logging to capture the actual statements and then run the captures .. i will get impatient quickly 🙂 (usually the need for this arises when the servers are hogged or almost down due to some anomaly, no time to play around)

kulminaator22:02:51

so whatever i use, has to stay native sql in writeup. placeholders are ok. mix of 2 languages is not my cup of tea.

seancorfield23:02:34

@kulminaator A final comment on HoneySQL v2 then: in response to specific requests -- for exactly that scenario -- there's an option for sql/format that renders the SQL statement with all the parameters inlined into the string so that you can just copy/paste it into your favorite database tool.

👍 3