diff --git a/CHANGES.md b/CHANGES.md index d234883..ad0ce95 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,8 @@ Add fn-handler for CASE statement (@loganlinn) Build/test with Clojure 1.7 (@michaelblume) Refactors for clarity (@michaelblume) +* Support mysql upserts (@dball) + ## 0.6.1 * Define parameterizable protocol on nil (@dball) diff --git a/README.md b/README.md index b96a80a..04e5cc2 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 974819d..a2eddc2 100644 --- a/src/honeysql/format.clj +++ b/src/honeysql/format.clj @@ -194,6 +194,7 @@ :offset 210 :lock 215 :values 220 + :upsert 225 :query-values 230}) (def clause-store (atom default-clause-priorities)) @@ -497,6 +498,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 69dde96..ac84f80 100644 --- a/src/honeysql/helpers.clj +++ b/src/honeysql/helpers.clj @@ -168,6 +168,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 d35df79..ab5a20e 100644 --- a/test/honeysql/format_test.clj +++ b/test/honeysql/format_test.clj @@ -51,7 +51,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]}})