honeysql/src/honey/sql/helpers.cljc

881 lines
24 KiB
Text
Raw Normal View History

2021-01-30 00:11:52 +00:00
;; copyright (c) 2020-2021 sean corfield, all rights reserved
2020-09-28 20:47:55 +00:00
(ns honey.sql.helpers
"Helper functions for the built-in clauses in honey.sql."
2021-03-13 23:35:47 +00:00
(:refer-clojure :exclude [for group-by into partition-by set update])
(:require [clojure.core :as c]
[honey.sql]))
;; implementation helpers:
2020-09-28 20:47:55 +00:00
(defn- default-merge [current args]
(c/into (vec current) args))
2020-09-28 20:47:55 +00:00
(defn- and-merge
2021-03-13 23:35:47 +00:00
"Merge a single conjunction expression into an existing one.
This merges `AND` to avoid nesting."
2021-03-07 06:10:43 +00:00
[current arg]
2021-03-07 17:43:03 +00:00
(if-let [conj' (and (sequential? arg)
(ident? (first arg))
(#{:and :or} (keyword (first arg))))]
2021-03-07 06:10:43 +00:00
(cond (= conj' (first current))
(c/into (vec current) (rest arg))
2021-03-07 06:10:43 +00:00
(seq current)
(c/into [conj' current] (rest arg))
2021-03-07 06:10:43 +00:00
:else
(c/into [conj'] (rest arg)))
2021-03-07 17:43:03 +00:00
(cond (#{:and 'and} (first current))
2021-03-07 06:10:43 +00:00
(conj (vec current) arg)
(seq current)
(conj [:and current] arg)
:else
(conj [:and] arg))))
(defn- and-merges
2021-03-13 23:35:47 +00:00
"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]
2021-03-07 06:10:43 +00:00
(let [args (remove nil? args)
result
2021-03-07 17:43:03 +00:00
(cond (ident? (first args))
2021-03-07 06:10:43 +00:00
(and-merges current [args])
(seq args)
(let [[arg & args] args]
(and-merges (and-merge current arg) args))
:else
current)]
(case (count result)
0 nil
1 (if (sequential? (first result))(first result) result)
2 (if (#{:and :or} (first result))
(second result)
result)
result)))
2020-09-28 20:47:55 +00:00
(def ^:private special-merges
2021-03-13 23:35:47 +00:00
"Identify the conjunction merge clauses."
2021-03-07 06:10:43 +00:00
{:where #'and-merges
:having #'and-merges})
2020-09-28 20:47:55 +00:00
(defn- helper-merge [data k args]
2021-03-07 06:10:43 +00:00
(if-let [merge-fn (special-merges k)]
(if-let [clause (merge-fn (get data k) args)]
(assoc data k clause)
data)
(clojure.core/update data k default-merge args)))
2020-09-28 20:47:55 +00:00
(defn- generic [k args]
(if (map? (first args))
(let [[data & args] args]
(helper-merge data k args))
(helper-merge {} k args)))
(defn- generic-1 [k [data arg]]
(if (some? arg)
(assoc data k arg)
(assoc {} k data)))
;; for every clause, there is a public helper
(defn alter-table
2021-02-15 00:52:35 +00:00
"Alter table takes a SQL entity (the name of the
table to modify) and any number of optional SQL
clauses to be applied in a single statement.
(alter-table :foo (add-column :id :int nil))
If only the SQL entity is provided, the result
needs to be combined with another SQL clause to
modify the table.
(-> (alter-table :foo) (add-column :id :int nil))"
{:arglists '([table & clauses])}
[& args]
(generic :alter-table args))
(defn add-column
2021-02-15 00:52:35 +00:00
"Add a single column to a table (see `alter-table`).
Accepts any number of SQL elements that describe
a column:
(add-column :name [:varchar 32] [:not nil])"
[& col-elems]
(generic :add-column col-elems))
(defn drop-column
2021-02-15 00:52:35 +00:00
"Takes a single column name (use with `alter-table`).
(alter-table :foo (drop-column :bar))"
{:arglists '([col])}
[& args]
(generic-1 :drop-column args))
(defn modify-column
2021-02-15 00:52:35 +00:00
"Like add-column, accepts any number of SQL elements
that describe the new column definition:
(modify-column :name [:varchar 64] [:not nil])"
[& col-elems]
(generic :modify-column col-elems))
(defn rename-column
2021-02-15 00:52:35 +00:00
"Accepts two column names: the original name and the
new name to which it should be renamed:
(rename-column :name :full-name)"
{:arglists '([old-col new-col])}
[& args]
(generic :rename-column args))
(defn add-index
2021-02-15 00:52:35 +00:00
"Like add-column, this accepts any number of SQL
elements that describe a new index to be added:
(add-index :unique :name-key :first-name :last-name)
Produces: UNIQUE name_key(first_name, last_name)"
{:arglist '([& index-elems])}
[& args]
(generic :add-index args))
(defn drop-index
2021-02-15 00:52:35 +00:00
"Like drop-table, accepts a single index name:
(drop-index :name-key)"
[& args]
(generic-1 :drop-index args))
(defn rename-table
2021-02-15 00:52:35 +00:00
"Accepts a single table name and, despite its name,
actually means RENAME TO:
(alter-table :foo (rename-table :bar))
Produces: ALTER TABLE foo RENAME TO bar"
{:arglist '([new-table])}
[& args]
(generic-1 :rename-table args))
(defn create-table
2021-02-15 00:52:35 +00:00
"Accepts a table name to create and optionally a
flag to trigger IF NOT EXISTS in the SQL:
(create-table :foo)
2021-03-12 04:07:59 +00:00
(create-table :foo :if-not-exists)"
2021-02-15 00:52:35 +00:00
{:arglists '([table] [table if-not-exists])}
[& args]
(generic :create-table args))
2021-03-12 04:07:59 +00:00
(defn create-table-as
"Accepts a table name to create and optionally a
flag to trigger IF NOT EXISTS in the SQL:
(create-table-as :foo)
(create-table-as :foo :if-not-exists)"
{:arglists '([table] [table if-not-exists])}
[& args]
(generic :create-table-as args))
(defn create-extension
2021-02-15 00:52:35 +00:00
"Accepts an extension name to create and optionally a
flag to trigger IF NOT EXISTS in the SQL:
(create-extension :postgis)
2021-03-12 04:07:59 +00:00
(create-extension :postgis :if-not-exists)"
2021-02-15 00:52:35 +00:00
{:arglists '([extension] [extension if-not-exists])}
[& args]
(generic :create-extension args))
(defn with-columns
2021-02-15 00:52:35 +00:00
"Accepts any number of column descriptions. Each
column description is a sequence of SQL elements
that specify the name and the attributes.
2021-02-15 01:20:24 +00:00
(with-columns [:id :int [:not nil]]
[:name [:varchar 32] [:default \"\"]])
Produces:
id INT NOT NULL,
name VARCHAR(32) DEFAULT ''
2021-02-15 00:52:35 +00:00
Can also accept a single argument which is a
collection of column descriptions (mostly for
compatibility with nilenso/honeysql-postgres
which used to be needed for DDL)."
{:arglists '([& col-specs] [col-spec-coll])}
[& args]
2021-02-13 18:50:36 +00:00
;; special case so (with-columns [[:col-1 :definition] [:col-2 :definition]])
;; also works in addition to (with-columns [:col-1 :definition] [:col-2 :definition])
(cond (and (= 1 (count args)) (sequential? (first args)) (sequential? (ffirst args)))
(generic-1 :with-columns args)
(and (= 2 (count args)) (sequential? (second args)) (sequential? (fnext args)))
(generic-1 :with-columns args)
:else
(generic :with-columns args)))
(defn create-view
2021-02-15 00:52:35 +00:00
"Accepts a single view name to create.
(-> (create-view :cities)
(select :*) (from :city))"
{:arglists '([view])}
[& args]
(generic :create-view args))
(defn create-materialized-view
"Accepts a single view name to create.
(-> (create-materialized-view :cities)
(select :*) (from :city))
(with-data true)"
{:arglists '([view])}
[& args]
(generic :create-materialized-view args))
(defn drop-table
2021-02-15 00:52:35 +00:00
"Accepts one or more table names to drop.
(drop-table :foo)"
[& tables]
(generic :drop-table tables))
(defn drop-extension
2021-02-15 00:52:35 +00:00
"Accepts one or more extension names to drop."
[& extensions]
(generic :drop-extension extensions))
(defn drop-view
"Accepts one or more view names to drop."
[& views]
(generic :drop-view views))
(defn drop-materialized-view
"Accepts one or more materialied view names to drop."
[& views]
(generic :drop-materialized-view views))
(defn refresh-materialized-view
"Accepts a materialied view name to refresh."
{:arglists '([view])}
[& views]
(generic :refresh-materialized-view views))
(defn with
2021-03-13 23:35:47 +00:00
"Accepts one or more CTE definitions.
See the documentation for the `:with` clause."
[& args]
(generic :with args))
(defn with-recursive
2021-03-13 23:35:47 +00:00
"Accepts one or more CTE definitions.
See the documentation for the `:with` clause."
[& args]
(generic :with-recursive args))
2021-02-01 22:49:17 +00:00
;; these five need to supply an empty hash map since they wrap
;; all of their arguments:
(defn intersect
2021-02-15 01:20:24 +00:00
"Accepts any number of SQL clauses (queries) on
which to perform a set intersection."
[& clauses]
(generic :intersect (cons {} clauses)))
(defn union
2021-02-15 01:20:24 +00:00
"Accepts any number of SQL clauses (queries) on
which to perform a set union."
[& clauses]
(generic :union (cons {} clauses)))
(defn union-all
2021-02-15 01:20:24 +00:00
"Accepts any number of SQL clauses (queries) on
which to perform a set union all."
[& clauses]
(generic :union-all (cons {} clauses)))
(defn except
2021-02-15 01:20:24 +00:00
"Accepts any number of SQL clauses (queries) on
which to perform a set except."
[& clauses]
(generic :except (cons {} clauses)))
(defn except-all
2021-02-15 01:20:24 +00:00
"Accepts any number of SQL clauses (queries) on
which to perform a set except all."
[& clauses]
(generic :except-all (cons {} clauses)))
(defn select
2021-02-15 01:20:24 +00:00
"Accepts any number of column names, or column/alias
pairs, or SQL expressions (optionally aliased):
(select :id [:foo :bar] [[:max :quux]])
2021-02-15 01:54:13 +00:00
Produces: SELECT id, foo AS bar, MAX(quux)
The special column name :* produces * for 'all columns'.
You can also specify :t.* for 'all columns' from the
table (or alias) t."
2021-02-15 01:20:24 +00:00
[& exprs]
(generic :select exprs))
(defn select-distinct
2021-02-15 01:20:24 +00:00
"Like `select` but produces SELECT DISTINCT."
[& args]
(generic :select-distinct args))
(defn select-distinct-on
2021-02-15 01:20:24 +00:00
"Accepts a sequence of one or more columns for the
distinct clause, followed by any number of column
names, or column/alias pairs, or SQL expressions
(optionally aliased), as for `select`:
(select-distinct-on [:a :b] :c [:d :dd])
Produces: SELECT DISTINCT ON(a, b) c, d AS dd"
{:arglists '([distinct-cols & exprs])}
[& args]
(generic :select-distinct-on args))
(defn select-top
"Accepts a TOP expression, followed by any number of
column names, or column/alias pairs, or SQL expressions
(optionally aliased), as for `select`. The TOP expression
can be a simple numeric expression, or a sequence with
a numeric expression followed by keywords (or symbols)
for PERCENT and/or WITH TIES."
[& args]
(generic :select-top args))
(defn select-distinct-top
"Like `select-top` but produces SELECT DISTINCT TOP..."
[& args]
(generic :select-distinct-top args))
(defn into
"Accepts table name, optionally followed a database name."
{:arglist '([table] [table dbname])}
[& args]
(generic :into args))
(defn bulk-collect-into
"Accepts a variable name, optionally followed by a limit
expression."
{:arglist '([varname] [varname n])}
[& args]
(generic :bulk-collect-into args))
(defn insert-into
"Accepts a table name or a table/alias pair. That
can optionally be followed by a collection of
column names. That can optionally be followed by
a (select) statement clause.
(insert-into :table)
(insert-into [:table :t])
(insert-into :table [:id :name :cost])
(insert-into :table (-> (select :*) (from :other)))
(insert-into [:table :t]
[:id :name :cost]
(-> (select :*) (from :other)))"
{:arglists '([table] [table cols] [table statement] [table cols statement])}
[& args]
(let [[table cols statement] args]
(if (and (sequential? cols) (map? statement))
(generic :insert-into [[table cols] statement])
(generic :insert-into args))))
(defn update
"Accepts either a table name or a table/alias pair.
(-> (update :table) (set {:id 1 :cost 32.1}))"
2021-02-15 04:45:14 +00:00
{:arglists '([table])}
[& args]
(generic-1 :update args))
(defn delete
"For deleting from multiple tables.
Accepts a collection of table names to delete from.
(-> (delete [:films :directors]) (where [:= :id 1]))"
2021-02-15 04:45:14 +00:00
{:arglists '([table-coll])}
[& args]
(generic-1 :delete args))
(defn delete-from
"For deleting from a single table.
Accepts a single table name to delete from.
(-> (delete-from :films) (where [:= :id 1]))"
2021-02-15 04:45:14 +00:00
{:arglists '([table])}
[& args]
(generic :delete-from args))
(defn truncate
2021-02-15 01:20:24 +00:00
"Accepts a single table name to truncate."
{:arglists '([table])}
[& args]
2021-02-15 01:20:24 +00:00
(generic-1 :truncate args))
(defn columns
2021-02-15 01:54:13 +00:00
"To be used with `insert-into` to specify the list of
column names for the insert operation. Accepts any number
of column names:
(-> (insert-into :foo)
(columns :a :b :c)
(values [[1 2 3] [2 4 6]]))
Produces:
INSERT INTO foo (a, b, c) VALUES (?, ?, ?), (?, ?, ?)
Parameters: 1 2 3 2 4 6"
[& cols]
(generic :columns cols))
(defn set
2021-02-15 01:54:13 +00:00
"Accepts a hash map specifying column names and the
values to be assigned to them, as part of `update`:
(-> (update :foo)
(set {:a 1 :b nil}))
Produces: UPDATE foo SET a = ?, b = NULL"
{:arglists '([col-set-map])}
[& args]
(generic-1 :set args))
(defn from
2021-02-15 01:54:13 +00:00
"Accepts one or more table names, or table/alias pairs.
(-> (select :*)
(from [:foo :bar]))
Produces: SELECT * FROM foo AS bar"
[& tables]
(generic :from tables))
(defn using
2021-03-13 23:35:47 +00:00
"Accepts similar arguments to `select` as part of
a SQL `USING` clause."
[& args]
(generic :using args))
2021-03-08 03:21:13 +00:00
(defn join-by
"Accepts a sequence of join clauses to be generated
in a specific order.
(-> (select :*)
(from :foo)
(join-by :left :bar [:= :foo.id :bar.id]
:join :quux [:= :bar.qid = :quux.id])
This produces a LEFT JOIN followed by an INNER JOIN
even though the 'natural' order for `left-join` and
`join` would be to generate the INNER JOIN first,
followed by the LEFT JOIN."
[& args]
(generic :join-by args))
(defn join
2021-03-13 23:35:47 +00:00
"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]
(generic :join args))
(defn left-join
2021-03-13 23:35:47 +00:00
"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]
(generic :left-join args))
(defn right-join
2021-03-13 23:35:47 +00:00
"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]
(generic :right-join args))
(defn inner-join
2021-03-13 23:35:47 +00:00
"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]
(generic :inner-join args))
(defn outer-join
2021-03-13 23:35:47 +00:00
"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]
(generic :outer-join args))
(defn full-join
2021-03-13 23:35:47 +00:00
"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]
(generic :full-join args))
(defn cross-join
2021-03-13 23:35:47 +00:00
"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]
(generic :cross-join args))
(defn where
2021-02-15 01:54:13 +00:00
"Accepts one or more SQL expressions (conditions) and
combines them with AND:
(where [:= :status 0] [:<> :task \"backup\"])
Produces: WHERE (status = ?) AND (task <> ?)
Parameters: 0 \"backup\""
[& exprs]
(generic :where exprs))
(defn group-by
2021-03-13 23:35:47 +00:00
"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]
(generic :group-by args))
(defn having
2021-02-15 01:54:13 +00:00
"Like `where`, accepts one or more SQL expressions
(conditions) and combines them with AND:
(having [:> :count 0] [:<> :name nil])
Produces: HAVING (count > ?) AND (name IS NOT NULL)
Parameters: 0"
[& exprs]
(generic :having exprs))
(defn window
2021-03-13 23:35:47 +00:00
"Accepts a window name followed by a partition by clause."
[& args]
(generic :window args))
(defn partition-by
2021-03-13 23:35:47 +00:00
"Accepts one or more columns or SQL expressions to
partition by as part of a `WINDOW` expression."
[& args]
(generic :partition-by args))
(defn order-by
2021-03-13 23:35:47 +00:00
"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]
(generic :order-by args))
(defn limit
2021-03-13 23:35:47 +00:00
"Specific to some databases (notabley MySQL),
accepts a single SQL expression:
2021-02-15 01:54:13 +00:00
(limit 40)
Produces: LIMIT ?
2021-03-13 20:36:25 +00:00
Parameters: 40
The two-argument syntax is not supported: use `offset`
instead:
2021-03-13 23:35:47 +00:00
`LIMIT 20,10` is equivalent to `LIMIT 10 OFFSET 20`
(-> (limit 10) (offset 20))"
2021-02-15 01:54:13 +00:00
{:arglists '([limit])}
[& args]
(generic-1 :limit args))
(defn offset
2021-03-13 20:36:25 +00:00
"Accepts a single SQL expression:
2021-02-15 01:54:13 +00:00
(offset 10)
Produces: OFFSET ?
Parameters: 10"
{:arglists '([offset])}
[& args]
(generic-1 :offset args))
2021-03-13 20:36:25 +00:00
(defn fetch
"Accepts a single SQL expression:
(fetch 10)
Produces: FETCH ? ONLY
Parameters: 10"
{:arglists '([offset])}
[& args]
(generic-1 :offset args))
(defn for
2021-03-13 23:35:47 +00:00
"Accepts a lock strength, optionally followed by one or
more table names, optionally followed by a qualifier."
{:arglists '([lock-strength table* qualifier*])}
[& args]
(generic-1 :for args))
2021-03-13 20:36:25 +00:00
(defn lock
2021-03-13 23:35:47 +00:00
"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])}
2021-03-13 20:36:25 +00:00
[& args]
(generic-1 :lock args))
(defn values
2021-02-15 01:54:13 +00:00
"Accepts a single argument: a collection of row values.
Each row value can be either a sequence of column values
or a hash map of column name/column value pairs.
Used with `insert-into`.
(-> (insert-into :foo)
(values [{:id 1, :name \"John\"}
{:id 2, :name \"Fred\"}]))
Produces: INSERT INTO foo (id, name) VALUES (?, ?), (?, ?)
Parameters: 1 \"John\" 2 \"Fred\""
{:arglists '([row-value-coll])}
[& args]
(generic-1 :values args))
(defn on-conflict
2021-03-13 23:35:47 +00:00
"Accepts a single column name to detect conflicts
during an upsert, optionally followed by a `WHERE`
clause."
{:arglists '([column] [column where-clause])}
[& args]
(generic :on-conflict args))
(defn on-constraint
2021-02-15 04:45:14 +00:00
"Accepts a single constraint name."
{:arglists '([constraint])}
[& args]
(generic-1 :on-constraint args))
(defn do-nothing
2021-02-15 04:45:14 +00:00
"Called with no arguments, produces DO NOTHING"
{:arglists '([])}
[& args]
(generic :do-nothing args))
(defn do-update-set
2021-03-13 23:35:47 +00:00
"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]
(generic :do-update-set args))
(defn on-duplicate-key-update
2021-03-13 23:35:47 +00:00
"MySQL's upsert facility. Accepts a hash map of
column/value pairs to be updated (like `set` does)."
{:arglists '([column-value-map])}
[& args]
(generic :on-duplicate-key-update args))
(defn returning
2021-02-15 01:54:13 +00:00
"Accepts any number of column names to return from an
insert operation:
(returning :*)
Produces: RETURNING *"
[& cols]
(generic :returning cols))
2020-09-28 20:47:55 +00:00
2021-03-12 04:07:59 +00:00
(defn with-data
"Accepts a Boolean determining WITH DATA vs WITH NO DATA."
{:arglists '([data?])}
[& args]
(generic-1 :with-data args))
;; helpers that produce non-clause expressions -- must be listed below:
(defn composite
2021-02-15 02:28:35 +00:00
"Accepts any number of SQL expressions and produces
a composite value from them:
(composite :a 42)
Produces: (a, ?)
Parameters: 42"
[& args]
(c/into [:composite] args))
(defn lateral
"Accepts a SQL clause or a SQL expression:
(lateral (-> (select '*) (from 'foo)))
(lateral '(calc_value bar))
Produces:
LATERAL (SELECT * FROM foo)
LATERAL CALC_VALUE(bar)"
{:arglists '([clause-or-expression])}
[& args]
(c/into [:lateral] args))
;; to make this easy to use in a select, wrap it so it becomes a function:
(defn over
2021-02-15 02:28:35 +00:00
"Accepts any number of OVER clauses, each of which
is a pair of an aggregate function and a window function
or a triple of an aggregate function, a window function,
and an alias:
(select :id (over [[:avg :salary] (partition-by :department)]))
Produces: SELECT id, AVG(salary) OVER ()PARTITION BY department)"
[& args]
[(c/into [:over] args)])
2021-02-14 03:08:40 +00:00
;; this helper is intended to ease the migration from nilenso:
(defn upsert
2021-02-15 02:28:35 +00:00
"Provided purely to ease migration from nilenso/honeysql-postgres
this accepts a single clause, constructed from on-conflict,
do-nothing or do-update-set, and where. Any of those are optional.
This helper unpacks that clause and turns it into what HoneySQL
2.x expects, with any where clause being an argument to the
do-update-set helper, along with the `:fields`.
nilenso/honeysql-postgres:
(-> ...
(upsert (-> (on-conflict :col)
do-nothing)))
(-> ...
(upsert (-> (on-conflict :col)
(do-update-set :x)
(where [:<> :x nil]))))
HoneySQL 2.x:
(-> ...
(on-conflict :col)
do-nothing)
(-> ...
(on-conflict :col)
(do-update-set {:fields [:x]
:where [:<> :x nil]}))
Alternative structure for that second one:
(-> ...
(on-conflict :col)
(do-update-set :x {:where [:<> :x nil]}))"
2021-02-14 03:08:40 +00:00
([clause] (upsert {} clause))
([data clause]
(let [{:keys [on-conflict on-constraint do-nothing do-update-set where]} clause]
2021-02-14 03:08:40 +00:00
(cond-> data
on-conflict
(assoc :on-conflict on-conflict)
on-constraint
(assoc :on-constraint on-constraint)
2021-02-14 03:08:40 +00:00
do-nothing
(assoc :do-nothing do-nothing)
do-update-set
(assoc :do-update-set (if where
{:fields do-update-set
:where where}
do-update-set))))))
2021-02-13 18:50:36 +00:00
2020-09-28 20:47:55 +00:00
#?(:clj
2021-03-13 23:35:47 +00:00
(do
;; ensure #295 stays true (all public functions have docstring):
(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 (conj (map keyword (keys (ns-publics *ns*)))
:nest :raw))))))