Docstring overhaul
This commit is contained in:
parent
7aab640e30
commit
2ef9d4dad2
2 changed files with 95 additions and 35 deletions
|
|
@ -1,5 +1,11 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
* 2.0.alpha in progress
|
||||||
|
* This is a complete rewrite/simplification of HoneySQL that provides just two namespaces:
|
||||||
|
* `honey.sql` -- this is the primary API via the `format` function as well as the various extension points.
|
||||||
|
* `honey.sql.helpers` -- provides a helper function for every piece of the DSL that is supported out-of-the-box.
|
||||||
|
* The coordinates for HoneySQL 2.0 are `seancorfield/honeysql` so it can be added to a project that already uses HoneySQL 1.0 without any conflicts, making it easier to migrate piecemeal from 1.0 to 2.0.
|
||||||
|
|
||||||
* 1.0.444 -- 2020-05-29
|
* 1.0.444 -- 2020-05-29
|
||||||
* Fix #259 so column names are always unqualified in inserts. (@jrdoane)
|
* Fix #259 so column names are always unqualified in inserts. (@jrdoane)
|
||||||
* Fix #257 by adding support for `cross-join` / `merge-cross-join` / `:cross-join`. (@dcj)
|
* Fix #257 by adding support for `cross-join` / `merge-cross-join` / `:cross-join`. (@dcj)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,27 @@
|
||||||
;; copyright (c) 2020-2021 sean corfield, all rights reserved
|
;; copyright (c) 2020-2021 sean corfield, all rights reserved
|
||||||
|
|
||||||
(ns honey.sql
|
(ns honey.sql
|
||||||
"Primary API for HoneySQL 2.x."
|
"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."
|
||||||
(:refer-clojure :exclude [format])
|
(:refer-clojure :exclude [format])
|
||||||
(:require [clojure.string :as str]))
|
(:require [clojure.string :as str]))
|
||||||
|
|
||||||
|
|
@ -83,7 +103,12 @@
|
||||||
#?(:clj (fn [^String s] (.. s toString (toUpperCase (java.util.Locale/US))))
|
#?(:clj (fn [^String s] (.. s toString (toUpperCase (java.util.Locale/US))))
|
||||||
:cljs str/upper-case))
|
:cljs str/upper-case))
|
||||||
|
|
||||||
(defn sql-kw [k]
|
(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
|
||||||
|
with hyphens replaced by spaces, e.g., `:insert-into` => `INSERT INTO`."
|
||||||
|
[k]
|
||||||
(-> k (name) (upper-case)
|
(-> k (name) (upper-case)
|
||||||
(as-> s (if (= "-" s) s (str/replace s "-" " ")))))
|
(as-> s (if (= "-" s) s (str/replace s "-" " ")))))
|
||||||
|
|
||||||
|
|
@ -190,11 +215,24 @@
|
||||||
(map #(format-dsl % {:nested? true}) xs))]
|
(map #(format-dsl % {:nested? true}) xs))]
|
||||||
(into [(str/join (str " " (sql-kw k) " ") sqls)] params)))
|
(into [(str/join (str " " (sql-kw k) " ") sqls)] params)))
|
||||||
|
|
||||||
(defn- format-expr-list [xs & [opts]]
|
(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]]
|
||||||
(reduce (fn [[sql params] [sql' & params']]
|
(reduce (fn [[sql params] [sql' & params']]
|
||||||
[(conj sql sql') (if params' (into params params') params)])
|
[(conj sql sql') (if params' (into params params') params)])
|
||||||
[[] []]
|
[[] []]
|
||||||
(map #(format-expr % opts) xs)))
|
(map #(format-expr % opts) exprs)))
|
||||||
|
|
||||||
(defn- format-columns [k xs]
|
(defn- format-columns [k xs]
|
||||||
(let [[sqls params] (format-expr-list xs {:drop-ns? (= :columns k)})]
|
(let [[sqls params] (format-expr-list xs {:drop-ns? (= :columns k)})]
|
||||||
|
|
@ -444,27 +482,31 @@
|
||||||
(set @current-clause-order)
|
(set @current-clause-order)
|
||||||
(set (keys @clause-format))))
|
(set (keys @clause-format))))
|
||||||
|
|
||||||
(defn format-dsl [x & [{:keys [aliased? nested? pretty?]}]]
|
(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."
|
||||||
|
[statement-map & [{:keys [aliased? nested? pretty?]}]]
|
||||||
(let [[sqls params leftover]
|
(let [[sqls params leftover]
|
||||||
(reduce (fn [[sql params leftover] k]
|
(reduce (fn [[sql params leftover] k]
|
||||||
(if-let [xs (or (k x) (let [s (symbol (name k))] (get x s)))]
|
(if-let [xs (or (k statement-map)
|
||||||
|
(let [s (symbol (name k))]
|
||||||
|
(get statement-map s)))]
|
||||||
(let [formatter (k @clause-format)
|
(let [formatter (k @clause-format)
|
||||||
[sql' & params'] (formatter k xs)]
|
[sql' & params'] (formatter k xs)]
|
||||||
[(conj sql sql')
|
[(conj sql sql')
|
||||||
(if params' (into params params') params)
|
(if params' (into params params') params)
|
||||||
(dissoc leftover k (symbol (name k)))])
|
(dissoc leftover k (symbol (name k)))])
|
||||||
[sql params leftover]))
|
[sql params leftover]))
|
||||||
[[] [] x]
|
[[] [] statement-map]
|
||||||
*clause-order*)]
|
*clause-order*)]
|
||||||
(if (seq leftover)
|
(if (seq leftover)
|
||||||
(do
|
(throw (ex-info (str "Unknown SQL clauses: "
|
||||||
;; TODO: for testing purposes, make this less noisy
|
(str/join ", " (keys leftover)))
|
||||||
(println (str "\n-------------------\nUnknown SQL clauses: "
|
leftover))
|
||||||
(str/join ", " (keys leftover))))
|
|
||||||
#_(throw (ex-info (str "Unknown SQL clauses: "
|
|
||||||
(str/join ", " (keys leftover)))
|
|
||||||
leftover))
|
|
||||||
[(str "<unknown" (str/join (keys leftover)) ">")])
|
|
||||||
(into [(cond-> (str/join (if pretty? "\n" " ") (filter seq sqls))
|
(into [(cond-> (str/join (if pretty? "\n" " ") (filter seq sqls))
|
||||||
pretty?
|
pretty?
|
||||||
(as-> s (str "\n" s "\n"))
|
(as-> s (str "\n" s "\n"))
|
||||||
|
|
@ -606,22 +648,31 @@
|
||||||
(into [(str/join sqls)] params))
|
(into [(str/join sqls)] params))
|
||||||
[s]))}))
|
[s]))}))
|
||||||
|
|
||||||
(defn format-expr [x & [{:keys [nested?] :as opts}]]
|
(defn format-expr
|
||||||
(cond (or (keyword? x) (symbol? x))
|
"Given a data structure that represents a SQL expression and a hash
|
||||||
(format-var x opts)
|
map of options, return a vector containing a string -- the formatted
|
||||||
|
SQL statement -- followed by any parameter values that SQL needs.
|
||||||
|
|
||||||
(map? x)
|
This is intended to be used when writing your own formatters to
|
||||||
(format-dsl x (assoc opts :nested? true))
|
extend the DSL supported by HoneySQL."
|
||||||
|
[expr & [{:keys [nested?] :as opts}]]
|
||||||
|
(cond (or (keyword? expr) (symbol? expr))
|
||||||
|
(format-var expr opts)
|
||||||
|
|
||||||
(sequential? x)
|
(map? expr)
|
||||||
(let [op (first x)
|
(format-dsl expr (assoc opts :nested? true))
|
||||||
|
|
||||||
|
(sequential? expr)
|
||||||
|
(let [op (first expr)
|
||||||
;; normalize symbols to keywords here -- makes the subsequent
|
;; normalize symbols to keywords here -- makes the subsequent
|
||||||
;; logic easier since we use op to lookup things in hash maps:
|
;; logic easier since we use op to lookup things in hash maps:
|
||||||
op (if (symbol? op) (keyword (name op)) op)]
|
op (if (symbol? op) (keyword (name op)) op)]
|
||||||
(if (keyword? op)
|
(if (keyword? op)
|
||||||
(cond (contains? @infix-ops op)
|
(cond (contains? @infix-ops op)
|
||||||
(if (contains? @op-variadic op) ; no aliases here, no special semantics
|
(if (contains? @op-variadic op) ; no aliases here, no special semantics
|
||||||
(let [x (if (contains? @op-ignore-nil op) (remove nil? x) x)
|
(let [x (if (contains? @op-ignore-nil op)
|
||||||
|
(remove nil? expr)
|
||||||
|
expr)
|
||||||
[sqls params]
|
[sqls params]
|
||||||
(reduce (fn [[sql params] [sql' & params']]
|
(reduce (fn [[sql params] [sql' & params']]
|
||||||
[(conj sql sql')
|
[(conj sql sql')
|
||||||
|
|
@ -633,12 +684,12 @@
|
||||||
nested?
|
nested?
|
||||||
(as-> s (str "(" s ")")))]
|
(as-> s (str "(" s ")")))]
|
||||||
params))
|
params))
|
||||||
(let [[_ a b & y] x
|
(let [[_ a b & y] expr
|
||||||
_ (when (seq y)
|
_ (when (seq y)
|
||||||
(throw (ex-info (str "only binary "
|
(throw (ex-info (str "only binary "
|
||||||
op
|
op
|
||||||
" is supported")
|
" is supported")
|
||||||
{:expr x})))
|
{:expr expr})))
|
||||||
[s1 & p1] (format-expr a {:nested? true})
|
[s1 & p1] (format-expr a {:nested? true})
|
||||||
[s2 & p2] (format-expr b {:nested? true})
|
[s2 & p2] (format-expr b {:nested? true})
|
||||||
op (get infix-aliases op op)]
|
op (get infix-aliases op op)]
|
||||||
|
|
@ -657,13 +708,13 @@
|
||||||
(into p1)
|
(into p1)
|
||||||
(into p2)))))
|
(into p2)))))
|
||||||
(contains? #{:in :not-in} op)
|
(contains? #{:in :not-in} op)
|
||||||
(let [[sql & params] (format-in op (rest x))]
|
(let [[sql & params] (format-in op (rest expr))]
|
||||||
(into [(if nested? (str "(" sql ")") sql)] params))
|
(into [(if nested? (str "(" sql ")") sql)] params))
|
||||||
(contains? @special-syntax op)
|
(contains? @special-syntax op)
|
||||||
(let [formatter (get @special-syntax op)]
|
(let [formatter (get @special-syntax op)]
|
||||||
(formatter op (rest x)))
|
(formatter op (rest expr)))
|
||||||
:else
|
:else
|
||||||
(let [args (rest x)
|
(let [args (rest expr)
|
||||||
[sqls params] (format-expr-list args)]
|
[sqls params] (format-expr-list args)]
|
||||||
(into [(str (sql-kw op)
|
(into [(str (sql-kw op)
|
||||||
(if (and (= 1 (count args))
|
(if (and (= 1 (count args))
|
||||||
|
|
@ -672,19 +723,19 @@
|
||||||
(str " " (first sqls))
|
(str " " (first sqls))
|
||||||
(str "(" (str/join ", " sqls) ")")))]
|
(str "(" (str/join ", " sqls) ")")))]
|
||||||
params)))
|
params)))
|
||||||
(let [[sqls params] (format-expr-list x)]
|
(let [[sqls params] (format-expr-list expr)]
|
||||||
(into [(str "(" (str/join ", " sqls) ")")] params))))
|
(into [(str "(" (str/join ", " sqls) ")")] params))))
|
||||||
|
|
||||||
(boolean? x)
|
(boolean? expr)
|
||||||
[(upper-case (str x))]
|
[(upper-case (str expr))]
|
||||||
|
|
||||||
(nil? x)
|
(nil? expr)
|
||||||
["NULL"]
|
["NULL"]
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(if *inline*
|
(if *inline*
|
||||||
[(sqlize-value x)]
|
[(sqlize-value expr)]
|
||||||
["?" x])))
|
["?" expr])))
|
||||||
|
|
||||||
(defn- check-dialect [dialect]
|
(defn- check-dialect [dialect]
|
||||||
(when-not (contains? dialects dialect)
|
(when-not (contains? dialects dialect)
|
||||||
|
|
@ -694,7 +745,10 @@
|
||||||
|
|
||||||
(defn format
|
(defn format
|
||||||
"Turn the data DSL into a vector containing a SQL string followed by
|
"Turn the data DSL into a vector containing a SQL string followed by
|
||||||
any parameter values that were encountered in the DSL structure."
|
any parameter values that were encountered in the DSL structure.
|
||||||
|
|
||||||
|
This is the primary API for HoneySQL and handles dialects, quoting,
|
||||||
|
and named parameters."
|
||||||
([data] (format data {}))
|
([data] (format data {}))
|
||||||
([data opts]
|
([data opts]
|
||||||
(let [dialect? (contains? opts :dialect)
|
(let [dialect? (contains? opts :dialect)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue