2021-01-30 00:11:52 +00:00
|
|
|
;; copyright (c) 2020-2021 sean corfield, all rights reserved
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
(ns honey.sql
|
2021-02-01 20:00:42 +00:00
|
|
|
"Primary API for HoneySQL 2.x.
|
|
|
|
|
|
|
|
|
|
This includes the `format` function -- the primary entry point -- as well
|
|
|
|
|
as several public formatters that are intended to help users extend the
|
|
|
|
|
supported syntax.
|
|
|
|
|
|
|
|
|
|
In addition, functions to extend HoneySQL are also provided here:
|
|
|
|
|
* `sql-kw` -- turns a Clojure keyword into SQL code (makes it uppercase
|
|
|
|
|
and replaces - with space).
|
|
|
|
|
* `format-dsl` -- intended to format SQL statements; returns a vector
|
|
|
|
|
containing a SQL string followed by parameter values.
|
|
|
|
|
* `format-expr` -- intended to format SQL expressions; returns a vector
|
|
|
|
|
containing a SQL string followed by parameter values.
|
|
|
|
|
* `format-expr-list` -- intended to format a list of SQL expressions;
|
|
|
|
|
returns a pair comprising: a sequence of SQL expressions (to be
|
|
|
|
|
join with a delimiter) and a sequence of parameter values.
|
|
|
|
|
* `set-dialect!` -- set the default dialect to be used for formatting.
|
|
|
|
|
* `register-clause!` -- register a new statement/clause formatter.
|
|
|
|
|
* `register-fn!` -- register a new function call (or special syntax)
|
|
|
|
|
formatter.
|
|
|
|
|
* `register-op!` -- register a new operator formatter."
|
2020-09-21 01:48:07 +00:00
|
|
|
(:refer-clojure :exclude [format])
|
|
|
|
|
(:require [clojure.string :as str]))
|
|
|
|
|
|
|
|
|
|
;; default formatting for known clauses
|
|
|
|
|
|
|
|
|
|
(declare format-dsl)
|
|
|
|
|
(declare format-expr)
|
2020-09-24 01:15:20 +00:00
|
|
|
(declare format-expr-list)
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
;; dynamic dialect handling for formatting
|
|
|
|
|
|
2020-09-29 03:45:43 +00:00
|
|
|
(declare clause-format)
|
2020-09-23 19:55:02 +00:00
|
|
|
(def ^:private default-clause-order
|
|
|
|
|
"The (default) order for known clauses. Can have items added and removed."
|
2021-02-11 04:07:05 +00:00
|
|
|
[;; DDL comes first (these don't really have a precedence):
|
2021-02-13 05:50:22 +00:00
|
|
|
:alter-table :add-column :drop-column :modify-column :rename-column
|
2021-02-13 01:08:37 +00:00
|
|
|
:add-index :drop-index :rename-table
|
2021-03-12 19:43:21 +00:00
|
|
|
:create-table :create-table-as :with-columns
|
|
|
|
|
:create-view :create-materialized-view :create-extension
|
|
|
|
|
:drop-table :drop-view :drop-materialized-view :drop-extension
|
|
|
|
|
:refresh-materialized-view
|
2021-02-11 04:07:05 +00:00
|
|
|
;; then SQL clauses in priority order:
|
|
|
|
|
:nest :with :with-recursive :intersect :union :union-all :except :except-all
|
2021-03-13 02:54:19 +00:00
|
|
|
:select :select-distinct :select-distinct-on :select-top :select-distinct-top
|
2021-02-13 23:58:56 +00:00
|
|
|
:insert-into :update :delete :delete-from :truncate
|
2021-01-30 19:19:12 +00:00
|
|
|
:columns :set :from :using
|
2021-03-08 03:21:13 +00:00
|
|
|
:join-by
|
2020-09-23 19:55:02 +00:00
|
|
|
:join :left-join :right-join :inner-join :outer-join :full-join
|
|
|
|
|
:cross-join
|
2021-02-11 00:25:31 +00:00
|
|
|
:where :group-by :having
|
|
|
|
|
:window :partition-by
|
2021-03-13 20:36:25 +00:00
|
|
|
:order-by :limit :offset :fetch :for :lock :values
|
2021-03-13 00:13:52 +00:00
|
|
|
:on-conflict :on-constraint :do-nothing :do-update-set :on-duplicate-key-update
|
2021-03-12 04:07:59 +00:00
|
|
|
:returning
|
|
|
|
|
:with-data])
|
2020-09-23 19:55:02 +00:00
|
|
|
|
|
|
|
|
(defn- add-clause-before
|
|
|
|
|
"Low-level helper just to insert a new clause."
|
|
|
|
|
[order clause before]
|
|
|
|
|
(if before
|
|
|
|
|
(do
|
|
|
|
|
(when-not (contains? (set order) before)
|
|
|
|
|
(throw (ex-info (str "Unrecognized clause: " before)
|
|
|
|
|
{:known-clauses order})))
|
|
|
|
|
(reduce (fn [v k]
|
|
|
|
|
(if (= k before)
|
|
|
|
|
(conj v clause k)
|
|
|
|
|
(conj v k)))
|
|
|
|
|
[]
|
|
|
|
|
order))
|
|
|
|
|
(conj order clause)))
|
|
|
|
|
|
2020-09-21 01:48:07 +00:00
|
|
|
(def ^:private dialects
|
2020-09-25 02:07:32 +00:00
|
|
|
{:ansi {:quote #(str \" % \")}
|
|
|
|
|
:sqlserver {:quote #(str \[ % \])}
|
|
|
|
|
:mysql {:quote #(str \` % \`)
|
2020-09-29 03:45:43 +00:00
|
|
|
:clause-order-fn (fn [order]
|
|
|
|
|
;; MySQL :set has different priority
|
|
|
|
|
(-> (filterv (complement #{:set}) order)
|
2021-03-13 20:36:25 +00:00
|
|
|
(add-clause-before :set :where)))}
|
2020-09-26 07:17:31 +00:00
|
|
|
:oracle {:quote #(str \" % \") :as false}})
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
; should become defonce
|
|
|
|
|
(def ^:private default-dialect (atom (:ansi dialects)))
|
|
|
|
|
|
|
|
|
|
(def ^:private ^:dynamic *dialect* nil)
|
2020-09-24 01:15:20 +00:00
|
|
|
;; nil would be a better default but that makes testing individual
|
|
|
|
|
;; functions harder than necessary:
|
|
|
|
|
(def ^:private ^:dynamic *clause-order* default-clause-order)
|
2020-09-21 01:48:07 +00:00
|
|
|
(def ^:private ^:dynamic *quoted* nil)
|
2020-10-10 04:52:18 +00:00
|
|
|
(def ^:private ^:dynamic *inline* nil)
|
2021-01-31 13:08:21 +00:00
|
|
|
(def ^:private ^:dynamic *params* nil)
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
;; clause helpers
|
|
|
|
|
|
2020-09-23 19:55:02 +00:00
|
|
|
;; String.toUpperCase() or `str/upper-case` for that matter converts the
|
|
|
|
|
;; string to uppercase for the DEFAULT LOCALE. Normally this does what you'd
|
|
|
|
|
;; expect but things like `inner join` get converted to `İNNER JOİN` (dot over
|
|
|
|
|
;; the I) when user locale is Turkish. This predictably has bad consequences
|
|
|
|
|
;; for people who like their SQL queries to work. The fix here is to use
|
|
|
|
|
;; String.toUpperCase(Locale/US) instead which always converts things the
|
|
|
|
|
;; way we'd expect.
|
|
|
|
|
;;
|
|
|
|
|
;; Use this instead of `str/upper-case` as it will always use Locale/US.
|
|
|
|
|
(def ^:private ^{:arglists '([s])} upper-case
|
|
|
|
|
;; TODO - not sure if there's a JavaScript equivalent here we should be using as well
|
|
|
|
|
#?(:clj (fn [^String s] (.. s toString (toUpperCase (java.util.Locale/US))))
|
|
|
|
|
:cljs str/upper-case))
|
|
|
|
|
|
2021-02-01 20:00:42 +00:00
|
|
|
(defn sql-kw
|
|
|
|
|
"Given a keyword, return a SQL representation of it as a string.
|
|
|
|
|
|
|
|
|
|
A `:kebab-case` keyword becomes a `KEBAB CASE` (uppercase) string
|
2021-02-02 20:25:26 +00:00
|
|
|
with hyphens replaced by spaces, e.g., `:insert-into` => `INSERT INTO`.
|
|
|
|
|
|
|
|
|
|
Any namespace qualifier is ignored."
|
2021-02-01 20:00:42 +00:00
|
|
|
[k]
|
2020-10-14 18:00:20 +00:00
|
|
|
(-> k (name) (upper-case)
|
|
|
|
|
(as-> s (if (= "-" s) s (str/replace s "-" " ")))))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2021-02-02 20:25:26 +00:00
|
|
|
(defn- sym->kw
|
|
|
|
|
"Given a symbol, produce a keyword, retaining the namespace
|
|
|
|
|
qualifier, if any."
|
|
|
|
|
[s]
|
|
|
|
|
(if (symbol? s)
|
|
|
|
|
(if-let [n (namespace s)]
|
|
|
|
|
(keyword n (name s))
|
|
|
|
|
(keyword (name s)))
|
|
|
|
|
s))
|
|
|
|
|
|
2020-10-13 05:54:46 +00:00
|
|
|
(defn- namespace-_ [x] (some-> (namespace x) (str/replace "-" "_")))
|
|
|
|
|
(defn- name-_ [x] (str/replace (name x) "-" "_"))
|
|
|
|
|
|
2021-02-14 05:29:22 +00:00
|
|
|
(defn- sqlize-value [x]
|
|
|
|
|
(cond
|
|
|
|
|
(nil? x) "NULL"
|
|
|
|
|
(string? x) (str \' (str/replace x "'" "''") \')
|
2021-03-07 17:43:03 +00:00
|
|
|
(ident? x) (sql-kw x)
|
2021-02-14 05:29:22 +00:00
|
|
|
:else (str x)))
|
|
|
|
|
|
2021-02-11 04:07:05 +00:00
|
|
|
(defn format-entity
|
|
|
|
|
"Given a simple SQL entity (a keyword or symbol -- or string),
|
|
|
|
|
return the equivalent SQL fragment (as a string -- no parameters).
|
|
|
|
|
|
|
|
|
|
Handles quoting, splitting at / or ., replacing - with _ etc."
|
|
|
|
|
[x & [{:keys [aliased drop-ns]}]]
|
2020-10-13 05:54:46 +00:00
|
|
|
(let [nn (if (or *quoted* (string? x)) name name-_)
|
|
|
|
|
q (if (or *quoted* (string? x)) (:quote *dialect*) identity)
|
2021-02-01 21:10:57 +00:00
|
|
|
[t c] (if-let [n (when-not (or drop-ns (string? x))
|
2020-10-13 05:54:46 +00:00
|
|
|
(namespace-_ x))]
|
|
|
|
|
[n (nn x)]
|
2021-02-01 21:10:57 +00:00
|
|
|
(if aliased
|
2020-10-13 05:54:46 +00:00
|
|
|
[nil (nn x)]
|
|
|
|
|
(let [[t c] (str/split (nn x) #"\.")]
|
2020-10-02 06:30:18 +00:00
|
|
|
(if c [t c] [nil t]))))]
|
2020-09-21 01:48:07 +00:00
|
|
|
(cond->> c
|
|
|
|
|
(not= "*" c)
|
|
|
|
|
(q)
|
|
|
|
|
t
|
2020-10-02 06:30:18 +00:00
|
|
|
(str (q t) "."))))
|
|
|
|
|
|
2021-02-14 05:29:22 +00:00
|
|
|
(defn- param-value [k]
|
|
|
|
|
(if (contains? *params* k)
|
|
|
|
|
(get *params* k)
|
|
|
|
|
(throw (ex-info (str "missing parameter value for " k)
|
|
|
|
|
{:params (keys *params*)}))))
|
|
|
|
|
|
2020-10-02 06:30:18 +00:00
|
|
|
(defn- ->param [k]
|
|
|
|
|
(with-meta (constantly k)
|
|
|
|
|
{::wrapper
|
2021-02-14 05:29:22 +00:00
|
|
|
(fn [fk _] (param-value (fk)))}))
|
2020-10-02 06:30:18 +00:00
|
|
|
|
|
|
|
|
(defn- format-var [x & [opts]]
|
2020-10-13 05:54:46 +00:00
|
|
|
(let [c (name-_ x)]
|
2020-10-02 06:30:18 +00:00
|
|
|
(cond (= \% (first c))
|
|
|
|
|
(let [[f & args] (str/split (subs c 1) #"\.")]
|
|
|
|
|
;; TODO: this does not quote arguments -- does that matter?
|
2021-02-01 22:49:17 +00:00
|
|
|
[(str (upper-case f) "(" (str/join "," args) ")")])
|
2020-10-02 06:30:18 +00:00
|
|
|
(= \? (first c))
|
2021-02-14 05:29:22 +00:00
|
|
|
(let [k (keyword (subs c 1))]
|
|
|
|
|
(if *inline*
|
|
|
|
|
[(sqlize-value (param-value k))]
|
|
|
|
|
["?" (->param k)]))
|
2020-10-02 06:30:18 +00:00
|
|
|
:else
|
|
|
|
|
[(format-entity x opts)])))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2020-09-24 01:15:20 +00:00
|
|
|
(defn- format-entity-alias [x]
|
2020-09-23 07:14:25 +00:00
|
|
|
(cond (sequential? x)
|
2020-09-24 05:52:57 +00:00
|
|
|
(let [s (first x)
|
|
|
|
|
pair? (< 1 (count x))]
|
|
|
|
|
(when (map? s)
|
|
|
|
|
(throw (ex-info "selectable cannot be statement!"
|
|
|
|
|
{:selectable s})))
|
|
|
|
|
(cond-> (format-entity s)
|
|
|
|
|
pair?
|
2020-09-26 07:17:31 +00:00
|
|
|
(str (if (and (contains? *dialect* :as) (not (:as *dialect*))) " " " AS ")
|
2021-02-01 21:10:57 +00:00
|
|
|
(format-entity (second x) {:aliased true}))))
|
2020-09-23 07:14:25 +00:00
|
|
|
|
|
|
|
|
:else
|
|
|
|
|
(format-entity x)))
|
|
|
|
|
|
2021-02-01 21:10:57 +00:00
|
|
|
(defn- format-selectable-dsl [x & [{:keys [as aliased] :as opts}]]
|
2020-09-23 07:14:25 +00:00
|
|
|
(cond (map? x)
|
2021-02-01 21:10:57 +00:00
|
|
|
(format-dsl x {:nested true})
|
2020-09-23 07:14:25 +00:00
|
|
|
|
|
|
|
|
(sequential? x)
|
2020-09-24 05:52:57 +00:00
|
|
|
(let [s (first x)
|
|
|
|
|
pair? (< 1 (count x))
|
|
|
|
|
a (second x)
|
2020-09-24 01:15:20 +00:00
|
|
|
[sql & params] (if (map? s)
|
2021-02-01 21:10:57 +00:00
|
|
|
(format-dsl s {:nested true})
|
2020-09-24 01:15:20 +00:00
|
|
|
(format-expr s))
|
2020-09-24 05:52:57 +00:00
|
|
|
[sql' & params'] (when pair?
|
|
|
|
|
(if (sequential? a)
|
2021-02-01 21:10:57 +00:00
|
|
|
(let [[sql params] (format-expr-list a {:aliased true})]
|
2020-09-24 05:52:57 +00:00
|
|
|
(into [(str/join " " sql)] params))
|
2021-02-01 21:10:57 +00:00
|
|
|
(format-selectable-dsl a {:aliased true})))]
|
2020-09-24 05:52:57 +00:00
|
|
|
(-> [(cond-> sql
|
|
|
|
|
pair?
|
2021-02-01 21:10:57 +00:00
|
|
|
(str (if as
|
2020-09-26 07:17:31 +00:00
|
|
|
(if (and (contains? *dialect* :as)
|
|
|
|
|
(not (:as *dialect*)))
|
|
|
|
|
" "
|
|
|
|
|
" AS ")
|
|
|
|
|
" ") sql'))]
|
2020-09-24 01:15:20 +00:00
|
|
|
(into params)
|
|
|
|
|
(into params')))
|
|
|
|
|
|
2021-03-07 17:43:03 +00:00
|
|
|
(ident? x)
|
2021-02-01 21:10:57 +00:00
|
|
|
(if aliased
|
2020-10-02 06:30:18 +00:00
|
|
|
[(format-entity x opts)]
|
|
|
|
|
(format-var x opts))
|
2020-09-24 01:15:20 +00:00
|
|
|
|
2021-02-01 21:10:57 +00:00
|
|
|
(and aliased (string? x))
|
2020-09-24 01:15:20 +00:00
|
|
|
[(format-entity x opts)]
|
2020-09-23 19:55:02 +00:00
|
|
|
|
2020-09-23 07:14:25 +00:00
|
|
|
:else
|
2020-09-23 19:55:02 +00:00
|
|
|
(format-expr x)))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
;; primary clauses
|
|
|
|
|
|
2020-09-23 19:55:02 +00:00
|
|
|
(defn- format-on-set-op [k xs]
|
2020-09-23 07:14:25 +00:00
|
|
|
(let [[sqls params]
|
|
|
|
|
(reduce (fn [[sql params] [sql' & params']]
|
|
|
|
|
[(conj sql sql') (if params' (into params params') params)])
|
|
|
|
|
[[] []]
|
2021-02-01 21:10:57 +00:00
|
|
|
(map #(format-dsl % {:nested true}) xs))]
|
2020-09-23 07:14:25 +00:00
|
|
|
(into [(str/join (str " " (sql-kw k) " ") sqls)] params)))
|
|
|
|
|
|
2021-02-01 20:00:42 +00:00
|
|
|
(defn format-expr-list
|
|
|
|
|
"Given a sequence of expressions represented as data, return a pair
|
|
|
|
|
where the first element is a sequence of SQL fragments and the second
|
|
|
|
|
element is a sequence of parameters. The caller should join the SQL
|
|
|
|
|
fragments with whatever appropriate delimiter is needed and then
|
|
|
|
|
return a vector whose first element is the complete SQL string and
|
|
|
|
|
whose subsequent elements are the parameters:
|
|
|
|
|
|
|
|
|
|
(let [[sqls params] (format-expr-list data opts)]
|
|
|
|
|
(into [(str/join delim sqls)] params))
|
|
|
|
|
|
|
|
|
|
This is intended to be used when writing your own formatters to
|
|
|
|
|
extend the DSL supported by HoneySQL."
|
|
|
|
|
[exprs & [opts]]
|
2020-09-24 01:15:20 +00:00
|
|
|
(reduce (fn [[sql params] [sql' & params']]
|
|
|
|
|
[(conj sql sql') (if params' (into params params') params)])
|
|
|
|
|
[[] []]
|
2021-02-01 20:00:42 +00:00
|
|
|
(map #(format-expr % opts) exprs)))
|
2020-09-24 01:15:20 +00:00
|
|
|
|
2020-09-29 02:24:17 +00:00
|
|
|
(defn- format-columns [k xs]
|
2021-02-01 21:10:57 +00:00
|
|
|
(let [[sqls params] (format-expr-list xs {:drop-ns (= :columns k)})]
|
2020-09-24 01:15:20 +00:00
|
|
|
(into [(str "(" (str/join ", " sqls) ")")] params)))
|
|
|
|
|
|
2021-02-13 23:58:56 +00:00
|
|
|
(defn- format-selects-common [prefix as xs]
|
2020-09-21 02:17:37 +00:00
|
|
|
(if (sequential? xs)
|
2020-09-23 07:14:25 +00:00
|
|
|
(let [[sqls params]
|
|
|
|
|
(reduce (fn [[sql params] [sql' & params']]
|
|
|
|
|
[(conj sql sql') (if params' (into params params') params)])
|
|
|
|
|
[[] []]
|
2021-02-13 23:58:56 +00:00
|
|
|
(map #(format-selectable-dsl % {:as as}) xs))]
|
|
|
|
|
(into [(str prefix " " (str/join ", " sqls))] params))
|
|
|
|
|
(let [[sql & params] (format-selectable-dsl xs {:as as})]
|
|
|
|
|
(into [(str prefix " " sql)] params))))
|
|
|
|
|
|
|
|
|
|
(defn- format-selects [k xs]
|
|
|
|
|
(format-selects-common
|
|
|
|
|
(sql-kw k)
|
|
|
|
|
(#{:select :select-distinct :from :window
|
|
|
|
|
'select 'select-distinct 'from 'window}
|
|
|
|
|
k)
|
|
|
|
|
xs))
|
|
|
|
|
|
2021-03-13 02:54:19 +00:00
|
|
|
(defn- format-selects-on [_ xs]
|
2021-02-13 23:58:56 +00:00
|
|
|
(let [[on & cols] xs
|
|
|
|
|
[sql & params]
|
|
|
|
|
(format-expr (into [:distinct-on] on))
|
|
|
|
|
[sql' & params']
|
|
|
|
|
(format-selects-common
|
|
|
|
|
(str (sql-kw :select) " " sql)
|
|
|
|
|
true
|
|
|
|
|
cols)]
|
|
|
|
|
(-> [sql'] (into params) (into params'))))
|
2020-09-23 07:14:25 +00:00
|
|
|
|
2021-03-13 02:54:19 +00:00
|
|
|
(defn- format-select-top [k xs]
|
|
|
|
|
(let [[top & cols] xs
|
|
|
|
|
[top & parts]
|
|
|
|
|
(if (sequential? top)
|
|
|
|
|
;; could be an expression or a number followed by :percent :with-ties
|
|
|
|
|
(let [top-q? #(and (ident? %)
|
|
|
|
|
(#{:percent :with-ties} (sym->kw %)))
|
|
|
|
|
r-top (reverse top)
|
|
|
|
|
top-quals (take-while top-q? r-top)
|
|
|
|
|
top-list (drop-while top-q? r-top)]
|
|
|
|
|
(if (seq top-quals)
|
|
|
|
|
(if (= 1 (count top-list))
|
|
|
|
|
(into (vec top-list) (reverse top-quals))
|
|
|
|
|
(throw (ex-info "unparseable TOP expression"
|
|
|
|
|
{:top top})))
|
|
|
|
|
[top]))
|
|
|
|
|
[top])
|
|
|
|
|
[sql & params]
|
|
|
|
|
(format-expr top)
|
|
|
|
|
[sql' & params']
|
|
|
|
|
(format-selects-common
|
2021-03-13 20:10:42 +00:00
|
|
|
(str (sql-kw k) "(" sql ")"
|
|
|
|
|
(when (seq parts) " ")
|
|
|
|
|
(str/join " " (map sql-kw parts)))
|
2021-03-13 02:54:19 +00:00
|
|
|
true
|
|
|
|
|
cols)]
|
|
|
|
|
(-> [sql'] (into params) (into params'))))
|
|
|
|
|
|
2020-09-24 01:15:20 +00:00
|
|
|
(defn- format-with-part [x]
|
|
|
|
|
(if (sequential? x)
|
|
|
|
|
(let [[sql & params] (format-dsl (second x))]
|
|
|
|
|
(into [(str (format-entity (first x)) " " sql)] params))
|
|
|
|
|
[(format-entity x)]))
|
|
|
|
|
|
|
|
|
|
(defn- format-with [k xs]
|
2021-02-10 19:50:00 +00:00
|
|
|
;; TODO: a sequence of pairs -- X AS expr -- where X is either [entity expr]
|
|
|
|
|
;; or just entity, as far as I can tell...
|
|
|
|
|
(let [[sqls params]
|
|
|
|
|
(reduce (fn [[sql params] [sql' & params']]
|
|
|
|
|
[(conj sql sql') (if params' (into params params') params)])
|
|
|
|
|
[[] []]
|
|
|
|
|
(map (fn [[x expr]]
|
|
|
|
|
(let [[sql & params] (format-with-part x)
|
|
|
|
|
[sql' & params'] (format-dsl expr)]
|
|
|
|
|
;; according to docs, CTE should _always_ be wrapped:
|
|
|
|
|
(cond-> [(str sql " AS " (str "(" sql' ")"))]
|
|
|
|
|
params (into params)
|
|
|
|
|
params' (into params'))))
|
|
|
|
|
xs))]
|
|
|
|
|
(into [(str (sql-kw k) " " (str/join ", " sqls))] params)))
|
2020-09-24 01:15:20 +00:00
|
|
|
|
|
|
|
|
(defn- format-selector [k xs]
|
|
|
|
|
(format-selects k [xs]))
|
|
|
|
|
|
2020-09-23 07:14:25 +00:00
|
|
|
(defn- format-insert [k table]
|
|
|
|
|
(if (sequential? table)
|
2021-02-10 23:07:16 +00:00
|
|
|
(cond (map? (second table))
|
2020-09-28 19:12:40 +00:00
|
|
|
(let [[table statement] table
|
2021-02-10 23:07:16 +00:00
|
|
|
[table cols]
|
|
|
|
|
(if (and (sequential? table) (sequential? (second table)))
|
|
|
|
|
table
|
|
|
|
|
[table])
|
2020-09-28 19:12:40 +00:00
|
|
|
[sql & params] (format-dsl statement)]
|
|
|
|
|
(into [(str (sql-kw k) " " (format-entity-alias table)
|
2021-02-10 23:07:16 +00:00
|
|
|
" "
|
|
|
|
|
(when (seq cols)
|
|
|
|
|
(str "("
|
|
|
|
|
(str/join ", " (map #'format-entity-alias cols))
|
|
|
|
|
") "))
|
|
|
|
|
sql)]
|
2020-09-28 19:12:40 +00:00
|
|
|
params))
|
2021-02-10 23:07:16 +00:00
|
|
|
(sequential? (second table))
|
|
|
|
|
(let [[table cols] table]
|
|
|
|
|
[(str (sql-kw k) " " (format-entity-alias table)
|
|
|
|
|
" ("
|
|
|
|
|
(str/join ", " (map #'format-entity-alias cols))
|
|
|
|
|
")")])
|
2020-09-28 19:12:40 +00:00
|
|
|
:else
|
|
|
|
|
[(str (sql-kw k) " " (format-entity-alias table))])
|
2020-09-24 01:15:20 +00:00
|
|
|
[(str (sql-kw k) " " (format-entity-alias table))]))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2020-10-13 05:54:59 +00:00
|
|
|
(defn- format-join [k clauses]
|
|
|
|
|
(let [[sqls params]
|
|
|
|
|
(reduce (fn [[sqls params] [j e]]
|
2021-02-27 17:33:20 +00:00
|
|
|
(let [[sql-j & params-j]
|
|
|
|
|
(format-selects-common
|
|
|
|
|
(sql-kw (if (= :join k) :inner-join k))
|
|
|
|
|
true
|
|
|
|
|
[j])
|
|
|
|
|
sqls (conj sqls sql-j)]
|
2021-01-30 19:00:34 +00:00
|
|
|
(if (and (sequential? e) (= :using (first e)))
|
|
|
|
|
[(conj sqls
|
|
|
|
|
"USING"
|
|
|
|
|
(str "("
|
|
|
|
|
(str/join ", " (map #'format-entity-alias (rest e)))
|
|
|
|
|
")"))
|
2021-02-27 17:33:20 +00:00
|
|
|
(into params params-j)]
|
2021-01-30 19:00:34 +00:00
|
|
|
(let [[sql & params'] (when e (format-expr e))]
|
|
|
|
|
[(cond-> sqls e (conj "ON" sql))
|
|
|
|
|
(into params params')]))))
|
2020-10-13 05:54:59 +00:00
|
|
|
[[] []]
|
|
|
|
|
(partition 2 clauses))]
|
|
|
|
|
(into [(str/join " " sqls)] params)))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2021-03-08 03:21:13 +00:00
|
|
|
(def ^:private join-by-aliases
|
|
|
|
|
"Map of shorthand to longhand join names."
|
|
|
|
|
{:join :inner-join
|
|
|
|
|
:left :left-join
|
|
|
|
|
:right :right-join
|
|
|
|
|
:inner :inner-join
|
|
|
|
|
:outer :outer-join
|
|
|
|
|
:full :full-join
|
|
|
|
|
:cross :cross-join})
|
|
|
|
|
|
|
|
|
|
(def ^:private valid-joins
|
|
|
|
|
(set (vals join-by-aliases)))
|
|
|
|
|
|
|
|
|
|
(defn- format-join-by
|
|
|
|
|
"Clauses should be a sequence of join types followed
|
|
|
|
|
by their table and condition, so that you can construct
|
|
|
|
|
a series of joins in a specific order."
|
|
|
|
|
[_ clauses]
|
|
|
|
|
(let [joins (partition-by ident? clauses)]
|
|
|
|
|
(when-not (even? (count joins))
|
|
|
|
|
(throw (ex-info ":join-by expects a sequence of join clauses"
|
|
|
|
|
{:clauses clauses})))
|
|
|
|
|
(let [[sqls params]
|
|
|
|
|
(reduce (fn [[sqls params] [[j] [clauses]]]
|
|
|
|
|
(let [j' (sym->kw j)
|
|
|
|
|
j' (sym->kw (join-by-aliases j' j'))]
|
|
|
|
|
(when-not (valid-joins j')
|
|
|
|
|
(throw (ex-info (str ":join-by found an invalid join type "
|
|
|
|
|
j)
|
|
|
|
|
{})))
|
|
|
|
|
(let [[sql' & params'] (format-dsl {j' clauses})]
|
|
|
|
|
[(conj sqls sql') (into params params')])))
|
|
|
|
|
[[] []]
|
|
|
|
|
(partition 2 joins))]
|
|
|
|
|
(into [(str/join " " sqls)] params))))
|
|
|
|
|
|
2020-09-23 07:14:25 +00:00
|
|
|
(defn- format-on-expr [k e]
|
2020-10-13 01:50:07 +00:00
|
|
|
(if (or (not (sequential? e)) (seq e))
|
2020-10-13 01:37:48 +00:00
|
|
|
(let [[sql & params] (format-expr e)]
|
|
|
|
|
(into [(str (sql-kw k) " " sql)] params))
|
|
|
|
|
[]))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
(defn- format-group-by [k xs]
|
|
|
|
|
(let [[sqls params] (format-expr-list xs)]
|
|
|
|
|
(into [(str (sql-kw k) " " (str/join ", " sqls))] params)))
|
|
|
|
|
|
|
|
|
|
(defn- format-order-by [k xs]
|
2021-02-07 12:47:08 +00:00
|
|
|
(let [dirs (map #(when (sequential? %) (second %)) xs)
|
2020-09-21 02:17:37 +00:00
|
|
|
[sqls params]
|
|
|
|
|
(format-expr-list (map #(if (sequential? %) (first %) %) xs))]
|
2020-09-21 01:48:07 +00:00
|
|
|
(into [(str (sql-kw k) " "
|
2021-02-07 12:47:08 +00:00
|
|
|
(str/join ", " (map (fn [sql dir]
|
|
|
|
|
(str sql " " (sql-kw (or dir :asc))))
|
2020-09-21 01:48:07 +00:00
|
|
|
sqls
|
|
|
|
|
dirs)))] params)))
|
|
|
|
|
|
2020-09-29 03:45:43 +00:00
|
|
|
(defn- format-lock-strength [k xs]
|
|
|
|
|
(let [[strength tables nowait] (if (sequential? xs) xs [xs])]
|
|
|
|
|
[(str (sql-kw k) " " (sql-kw strength)
|
|
|
|
|
(when tables
|
|
|
|
|
(str
|
2021-03-07 17:43:03 +00:00
|
|
|
(cond (and (ident? tables)
|
|
|
|
|
(#{:nowait :skip-locked :wait} (sym->kw tables)))
|
2021-01-30 19:43:48 +00:00
|
|
|
(str " " (sql-kw tables))
|
2020-09-29 03:45:43 +00:00
|
|
|
(sequential? tables)
|
|
|
|
|
(str " OF "
|
|
|
|
|
(str/join ", " (map #'format-entity tables)))
|
|
|
|
|
:else
|
|
|
|
|
(str " OF " (format-entity tables)))
|
|
|
|
|
(when nowait
|
2021-01-30 19:43:48 +00:00
|
|
|
(str " " (sql-kw nowait))))))]))
|
2020-09-29 03:45:43 +00:00
|
|
|
|
2020-09-23 19:55:02 +00:00
|
|
|
(defn- format-values [k xs]
|
2020-09-24 01:15:20 +00:00
|
|
|
(cond (sequential? (first xs))
|
|
|
|
|
;; [[1 2 3] [4 5 6]]
|
2021-02-01 18:44:07 +00:00
|
|
|
(let [n-1 (map count xs)
|
|
|
|
|
;; issue #291: ensure all value sequences are the same length
|
|
|
|
|
xs' (if (apply = n-1)
|
|
|
|
|
xs
|
|
|
|
|
(let [n-n (apply max n-1)]
|
|
|
|
|
(map (fn [x] (take n-n (concat x (repeat nil)))) xs)))
|
|
|
|
|
[sqls params]
|
2020-09-24 01:15:20 +00:00
|
|
|
(reduce (fn [[sql params] [sqls' params']]
|
|
|
|
|
[(conj sql (str "(" (str/join ", " sqls') ")"))
|
|
|
|
|
(into params params')])
|
|
|
|
|
[[] []]
|
2021-02-01 18:44:07 +00:00
|
|
|
(map #'format-expr-list xs'))]
|
2020-09-24 01:15:20 +00:00
|
|
|
(into [(str (sql-kw k) " " (str/join ", " sqls))] params))
|
|
|
|
|
|
|
|
|
|
(map? (first xs))
|
|
|
|
|
;; [{:a 1 :b 2 :c 3}]
|
2021-02-01 18:44:07 +00:00
|
|
|
(let [cols-1 (keys (first xs))
|
|
|
|
|
;; issue #291: check for all keys in all maps but still
|
|
|
|
|
;; use the keys from the first map if they match so that
|
|
|
|
|
;; users can rely on the key ordering if they want to,
|
|
|
|
|
;; e.g., see test that uses array-map for the first row
|
|
|
|
|
cols-n (into #{} (mapcat keys) xs)
|
|
|
|
|
cols (if (= (set cols-1) cols-n) cols-1 cols-n)
|
2020-09-24 01:15:20 +00:00
|
|
|
[sqls params]
|
|
|
|
|
(reduce (fn [[sql params] [sqls' params']]
|
2020-10-10 06:58:55 +00:00
|
|
|
[(conj sql (str "(" (str/join ", " sqls') ")"))
|
2020-09-24 01:15:20 +00:00
|
|
|
(if params' (into params params') params')])
|
|
|
|
|
[[] []]
|
|
|
|
|
(map (fn [m]
|
|
|
|
|
(format-expr-list (map #(get m %) cols)))
|
|
|
|
|
xs))]
|
|
|
|
|
(into [(str "("
|
|
|
|
|
(str/join ", "
|
2021-02-01 21:10:57 +00:00
|
|
|
(map #(format-entity % {:drop-ns true}) cols))
|
2020-09-24 01:15:20 +00:00
|
|
|
") "
|
2020-10-10 06:58:55 +00:00
|
|
|
(sql-kw k)
|
|
|
|
|
" "
|
|
|
|
|
(str/join ", " sqls))]
|
2020-09-24 01:15:20 +00:00
|
|
|
params))
|
|
|
|
|
|
|
|
|
|
:else
|
|
|
|
|
(throw (ex-info ":values expects sequences or maps"
|
|
|
|
|
{:first (first xs)}))))
|
2020-09-23 19:55:02 +00:00
|
|
|
|
2021-02-01 18:44:07 +00:00
|
|
|
(comment
|
|
|
|
|
(into #{} (mapcat keys) [{:a 1 :b 2} {:b 3 :c 4}])
|
|
|
|
|
,)
|
|
|
|
|
|
2020-09-23 19:55:02 +00:00
|
|
|
(defn- format-set-exprs [k xs]
|
2020-09-24 01:15:20 +00:00
|
|
|
(let [[sqls params]
|
|
|
|
|
(reduce-kv (fn [[sql params] v e]
|
|
|
|
|
(let [[sql' & params'] (format-expr e)]
|
|
|
|
|
[(conj sql (str (format-entity v) " = " sql'))
|
|
|
|
|
(if params' (into params params') params)]))
|
|
|
|
|
[[] []]
|
|
|
|
|
xs)]
|
|
|
|
|
(into [(str (sql-kw k) " " (str/join ", " sqls))] params)))
|
2020-09-23 19:55:02 +00:00
|
|
|
|
2020-09-26 22:16:12 +00:00
|
|
|
(defn- format-on-conflict [k x]
|
2021-03-07 17:43:03 +00:00
|
|
|
(cond (ident? x)
|
2021-02-13 23:58:56 +00:00
|
|
|
[(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)
|
2021-03-07 17:43:03 +00:00
|
|
|
(ident? (first x))
|
2021-02-13 23:58:56 +00:00
|
|
|
(map? (second x)))
|
|
|
|
|
(let [[sql & params] (format-dsl (second x))]
|
|
|
|
|
(into [(str (sql-kw k)
|
|
|
|
|
" (" (format-entity (first x)) ") "
|
|
|
|
|
sql)]
|
|
|
|
|
params))
|
2021-02-16 01:56:47 +00:00
|
|
|
(and (sequential? x) (= 1 (count x)))
|
|
|
|
|
(format-on-conflict k (first x))
|
|
|
|
|
(and (sequential? x) (= 0 (count x)))
|
|
|
|
|
[(sql-kw k)]
|
2021-02-13 23:58:56 +00:00
|
|
|
:else
|
|
|
|
|
(throw (ex-info "unsupported :on-conflict format"
|
|
|
|
|
{:clause x}))))
|
2020-09-26 22:16:12 +00:00
|
|
|
|
|
|
|
|
(defn- format-do-update-set [k x]
|
2021-02-14 03:08:40 +00:00
|
|
|
(cond (map? x)
|
|
|
|
|
(if (or (contains? x :fields) (contains? x 'fields))
|
|
|
|
|
(let [sets (str/join ", "
|
|
|
|
|
(map (fn [e]
|
|
|
|
|
(let [e (format-entity e {:drop-ns true})]
|
|
|
|
|
(str e " = EXCLUDED." e)))
|
|
|
|
|
(or (:fields x)
|
|
|
|
|
('fields x))))
|
|
|
|
|
where (or (:where x) ('where x))
|
|
|
|
|
[sql & params] (when where (format-dsl {:where where}))]
|
|
|
|
|
(into [(str (sql-kw k) " " sets
|
|
|
|
|
(when sql (str " " sql)))] params))
|
|
|
|
|
(format-set-exprs k x))
|
|
|
|
|
(sequential? x)
|
|
|
|
|
(let [[cols clauses] (split-with (complement map?) x)]
|
|
|
|
|
(if (seq cols)
|
|
|
|
|
(recur k {:fields cols :where (:where (first clauses))})
|
|
|
|
|
(recur k (first clauses))))
|
|
|
|
|
:else
|
|
|
|
|
(let [e (format-entity x {:drop-ns true})]
|
|
|
|
|
[(str (sql-kw k) " " e " = EXCLUDED." e)])))
|
2020-09-26 22:16:12 +00:00
|
|
|
|
2021-03-12 19:43:21 +00:00
|
|
|
(defn- format-simple-clause [c context]
|
2021-02-13 05:50:22 +00:00
|
|
|
(binding [*inline* true]
|
2021-03-12 19:43:21 +00:00
|
|
|
(let [[sql & params] (format-dsl c)]
|
|
|
|
|
(when (seq params)
|
|
|
|
|
(throw (ex-info (str "parameters are not accepted in " context)
|
|
|
|
|
{:clause c :params params})))
|
|
|
|
|
sql)))
|
|
|
|
|
|
|
|
|
|
(defn- format-simple-expr [e context]
|
|
|
|
|
(binding [*inline* true]
|
|
|
|
|
(let [[sql & params] (format-expr e)]
|
|
|
|
|
(when (seq params)
|
|
|
|
|
(throw (ex-info (str "parameters are not accepted in " context)
|
|
|
|
|
{:expr e :params params})))
|
|
|
|
|
sql)))
|
2021-02-13 01:08:37 +00:00
|
|
|
|
|
|
|
|
(defn- format-alter-table [k x]
|
|
|
|
|
(if (sequential? x)
|
|
|
|
|
[(str (sql-kw k) " " (format-entity (first x))
|
|
|
|
|
(when-let [clauses (next x)]
|
2021-03-12 19:43:21 +00:00
|
|
|
(str " " (str/join ", " (map #(format-simple-clause % "column/index operations") clauses)))))]
|
2021-02-13 01:08:37 +00:00
|
|
|
[(str (sql-kw k) " " (format-entity x))]))
|
2021-02-11 04:07:05 +00:00
|
|
|
|
2021-03-12 19:43:21 +00:00
|
|
|
(defn- format-ddl-options
|
|
|
|
|
"Given a sequence of options for a DDL statement (the part that
|
|
|
|
|
comes between the entity name being created/dropped and the
|
|
|
|
|
remaining part of the statement), render clauses and sequences
|
|
|
|
|
of keywords and entity names. Returns a sequence of SQL strings."
|
|
|
|
|
[opts context]
|
|
|
|
|
(for [opt opts]
|
|
|
|
|
(cond (map? opt)
|
|
|
|
|
(format-simple-clause opt context)
|
|
|
|
|
(sequential? opt)
|
|
|
|
|
(str/join " "
|
|
|
|
|
(map (fn [e]
|
|
|
|
|
(if (ident? e)
|
|
|
|
|
(sql-kw e)
|
|
|
|
|
(format-simple-expr e context)))
|
|
|
|
|
opt))
|
|
|
|
|
:else
|
|
|
|
|
(sql-kw opt))))
|
|
|
|
|
|
|
|
|
|
(defn- destructure-create-item [table context]
|
|
|
|
|
(let [params
|
2021-03-08 06:13:25 +00:00
|
|
|
(if (sequential? table)
|
|
|
|
|
table
|
|
|
|
|
[table])
|
2021-03-12 19:43:21 +00:00
|
|
|
coll (take-while ident? params)
|
|
|
|
|
opts (drop-while ident? params)
|
|
|
|
|
ine (last coll)
|
2021-03-08 06:13:25 +00:00
|
|
|
[prequel table ine]
|
|
|
|
|
(if (= :if-not-exists (sym->kw ine))
|
|
|
|
|
[(butlast (butlast coll)) (last (butlast coll)) ine]
|
|
|
|
|
[(butlast coll) (last coll) nil])]
|
2021-03-12 19:43:21 +00:00
|
|
|
(into [(str/join " " (map sql-kw prequel))
|
|
|
|
|
(format-entity table)
|
|
|
|
|
(when ine (sql-kw ine))]
|
|
|
|
|
(format-ddl-options opts context))))
|
|
|
|
|
|
|
|
|
|
(defn- format-create [q k item as]
|
|
|
|
|
(let [[pre entity ine & more]
|
|
|
|
|
(destructure-create-item item (str (sql-kw q) " options"))]
|
2021-03-08 06:13:25 +00:00
|
|
|
[(str/join " " (remove nil?
|
2021-03-12 19:43:21 +00:00
|
|
|
(-> [(sql-kw q)
|
|
|
|
|
(when (and (= :create q) (seq pre)) pre)
|
|
|
|
|
(sql-kw k)
|
|
|
|
|
ine
|
|
|
|
|
(when (and (= :refresh q) (seq pre)) pre)
|
|
|
|
|
entity]
|
|
|
|
|
(into more)
|
|
|
|
|
(conj (when as (sql-kw as))))))]))
|
|
|
|
|
|
|
|
|
|
(defn- format-with-data [_ data]
|
|
|
|
|
(let [data (if (sequential? data) (first data) data)]
|
|
|
|
|
[(str/join " " (remove nil?
|
|
|
|
|
[(sql-kw :with)
|
|
|
|
|
(when-not data (sql-kw :no))
|
|
|
|
|
(sql-kw :data)]))]))
|
|
|
|
|
|
|
|
|
|
(defn- destructure-drop-items [tables context]
|
|
|
|
|
(let [params
|
|
|
|
|
(if (sequential? tables)
|
|
|
|
|
tables
|
|
|
|
|
[tables])
|
|
|
|
|
coll (take-while ident? params)
|
|
|
|
|
opts (drop-while ident? params)
|
|
|
|
|
[if-exists & tables]
|
|
|
|
|
(if (#{:if-exists 'if-exists} (first coll))
|
|
|
|
|
coll
|
|
|
|
|
(cons nil coll))]
|
|
|
|
|
(into [(when if-exists (sql-kw :if-exists))
|
|
|
|
|
(str/join ", " (map #'format-entity tables))]
|
|
|
|
|
(format-ddl-options opts context))))
|
|
|
|
|
|
|
|
|
|
(defn- format-drop-items
|
2021-02-11 04:07:05 +00:00
|
|
|
[k params]
|
2021-03-12 19:43:21 +00:00
|
|
|
(let [[if-exists tables & more] (destructure-drop-items params "DROP options")]
|
|
|
|
|
[(str/join " " (remove nil? (into [(sql-kw k) if-exists tables] more)))]))
|
2021-02-13 01:08:37 +00:00
|
|
|
|
|
|
|
|
(defn- format-single-column [xs]
|
2021-03-12 19:43:21 +00:00
|
|
|
(str/join " " (let [[id & spec] (map #(format-simple-expr % "column operation") xs)]
|
2021-02-13 05:50:22 +00:00
|
|
|
(cons id (map upper-case spec)))))
|
2021-02-13 01:08:37 +00:00
|
|
|
|
2021-03-12 19:43:21 +00:00
|
|
|
(defn- format-table-columns [_ xs]
|
2021-02-13 18:50:36 +00:00
|
|
|
[(str "("
|
|
|
|
|
(str/join ", " (map #'format-single-column xs))
|
|
|
|
|
")")])
|
2021-02-13 01:08:37 +00:00
|
|
|
|
|
|
|
|
(defn- format-add-item [k spec]
|
|
|
|
|
[(str (sql-kw k) " " (format-single-column spec))])
|
|
|
|
|
|
|
|
|
|
(defn- format-rename-item [k [x y]]
|
|
|
|
|
[(str (sql-kw k) " " (format-entity x) " TO " (format-entity y))])
|
2021-02-11 04:07:05 +00:00
|
|
|
|
2020-09-24 18:13:08 +00:00
|
|
|
(def ^:private base-clause-order
|
|
|
|
|
"The (base) order for known clauses. Can have items added and removed.
|
|
|
|
|
|
|
|
|
|
This is the 'pre-dialect' ordering."
|
|
|
|
|
(atom default-clause-order))
|
|
|
|
|
|
2020-09-23 19:55:02 +00:00
|
|
|
(def ^:private current-clause-order
|
2020-09-24 18:13:08 +00:00
|
|
|
"The (current) order for known clauses. Can have items added and removed.
|
|
|
|
|
|
|
|
|
|
This is the 'post-dialect` ordering when a new default dialect is set."
|
2020-09-23 19:55:02 +00:00
|
|
|
(atom default-clause-order))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
(def ^:private clause-format
|
|
|
|
|
"The (default) behavior for each known clause. Can also have items added
|
|
|
|
|
and removed."
|
2021-02-11 04:07:05 +00:00
|
|
|
(atom {:alter-table #'format-alter-table
|
2021-02-13 01:08:37 +00:00
|
|
|
:add-column #'format-add-item
|
|
|
|
|
:drop-column #'format-selector
|
2021-02-13 05:50:22 +00:00
|
|
|
:modify-column #'format-add-item
|
2021-02-13 01:08:37 +00:00
|
|
|
:rename-column #'format-rename-item
|
2021-02-13 05:50:22 +00:00
|
|
|
;; so :add-index works with both [:index] and [:unique]
|
|
|
|
|
:add-index (fn [_ x] (format-on-expr :add x))
|
2021-02-13 01:08:37 +00:00
|
|
|
:drop-index #'format-selector
|
2021-02-13 20:24:21 +00:00
|
|
|
:rename-table (fn [_ x] (format-selector :rename-to x))
|
2021-03-12 19:43:21 +00:00
|
|
|
:create-table (fn [_ x] (format-create :create :table x nil))
|
|
|
|
|
:create-table-as (fn [_ x] (format-create :create :table x :as))
|
|
|
|
|
:create-extension (fn [_ x] (format-create :create :extension x nil))
|
2021-02-11 04:07:05 +00:00
|
|
|
:with-columns #'format-table-columns
|
2021-03-12 19:43:21 +00:00
|
|
|
:create-view (fn [_ x] (format-create :create :view x :as))
|
|
|
|
|
:create-materialized-view (fn [_ x] (format-create :create :materialized-view x :as))
|
|
|
|
|
:drop-table #'format-drop-items
|
|
|
|
|
:drop-extension #'format-drop-items
|
|
|
|
|
:drop-view #'format-drop-items
|
|
|
|
|
:drop-materialized-view #'format-drop-items
|
|
|
|
|
:refresh-materialized-view (fn [_ x] (format-create :refresh :materialized-view x nil))
|
2021-02-11 04:07:05 +00:00
|
|
|
:nest (fn [_ x] (format-expr x))
|
2020-10-10 06:05:05 +00:00
|
|
|
:with #'format-with
|
|
|
|
|
:with-recursive #'format-with
|
|
|
|
|
:intersect #'format-on-set-op
|
|
|
|
|
:union #'format-on-set-op
|
|
|
|
|
:union-all #'format-on-set-op
|
|
|
|
|
:except #'format-on-set-op
|
|
|
|
|
:except-all #'format-on-set-op
|
|
|
|
|
:select #'format-selects
|
|
|
|
|
:select-distinct #'format-selects
|
2021-02-13 23:58:56 +00:00
|
|
|
:select-distinct-on #'format-selects-on
|
2021-03-13 02:54:19 +00:00
|
|
|
:select-top #'format-select-top
|
|
|
|
|
:select-distinct-top #'format-select-top
|
2020-10-10 06:05:05 +00:00
|
|
|
:insert-into #'format-insert
|
|
|
|
|
:update #'format-selector
|
|
|
|
|
:delete #'format-selects
|
|
|
|
|
:delete-from #'format-selector
|
|
|
|
|
:truncate #'format-selector
|
|
|
|
|
:columns #'format-columns
|
|
|
|
|
:set #'format-set-exprs
|
|
|
|
|
:from #'format-selects
|
2021-01-30 19:19:12 +00:00
|
|
|
:using #'format-selects
|
2021-03-08 03:21:13 +00:00
|
|
|
:join-by #'format-join-by
|
2020-10-10 06:05:05 +00:00
|
|
|
:join #'format-join
|
|
|
|
|
:left-join #'format-join
|
|
|
|
|
:right-join #'format-join
|
|
|
|
|
:inner-join #'format-join
|
|
|
|
|
:outer-join #'format-join
|
|
|
|
|
:full-join #'format-join
|
|
|
|
|
:cross-join #'format-selects
|
|
|
|
|
:where #'format-on-expr
|
|
|
|
|
:group-by #'format-group-by
|
|
|
|
|
:having #'format-on-expr
|
2021-02-11 00:25:31 +00:00
|
|
|
:window #'format-selector
|
|
|
|
|
:partition-by #'format-selects
|
2020-10-10 06:05:05 +00:00
|
|
|
:order-by #'format-order-by
|
|
|
|
|
:limit #'format-on-expr
|
|
|
|
|
:offset #'format-on-expr
|
2021-03-13 20:36:25 +00:00
|
|
|
:fetch #'format-on-expr
|
2020-10-10 06:05:05 +00:00
|
|
|
:for #'format-lock-strength
|
2021-03-13 20:36:25 +00:00
|
|
|
:lock #'format-lock-strength
|
2020-10-10 06:05:05 +00:00
|
|
|
:values #'format-values
|
|
|
|
|
:on-conflict #'format-on-conflict
|
|
|
|
|
:on-constraint #'format-selector
|
|
|
|
|
:do-nothing (fn [k _] (vector (sql-kw k)))
|
|
|
|
|
:do-update-set #'format-do-update-set
|
2021-03-13 00:13:52 +00:00
|
|
|
;; MySQL-specific but might as well be always enabled:
|
|
|
|
|
:on-duplicate-key-update #'format-do-update-set
|
2021-03-12 04:07:59 +00:00
|
|
|
:returning #'format-selects
|
|
|
|
|
:with-data #'format-with-data}))
|
2020-09-23 07:14:25 +00:00
|
|
|
|
2020-09-24 18:13:08 +00:00
|
|
|
(assert (= (set @base-clause-order)
|
|
|
|
|
(set @current-clause-order)
|
|
|
|
|
(set (keys @clause-format))))
|
2020-09-21 02:17:37 +00:00
|
|
|
|
2021-03-07 17:43:03 +00:00
|
|
|
(defn- kw->sym
|
|
|
|
|
"Given a keyword, produce a symbol, retaining the namespace
|
|
|
|
|
qualifier, if any."
|
|
|
|
|
[k]
|
|
|
|
|
(if (keyword? k)
|
|
|
|
|
(if-let [n (namespace k)]
|
|
|
|
|
(symbol n (name k))
|
|
|
|
|
(symbol (name k)))
|
|
|
|
|
k))
|
|
|
|
|
|
2021-02-01 20:00:42 +00:00
|
|
|
(defn format-dsl
|
|
|
|
|
"Given a hash map representing a SQL statement and a hash map
|
|
|
|
|
of options, return a vector containing a string -- the formatted
|
|
|
|
|
SQL statement -- followed by any parameter values that SQL needs.
|
|
|
|
|
|
|
|
|
|
This is intended to be used when writing your own formatters to
|
|
|
|
|
extend the DSL supported by HoneySQL."
|
2021-02-01 21:10:57 +00:00
|
|
|
[statement-map & [{:keys [aliased nested pretty]}]]
|
2020-09-23 07:14:25 +00:00
|
|
|
(let [[sqls params leftover]
|
|
|
|
|
(reduce (fn [[sql params leftover] k]
|
2021-03-12 19:43:21 +00:00
|
|
|
(if-some [xs (if-some [xs (k statement-map)]
|
|
|
|
|
xs
|
|
|
|
|
(let [s (kw->sym k)]
|
|
|
|
|
(get statement-map s)))]
|
2020-09-21 01:48:07 +00:00
|
|
|
(let [formatter (k @clause-format)
|
|
|
|
|
[sql' & params'] (formatter k xs)]
|
2020-09-23 07:14:25 +00:00
|
|
|
[(conj sql sql')
|
|
|
|
|
(if params' (into params params') params)
|
2021-02-02 20:25:26 +00:00
|
|
|
(dissoc leftover k (kw->sym k))])
|
2020-09-23 07:14:25 +00:00
|
|
|
[sql params leftover]))
|
2021-02-01 20:00:42 +00:00
|
|
|
[[] [] statement-map]
|
2020-09-23 19:55:02 +00:00
|
|
|
*clause-order*)]
|
|
|
|
|
(if (seq leftover)
|
2021-02-01 20:00:42 +00:00
|
|
|
(throw (ex-info (str "Unknown SQL clauses: "
|
|
|
|
|
(str/join ", " (keys leftover)))
|
|
|
|
|
leftover))
|
2021-02-01 21:10:57 +00:00
|
|
|
(into [(cond-> (str/join (if pretty "\n" " ") (filter seq sqls))
|
|
|
|
|
pretty
|
2020-09-26 06:58:51 +00:00
|
|
|
(as-> s (str "\n" s "\n"))
|
2021-02-01 21:10:57 +00:00
|
|
|
(and nested (not aliased))
|
2020-09-24 01:15:20 +00:00
|
|
|
(as-> s (str "(" s ")")))] params))))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
(def ^:private infix-aliases
|
|
|
|
|
"Provided for backward compatibility with earlier HoneySQL versions."
|
|
|
|
|
{:is :=
|
|
|
|
|
:is-not :<>
|
|
|
|
|
:not= :<>
|
|
|
|
|
:!= :<>
|
|
|
|
|
:regex :regexp})
|
|
|
|
|
|
|
|
|
|
(def ^:private infix-ops
|
2020-09-28 18:49:13 +00:00
|
|
|
(-> #{"mod" "and" "or" "xor" "<>" "<=" ">=" "||"
|
2021-01-31 13:08:21 +00:00
|
|
|
"like" "not-like" "regexp"
|
2020-10-14 18:00:39 +00:00
|
|
|
"ilike" "not-ilike" "similar-to" "not-similar-to"
|
2020-09-21 01:48:07 +00:00
|
|
|
"is" "is-not" "not=" "!=" "regex"}
|
2021-01-31 01:27:00 +00:00
|
|
|
(into (map str "+-*%|&^=<>"))
|
2020-09-21 01:48:07 +00:00
|
|
|
(into (keys infix-aliases))
|
|
|
|
|
(into (vals infix-aliases))
|
2020-09-29 21:40:03 +00:00
|
|
|
(->> (into #{} (map keyword)))
|
2021-01-31 01:27:00 +00:00
|
|
|
(conj :/) ; because (keyword "/") does not work in cljs
|
2020-09-29 21:40:03 +00:00
|
|
|
(atom)))
|
|
|
|
|
|
|
|
|
|
(def ^:private op-ignore-nil (atom #{:and :or}))
|
|
|
|
|
(def ^:private op-variadic (atom #{:and :or :+ :* :||}))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2021-01-31 13:08:21 +00:00
|
|
|
(defn- unwrap [x opts]
|
|
|
|
|
(if-let [m (meta x)]
|
|
|
|
|
(if-let [f (::wrapper m)]
|
|
|
|
|
(f x opts)
|
|
|
|
|
x)
|
|
|
|
|
x))
|
|
|
|
|
|
|
|
|
|
(defn- format-in [in [x y]]
|
2021-02-01 21:10:57 +00:00
|
|
|
(let [[sql-x & params-x] (format-expr x {:nested true})
|
|
|
|
|
[sql-y & params-y] (format-expr y {:nested true})
|
2021-01-31 13:08:21 +00:00
|
|
|
values (unwrap (first params-y) {})]
|
|
|
|
|
(if (and (= "?" sql-y) (= 1 (count params-y)) (coll? values))
|
|
|
|
|
(let [sql (str "(" (str/join ", " (repeat (count values) "?")) ")")]
|
|
|
|
|
(-> [(str sql-x " " (sql-kw in) " " sql)]
|
|
|
|
|
(into params-x)
|
|
|
|
|
(into values)))
|
|
|
|
|
(-> [(str sql-x " " (sql-kw in) " " sql-y)]
|
|
|
|
|
(into params-x)
|
|
|
|
|
(into params-y)))))
|
|
|
|
|
|
2021-02-13 05:50:22 +00:00
|
|
|
(defn- function-0 [k xs]
|
|
|
|
|
[(str (sql-kw k)
|
|
|
|
|
(when (seq xs)
|
2021-03-12 19:43:21 +00:00
|
|
|
(str "("
|
|
|
|
|
(str/join ", "
|
|
|
|
|
(map #(format-simple-expr % "column/index operation")
|
|
|
|
|
xs))
|
|
|
|
|
")")))])
|
2021-02-13 05:50:22 +00:00
|
|
|
|
|
|
|
|
(defn- function-1 [k xs]
|
|
|
|
|
[(str (sql-kw k)
|
|
|
|
|
(when (seq xs)
|
2021-03-12 19:43:21 +00:00
|
|
|
(str " " (format-simple-expr (first xs)
|
|
|
|
|
"column/index operation")
|
2021-02-13 05:50:22 +00:00
|
|
|
(when-let [args (next xs)]
|
2021-03-12 19:43:21 +00:00
|
|
|
(str "("
|
|
|
|
|
(str/join ", "
|
|
|
|
|
(map #(format-simple-expr % "column/index operation")
|
|
|
|
|
args))
|
|
|
|
|
")")))))])
|
2021-02-13 05:50:22 +00:00
|
|
|
|
|
|
|
|
(defn- function-1-opt [k xs]
|
|
|
|
|
[(str (sql-kw k)
|
|
|
|
|
(when (seq xs)
|
|
|
|
|
(str (when-let [e (first xs)]
|
2021-03-12 19:43:21 +00:00
|
|
|
(str " " (format-simple-expr e "column/index operation")))
|
2021-02-13 05:50:22 +00:00
|
|
|
(when-let [args (next xs)]
|
2021-03-12 19:43:21 +00:00
|
|
|
(str "("
|
|
|
|
|
(str/join ", "
|
|
|
|
|
(map #(format-simple-expr % "column/index operation")
|
|
|
|
|
args))
|
|
|
|
|
")")))))])
|
2021-02-13 05:50:22 +00:00
|
|
|
|
2020-09-21 01:48:07 +00:00
|
|
|
(def ^:private special-syntax
|
2020-09-29 21:40:03 +00:00
|
|
|
(atom
|
2021-02-13 05:50:22 +00:00
|
|
|
{;; these "functions" are mostly used in column
|
|
|
|
|
;; descriptions so they generally have one of two forms:
|
|
|
|
|
;; function-0 - with zero arguments, renders as a keyword,
|
|
|
|
|
;; otherwise renders as a function call
|
|
|
|
|
;; function-1 - with zero arguments, renders as a keyword,
|
|
|
|
|
;; with one argument, as a keyword followed by an entity,
|
|
|
|
|
;; otherwise renders as a keyword followed by a function
|
|
|
|
|
;; call using the first entity as the function
|
|
|
|
|
;; function-1-opt - like function-1 except if the first
|
|
|
|
|
;; argument is nil, it is omitted
|
|
|
|
|
:constraint #'function-1
|
|
|
|
|
:default #'function-1
|
|
|
|
|
:foreign-key #'function-0
|
|
|
|
|
:index #'function-1-opt
|
|
|
|
|
:primary-key #'function-0
|
|
|
|
|
:references #'function-1
|
|
|
|
|
:unique #'function-1-opt
|
2021-03-12 19:43:21 +00:00
|
|
|
;; used in DDL to force rendering as a SQL entity instead
|
|
|
|
|
;; of a SQL keyword:
|
|
|
|
|
:entity (fn [_ [e]] [(format-entity e)])
|
2021-02-13 05:50:22 +00:00
|
|
|
:array
|
2020-09-29 21:40:03 +00:00
|
|
|
(fn [_ [arr]]
|
|
|
|
|
(let [[sqls params] (format-expr-list arr)]
|
|
|
|
|
(into [(str "ARRAY[" (str/join ", " sqls) "]")] params)))
|
|
|
|
|
:between
|
|
|
|
|
(fn [_ [x a b]]
|
2021-02-01 21:10:57 +00:00
|
|
|
(let [[sql-x & params-x] (format-expr x {:nested true})
|
|
|
|
|
[sql-a & params-a] (format-expr a {:nested true})
|
|
|
|
|
[sql-b & params-b] (format-expr b {:nested true})]
|
2020-09-29 21:40:03 +00:00
|
|
|
(-> [(str sql-x " BETWEEN " sql-a " AND " sql-b)]
|
|
|
|
|
(into params-x)
|
|
|
|
|
(into params-a)
|
|
|
|
|
(into params-b))))
|
2020-10-13 01:38:05 +00:00
|
|
|
:case
|
|
|
|
|
(fn [_ clauses]
|
|
|
|
|
(let [[sqls params]
|
|
|
|
|
(reduce (fn [[sqls params] [condition value]]
|
|
|
|
|
(let [[sqlc & paramsc] (when-not (= :else condition)
|
|
|
|
|
(format-expr condition))
|
|
|
|
|
[sqlv & paramsv] (format-expr value)]
|
2021-02-03 00:43:09 +00:00
|
|
|
[(if (or (= :else condition)
|
|
|
|
|
(= 'else condition))
|
2020-10-13 01:38:05 +00:00
|
|
|
(conj sqls (sql-kw :else) sqlv)
|
|
|
|
|
(conj sqls (sql-kw :when) sqlc (sql-kw :then) sqlv))
|
|
|
|
|
(-> params (into paramsc) (into paramsv))]))
|
|
|
|
|
[[] []]
|
|
|
|
|
(partition 2 clauses))]
|
|
|
|
|
(into [(str (sql-kw :case) " "
|
|
|
|
|
(str/join " " sqls)
|
|
|
|
|
" " (sql-kw :end))]
|
|
|
|
|
params)))
|
2020-09-29 21:40:03 +00:00
|
|
|
:cast
|
|
|
|
|
(fn [_ [x type]]
|
2020-10-02 20:24:44 +00:00
|
|
|
(let [[sql & params] (format-expr x)
|
|
|
|
|
[sql' & params'] (format-expr type)]
|
|
|
|
|
(-> [(str "CAST(" sql " AS " sql' ")")]
|
|
|
|
|
(into params)
|
|
|
|
|
(into params'))))
|
2020-10-12 18:42:47 +00:00
|
|
|
:composite
|
|
|
|
|
(fn [_ [& args]]
|
|
|
|
|
(let [[sqls params] (format-expr-list args)]
|
|
|
|
|
(into [(str "(" (str/join ", " sqls) ")")] params)))
|
2021-03-12 23:39:54 +00:00
|
|
|
:escape
|
|
|
|
|
(fn [_ [pattern escape-chars]]
|
|
|
|
|
(let [[sql-p & params-p] (format-expr pattern)
|
|
|
|
|
[sql-e & params-e] (format-expr escape-chars)]
|
|
|
|
|
(-> [(str sql-p " " (sql-kw :escape) " " sql-e)]
|
|
|
|
|
(into params-p)
|
|
|
|
|
(into params-e))))
|
2020-09-29 21:40:03 +00:00
|
|
|
:inline
|
|
|
|
|
(fn [_ [x]]
|
2020-10-14 18:50:32 +00:00
|
|
|
(if (sequential? x)
|
|
|
|
|
[(str/join " " (map #'sqlize-value x))]
|
|
|
|
|
[(sqlize-value x)]))
|
2020-09-29 21:40:03 +00:00
|
|
|
:interval
|
|
|
|
|
(fn [_ [n units]]
|
|
|
|
|
(let [[sql & params] (format-expr n)]
|
|
|
|
|
(into [(str "INTERVAL " sql " " (sql-kw units))] params)))
|
2020-10-02 20:43:44 +00:00
|
|
|
:lift
|
|
|
|
|
(fn [_ [x]]
|
2021-02-14 05:29:22 +00:00
|
|
|
(if *inline*
|
|
|
|
|
;; this is pretty much always going to be wrong,
|
|
|
|
|
;; but it could produce a valid result so we just
|
|
|
|
|
;; assume that the user knows what they are doing:
|
|
|
|
|
[(sqlize-value x)]
|
|
|
|
|
["?" (with-meta (constantly x)
|
|
|
|
|
{::wrapper (fn [fx _] (fx))})]))
|
2020-10-02 20:24:44 +00:00
|
|
|
:nest
|
|
|
|
|
(fn [_ [x]]
|
2021-02-01 21:10:57 +00:00
|
|
|
(format-expr x {:nested true}))
|
2020-09-29 21:40:03 +00:00
|
|
|
:not
|
|
|
|
|
(fn [_ [x]]
|
|
|
|
|
(let [[sql & params] (format-expr x)]
|
2020-10-02 06:30:18 +00:00
|
|
|
(into [(str "NOT " sql)] params)))
|
2021-02-11 00:25:31 +00:00
|
|
|
:over
|
|
|
|
|
(fn [_ [& args]]
|
|
|
|
|
(let [[sqls params]
|
|
|
|
|
(reduce (fn [[sqls params] [e p a]]
|
|
|
|
|
(let [[sql-e & params-e] (format-expr e)
|
|
|
|
|
[sql-p & params-p] (if (or (nil? p) (map? p))
|
|
|
|
|
(format-dsl p {:nested true})
|
|
|
|
|
[(format-entity p)])]
|
|
|
|
|
[(conj sqls (str sql-e " OVER " sql-p
|
|
|
|
|
(when a (str " AS " (format-entity a)))))
|
|
|
|
|
(-> params (into params-e) (into params-p))]))
|
|
|
|
|
[[] []]
|
|
|
|
|
args)]
|
|
|
|
|
(into [(str/join ", " sqls)] params)))
|
2020-10-02 06:30:18 +00:00
|
|
|
:param
|
|
|
|
|
(fn [_ [k]]
|
2021-02-14 05:29:22 +00:00
|
|
|
(if *inline*
|
|
|
|
|
[(sqlize-value (param-value k))]
|
|
|
|
|
["?" (->param k)]))
|
2020-10-02 06:30:18 +00:00
|
|
|
:raw
|
|
|
|
|
(fn [_ [s]]
|
2020-10-14 18:50:32 +00:00
|
|
|
(if (sequential? s)
|
2021-01-30 20:35:51 +00:00
|
|
|
(let [[sqls params]
|
|
|
|
|
(reduce (fn [[sqls params] s]
|
2021-02-02 20:25:26 +00:00
|
|
|
(if (sequential? s)
|
2021-01-30 20:35:51 +00:00
|
|
|
(let [[sql & params'] (format-expr s)]
|
|
|
|
|
[(conj sqls sql)
|
|
|
|
|
(into params params')])
|
|
|
|
|
[(conj sqls s) params]))
|
|
|
|
|
[[] []]
|
|
|
|
|
s)]
|
|
|
|
|
(into [(str/join sqls)] params))
|
2020-10-14 18:50:32 +00:00
|
|
|
[s]))}))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2021-02-01 20:00:42 +00:00
|
|
|
(defn format-expr
|
|
|
|
|
"Given a data structure that represents a SQL expression and a hash
|
|
|
|
|
map of options, return a vector containing a string -- the formatted
|
|
|
|
|
SQL statement -- followed by any parameter values that SQL needs.
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2021-02-01 20:00:42 +00:00
|
|
|
This is intended to be used when writing your own formatters to
|
|
|
|
|
extend the DSL supported by HoneySQL."
|
2021-02-01 21:10:57 +00:00
|
|
|
[expr & [{:keys [nested] :as opts}]]
|
2021-03-07 17:43:03 +00:00
|
|
|
(cond (ident? expr)
|
2021-02-01 20:00:42 +00:00
|
|
|
(format-var expr opts)
|
2020-09-23 19:55:02 +00:00
|
|
|
|
2021-02-01 20:00:42 +00:00
|
|
|
(map? expr)
|
2021-02-01 21:10:57 +00:00
|
|
|
(format-dsl expr (assoc opts :nested true))
|
2021-02-01 20:00:42 +00:00
|
|
|
|
|
|
|
|
(sequential? expr)
|
2021-02-02 20:25:26 +00:00
|
|
|
(let [op (sym->kw (first expr))]
|
2020-09-21 01:48:07 +00:00
|
|
|
(if (keyword? op)
|
2020-09-29 21:40:03 +00:00
|
|
|
(cond (contains? @infix-ops op)
|
|
|
|
|
(if (contains? @op-variadic op) ; no aliases here, no special semantics
|
2021-02-01 20:00:42 +00:00
|
|
|
(let [x (if (contains? @op-ignore-nil op)
|
|
|
|
|
(remove nil? expr)
|
|
|
|
|
expr)
|
2020-09-25 23:40:15 +00:00
|
|
|
[sqls params]
|
|
|
|
|
(reduce (fn [[sql params] [sql' & params']]
|
|
|
|
|
[(conj sql sql')
|
|
|
|
|
(if params' (into params params') params)])
|
|
|
|
|
[[] []]
|
2021-02-01 21:10:57 +00:00
|
|
|
(map #(format-expr % {:nested true})
|
2020-09-25 23:40:15 +00:00
|
|
|
(rest x)))]
|
|
|
|
|
(into [(cond-> (str/join (str " " (sql-kw op) " ") sqls)
|
2021-02-01 21:10:57 +00:00
|
|
|
nested
|
2020-09-25 23:40:15 +00:00
|
|
|
(as-> s (str "(" s ")")))]
|
|
|
|
|
params))
|
2021-02-01 20:00:42 +00:00
|
|
|
(let [[_ a b & y] expr
|
2020-09-25 23:40:15 +00:00
|
|
|
_ (when (seq y)
|
|
|
|
|
(throw (ex-info (str "only binary "
|
|
|
|
|
op
|
2020-10-10 06:59:43 +00:00
|
|
|
" is supported")
|
2021-02-01 20:00:42 +00:00
|
|
|
{:expr expr})))
|
2021-02-01 21:10:57 +00:00
|
|
|
[s1 & p1] (format-expr a {:nested true})
|
|
|
|
|
[s2 & p2] (format-expr b {:nested true})
|
2020-09-25 23:40:15 +00:00
|
|
|
op (get infix-aliases op op)]
|
|
|
|
|
(if (and (#{:= :<>} op) (or (nil? a) (nil? b)))
|
|
|
|
|
(-> (str (if (nil? a)
|
|
|
|
|
(if (nil? b) "NULL" s2)
|
|
|
|
|
s1)
|
|
|
|
|
(if (= := op) " IS NULL" " IS NOT NULL"))
|
2021-02-01 21:10:57 +00:00
|
|
|
(cond-> nested
|
2020-09-25 23:40:15 +00:00
|
|
|
(as-> s (str "(" s ")")))
|
|
|
|
|
(vector))
|
|
|
|
|
(-> (str s1 " " (sql-kw op) " " s2)
|
2021-02-01 21:10:57 +00:00
|
|
|
(cond-> nested
|
2020-09-25 23:40:15 +00:00
|
|
|
(as-> s (str "(" s ")")))
|
|
|
|
|
(vector)
|
|
|
|
|
(into p1)
|
|
|
|
|
(into p2)))))
|
2021-01-31 13:08:21 +00:00
|
|
|
(contains? #{:in :not-in} op)
|
2021-02-01 20:00:42 +00:00
|
|
|
(let [[sql & params] (format-in op (rest expr))]
|
2021-02-01 21:10:57 +00:00
|
|
|
(into [(if nested (str "(" sql ")") sql)] params))
|
2020-09-29 21:40:03 +00:00
|
|
|
(contains? @special-syntax op)
|
|
|
|
|
(let [formatter (get @special-syntax op)]
|
2021-02-01 20:00:42 +00:00
|
|
|
(formatter op (rest expr)))
|
2020-09-21 01:48:07 +00:00
|
|
|
:else
|
2021-02-01 20:00:42 +00:00
|
|
|
(let [args (rest expr)
|
2020-09-24 01:15:20 +00:00
|
|
|
[sqls params] (format-expr-list args)]
|
2020-09-21 01:48:07 +00:00
|
|
|
(into [(str (sql-kw op)
|
2020-09-24 01:15:20 +00:00
|
|
|
(if (and (= 1 (count args))
|
|
|
|
|
(map? (first args))
|
|
|
|
|
(= 1 (count sqls)))
|
|
|
|
|
(str " " (first sqls))
|
|
|
|
|
(str "(" (str/join ", " sqls) ")")))]
|
2020-09-21 01:48:07 +00:00
|
|
|
params)))
|
2021-02-01 20:00:42 +00:00
|
|
|
(let [[sqls params] (format-expr-list expr)]
|
2020-10-12 18:33:18 +00:00
|
|
|
(into [(str "(" (str/join ", " sqls) ")")] params))))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2021-02-01 20:00:42 +00:00
|
|
|
(boolean? expr)
|
|
|
|
|
[(upper-case (str expr))]
|
2020-09-24 01:15:20 +00:00
|
|
|
|
2021-02-01 20:00:42 +00:00
|
|
|
(nil? expr)
|
2020-09-28 19:12:10 +00:00
|
|
|
["NULL"]
|
|
|
|
|
|
2020-09-21 01:48:07 +00:00
|
|
|
:else
|
2020-10-10 04:52:18 +00:00
|
|
|
(if *inline*
|
2021-02-01 20:00:42 +00:00
|
|
|
[(sqlize-value expr)]
|
|
|
|
|
["?" expr])))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2020-09-23 19:55:02 +00:00
|
|
|
(defn- check-dialect [dialect]
|
|
|
|
|
(when-not (contains? dialects dialect)
|
|
|
|
|
(throw (ex-info (str "Invalid dialect: " dialect)
|
|
|
|
|
{:valid-dialects (vec (sort (keys dialects)))})))
|
|
|
|
|
dialect)
|
|
|
|
|
|
2020-09-21 02:17:37 +00:00
|
|
|
(defn format
|
2020-09-21 01:48:07 +00:00
|
|
|
"Turn the data DSL into a vector containing a SQL string followed by
|
2021-02-01 20:00:42 +00:00
|
|
|
any parameter values that were encountered in the DSL structure.
|
|
|
|
|
|
|
|
|
|
This is the primary API for HoneySQL and handles dialects, quoting,
|
|
|
|
|
and named parameters."
|
2020-09-21 17:56:05 +00:00
|
|
|
([data] (format data {}))
|
|
|
|
|
([data opts]
|
2020-09-23 19:55:02 +00:00
|
|
|
(let [dialect? (contains? opts :dialect)
|
|
|
|
|
dialect (when dialect? (get dialects (check-dialect (:dialect opts))))]
|
|
|
|
|
(binding [*dialect* (if dialect? dialect @default-dialect)
|
|
|
|
|
*clause-order* (if dialect?
|
|
|
|
|
(if-let [f (:clause-order-fn dialect)]
|
2020-09-24 18:13:08 +00:00
|
|
|
(f @base-clause-order)
|
2020-09-23 19:55:02 +00:00
|
|
|
@current-clause-order)
|
|
|
|
|
@current-clause-order)
|
2020-10-10 04:52:18 +00:00
|
|
|
*inline* (when (contains? opts :inline)
|
|
|
|
|
(:inline opts))
|
2020-09-23 07:14:25 +00:00
|
|
|
*quoted* (if (contains? opts :quoted)
|
|
|
|
|
(:quoted opts)
|
2021-01-31 13:08:21 +00:00
|
|
|
dialect?)
|
|
|
|
|
*params* (:params opts)]
|
2020-09-26 06:58:51 +00:00
|
|
|
(mapv #(unwrap % opts) (format-dsl data opts))))))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
(defn set-dialect!
|
|
|
|
|
"Set the default dialect for formatting.
|
|
|
|
|
|
2020-09-25 02:07:32 +00:00
|
|
|
Can be: `:ansi` (the default), `:mysql`, `:oracle`, or `:sqlserver`.
|
2020-09-24 18:13:08 +00:00
|
|
|
|
|
|
|
|
Dialects are always applied to the base order to create the current order."
|
2020-09-21 01:48:07 +00:00
|
|
|
[dialect]
|
2020-09-23 19:55:02 +00:00
|
|
|
(reset! default-dialect (get dialects (check-dialect dialect)))
|
|
|
|
|
(when-let [f (:clause-order-fn @default-dialect)]
|
2020-09-24 18:13:08 +00:00
|
|
|
(reset! current-clause-order (f @base-clause-order))))
|
2020-09-23 19:55:02 +00:00
|
|
|
|
|
|
|
|
(defn register-clause!
|
|
|
|
|
"Register a new clause formatter. If `before` is `nil`, the clause is
|
|
|
|
|
added to the end of the list of known clauses, otherwise it is inserted
|
2020-09-24 18:13:08 +00:00
|
|
|
immediately prior to that clause.
|
|
|
|
|
|
|
|
|
|
New clauses are registered in the base order and the current order so
|
|
|
|
|
that any dialect selections are able to include them while still working
|
2020-09-29 21:40:03 +00:00
|
|
|
predictably from the base order. Caveat: that means if you register a new
|
2020-09-24 18:13:08 +00:00
|
|
|
clause `before` a clause that is ordered differently in different
|
|
|
|
|
dialects, your new clause may also end up in a different place. The
|
|
|
|
|
only clause so far where that would matter is `:set` which differs in
|
2020-09-29 21:40:03 +00:00
|
|
|
MySQL."
|
2020-09-23 19:55:02 +00:00
|
|
|
[clause formatter before]
|
2021-02-02 22:50:12 +00:00
|
|
|
(let [clause (sym->kw clause)
|
|
|
|
|
before (sym->kw before)]
|
2021-02-02 20:25:26 +00:00
|
|
|
(assert (keyword? clause))
|
|
|
|
|
(let [k (sym->kw formatter)
|
|
|
|
|
f (if (keyword? k)
|
|
|
|
|
(get @clause-format k)
|
|
|
|
|
formatter)]
|
|
|
|
|
(when-not (and f (fn? f))
|
|
|
|
|
(throw (ex-info "The formatter must be a function or existing clause"
|
|
|
|
|
{:type (type formatter)})))
|
|
|
|
|
(swap! base-clause-order add-clause-before clause before)
|
|
|
|
|
(swap! current-clause-order add-clause-before clause before)
|
|
|
|
|
(swap! clause-format assoc clause f))))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2020-09-29 21:40:03 +00:00
|
|
|
(defn register-fn!
|
|
|
|
|
"Register a new function (as special syntax). The `formatter` is either
|
|
|
|
|
a keyword, meaning that this new function should use the same syntax as
|
|
|
|
|
an existing function, or a function of two arguments that generates a
|
|
|
|
|
SQL string and parameters (as a vector). The two arguments are the name
|
|
|
|
|
of the function (as a keyword) and a sequence of the arguments from the
|
|
|
|
|
DSL."
|
|
|
|
|
[function formatter]
|
2021-02-02 20:25:26 +00:00
|
|
|
(let [function (sym->kw function)]
|
|
|
|
|
(assert (keyword? function))
|
|
|
|
|
(let [k (sym->kw formatter)
|
|
|
|
|
f (if (keyword? k)
|
|
|
|
|
(get @special-syntax k)
|
|
|
|
|
formatter)]
|
|
|
|
|
(when-not (and f (fn? f))
|
|
|
|
|
(throw (ex-info "The formatter must be a function or existing fn name"
|
|
|
|
|
{:type (type formatter)})))
|
|
|
|
|
(swap! special-syntax assoc function f))))
|
2020-09-29 21:40:03 +00:00
|
|
|
|
|
|
|
|
(defn register-op!
|
|
|
|
|
"Register a new infix operator. Operators can be defined to be variadic (the
|
|
|
|
|
default is that they are binary) and may choose to ignore `nil` arguments
|
|
|
|
|
(this can make it easier to programmatically construct the DSL)."
|
2021-02-02 00:24:00 +00:00
|
|
|
[op & {:keys [variadic ignore-nil]}]
|
2021-02-02 20:25:26 +00:00
|
|
|
(let [op (sym->kw op)]
|
|
|
|
|
(assert (keyword? op))
|
|
|
|
|
(swap! infix-ops conj op)
|
|
|
|
|
(when variadic
|
|
|
|
|
(swap! op-variadic conj op))
|
|
|
|
|
(when ignore-nil
|
|
|
|
|
(swap! op-ignore-nil conj op))))
|
2020-09-29 21:40:03 +00:00
|
|
|
|
2020-09-21 01:48:07 +00:00
|
|
|
(comment
|
2020-09-23 07:14:25 +00:00
|
|
|
(format {:truncate :foo})
|
2020-09-21 01:48:07 +00:00
|
|
|
(format-expr [:= :id 1])
|
|
|
|
|
(format-expr [:+ :id 1])
|
|
|
|
|
(format-expr [:+ 1 [:+ 1 :quux]])
|
|
|
|
|
(format-expr [:foo [:bar [:+ 2 [:g :abc]]] [:f 1 :quux]])
|
|
|
|
|
(format-expr :id)
|
|
|
|
|
(format-expr 1)
|
2020-09-25 02:07:32 +00:00
|
|
|
(format {:select [:a [:b :c] [[:d :e]] [[:f :g] :h]]})
|
2021-02-13 18:50:36 +00:00
|
|
|
(format {:select [[[:d :e]] :a [:b :c]]})
|
2020-09-23 07:14:25 +00:00
|
|
|
(format-on-expr :where [:= :id 1])
|
2020-09-21 01:48:07 +00:00
|
|
|
(format-dsl {:select [:*] :from [:table] :where [:= :id 1]})
|
2020-09-21 17:56:05 +00:00
|
|
|
(format {:select [:t.*] :from [[:table :t]] :where [:= :id 1]} {})
|
|
|
|
|
(format {:select [:*] :from [:table] :group-by [:foo :bar]} {})
|
|
|
|
|
(format {:select [:*] :from [:table] :group-by [[:date :bar]]} {})
|
|
|
|
|
(format {:select [:*] :from [:table] :order-by [[:foo :desc] :bar]} {})
|
2020-10-10 04:52:18 +00:00
|
|
|
(format {:select [:*] :from [:table]
|
|
|
|
|
:order-by [[[:date :expiry] :desc] :bar]} {})
|
|
|
|
|
(println (format {:select [:*] :from [:table]
|
2021-02-01 21:10:57 +00:00
|
|
|
:order-by [[[:date :expiry] :desc] :bar]} {:pretty true}))
|
2020-10-10 04:52:18 +00:00
|
|
|
(format {:select [:*] :from [:table]
|
|
|
|
|
:where [:< [:date_add :expiry [:interval 30 :days]] [:now]]} {})
|
2020-09-21 01:48:07 +00:00
|
|
|
(format-expr [:interval 30 :days])
|
2020-10-10 04:52:18 +00:00
|
|
|
(format {:select [:*] :from [:table]
|
|
|
|
|
:where [:= :id (int 1)]} {:dialect :mysql})
|
|
|
|
|
(map fn? (format {:select [:*] :from [:table]
|
|
|
|
|
:where [:= :id (with-meta (constantly 42) {:foo true})]}
|
|
|
|
|
{:dialect :mysql}))
|
|
|
|
|
(println (format {:select [:*] :from [:table]
|
2021-02-01 21:10:57 +00:00
|
|
|
:where [:in :id [1 2 3 4]]} {:pretty true}))
|
2020-10-12 18:33:18 +00:00
|
|
|
(println (format {:select [:*] :from [:table]
|
|
|
|
|
:where [:and [:in :id [1 [:param :foo]]]
|
|
|
|
|
[:= :bar [:param :quux]]]}
|
|
|
|
|
{:params {:foo 42 :quux 13}
|
2021-02-01 21:10:57 +00:00
|
|
|
:pretty true}))
|
2021-02-02 00:24:00 +00:00
|
|
|
;; while working on the docs
|
|
|
|
|
(require '[honey.sql :as sql])
|
2021-02-03 00:43:09 +00:00
|
|
|
(sql/format-expr [:array (range 5)])
|
2021-02-02 22:50:12 +00:00
|
|
|
(sql/format {:where [:and [:= :id 42] [:= :type "match"]]})
|
|
|
|
|
(sql/format {:where [:and [:= :type "match"] (when false [:in :status [1 5]])]})
|
2021-02-02 00:24:00 +00:00
|
|
|
(sql/format {:select [:*] :from [:table] :where [:= :id 1]})
|
2021-02-02 20:25:26 +00:00
|
|
|
(sql/format {:select [:t/id [:name :item]], :from [[:table :t]], :where [:= :id 1]})
|
|
|
|
|
(sql/format '{select [t/id [name item]], from [[table t]], where [= id 1]})
|
|
|
|
|
(sql/format '{select * from table where (= id 1)})
|
|
|
|
|
(require '[honey.sql.helpers :refer [select from where]])
|
|
|
|
|
(-> (select :t/id [:name :item])
|
|
|
|
|
(from [:table :t])
|
|
|
|
|
(where [:= :id 1])
|
|
|
|
|
(sql/format))
|
|
|
|
|
(-> (select :t/id)
|
|
|
|
|
(from [:table :t])
|
|
|
|
|
(where [:= :id 1])
|
|
|
|
|
(select [:name :item])
|
|
|
|
|
(sql/format))
|
2021-02-02 00:24:00 +00:00
|
|
|
(sql/format {:select [:*] :from [:table] :where [:= :id 1]} {:dialect :mysql})
|
|
|
|
|
(sql/format {:select [:foo/bar] :from [:q-u-u-x]} {:quoted true})
|
|
|
|
|
(sql/format {:select ["foo/bar"] :from [:q-u-u-x]} {:quoted true})
|
2021-02-02 20:25:26 +00:00
|
|
|
(sql/format-expr [:primary-key])
|
|
|
|
|
(sql/register-op! 'y)
|
|
|
|
|
(sql/format {:where '[y 2 3]})
|
|
|
|
|
(sql/register-op! :<=> :variadic true :ignore-nil true)
|
|
|
|
|
;; and then use the new operator:
|
|
|
|
|
(sql/format {:select [:*], :from [:table], :where [:<=> nil :x 42]})
|
|
|
|
|
(sql/register-fn! :foo (fn [f args] ["FOO(?)" (first args)]))
|
|
|
|
|
(sql/format {:select [:*], :from [:table], :where [:foo 1 2 3]})
|
|
|
|
|
(defn- foo-formatter [f [x]]
|
|
|
|
|
(let [[sql & params] (sql/format-expr x)]
|
|
|
|
|
(into [(str (sql/sql-kw f) "(" sql ")")] params)))
|
|
|
|
|
|
|
|
|
|
(sql/register-fn! :foo foo-formatter)
|
|
|
|
|
|
|
|
|
|
(sql/format {:select [:*], :from [:table], :where [:foo [:+ :a 1]]})
|
2020-09-21 01:48:07 +00:00
|
|
|
,)
|