sql

All things SQL and JDBC...
kulminaator 2021-02-21T10:09:09.001Z

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

kulminaator 2021-02-21T10:11:21.002800Z

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

borkdude 2021-02-21T11:40:54.002900Z

emccue 2021-02-21T20:37:42.003500Z

@kulminaator I have a macro for that

emccue 2021-02-21T20:38:01.003800Z

;; ------------------------------------------------------------------------
(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))

emccue 2021-02-21T20:38:33.004100Z

(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})))

1👍
emccue 2021-02-21T20:38:54.004600Z

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

emccue 2021-02-21T20:40:09.005Z

so for your example it would be

emccue 2021-02-21T20:41:05.006200Z

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

seancorfield 2021-02-21T20:52:12.008400Z

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"}))

1👍
kulminaator 2021-02-21T21:05:58.009Z

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

kulminaator 2021-02-21T21:06:18.009500Z

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

seancorfield 2021-02-21T21:14:29.011600Z

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).

kulminaator 2021-02-21T22:06:46.013100Z

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)

kulminaator 2021-02-21T22:07:51.014Z

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

seancorfield 2021-02-21T23:05:34.015600Z

@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.

1👍