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-02-11 00:25:31 +00:00
|
|
|
(:refer-clojure :exclude [update set group-by for partition-by])
|
2021-02-14 19:37:27 +00:00
|
|
|
(:require [honey.sql]))
|
|
|
|
|
|
|
|
|
|
;; implementation helpers:
|
2020-09-28 20:47:55 +00:00
|
|
|
|
|
|
|
|
(defn- default-merge [current args]
|
|
|
|
|
(into (vec current) args))
|
|
|
|
|
|
2020-10-10 06:05:05 +00:00
|
|
|
(defn- and-merge
|
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))
|
|
|
|
|
(into (vec current) (rest arg))
|
|
|
|
|
(seq current)
|
|
|
|
|
(into [conj' current] (rest arg))
|
|
|
|
|
:else
|
|
|
|
|
(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
|
2020-10-10 06:05:05 +00:00
|
|
|
[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-10-10 06:05:05 +00:00
|
|
|
|
2020-09-28 20:47:55 +00:00
|
|
|
(def ^:private special-merges
|
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)))
|
|
|
|
|
|
2020-10-10 06:05:05 +00:00
|
|
|
(defn- generic-1 [k [data arg]]
|
2020-10-10 06:59:30 +00:00
|
|
|
(if arg
|
2020-10-10 06:05:05 +00:00
|
|
|
(assoc data k arg)
|
|
|
|
|
(assoc {} k data)))
|
|
|
|
|
|
2021-02-14 19:37:27 +00:00
|
|
|
;; 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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& 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))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& 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))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& 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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& 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)"
|
2021-02-14 19:37:27 +00:00
|
|
|
[& 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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& 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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& 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))
|
|
|
|
|
|
2021-02-14 19:37:27 +00:00
|
|
|
(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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& 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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& 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)))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(defn create-view
|
2021-02-15 00:52:35 +00:00
|
|
|
"Accepts a single view name to create.
|
|
|
|
|
|
|
|
|
|
(-> (create-view :cities)
|
|
|
|
|
(select :*) (from :city))"
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
|
|
|
|
(generic-1 :create-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))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(defn drop-extension
|
2021-02-15 00:52:35 +00:00
|
|
|
"Accepts one or more extension names to drop."
|
|
|
|
|
[& extensions]
|
|
|
|
|
(generic :drop-extension extensions))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(defn nest
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :nest args))
|
|
|
|
|
|
|
|
|
|
(defn with
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :with args))
|
|
|
|
|
|
|
|
|
|
(defn with-recursive
|
|
|
|
|
[& 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:
|
2021-02-14 19:37:27 +00:00
|
|
|
(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)))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(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)))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(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)))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(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)))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(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)))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(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))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(defn select-distinct
|
2021-02-15 01:20:24 +00:00
|
|
|
"Like `select` but produces SELECT DISTINCT."
|
2021-02-14 19:37:27 +00:00
|
|
|
[& 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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
|
|
|
|
(generic :select-distinct-on args))
|
|
|
|
|
|
|
|
|
|
(defn insert-into
|
2021-02-15 04:39:32 +00:00
|
|
|
"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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
2021-02-15 04:39:32 +00:00
|
|
|
(let [[table cols statement] args]
|
|
|
|
|
(if (and (sequential? cols) (map? statement))
|
|
|
|
|
(generic :insert-into [[table cols] statement])
|
|
|
|
|
(generic :insert-into args))))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(defn update
|
2021-02-15 04:39:32 +00:00
|
|
|
"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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
2021-02-15 04:39:32 +00:00
|
|
|
(generic-1 :update args))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(defn delete
|
2021-02-15 04:39:32 +00:00
|
|
|
"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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
|
|
|
|
(generic-1 :delete args))
|
|
|
|
|
|
|
|
|
|
(defn delete-from
|
2021-02-15 04:39:32 +00:00
|
|
|
"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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
|
|
|
|
(generic :delete-from args))
|
|
|
|
|
|
|
|
|
|
(defn truncate
|
2021-02-15 01:20:24 +00:00
|
|
|
"Accepts a single table name to truncate."
|
|
|
|
|
{:arglists '([table])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
2021-02-15 01:20:24 +00:00
|
|
|
(generic-1 :truncate args))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(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))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& 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))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(defn using
|
|
|
|
|
[& 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))
|
|
|
|
|
|
2021-02-14 19:37:27 +00:00
|
|
|
(defn join
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :join args))
|
|
|
|
|
|
|
|
|
|
(defn left-join
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :left-join args))
|
|
|
|
|
|
|
|
|
|
(defn right-join
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :right-join args))
|
|
|
|
|
|
|
|
|
|
(defn inner-join
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :inner-join args))
|
|
|
|
|
|
|
|
|
|
(defn outer-join
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :outer-join args))
|
|
|
|
|
|
|
|
|
|
(defn full-join
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :full-join args))
|
|
|
|
|
|
|
|
|
|
(defn cross-join
|
|
|
|
|
[& 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))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(defn group-by
|
|
|
|
|
[& 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))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(defn window
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :window args))
|
|
|
|
|
|
|
|
|
|
(defn partition-by
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :partition-by args))
|
|
|
|
|
|
|
|
|
|
(defn order-by
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :order-by args))
|
|
|
|
|
|
|
|
|
|
(defn limit
|
2021-02-15 01:54:13 +00:00
|
|
|
"Specific to MySQL, accepts a single SQL expression:
|
|
|
|
|
|
|
|
|
|
(limit 40)
|
|
|
|
|
|
|
|
|
|
Produces: LIMIT ?
|
|
|
|
|
Parameters: 40"
|
|
|
|
|
{:arglists '([limit])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
|
|
|
|
(generic-1 :limit args))
|
|
|
|
|
|
|
|
|
|
(defn offset
|
2021-02-15 01:54:13 +00:00
|
|
|
"Specific to MySQL, accepts a single SQL expression:
|
|
|
|
|
|
|
|
|
|
(offset 10)
|
|
|
|
|
|
|
|
|
|
Produces: OFFSET ?
|
|
|
|
|
Parameters: 10"
|
|
|
|
|
{:arglists '([offset])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
|
|
|
|
(generic-1 :offset args))
|
|
|
|
|
|
|
|
|
|
(defn for
|
|
|
|
|
[& args]
|
|
|
|
|
(generic-1 :for 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])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
|
|
|
|
(generic-1 :values args))
|
|
|
|
|
|
|
|
|
|
(defn on-conflict
|
|
|
|
|
[& args]
|
2021-02-16 01:56:47 +00:00
|
|
|
(generic :on-conflict args))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(defn on-constraint
|
2021-02-15 04:45:14 +00:00
|
|
|
"Accepts a single constraint name."
|
|
|
|
|
{:arglists '([constraint])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
2021-02-16 04:43:53 +00:00
|
|
|
(generic-1 :on-constraint args))
|
2021-02-14 19:37:27 +00:00
|
|
|
|
|
|
|
|
(defn do-nothing
|
2021-02-15 04:45:14 +00:00
|
|
|
"Called with no arguments, produces DO NOTHING"
|
|
|
|
|
{:arglists '([])}
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
|
|
|
|
(generic :do-nothing args))
|
|
|
|
|
|
|
|
|
|
(defn do-update-set
|
|
|
|
|
[& args]
|
|
|
|
|
(generic :do-update-set 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))
|
|
|
|
|
|
2020-10-12 18:42:47 +00:00
|
|
|
;; helpers that produce non-clause expressions -- must be listed below:
|
2021-02-14 19:37:27 +00:00
|
|
|
(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"
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
|
|
|
|
(into [:composite] args))
|
|
|
|
|
|
2021-02-11 00:25:31 +00:00
|
|
|
;; to make this easy to use in a select, wrap it so it becomes a function:
|
2021-02-14 19:37:27 +00:00
|
|
|
(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)"
|
2021-02-14 19:37:27 +00:00
|
|
|
[& args]
|
|
|
|
|
[(into [:over] args)])
|
2020-10-12 18:42:47 +00:00
|
|
|
|
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]
|
2021-02-16 01:56:47 +00:00
|
|
|
(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)
|
2021-02-16 01:56:47 +00:00
|
|
|
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-02-14 19:37:27 +00:00
|
|
|
(assert (= (clojure.core/set (conj @@#'honey.sql/base-clause-order
|
2021-02-13 18:50:36 +00:00
|
|
|
:composite :over :upsert))
|
2020-09-28 20:47:55 +00:00
|
|
|
(clojure.core/set (map keyword (keys (ns-publics *ns*)))))))
|