From c573f3bd9c60acd9bbc02a0faed7aa3bca6f7bba Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 7 Sep 2019 14:55:06 -0700 Subject: [PATCH] Fixes #235 by adding two different SET priority variants This allows the behavior change in #200 to be worked around. --- CHANGES.md | 7 ++++--- README.md | 8 ++++++++ src/honeysql/format.cljc | 10 ++++++++++ src/honeysql/helpers.cljc | 18 +++++++++++++++++- test/honeysql/format_test.cljc | 24 ++++++++++++++++++++++-- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e3bf2a2..51143cb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,10 @@ ## Changes coming in 0.9.7 * Fix #248 by treating alias as "not a subquery" when generating SQL for it. (@seancorfield) -* Fix #139 by checking arguments to `columns`/`merge-columns` and throwing an exception if a single collection is supplied (instead of varargs). -* Fix #128 by adding `truncate` support. -* Fix #99 by adding a note to the first use of `select` in the README that column names can be keywords or symbols but not strings. +* Fix #235 by adding `set0` (`:set0`) and `set1` (`:set1`) variants of `sset` (`:set`) to support different placements of `SET` (before `FROM` or after `JOIN` respectively) that different databases require. See also #200. (@seancorfield) +* Fix #139 by checking arguments to `columns`/`merge-columns` and throwing an exception if a single collection is supplied (instead of varargs). (@seancorfield) +* Fix #128 by adding `truncate` support. (@seancorfield) +* Fix #99 by adding a note to the first use of `select` in the README that column names can be keywords or symbols but not strings. (@seancorfield) ## 0.9.6 diff --git a/README.md b/README.md index a499771..96768d2 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,14 @@ with `clojure.core/set`): "drama"] ``` +If you are trying to build a compound update statement (with `from` or `join`), +be aware that different databases have slightly different syntax in terms of +where `SET` should appear. The default above is to put `SET` after any `JOIN`. +There are two variants of `sset` (and the underlying `:set` in the SQL map): + +* `set0` (and `:set0`) -- this puts the `SET` before `FROM`, +* `set1` (and `:set1`) -- a synonym for `sset` (and `:set`) that puts the `SET` after `JOIN`. + Deletes look as you would expect: ```clojure diff --git a/src/honeysql/format.cljc b/src/honeysql/format.cljc index 5e5dcbf..6d9b017 100644 --- a/src/honeysql/format.cljc +++ b/src/honeysql/format.cljc @@ -221,12 +221,14 @@ :delete-from 80 :truncate 85 :columns 90 + :set0 100 ; low-priority set clause :from 110 :join 120 :left-join 130 :right-join 140 :full-join 150 :set 155 + :set1 156 ; high-priority set clause (synonym for :set) :where 160 :group-by 170 :having 180 @@ -624,6 +626,14 @@ (str "SET " (comma-join (for [[k v] values] (str (to-sql k) " = " (to-sql v)))))) +(defmethod format-clause :set0 [[_ values] _] + (str "SET " (comma-join (for [[k v] values] + (str (to-sql k) " = " (to-sql v)))))) + +(defmethod format-clause :set1 [[_ values] _] + (str "SET " (comma-join (for [[k v] values] + (str (to-sql k) " = " (to-sql v)))))) + (defmethod format-clause :delete-from [[_ table] _] (str "DELETE FROM " (to-sql table))) diff --git a/src/honeysql/helpers.cljc b/src/honeysql/helpers.cljc index 1070777..1f0b206 100644 --- a/src/honeysql/helpers.cljc +++ b/src/honeysql/helpers.cljc @@ -283,9 +283,25 @@ ;; short for sql set, to avoid name collision with clojure.core/set (defn sset - ([vs] (values nil vs)) + ([vs] (sset nil vs)) ([m vs] (build-clause :set m vs))) +(defmethod build-clause :set0 [_ m values] + (assoc m :set0 values)) + +;; set with lower priority (before from) +(defn set0 + ([vs] (set0 nil vs)) + ([m vs] (build-clause :set0 m vs))) + +(defmethod build-clause :set [_ m values] + (assoc m :set values)) + +;; set with higher priority (after join) +(defn set1 + ([vs] (set1 nil vs)) + ([m vs] (build-clause :set1 m vs))) + (defmethod build-clause :delete-from [_ m table] (assoc m :delete-from table)) diff --git a/test/honeysql/format_test.cljc b/test/honeysql/format_test.cljc index 09459e7..5b85409 100644 --- a/test/honeysql/format_test.cljc +++ b/test/honeysql/format_test.cljc @@ -204,13 +204,33 @@ ["WHERE (foo = ? AND bar = ?)" "foo" "bar"])))) +(deftest set-before-from ; issue 235 + (is (= + ["UPDATE `films` `f` SET `kind` = `c`.`test` FROM (SELECT `b`.`test` FROM `bar` `b` WHERE `b`.`id` = ?) `c` WHERE `f`.`kind` = ?" 1 "drama"] + (-> + {:update [:films :f] + :set0 {:kind :c.test} + :from [[{:select [:b.test] + :from [[:bar :b]] + :where [:= :b.id 1]} :c]] + :where [:= :f.kind "drama"]} + (format :quoting :mysql))))) + (deftest set-after-join (is (= ["UPDATE `foo` INNER JOIN `bar` ON `bar`.`id` = `foo`.`bar_id` SET `a` = ? WHERE `bar`.`b` = ?" 1 42] (-> {:update :foo - :join [:bar [:= :bar.id :foo.bar_id]] - :set {:a 1} + :join [:bar [:= :bar.id :foo.bar_id]] + :set {:a 1} + :where [:= :bar.b 42]} + (format :quoting :mysql)))) + (is (= + ["UPDATE `foo` INNER JOIN `bar` ON `bar`.`id` = `foo`.`bar_id` SET `a` = ? WHERE `bar`.`b` = ?" 1 42] + (-> + {:update :foo + :join [:bar [:= :bar.id :foo.bar_id]] + :set1 {:a 1} :where [:= :bar.b 42]} (format :quoting :mysql)))))