address #423 by adding support for default

needs documentation
This commit is contained in:
Sean Corfield 2022-09-01 22:54:00 -07:00
parent 737699c11a
commit 3073d28525
3 changed files with 84 additions and 49 deletions

View file

@ -2,6 +2,7 @@
* 2.3.next in progress * 2.3.next in progress
* Address [#425](https://github.com/seancorfield/honeysql/issues/425) by clarifying that `INTERVAL` as special syntax may be MySQL-specific and PostgreSQL uses difference syntax (because `INTERVAL` is a data type there). * Address [#425](https://github.com/seancorfield/honeysql/issues/425) by clarifying that `INTERVAL` as special syntax may be MySQL-specific and PostgreSQL uses difference syntax (because `INTERVAL` is a data type there).
* Address [#423](https://github.com/seancorfield/honeysql/issues/423) by supporting `DEFAULT` values and `DEFAULT` rows in `VALUES` clause -- NEEDS DOCUMENTATION!
* **WIP** Address [#422](https://github.com/seancorfield/honeysql/issues/422) by auto-quoting unusual entity names when `:quoted` (and `:dialect`) are not specified, making HoneySQL more secure by default. * **WIP** Address [#422](https://github.com/seancorfield/honeysql/issues/422) by auto-quoting unusual entity names when `:quoted` (and `:dialect`) are not specified, making HoneySQL more secure by default.
* Address [#419](https://github.com/seancorfield/honeysql/issues/419) by adding `honey.sql.protocols` and `InlineValue` with a `sqlize` function. * Address [#419](https://github.com/seancorfield/honeysql/issues/419) by adding `honey.sql.protocols` and `InlineValue` with a `sqlize` function.
* Address [#413](https://github.com/seancorfield/honeysql/issues/413) by flagging a lack of `WHERE` clause for `DELETE`, `DELETE FROM`, and `UPDATE` when `:checking :basic` (or `:checking :strict`). * Address [#413](https://github.com/seancorfield/honeysql/issues/413) by flagging a lack of `WHERE` clause for `DELETE`, `DELETE FROM`, and `UPDATE` when `:checking :basic` (or `:checking :strict`).

View file

@ -699,59 +699,74 @@
(str " " (sql-kw nowait))))))])) (str " " (sql-kw nowait))))))]))
(defn- format-values [k xs] (defn- format-values [k xs]
(cond (sequential? (first xs)) (let [first-xs (when (sequential? xs) (first (drop-while ident? xs)))]
;; [[1 2 3] [4 5 6]] (cond (contains? #{:default 'default} xs)
(let [n-1 (map count xs) [(str (sql-kw xs) " " (sql-kw k))]
;; issue #291: ensure all value sequences are the same length (empty? xs)
xs' (if (apply = n-1) [(str (sql-kw k) " ()")]
xs (sequential? first-xs)
(let [n-n (apply max n-1)] ;; [[1 2 3] [4 5 6]]
(map (fn [x] (take n-n (concat x (repeat nil)))) xs))) (let [n-1 (map count (filter sequential? xs))
[sqls params] ;; issue #291: ensure all value sequences are the same length
(reduce (fn [[sql params] [sqls' params']] xs' (if (apply = n-1)
[(conj sql (str "(" (str/join ", " sqls') ")")) xs
(into params params')]) (let [n-n (when (seq n-1) (apply max n-1))]
[[] []] (map (fn [x]
(map #'format-expr-list xs'))] (if (sequential? x)
(into [(str (sql-kw k) " " (str/join ", " sqls))] params)) (take n-n (concat x (repeat nil)))
x))
xs)))
[sqls params]
(reduce (fn [[sql params] [sqls' params']]
[(conj sql
(if (sequential? sqls')
(str "(" (str/join ", " sqls') ")")
sqls'))
(into params params')])
[[] []]
(map #(if (sequential? %)
(format-expr-list %)
[(sql-kw %)])
xs'))]
(into [(str (sql-kw k) " " (str/join ", " sqls))] params))
(map? (first xs)) (map? first-xs)
;; [{:a 1 :b 2 :c 3}] ;; [{:a 1 :b 2 :c 3}]
(let [cols-1 (keys (first xs)) (let [cols-1 (keys (first xs))
;; issue #291: check for all keys in all maps but still ;; issue #291: check for all keys in all maps but still
;; use the keys from the first map if they match so that ;; use the keys from the first map if they match so that
;; users can rely on the key ordering if they want to, ;; users can rely on the key ordering if they want to,
;; e.g., see test that uses array-map for the first row ;; e.g., see test that uses array-map for the first row
cols-n (into #{} (mapcat keys) xs) cols-n (into #{} (mapcat keys) xs)
cols (if (= (set cols-1) cols-n) cols-1 cols-n) cols (if (= (set cols-1) cols-n) cols-1 cols-n)
[sqls params] [sqls params]
(reduce (fn [[sql params] [sqls' params']] (reduce (fn [[sql params] [sqls' params']]
[(conj sql (str "(" (str/join ", " sqls') ")")) [(conj sql (str "(" (str/join ", " sqls') ")"))
(if params' (into params params') params')]) (if params' (into params params') params')])
[[] []] [[] []]
(map (fn [m] (map (fn [m]
(format-expr-list (format-expr-list
(map #(get m (map #(get m
% %
;; issue #366: use NULL or DEFAULT ;; issue #366: use NULL or DEFAULT
;; for missing column values: ;; for missing column values:
(if (contains? *values-default-columns* %) (if (contains? *values-default-columns* %)
[:default] [:default]
nil)) nil))
cols))) cols)))
xs))] xs))]
(into [(str "(" (into [(str "("
(str/join ", " (str/join ", "
(map #(format-entity % {:drop-ns true}) cols)) (map #(format-entity % {:drop-ns true}) cols))
") " ") "
(sql-kw k) (sql-kw k)
" " " "
(str/join ", " sqls))] (str/join ", " sqls))]
params)) params))
:else :else
(throw (ex-info ":values expects sequences or maps" (throw (ex-info ":values expects sequences or maps"
{:first (first xs)})))) {:first (first xs)})))))
(comment (comment
(into #{} (mapcat keys) [{:a 1 :b 2} {:b 3 :c 4}]) (into #{} (mapcat keys) [{:a 1 :b 2} {:b 3 :c 4}])

View file

@ -737,6 +737,25 @@ ORDER BY id = ? DESC
:values [{:name name :values [{:name name
:enabled enabled}]}))))) :enabled enabled}]})))))
(deftest issue-425-default-values-test
(testing "default values"
(is (= ["INSERT INTO table (a, b, c) DEFAULT VALUES"]
(format {:insert-into [:table [:a :b :c]] :values :default}))))
(testing "values with default row"
(is (= ["INSERT INTO table (a, b, c) VALUES (1, 2, 3), DEFAULT, (4, 5, 6)"]
(format {:insert-into [:table [:a :b :c]]
:values [[1 2 3] :default [4 5 6]]}
{:inline true}))))
(testing "values with default column"
(is (= ["INSERT INTO table (a, b, c) VALUES (1, DEFAULT, 3), DEFAULT"]
(format {:insert-into [:table [:a :b :c]]
:values [[1 [:default] 3] :default]}
{:inline true}))))
(testing "empty values"
(is (= ["INSERT INTO table (a, b, c) VALUES ()"]
(format {:insert-into [:table [:a :b :c]]
:values []})))))
(deftest issue-316-test (deftest issue-316-test
;; this is a pretty naive test -- there are other tricks to perform injection ;; this is a pretty naive test -- there are other tricks to perform injection
;; that are not detected by HoneySQL and you should generally use :quoted true ;; that are not detected by HoneySQL and you should generally use :quoted true