Last active
July 4, 2022 20:42
-
-
Save abhin4v/5a8dace4f308f2eeb358 to your computer and use it in GitHub Desktop.
Postgres Upsert in Clojure using clojure.java.jdbc and honeysql.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns postgres.upsert | |
(:require [honeysql.core :as sql] | |
[clojure.java.jdbc :as jdbc] | |
[clojure.string :as str] | |
[clojure.set :as set])) | |
(defn- keyword->colname [kwd] | |
(-> kwd | |
name | |
(str/replace #"-" "_"))) | |
(defn- colname->keyword [colname] | |
(-> colname | |
(str/replace #"_" "-") | |
keyword)) | |
(defn- map-values | |
"Apply a function on all values of a map and return the corresponding map (all | |
keys untouched)" | |
[f m] | |
(when m | |
(persistent! (reduce-kv (fn [out-m k v] | |
(assoc! out-m k (f v))) | |
(transient (empty m)) | |
m)))) | |
(defn upsert! | |
([db table uniq-cols row-maps] | |
(upsert! db true table uniq-cols row-maps)) | |
([db transaction? table uniq-cols row-maps] | |
{:pre [(not-empty uniq-cols)]} | |
(if (empty? row-maps) | |
[] | |
(let [cols (keys (first row-maps)) | |
non-uniq-cols (set/difference (set cols) (set uniq-cols)) | |
on-conflict (str "ON CONFLICT (" (str/join ", " (map keyword->colname uniq-cols)) ")") | |
qfied (fn [table col] (->> col name (str (name table) ".") keyword)) | |
[on-conflict-update] (sql/format | |
{:set (->> non-uniq-cols | |
(mapcat #(vector % (qfied :excluded %))) | |
(apply hash-map)) | |
:where (list* :and | |
(map #(vector := (qfied table %) (qfied :excluded %)) | |
uniq-cols))})] | |
(doall (map (fn [row-map] | |
(let [value (->> row-map seq (sort-by #(->> % first (.indexOf cols))) (map second)) | |
[query & params] (sql/format {:insert-into table | |
:columns cols | |
:values [value]}) | |
query (if (empty? non-uniq-cols) | |
(format "%s %s DO NOTHING" query on-conflict) | |
(format "%s %s DO UPDATE %s" query on-conflict on-conflict-update))] | |
(if-let [res (jdbc/db-do-prepared-return-keys db transaction? query params)] | |
(map-keys #(-> % name colname->keyword) res) | |
(let [sel-query (->> {:select [:*] :from [table] | |
:where (list* :and | |
(map #(vector := (qfied table %) (get row-map %)) | |
uniq-cols))} | |
(sql/format))] | |
(jdbc/query db sel-query :identifiers colname->keyword))))) | |
row-maps)))))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@gleenn It looks like a typo. It seems like that arity is only there in order to default
transaction?
totrue
. If you correct the typo by adding the exclamation point, it looks like it will 'just work' for you.