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