2019-04-01 01:22:04 +00:00
|
|
|
;; copyright (c) 2019 Sean Corfield, all rights reserved
|
|
|
|
|
|
|
|
|
|
(ns next.jdbc.sql
|
|
|
|
|
"Utilities to construct SQL strings (and lists of parameters) for
|
|
|
|
|
various types of SQL statements.
|
|
|
|
|
|
|
|
|
|
This is intended to provide a minimal level of parity with clojure.java.jdbc
|
|
|
|
|
(insert!, update!, delete!, etc). For anything more complex, use a library
|
2019-04-01 06:17:12 +00:00
|
|
|
like HoneySQL https://github.com/jkk/honeysql to generate SQL + parameters.
|
|
|
|
|
|
|
|
|
|
This is primarily intended to be an implementation detail."
|
2019-04-01 01:22:04 +00:00
|
|
|
(:require [clojure.string :as str]))
|
|
|
|
|
|
|
|
|
|
(defn by-keys
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a hash map of column names and values and a clause type (:set, :where),
|
|
|
|
|
return a vector of a SQL clause and its parameters.
|
|
|
|
|
|
|
|
|
|
Applies any :entities function supplied in the options."
|
2019-04-01 01:22:04 +00:00
|
|
|
[key-map clause opts]
|
|
|
|
|
(let [entity-fn (:entities opts identity)
|
|
|
|
|
[where params] (reduce-kv (fn [[conds params] k v]
|
|
|
|
|
(let [e (entity-fn (name k))]
|
|
|
|
|
(if (and (= :where clause) (nil? v))
|
|
|
|
|
[(conj conds (str e " IS NULL")) params]
|
|
|
|
|
[(conj conds (str e " = ?")) (conj params v)])))
|
|
|
|
|
[[] []]
|
|
|
|
|
key-map)]
|
|
|
|
|
(into [(str (str/upper-case (name clause)) " "
|
|
|
|
|
(str/join (if (= :where clause) " AND " ", ") where))]
|
|
|
|
|
params)))
|
|
|
|
|
|
|
|
|
|
(defn as-keys
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a hash map of column names and values, return a string of all the
|
|
|
|
|
column names.
|
|
|
|
|
|
|
|
|
|
Applies any :entities function supplied in the options."
|
2019-04-01 01:22:04 +00:00
|
|
|
[key-map opts]
|
|
|
|
|
(str/join ", " (map (comp (:entities opts identity) name) (keys key-map))))
|
|
|
|
|
|
|
|
|
|
(defn as-?
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a hash map of column names and values, or a vector of column names,
|
|
|
|
|
return a string of ? placeholders for them."
|
2019-04-01 01:22:04 +00:00
|
|
|
[key-map opts]
|
|
|
|
|
(str/join ", " (repeat (count key-map) "?")))
|
|
|
|
|
|
|
|
|
|
(defn for-query
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a table name and either a hash map of column names and values or a
|
|
|
|
|
vector of SQL (where clause) and its parameters, return a vector of the
|
|
|
|
|
full SELECT SQL string and its parameters.
|
|
|
|
|
|
|
|
|
|
Applies any :entities function supplied in the options."
|
2019-04-01 02:30:01 +00:00
|
|
|
[table where-params opts]
|
2019-04-01 01:22:04 +00:00
|
|
|
(let [entity-fn (:entities opts identity)
|
2019-04-01 02:30:01 +00:00
|
|
|
where-params (if (map? where-params)
|
|
|
|
|
(by-keys where-params :where opts)
|
|
|
|
|
(into [(str "WHERE " (first where-params))]
|
|
|
|
|
(rest where-params)))]
|
2019-04-01 01:22:04 +00:00
|
|
|
(into [(str "SELECT * FROM " (entity-fn (name table))
|
|
|
|
|
" " (first where-params))]
|
|
|
|
|
(rest where-params))))
|
|
|
|
|
|
2019-04-01 02:30:01 +00:00
|
|
|
(defn for-delete
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a table name and either a hash map of column names and values or a
|
|
|
|
|
vector of SQL (where clause) and its parameters, return a vector of the
|
|
|
|
|
full DELETE SQL string and its parameters.
|
|
|
|
|
|
|
|
|
|
Applies any :entities function supplied in the options."
|
2019-04-01 02:30:01 +00:00
|
|
|
[table where-params opts]
|
|
|
|
|
(let [entity-fn (:entities opts identity)
|
|
|
|
|
where-params (if (map? where-params)
|
|
|
|
|
(by-keys where-params :where opts)
|
|
|
|
|
(into [(str "WHERE " (first where-params))]
|
|
|
|
|
(rest where-params)))]
|
|
|
|
|
(into [(str "DELETE FROM " (entity-fn (name table))
|
|
|
|
|
" " (first where-params))]
|
|
|
|
|
(rest where-params))))
|
|
|
|
|
|
2019-04-01 01:22:04 +00:00
|
|
|
(defn for-update
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a table name, a vector of column names to set and their values, and
|
|
|
|
|
either a hash map of column names and values or a vector of SQL (where clause)
|
|
|
|
|
and its parameters, return a vector of the full UPDATE SQL string and its
|
|
|
|
|
parameters.
|
|
|
|
|
|
|
|
|
|
Applies any :entities function supplied in the options."
|
2019-04-01 01:22:04 +00:00
|
|
|
[table key-map where-params opts]
|
|
|
|
|
(let [entity-fn (:entities opts identity)
|
|
|
|
|
set-params (by-keys key-map :set opts)
|
|
|
|
|
where-params (if (map? where-params)
|
|
|
|
|
(by-keys where-params :where opts)
|
|
|
|
|
(into [(str "WHERE " (first where-params))]
|
|
|
|
|
(rest where-params)))]
|
|
|
|
|
(-> [(str "UPDATE " (entity-fn (name table))
|
|
|
|
|
" " (first set-params)
|
|
|
|
|
" " (first where-params))]
|
|
|
|
|
(into (rest set-params))
|
|
|
|
|
(into (rest where-params)))))
|
|
|
|
|
|
|
|
|
|
(defn for-insert
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a table name and a hash map of column names and their values,
|
|
|
|
|
return a vector of the full INSERT SQL string and its parameters.
|
|
|
|
|
|
|
|
|
|
Applies any :entities function supplied in the options."
|
2019-04-01 01:22:04 +00:00
|
|
|
[table key-map opts]
|
|
|
|
|
(let [entity-fn (:entities opts identity)
|
|
|
|
|
params (as-keys key-map opts)
|
|
|
|
|
places (as-? key-map opts)]
|
|
|
|
|
(into [(str "INSERT INTO " (entity-fn (name table))
|
|
|
|
|
" (" params ")"
|
|
|
|
|
" VALUES (" places ")")]
|
|
|
|
|
(vals key-map))))
|
|
|
|
|
|
2019-04-01 02:30:01 +00:00
|
|
|
(defn for-insert-multi
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a table name, a vector of column names, and a vector of row values
|
|
|
|
|
(each row is a vector of its values), return a vector of the full INSERT
|
|
|
|
|
SQL string and its parameters.
|
|
|
|
|
|
|
|
|
|
Applies any :entities function supplied in the options."
|
2019-04-01 02:30:01 +00:00
|
|
|
[table cols rows opts]
|
|
|
|
|
(assert (apply = (count cols) (map count rows)))
|
|
|
|
|
(let [entity-fn (:entities opts identity)
|
|
|
|
|
params (str/join ", " (map (comp entity-fn name) cols))
|
|
|
|
|
places (as-? (first rows) opts)]
|
|
|
|
|
(into [(str "INSERT INTO " (entity-fn (name table))
|
|
|
|
|
" (" params ")"
|
|
|
|
|
" VALUES "
|
|
|
|
|
(str/join ", " (repeat (count rows) (str "(" places ")"))))]
|
|
|
|
|
cat
|
|
|
|
|
rows)))
|
|
|
|
|
|
2019-04-01 01:22:04 +00:00
|
|
|
(comment
|
|
|
|
|
(require '[next.jdbc.quoted :refer [mysql]])
|
2019-04-01 02:30:01 +00:00
|
|
|
(by-keys {:a nil :b 42 :c "s"} :where {})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ["WHERE a IS NULL AND b = ? AND c = ?" 42 "s"]
|
2019-04-01 01:22:04 +00:00
|
|
|
(as-keys {:a nil :b 42 :c "s"} {})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> a, b, c
|
2019-04-01 01:22:04 +00:00
|
|
|
(as-? {:a nil :b 42 :c "s"} {})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ?, ?, ?
|
2019-04-01 01:22:04 +00:00
|
|
|
(for-query :user {:id 9} {:entities mysql})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ["SELECT * FROM `user` WHERE `id` = ?" 9]
|
2019-04-01 01:22:04 +00:00
|
|
|
(for-query :user {:id nil} {:entities mysql})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ["SELECT * FROM `user` WHERE `id` IS NULL"]
|
2019-04-01 02:30:01 +00:00
|
|
|
(for-query :user ["id = ? and opt is null" 9] {:entities mysql})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ["SELECT * FROM `user` WHERE id = ? and opt is null" 9]
|
2019-04-01 02:30:01 +00:00
|
|
|
(for-delete :user {:opt nil :id 9} {:entities mysql})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ["DELETE FROM `user` WHERE `opt` IS NULL AND `id` = ?" 9]
|
2019-04-01 02:30:01 +00:00
|
|
|
(for-delete :user ["id = ? and opt is null" 9] {:entities mysql})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ["DELETE FROM `user` WHERE id = ? and opt is null" 9]
|
2019-04-01 02:30:01 +00:00
|
|
|
(for-update :user {:status 42} {} {:entities mysql})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ["UPDATE `user` SET `status` = ? WHERE " 42]
|
2019-04-01 01:22:04 +00:00
|
|
|
(for-update :user {:status 42} {:id 9} {:entities mysql})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ["UPDATE `user` SET `status` = ? WHERE `id` = ?" 42 9]
|
2019-04-01 02:30:01 +00:00
|
|
|
(for-update :user {:status 42, :opt nil} ["id = ?" 9] {:entities mysql})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ["UPDATE `user` SET `status` = ?, `opt` = ? WHERE id = ?" 42 nil 9]
|
2019-04-01 02:30:01 +00:00
|
|
|
(for-insert :user {:id 9 :status 42 :opt nil} {:entities mysql})
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ["INSERT INTO `user` (`id`, `status`, `opt`) VALUES (?, ?, ?)" 9 42 nil]
|
2019-04-01 02:30:01 +00:00
|
|
|
(for-insert-multi :user [:id :status]
|
|
|
|
|
[[42 "hello"]
|
|
|
|
|
[35 "world"]
|
|
|
|
|
[64 "dollars"]]
|
|
|
|
|
{:entities mysql}))
|
2019-04-02 04:31:38 +00:00
|
|
|
;=> ["INSERT INTO `user` (`id`, `status`) VALUES (?, ?), (?, ?), (?, ?)" 42 "hello" 35 "world" 64 "dollars"])
|