0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-26 08:29:42 -05:00

🎉 Add new dynamic sql builder (for selects only).

This commit is contained in:
Andrey Antukh 2019-11-29 12:55:20 +01:00
parent 50b54683c4
commit efe94f8b44

View file

@ -25,227 +25,172 @@
(ns uxbox.util.sql (ns uxbox.util.sql
"A composable sql helpers." "A composable sql helpers."
(:refer-clojure :exclude [test update set format]) (:refer-clojure :exclude [test update set format])
(:require [clojure.core :as c])) (:require [clojure.core :as c]
[cuerdas.core :as str]))
(defn- query? ;; --- Low Level Helpers
(defn raw-expr
[m] [m]
(::query m)) (cond
(string? m)
{::type :raw-expr
:sql m
:params []}
(defn select (vector? m)
[] {::type :raw-expr
{::query true :sql (first m)
::type ::select}) :params (vec (rest m))}
(defn update (and (map? m)
([table] (= :raw-expr (::type m)))
(update table nil)) m
([table alias]
{::query true
::type ::update
::table [table alias]}))
(defn delete :else
[] (throw (ex-info "unexpected input" {:m m}))))
{::query true
::type ::delete})
(defn insert (defn alias-expr
[table fields] [m]
{::query true (cond
::table table (string? m)
::fields fields {::type :alias-expr
::type ::insert}) :sql m
:alias nil
:params []}
(vector? m)
{::type :alias-expr
:sql (first m)
:alias (second m)
:params (vec (drop 2 m))}
:else
(throw (ex-info "unexpected input" {:m m}))))
;; --- SQL API (Select only)
(defn from (defn from
([m name] [name]
(from m name nil)) {::type :query
([m name alias] ::from [(alias-expr name)]
{:pre [(query? m)]} ::order []
(c/update m ::from (fnil conj []) [name alias]))) ::select []
::join []
::where []})
(defn field (defn select
([m name]
(field m name nil))
([m name alias]
(c/update m ::fields (fnil conj []) [name alias])))
(defn fields
[m & fields] [m & fields]
(reduce (fn [acc item] (c/update m ::select into (map alias-expr fields)))
(if (vector? item)
(apply field acc item)
(field acc item)))
m
fields))
(defn limit (defn limit
[m n] [m n]
{:pre [(= (::type m) ::select) (assoc m ::limit [(raw-expr ["LIMIT ?" n])]))
(query? m)]}
(assoc m ::limit n))
(defn offset (defn offset
[m n] [m n]
{:pre [(= (::type m) ::select) (assoc m ::offset [(raw-expr ["OFFSET ?" n])]))
(query? m)]}
(assoc m ::offset n)) (defn order
[m e]
(c/update m ::order conj (raw-expr e)))
(defn- join* (defn- join*
[m type table alias condition] [m type table condition]
{:pre [(= (::type m) ::select) (c/update m ::join conj
(query? m)]} {::type :join-expr
(c/update m ::joins (fnil conj []) :type type
{:type type :table (alias-expr table)
:name table :condition (raw-expr condition)}))
:alias alias
:condition condition}))
(defn join (defn join
([m table condition] [m table condition]
(join m table nil condition)) (join* m :inner table condition))
([m table alias condition]
{:pre [(= (::type m) ::select)
(query? m)]}
(join* m :inner table alias condition)))
(defn left-join (defn ljoin
([m table condition] [m table condition]
(left-join m table nil condition)) (join* m :left table condition))
([m table alias condition]
{:pre [(= (::type m) ::select) (defn rjoin
(query? m)]} [m table condition]
(join* m :left table alias condition))) (join* m :right table condition))
(defn where (defn where
[m condition & params] [m & conditions]
{:pre [(query? m)]} (->> (filter identity conditions)
(-> m (reduce #(c/update %1 ::where conj (raw-expr %2)) m)))
(c/update ::where (fnil conj []) condition)
(cond-> (seq params)
(c/update ::params (fnil into []) params))))
(defn set
[m field value]
{:pre [(query? m)]}
(-> m
(c/update ::assignations (fnil conj []) field)
(c/update ::params (fnil conj []) value)))
(defn values
[m values]
{:pre [(query? m)]}
(assoc ::values values))
(defn raw
[m sql & params]
(-> m
(c/update ::raw (fnil conj []) sql)
(c/update ::params (fnil into []) params)))
(defmulti format ::type)
(defn fmt
[m]
(into [(format m)] (::params m)))
;; --- Formating ;; --- Formating
(defn- format-fields (defmulti format-expr ::type)
[fields]
(letfn [(transform [[name alias]]
(if (string? alias)
(str name " " alias)
name))]
(apply str (->> (map transform fields)
(interpose ", ")))))
(defn- format-join (defmethod format-expr :raw-expr
[{:keys [type name alias condition]}] [{:keys [sql params]}]
(str (case type [sql params])
:inner "INNER JOIN "
:left "LEFT JOIN ")
(if alias
(str name " " alias)
name)
" ON (" condition ")"))
(defn- format-joins (defmethod format-expr :alias-expr
[clauses] [{:keys [sql alias params]}]
(apply str (->> (map format-join clauses) (if alias
(interpose " ")))) [(str sql " AS " alias) params]
[sql params]))
(defn- format-where (defmethod format-expr :join-expr
[conditions] [{:keys [table type condition]}]
(when (seq conditions) (let [[csql cparams] (format-expr condition)
(str "WHERE (" (apply str (interpose ") AND (" conditions)) ")"))) [tsql tparams] (format-expr table)
prefix (str/upper (name type))]
[(str prefix " JOIN " tsql " ON (" csql ")") (into cparams tparams)]))
(defn- format-exprs
([items] (format-exprs items {}))
([items {:keys [prefix suffix join-with]
:or {prefix ""
suffix ""
join-with ","}}]
(loop [rs []
rp []
v (first items)
n (rest items)]
(if v
(let [[s p] (format-expr v)]
(recur (conj rs s)
(into rp p)
(first n)
(rest n)))
[(str prefix (str/join join-with rs) suffix) rp]))))
(defn- process-param-tokens
[sql]
(let [cnt (java.util.concurrent.atomic.AtomicInteger. 1)]
(str/replace sql #"\?" (fn [& args]
(str "$" (.getAndIncrement cnt))))))
(defn- format-assignations (def ^:private select-formatters
[assignations] [#(format-exprs (::select %) {:prefix "SELECT "})
(apply str (->> (map #(str % " = ?") assignations) #(format-exprs (::from %) {:prefix "FROM "})
(interpose ", ")))) #(format-exprs (::join %))
#(format-exprs (::where %) {:prefix "WHERE ("
:join-with ") AND ("
:suffix ")"})
#(format-exprs (::order %) {:prefix "ORDER BY "} )
#(format-exprs (::limit %))
#(format-exprs (::offset %))])
(defn- format-raw (defn- collect
[items] [formatters qdata]
(when (seq items) (loop [sqls []
(apply str (interpose " " items)))) params []
f (first formatters)
(defmethod format ::select r (rest formatters)]
[{:keys [::fields ::from ::joins ::where]}] (if (fn? f)
(str "SELECT " (let [[s p] (f qdata)]
(format-fields fields) (recur (conj sqls s)
" FROM " (into params p)
(format-fields from) (first r)
" " (rest r)))
(format-joins joins) [(str/join " " sqls) params])))
" "
(format-where where)))
(defmethod format ::update
[{:keys [::table ::assignations ::where]}]
(str "UPDATE "
(format-fields [table])
" SET "
(format-assignations assignations)
" "
(format-where where)))
(defmethod format ::delete
[{:keys [::from ::where]}]
(str "DELETE FROM "
(format-fields from)
" "
(format-where where)))
(defmethod format ::insert
[{:keys [::table ::fields ::values ::raw]}]
(let [fsize (count fields)
pholder (str "(" (apply str (->> (map (constantly "?") fields)
(interpose ", "))) ")")]
(str "INSERT INTO " table "(" (apply str (interpose ", " fields)) ")"
" VALUES " (apply str (->> (map (constantly pholder) values)
(interpose ", ")))
" "
(format-raw raw))))
;; (defn test-update
;; []
;; (-> (update "users" "u")
;; (set "u.username" "foobar")
;; (set "u.email" "niwi@niwi.nz")
;; (where "u.id = ? AND u.deleted_at IS null" 555)))
;; (defn test-delete
;; []
;; (-> (delete)
;; (from "users" "u")
;; (where "u.id = ? AND u.deleted_at IS null" 555)))
;; (defn test-insert
;; []
;; (-> (insert "users" ["id", "username"])
;; (values [[1 "niwinz"] [2 "niwibe"]])
;; (raw "RETURNING *")))
(defn fmt
[qdata]
(let [[sql params] (collect select-formatters qdata)]
(into [(process-param-tokens sql)] params)))