Fixes #323 by allowing multiple column names

This commit is contained in:
Sean Corfield 2021-04-22 19:16:30 -07:00
parent e227e1b9ab
commit f606dc6044
6 changed files with 70 additions and 28 deletions

View file

@ -1,5 +1,8 @@
# Changes # Changes
* 2.0.next in progress
* Fix #323 by supporting more than one SQL entity in `:on-conflict`.
* 2.0.0-beta2 (for testing; 2021-04-13) * 2.0.0-beta2 (for testing; 2021-04-13)
* The documentation continues to be expanded and clarified in response to feedback! * The documentation continues to be expanded and clarified in response to feedback!
* Fix #322 by rewriting/simplifying `WHERE`/`HAVING` merge logic. **Important bug fix!** * Fix #322 by rewriting/simplifying `WHERE`/`HAVING` merge logic. **Important bug fix!**

View file

@ -815,10 +815,11 @@ These are grouped together because they are handled
as if they are separate clauses but they will appear as if they are separate clauses but they will appear
in pairs: `ON ... DO ...`. in pairs: `ON ... DO ...`.
`:on-conflict` accepts either a single SQL entity `:on-conflict` accepts a sequence of zero or more
(a keyword or symbol), or a SQL clause, or a pair SQL entities (keywords or symbols), optionally
of a SQL entity and a SQL clause. The SQL entity is followed by a single SQL clause (hash map). It can also
a column name and the SQL clause can be an accept either a single SQL entity or a single SQL clause.
The SQL entities are column names and the SQL clause can be an
`:on-constraint` clause or a`:where` clause. `:on-constraint` clause or a`:where` clause.
_[For convenience of use with the `on-conflict` helper, this clause can also accept any of those arguments, wrapped in a sequence; it can also accept an empty sequence, and just produce `ON CONFLICT`, so that it can be combined with other clauses directly]_ _[For convenience of use with the `on-conflict` helper, this clause can also accept any of those arguments, wrapped in a sequence; it can also accept an empty sequence, and just produce `ON CONFLICT`, so that it can be combined with other clauses directly]_

View file

@ -589,26 +589,25 @@
(into [(str (sql-kw k) " " (str/join ", " sqls))] params))) (into [(str (sql-kw k) " " (str/join ", " sqls))] params)))
(defn- format-on-conflict [k x] (defn- format-on-conflict [k x]
(cond (ident? x) (if (sequential? x)
[(str (sql-kw k) " (" (format-entity x) ")")] (let [entities (take-while ident? x)
(map? x) n (count entities)
(let [[sql & params] (format-dsl x)] [clause & more] (drop n x)
(into [(str (sql-kw k) " " sql)] params)) _ (when (or (seq more)
(and (sequential? x) (and clause (not (map? clause))))
(ident? (first x)) (throw (ex-info "unsupported :on-conflict format"
(map? (second x))) {:clause x})))
(let [[sql & params] (format-dsl (second x))] [sql & params] (when clause
(into [(str (sql-kw k) (format-dsl clause))]
" (" (format-entity (first x)) ") " (into [(str (sql-kw k)
sql)] (when (pos? n)
params)) (str " ("
(and (sequential? x) (= 1 (count x))) (str/join ", " (map #'format-entity entities))
(format-on-conflict k (first x)) ")"))
(and (sequential? x) (= 0 (count x))) (when sql
[(sql-kw k)] (str " " sql)))]
:else params))
(throw (ex-info "unsupported :on-conflict format" (format-on-conflict k [x])))
{:clause x}))))
(defn- format-do-update-set [k x] (defn- format-do-update-set [k x]
(cond (map? x) (cond (map? x)

View file

@ -768,10 +768,9 @@
(generic-1 :values args)) (generic-1 :values args))
(defn on-conflict (defn on-conflict
"Accepts a single column name to detect conflicts "Accepts zero or more SQL entities (keywords or symbols),
during an upsert, optionally followed by a `WHERE` optionally followed by a single SQL clause (hash map)."
clause." {:arglists '([column* where-clause])}
{:arglists '([column] [column where-clause])}
[& args] [& args]
(generic :on-conflict args)) (generic :on-conflict args))

View file

@ -72,6 +72,20 @@
(on-conflict (on-constraint :distributors_pkey)) (on-conflict (on-constraint :distributors_pkey))
do-nothing do-nothing
sql/format))) sql/format)))
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT (did) ON CONSTRAINT distributors_pkey DO NOTHING" 9 "Antwerp Design"]
;; with both name and clause:
(-> (insert-into :distributors)
(values [{:did 9 :dname "Antwerp Design"}])
(on-conflict :did (on-constraint :distributors_pkey))
do-nothing
sql/format)))
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT (did, dname) ON CONSTRAINT distributors_pkey DO NOTHING" 9 "Antwerp Design"]
;; with multiple names and a clause:
(-> (insert-into :distributors)
(values [{:did 9 :dname "Antwerp Design"}])
(on-conflict :did :dname (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"] (is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING" 9 "Antwerp Design"]
;; almost identical to nilenso version: ;; almost identical to nilenso version:
(-> (insert-into :distributors) (-> (insert-into :distributors)

View file

@ -618,6 +618,32 @@ INSERT INTO customers
(name, email) (name, email)
VALUES ('Microsoft', 'hotline@microsoft.com') VALUES ('Microsoft', 'hotline@microsoft.com')
ON CONFLICT (name) ON CONFLICT (name)
DO NOTHING
"]
(format {:insert-into :customers
:columns [:name :email]
:values [[[:inline "Microsoft"], [:inline "hotline@microsoft.com"]]]
:on-conflict [:name]
:do-nothing true}
{:pretty true})))
(is (= ["
INSERT INTO customers
(name, email)
VALUES ('Microsoft', 'hotline@microsoft.com')
ON CONFLICT (name, email)
DO NOTHING
"]
(format {:insert-into :customers
:columns [:name :email]
:values [[[:inline "Microsoft"], [:inline "hotline@microsoft.com"]]]
:on-conflict [:name :email]
:do-nothing true}
{:pretty true})))
(is (= ["
INSERT INTO customers
(name, email)
VALUES ('Microsoft', 'hotline@microsoft.com')
ON CONFLICT (name)
DO UPDATE SET email = EXCLUDED.email || ';' || customers.email DO UPDATE SET email = EXCLUDED.email || ';' || customers.email
"] "]
(format {:insert-into :customers (format {:insert-into :customers