From 910d1c72a1bab20406c8f92fd8055f023e533669 Mon Sep 17 00:00:00 2001 From: Donald Ball Date: Sat, 22 Aug 2015 21:21:40 -0400 Subject: [PATCH] Allow mysql upserts While I have a bespoke version of this externally, it would be nice to gracefully take advantage of honeysql's reader literals for sql fn calls and the like. --- CHANGES.md | 2 ++ README.md | 16 ++++++++++++++++ src/honeysql/format.clj | 17 +++++++++++++++++ src/honeysql/helpers.clj | 3 +++ test/honeysql/format_test.clj | 18 +++++++++++++++++- 5 files changed, 55 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 4d2af95..018c9bc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ ## 0.6.2 In development +* Support mysql upserts (@dball) + ## 0.6.1 * Define parameterizable protocol on nil (@dball) diff --git a/README.md b/README.md index cd2cb6a..5f52176 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,22 @@ The column values do not have to be literals, they can be nested queries: "user"] ``` +MySQL upserts are supported: + +```clj +(-> (insert-into :properties) + (columns :name :surname :age) + (values + [["Jon" "Smith" 34] + ["Andrew" "Cooper" 12] + ["Jane" "Daniels" 56]]) + (upsert :mysql {:age #sql/call [:values :age]}) + sql/format) +=> ["INSERT INTO properties (name, surname, age) + VALUES (?, ?, 34), (?, ?, 12), (?, ?, 56)" + "Jon" "Smith" "Andrew" "Cooper" "Jane" "Daniels"] +``` + Updates are possible too (note the double S in `sset` to avoid clashing with `clojure.core/set`): diff --git a/src/honeysql/format.clj b/src/honeysql/format.clj index 590bfc7..c7ba85b 100644 --- a/src/honeysql/format.clj +++ b/src/honeysql/format.clj @@ -191,6 +191,7 @@ :offset 210 :lock 215 :values 220 + :upsert 225 :query-values 230}) (def clause-store (atom default-clause-priorities)) @@ -495,6 +496,22 @@ (comma-join (for [x values] (str "(" (comma-join (map to-sql (vals x))) ")")))))) +(defmulti format-upsert-clause + (fn [mode updates] mode)) + +(defmethod format-upsert-clause :mysql + [_ updates] + (when-not (seq updates) + (let [msg "upserts clause must have an :updates map"] + (throw (IllegalArgumentException. msg)))) + (str "ON DUPLICATE KEY UPDATE " + (comma-join (for [[column value] updates] + (str (to-sql column) "=" (to-sql value)))))) + +(defmethod format-clause :upsert [[_ upsert] _] + (let [{:keys [mode updates]} upsert] + (format-upsert-clause mode updates))) + (defmethod format-clause :query-values [[_ query-values] _] (to-sql query-values)) diff --git a/src/honeysql/helpers.clj b/src/honeysql/helpers.clj index a9825b3..c916867 100644 --- a/src/honeysql/helpers.clj +++ b/src/honeysql/helpers.clj @@ -159,6 +159,9 @@ lock (assoc :lock lock))) +(defhelper upsert [m mode updates] + (assoc m :upsert {:mode mode :updates updates})) + (defhelper modifiers [m ms] (if (nil? ms) m diff --git a/test/honeysql/format_test.clj b/test/honeysql/format_test.clj index e0f8bca..236d8c4 100644 --- a/test/honeysql/format_test.clj +++ b/test/honeysql/format_test.clj @@ -32,7 +32,23 @@ (is (= (format-clause (first {:insert-into [:foo {:select [:bar] :from [:baz]}]}) nil) "INSERT INTO foo SELECT bar FROM baz")) (is (= (format-clause (first {:insert-into [[:foo [:a :b :c]] {:select [:d :e :f] :from [:baz]}]}) nil) - "INSERT INTO foo (a, b, c) SELECT d, e, f FROM baz"))) + "INSERT INTO foo (a, b, c) SELECT d, e, f FROM baz")) + (is (= (format {:insert-into :letters + :columns [:domain_key] + :values [["a"] ["b"] ["c"]]}) + ["INSERT INTO letters (domain_key) VALUES (?), (?), (?)" "a" "b" "c"])) + (is (= (format {:insert-into :letters + :columns [:domain_key] + :values [["a"] ["b"] ["c"]] + :upsert {:mode :mysql + :updates {:id :id}}}) + ["INSERT INTO letters (domain_key) VALUES (?), (?), (?) ON DUPLICATE KEY UPDATE id=id" "a" "b" "c"])) + (is (= (format {:insert-into :letters + :columns [:domain_key :rank] + :values [["a" 1] ["b" 2] ["c" 3]] + :upsert {:mode :mysql + :updates {:rank #sql/call [:values :rank]}}}) + ["INSERT INTO letters (domain_key, rank) VALUES (?, 1), (?, 2), (?, 3) ON DUPLICATE KEY UPDATE rank=values(rank)" "a" "b" "c"]))) (deftest exists-test (is (= (format {:exists {:select [:a] :from [:foo]}})