Add postgres extension - upsert & returning

This commit is contained in:
Unnikrishnan 2016-04-07 13:23:36 +05:30
parent 43df955a86
commit c53ec6c78d
3 changed files with 136 additions and 0 deletions

View file

@ -0,0 +1,61 @@
(ns honeysql.postgres.format
(:refer-clojure :exclude [format])
(:require [honeysql.format :refer :all]))
;; Move the whole default priorities here ?
(def postgres-clause-priorities
{:upsert 225
:on-conflict 230
:on-conflict-constraint 230
:do-update-set 235
:do-update-set! 235
:do-nothing 235
:returning 240
:query-values 250})
;; FIXME : Not sure if this is the best way to implement this, but since the `clause-store` is being used
;; by format to decide the order of clauses, not really sure what would be a better implementation.
(doseq [[k v] postgres-clause-priorities]
(register-clause! k v))
(defn get-first [x]
(if (coll? x)
(first x)
x))
(defmethod format-clause :on-conflict-constraint [[_ k] _]
(str "ON CONFLICT ON CONSTRAINT " (-> k
get-first
to-sql)))
(defmethod format-clause :on-conflict [[_ id] _]
(str "ON CONFLICT (" (-> id
get-first
to-sql) ")"))
(defmethod format-clause :do-nothing [_ _]
"DO NOTHING")
;; Used when there is a need to update the columns with modified values if the
;; row(id) already exits - accepts a map of column and value
(defmethod format-clause :do-update-set! [[_ values] _]
(str "DO UPDATE SET " (comma-join (for [[k v] values]
(str (to-sql k) " = " (to-sql v))))))
;; Used when it is a simple upsert - accepts a vector of columns
(defmethod format-clause :do-update-set [[_ values] _]
(str "DO UPDATE SET "
(comma-join (map #(str (to-sql %) " = EXCLUDED." (to-sql %))
values))))
(defn format-upsert-clause [upsert]
(let [ks (keys upsert)]
(map #(format-clause % (find upsert %)) upsert)))
;; Accepts a map with the following possible keys
;; :on-conflict, :do-update-set or :do-update-set! or :do-nothing
(defmethod format-clause :upsert [[_ upsert] _]
(space-join (format-upsert-clause upsert)))
(defmethod format-clause :returning [[_ fields] _]
(str "RETURNING " (comma-join (map to-sql fields))))

View file

@ -0,0 +1,26 @@
(ns honeysql.postgres.helpers
(:refer-clojure :exclude [update])
(:require [honeysql.helpers :refer :all]))
(defn do-nothing [m]
(assoc m :do-nothing []))
(defhelper do-update-set [m args]
(assoc m :do-update-set (collify args)))
(defhelper db-update-set! [m args]
(assoc m :do-update-set! args))
(defhelper on-conflict [m args]
(assoc m :on-conflict args))
(defhelper on-conflict-constraint [m args]
(assoc m :on-conflict-constraint args))
(defhelper upsert [m args]
(if (plain-map? args)
(assoc m :upsert args)
(assoc m :upsert (first args))))
(defhelper returning [m fields]
(assoc m :returning (collify fields)))

View file

@ -0,0 +1,49 @@
(ns honeysql.postgres-test
(:refer-clojure :exclude [update])
(:require [honeysql.postgres.format :refer :all]
[honeysql.postgres.helpers :refer :all]
[honeysql.helpers :refer :all]
[honeysql.format :as sql]
[clojure.test :refer :all]))
(deftest upsert-test
(testing "upsert sql generation for postgresql"
(is (= ["INSERT INTO distributors d (did, dname) VALUES (5, ?), (6, ?) ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname RETURNING d.*" "Gizmo Transglobal" "Associated Computing, Inc"]
(-> (insert-into [:distributors :d])
(values [{:did 5 :dname "Gizmo Transglobal"}
{:did 6 :dname "Associated Computing, Inc"}])
(upsert (-> (on-conflict :did)
(do-update-set :dname)))
(returning :d.*)
sql/format)))
(is (= ["INSERT INTO distributors (did, dname) VALUES (7, ?) ON CONFLICT (did) DO NOTHING" "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 (9, ?) ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING" "Antwerp Design"]
(-> (insert-into :distributors)
(values [{:did 9 :dname "Antwerp Design"}])
(upsert (-> (on-conflict-constraint :distributors_pkey)
do-nothing))
sql/format)))
(is (= ["INSERT INTO distributors (did, dname) VALUES (10, ?), (11, ?) ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname" "Pinp Design" "Foo Bar Works"]
(sql/format {:insert-into :distributors
:values [{:did 10 :dname "Pinp Design"}
{:did 11 :dname "Foo Bar Works"}]
:upsert {:on-conflict :did
:do-update-set [:dname]}})))))
(deftest returning-test
(testing "returning clause in sql generation for postgresql"
(is (= ["DELETE FROM distributors WHERE did > 10 RETURNING *"]
(sql/format {:delete-from :distributors
:where [:> :did :10]
:returning [:*] })))
(is (= ["UPDATE distributors SET dname = ? WHERE did = 2 RETURNING did dname" "Foo Bar Designs"]
(-> (update :distributors)
(sset {:dname "Foo Bar Designs"})
(where [:= :did :2])
(returning [:did :dname])
sql/format)))))