Fixes #295 by documenting all helpers

This commit is contained in:
Sean Corfield 2021-03-13 15:35:47 -08:00
parent 9f6393a2fd
commit 7c21a403c2
2 changed files with 165 additions and 7 deletions

View file

@ -8,7 +8,8 @@
* Fix #301 by adding support for `CREATE`/`DROP`/`REFRESH` on `MATERIALIZED VIEW`. * Fix #301 by adding support for `CREATE`/`DROP`/`REFRESH` on `MATERIALIZED VIEW`.
* Add tests to confirm #299 does not affect v2. * Add tests to confirm #299 does not affect v2.
* Fix #297 by adding both `SELECT .. INTO ..` and `SELECT .. BULK COLLECT INTO ..`. * Fix #297 by adding both `SELECT .. INTO ..` and `SELECT .. BULK COLLECT INTO ..`.
* Confirm the whole of the [nilenso/honeysql-postgres](https://github.com/nilenso/honeysql-postgres) is implemented out-of-the-box (#293). * Fix #295 by adding docstrings to all helper functions (and adding an assert to ensure it stays that way as more are added in future).
* Confirm the whole of the [nilenso/honeysql-postgres](https://github.com/nilenso/honeysql-postgres) is implemented out-of-the-box (#293, but see #310 for recent additions not yet incorporated).
* Fix #292 by adding support for `SELECT TOP` and `OFFSET`/`FETCH`. * Fix #292 by adding support for `SELECT TOP` and `OFFSET`/`FETCH`.
* Fix #284 by adding support for `LATERAL` (as special syntax, with a helper). * Fix #284 by adding support for `LATERAL` (as special syntax, with a helper).
* Reconcile `where` behavior with recent 1.0 changes (porting #283 to v2). * Reconcile `where` behavior with recent 1.0 changes (porting #283 to v2).

View file

@ -2,7 +2,7 @@
(ns honey.sql.helpers (ns honey.sql.helpers
"Helper functions for the built-in clauses in honey.sql." "Helper functions for the built-in clauses in honey.sql."
(:refer-clojure :exclude [into update set group-by for partition-by]) (:refer-clojure :exclude [for group-by into partition-by set update])
(:require [clojure.core :as c] (:require [clojure.core :as c]
[honey.sql])) [honey.sql]))
@ -12,6 +12,8 @@
(c/into (vec current) args)) (c/into (vec current) args))
(defn- and-merge (defn- and-merge
"Merge a single conjunction expression into an existing one.
This merges `AND` to avoid nesting."
[current arg] [current arg]
(if-let [conj' (and (sequential? arg) (if-let [conj' (and (sequential? arg)
(ident? (first arg)) (ident? (first arg))
@ -30,6 +32,10 @@
(conj [:and] arg)))) (conj [:and] arg))))
(defn- and-merges (defn- and-merges
"Merge multiple conjunction expressions into an existing,
possibly empty, expression. This ensures AND expressions
are merged and that we do not end up with a single AND
or OR expression."
[current args] [current args]
(let [args (remove nil? args) (let [args (remove nil? args)
result result
@ -49,6 +55,7 @@
result))) result)))
(def ^:private special-merges (def ^:private special-merges
"Identify the conjunction merge clauses."
{:where #'and-merges {:where #'and-merges
:having #'and-merges}) :having #'and-merges})
@ -257,14 +264,27 @@
(generic :refresh-materialized-view views)) (generic :refresh-materialized-view views))
(defn nest (defn nest
"A pseudo clause that exists purely to cause nesting
in parentheses. Should only be needed very rarely in
cases where HoneySQL doesn't do the right thing for
your specific database dialect.
Wraps a single clause."
{:arglists '([clause])}
[& args] [& args]
(generic :nest args)) (generic :nest args))
(defn with (defn with
"Accepts one or more CTE definitions.
See the documentation for the `:with` clause."
[& args] [& args]
(generic :with args)) (generic :with args))
(defn with-recursive (defn with-recursive
"Accepts one or more CTE definitions.
See the documentation for the `:with` clause."
[& args] [& args]
(generic :with-recursive args)) (generic :with-recursive args))
@ -450,6 +470,8 @@
(generic :from tables)) (generic :from tables))
(defn using (defn using
"Accepts similar arguments to `select` as part of
a SQL `USING` clause."
[& args] [& args]
(generic :using args)) (generic :using args))
@ -470,30 +492,112 @@
(generic :join-by args)) (generic :join-by args))
(defn join (defn join
"Accepts one or more (INNER) JOIN expressions. Each
join expression is specified as a pair of arguments,
where the first one is the table name (or a pair of
table and alias) and the second one is the join
condition:
(join :table [:= :foo.id :table.foo_id])
(join [:table :t] [:= :foo.id :t.foo_id])
Produces:
INNER JOIN table ON foo.id = table.foo_id
INNER JOIN table AS t ON foo.id = t.foo_id"
[& args] [& args]
(generic :join args)) (generic :join args))
(defn left-join (defn left-join
"Accepts one or more LEFT JOIN expressions. Each
join expression is specified as a pair of arguments,
where the first one is the table name (or a pair of
table and alias) and the second one is the join
condition:
(left-join :table [:= :foo.id :table.foo_id])
(left-join [:table :t] [:= :foo.id :t.foo_id])
Produces:
LEFT JOIN table ON foo.id = table.foo_id
LEFT JOIN table AS t ON foo.id = t.foo_id"
[& args] [& args]
(generic :left-join args)) (generic :left-join args))
(defn right-join (defn right-join
"Accepts one or more RIGHT JOIN expressions. Each
join expression is specified as a pair of arguments,
where the first one is the table name (or a pair of
table and alias) and the second one is the join
condition:
(right-join :table [:= :foo.id :table.foo_id])
(right-join [:table :t] [:= :foo.id :t.foo_id])
Produces:
RIGHT JOIN table ON foo.id = table.foo_id
RIGHT JOIN table AS t ON foo.id = t.foo_id"
[& args] [& args]
(generic :right-join args)) (generic :right-join args))
(defn inner-join (defn inner-join
"An alternative name to `join`, this accepts one or
more INNER JOIN expressions. Each join expression
is specified as a pair of arguments, where the
first one is the table name (or a pair of table
and alias) and the second one is the join condition:
(inner-join :table [:= :foo.id :table.foo_id])
(inner-join [:table :t] [:= :foo.id :t.foo_id])
Produces:
INNER JOIN table ON foo.id = table.foo_id
INNER JOIN table AS t ON foo.id = t.foo_id"
[& args] [& args]
(generic :inner-join args)) (generic :inner-join args))
(defn outer-join (defn outer-join
"Accepts one or more OUTER JOIN expressions. Each
join expression is specified as a pair of arguments,
where the first one is the table name (or a pair of
table and alias) and the second one is the join
condition:
(outer-join :table [:= :foo.id :table.foo_id])
(outer-join [:table :t] [:= :foo.id :t.foo_id])
Produces:
OUTER JOIN table ON foo.id = table.foo_id
OUTER JOIN table AS t ON foo.id = t.foo_id"
[& args] [& args]
(generic :outer-join args)) (generic :outer-join args))
(defn full-join (defn full-join
"Accepts one or more FULL JOIN expressions. Each
join expression is specified as a pair of arguments,
where the first one is the table name (or a pair of
table and alias) and the second one is the join
condition:
(full-join :table [:= :foo.id :table.foo_id])
(full-join [:table :t] [:= :foo.id :t.foo_id])
Produces:
INNER JOIN table ON foo.id = table.foo_id
INNER JOIN table AS t ON foo.id = t.foo_id"
[& args] [& args]
(generic :full-join args)) (generic :full-join args))
(defn cross-join (defn cross-join
"Accepts one or more CROSS JOIN expressions. Each
cross join expression is specified as a table
name (or a pair of table and alias):
(cross-join :table)
(cross-join [:table :t])
Produces:
CROSS JOIN table
CROSS JOIN table AS t"
[& args] [& args]
(generic :cross-join args)) (generic :cross-join args))
@ -509,6 +613,14 @@
(generic :where exprs)) (generic :where exprs))
(defn group-by (defn group-by
"Accepts one or more SQL expressions to group by.
(group-by :foo :bar)
(group-by [:date :baz])
Produces:
GROUP BY foo, bar
GROUP BY DATE(baz)"
[& args] [& args]
(generic :group-by args)) (generic :group-by args))
@ -524,19 +636,37 @@
(generic :having exprs)) (generic :having exprs))
(defn window (defn window
"Accepts a window name followed by a partition by clause."
[& args] [& args]
(generic :window args)) (generic :window args))
(defn partition-by (defn partition-by
"Accepts one or more columns or SQL expressions to
partition by as part of a `WINDOW` expression."
[& args] [& args]
(generic :partition-by args)) (generic :partition-by args))
(defn order-by (defn order-by
"Accepts one or more expressions to order by.
An ordering expression may be a simple column name
which is assumed to be ordered `ASC`, or a pair of
an expression and a direction (`:asc` or `:desc`):
(order-by :foo)
(order-by [:bar :desc])
(order-by [[:date :baz] :asc])
Produces:
ORDER BY foo ASC
ORDER BY bar DESC
ORDER BY DATE(baz) ASC"
[& args] [& args]
(generic :order-by args)) (generic :order-by args))
(defn limit (defn limit
"Specific to MySQL, accepts a single SQL expression: "Specific to some databases (notabley MySQL),
accepts a single SQL expression:
(limit 40) (limit 40)
@ -546,7 +676,9 @@
The two-argument syntax is not supported: use `offset` The two-argument syntax is not supported: use `offset`
instead: instead:
`LIMIT 20,10` is equivalent to `LIMIT 10 OFFSET 20`" `LIMIT 20,10` is equivalent to `LIMIT 10 OFFSET 20`
(-> (limit 10) (offset 20))"
{:arglists '([limit])} {:arglists '([limit])}
[& args] [& args]
(generic-1 :limit args)) (generic-1 :limit args))
@ -574,10 +706,18 @@
(generic-1 :offset args)) (generic-1 :offset args))
(defn for (defn for
"Accepts a lock strength, optionally followed by one or
more table names, optionally followed by a qualifier."
{:arglists '([lock-strength table* qualifier*])}
[& args] [& args]
(generic-1 :for args)) (generic-1 :for args))
(defn lock (defn lock
"Intended for MySQL, this accepts a lock mode.
It will accept the same type of syntax as `for` even
though MySQL's `lock` clause is less powerful."
{:arglists '([lock-mode])}
[& args] [& args]
(generic-1 :lock args)) (generic-1 :lock args))
@ -599,6 +739,10 @@
(generic-1 :values args)) (generic-1 :values args))
(defn on-conflict (defn on-conflict
"Accepts a single column name to detect conflicts
during an upsert, optionally followed by a `WHERE`
clause."
{:arglists '([column] [column where-clause])}
[& args] [& args]
(generic :on-conflict args)) (generic :on-conflict args))
@ -615,10 +759,19 @@
(generic :do-nothing args)) (generic :do-nothing args))
(defn do-update-set (defn do-update-set
"Accepts one or more columns to update, or a hash map
of column/value pairs (like `set`), optionally followed
by a `WHERE` clause. Can also accept a single hash map
with a `:fields` entry specifying the columns to update
and a `:where` entry specifying the `WHERE` clause."
{:arglists '([field-where-map] [column-value-map] [column* opt-where-clause])}
[& args] [& args]
(generic :do-update-set args)) (generic :do-update-set args))
(defn on-duplicate-key-update (defn on-duplicate-key-update
"MySQL's upsert facility. Accepts a hash map of
column/value pairs to be updated (like `set` does)."
{:arglists '([column-value-map])}
[& args] [& args]
(generic :on-duplicate-key-update args)) (generic :on-duplicate-key-update args))
@ -728,6 +881,10 @@
do-update-set)))))) do-update-set))))))
#?(:clj #?(:clj
(assert (= (clojure.core/set (conj @@#'honey.sql/base-clause-order (do
:composite :lateral :over :upsert)) ;; ensure #295 stays true (all public functions have docstring):
(clojure.core/set (map keyword (keys (ns-publics *ns*))))))) (assert (empty? (->> (ns-publics *ns*) (vals) (filter (comp not :doc meta)))))
;; ensure all public functions match clauses:
(assert (= (clojure.core/set (conj @@#'honey.sql/base-clause-order
:composite :lateral :over :upsert))
(clojure.core/set (map keyword (keys (ns-publics *ns*))))))))