diff --git a/README.md b/README.md index 0541838..5018c6b 100644 --- a/README.md +++ b/README.md @@ -645,14 +645,6 @@ You can also register SQL clauses, specifying the keyword, the formatting functi If you find yourself registering an operator, a function (syntax), or a new clause, consider submitting a [pull request to HoneySQL](https://github.com/seancorfield/honeysql/pulls) so others can use it, too. If it is dialect-specific, let me know in the pull request. -## TODO - -- [ ] Create table, etc. - -## Extensions - -* [For PostgreSQL-specific extensions falling outside of ANSI SQL](https://github.com/nilenso/honeysql-postgres) -- these will all be core in 2.0! - ## License Copyright (c) 2020-2021 Sean Corfield. HoneySQL 1.x was copyright (c) 2012-2020 Justin Kramer and Sean Corfield. diff --git a/doc/clause-reference.md b/doc/clause-reference.md index c53099f..29bad85 100644 --- a/doc/clause-reference.md +++ b/doc/clause-reference.md @@ -646,6 +646,11 @@ reasonable choices. (a keyword or symbol), or hash map of columns and values, like `:set` (above), or a hash map of fields (a sequence of SQL entities) and a where clause. +For convenience of building clauses with helpers, +it also accepts a sequence of one or more column +names followed by an optional hash map: this is treated +as an alternative form of the hash map with fields +and a where clause. The single SQL entity and the list of fields produce `SET` clauses using `EXCLUDED`: diff --git a/src/honey/sql.cljc b/src/honey/sql.cljc index 8cf908a..2db8929 100644 --- a/src/honey/sql.cljc +++ b/src/honey/sql.cljc @@ -495,22 +495,27 @@ {:clause x})))) (defn- format-do-update-set [k x] - (if (map? x) - (if (and (or (contains? x :fields) (contains? x 'fields)) - (or (contains? x :where) (contains? x 'where))) - (let [sets (str/join ", " - (map (fn [e] - (let [e (format-entity e {:drop-ns true})] - (str e " = EXCLUDED." e))) - (or (:fields x) - ('fields x)))) - [sql & params] (format-dsl {:where - (or (:where x) - ('where x))})] - (into [(str (sql-kw k) " " sets " " sql)] params)) - (format-set-exprs k x)) - (let [e (format-entity x {:drop-ns true})] - [(str (sql-kw k) " " e " = EXCLUDED." e)]))) + (cond (map? x) + (if (or (contains? x :fields) (contains? x 'fields)) + (let [sets (str/join ", " + (map (fn [e] + (let [e (format-entity e {:drop-ns true})] + (str e " = EXCLUDED." e))) + (or (:fields x) + ('fields x)))) + where (or (:where x) ('where x)) + [sql & params] (when where (format-dsl {:where where}))] + (into [(str (sql-kw k) " " sets + (when sql (str " " sql)))] params)) + (format-set-exprs k x)) + (sequential? x) + (let [[cols clauses] (split-with (complement map?) x)] + (if (seq cols) + (recur k {:fields cols :where (:where (first clauses))}) + (recur k (first clauses)))) + :else + (let [e (format-entity x {:drop-ns true})] + [(str (sql-kw k) " " e " = EXCLUDED." e)]))) (defn- format-simple-clause [c] (binding [*inline* true] diff --git a/src/honey/sql/helpers.cljc b/src/honey/sql/helpers.cljc index a852f39..c09177d 100644 --- a/src/honey/sql/helpers.cljc +++ b/src/honey/sql/helpers.cljc @@ -106,7 +106,7 @@ (defn on-conflict [& args] (generic-1 :on-conflict args)) (defn on-constraint [& args] (generic :on-constraint args)) (defn do-nothing [& args] (generic :do-nothing args)) -(defn do-update-set [& args] (generic-1 :do-update-set args)) +(defn do-update-set [& args] (generic :do-update-set args)) (defn returning [& args] (generic :returning args)) ;; helpers that produce non-clause expressions -- must be listed below: @@ -114,8 +114,21 @@ ;; to make this easy to use in a select, wrap it so it becomes a function: (defn over [& args] [(into [:over] args)]) -;; helper to ease compatibility with former nilenso/honeysql-postgres code: -(defn upsert [data & clauses] (default-merge data clauses)) +;; this helper is intended to ease the migration from nilenso: +(defn upsert + ([clause] (upsert {} clause)) + ([data clause] + (let [{:keys [on-conflict do-nothing do-update-set where]} clause] + (cond-> data + on-conflict + (assoc :on-conflict on-conflict) + do-nothing + (assoc :do-nothing do-nothing) + do-update-set + (assoc :do-update-set (if where + {:fields do-update-set + :where where} + do-update-set)))))) #?(:clj (assert (= (clojure.core/set (conj @@#'h/base-clause-order diff --git a/test/honey/sql/postgres_test.cljc b/test/honey/sql/postgres_test.cljc index 98a745e..2812fbc 100644 --- a/test/honey/sql/postgres_test.cljc +++ b/test/honey/sql/postgres_test.cljc @@ -14,9 +14,7 @@ ;; pull in all the PostgreSQL helpers that the nilenso ;; library provided (as well as the regular HoneySQL ones): [honey.sql.helpers :as sqlh :refer - [;; not needed because on-conflict accepts clauses - #_upsert - on-conflict do-nothing on-constraint + [upsert on-conflict do-nothing on-constraint returning do-update-set ;; not needed because do-update-set can do this directly #_do-update-set! @@ -44,18 +42,38 @@ (do-update-set :dname) (returning :*) sql/format))) + (is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?), (?, ?) ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname RETURNING *" 5 "Gizmo Transglobal" 6 "Associated Computing, Inc"] + (-> (insert-into :distributors) + (values [{:did 5 :dname "Gizmo Transglobal"} + {:did 6 :dname "Associated Computing, Inc"}]) + (upsert (-> (on-conflict :did) + (do-update-set :dname))) + (returning :*) + sql/format))) (is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT (did) DO NOTHING" 7 "Redline GmbH"] (-> (insert-into :distributors) (values [{:did 7 :dname "Redline GmbH"}]) (on-conflict :did) do-nothing sql/format))) + (is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT (did) DO NOTHING" 7 "Redline GmbH"] + (-> (insert-into :distributors) + (values [{:did 7 :dname "Redline GmbH"}]) + (upsert (-> (on-conflict :did) + do-nothing)) + sql/format))) (is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING" 9 "Antwerp Design"] (-> (insert-into :distributors) (values [{:did 9 :dname "Antwerp Design"}]) (on-conflict (on-constraint :distributors_pkey)) do-nothing sql/format))) + (is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING" 9 "Antwerp Design"] + (-> (insert-into :distributors) + (values [{:did 9 :dname "Antwerp Design"}]) + (upsert (-> (on-conflict (on-constraint :distributors_pkey)) + do-nothing)) + sql/format))) (is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?), (?, ?) ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname" 10 "Pinp Design" 11 "Foo Bar Works"] (sql/format {:insert-into :distributors :values [{:did 10 :dname "Pinp Design"} @@ -73,8 +91,8 @@ (-> (insert-into [:distributors [:did :dname]] (select 1 "whatever")) #_(query-values (select 1 "whatever")) - (on-conflict (on-constraint :distributors_pkey)) - do-nothing + (upsert (-> (on-conflict (on-constraint :distributors_pkey)) + do-nothing)) sql/format))))) (deftest upsert-where-test