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
* 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)
* The documentation continues to be expanded and clarified in response to feedback!
* 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
in pairs: `ON ... DO ...`.
`:on-conflict` accepts either a single SQL entity
(a keyword or symbol), or a SQL clause, or a pair
of a SQL entity and a SQL clause. The SQL entity is
a column name and the SQL clause can be an
`:on-conflict` accepts a sequence of zero or more
SQL entities (keywords or symbols), optionally
followed by a single SQL clause (hash map). It can also
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.
_[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)))
(defn- format-on-conflict [k x]
(cond (ident? x)
[(str (sql-kw k) " (" (format-entity x) ")")]
(map? x)
(let [[sql & params] (format-dsl x)]
(into [(str (sql-kw k) " " sql)] params))
(and (sequential? x)
(ident? (first x))
(map? (second x)))
(let [[sql & params] (format-dsl (second x))]
(into [(str (sql-kw k)
" (" (format-entity (first x)) ") "
sql)]
params))
(and (sequential? x) (= 1 (count x)))
(format-on-conflict k (first x))
(and (sequential? x) (= 0 (count x)))
[(sql-kw k)]
:else
(throw (ex-info "unsupported :on-conflict format"
{:clause x}))))
(if (sequential? x)
(let [entities (take-while ident? x)
n (count entities)
[clause & more] (drop n x)
_ (when (or (seq more)
(and clause (not (map? clause))))
(throw (ex-info "unsupported :on-conflict format"
{:clause x})))
[sql & params] (when clause
(format-dsl clause))]
(into [(str (sql-kw k)
(when (pos? n)
(str " ("
(str/join ", " (map #'format-entity entities))
")"))
(when sql
(str " " sql)))]
params))
(format-on-conflict k [x])))
(defn- format-do-update-set [k x]
(cond (map? x)

View file

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

View file

@ -72,6 +72,20 @@
(on-conflict (on-constraint :distributors_pkey))
do-nothing
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"]
;; almost identical to nilenso version:
(-> (insert-into :distributors)

View file

@ -618,6 +618,32 @@ INSERT INTO customers
(name, email)
VALUES ('Microsoft', 'hotline@microsoft.com')
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
"]
(format {:insert-into :customers