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:
parent
50b54683c4
commit
efe94f8b44
1 changed files with 135 additions and 190 deletions
|
@ -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)))
|
||||||
|
|
Loading…
Add table
Reference in a new issue