2023-05-21 23:05:30 +00:00
|
|
|
;; copyright (c) 2020-2023 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:
|
2021-04-10 07:13:04 +00:00
|
|
|
* `clause-order` -- returns the current clause priority ordering;
|
|
|
|
|
intended as aid when registering new clauses.
|
2021-02-01 20:00:42 +00:00
|
|
|
* `format-dsl` -- intended to format SQL statements; returns a vector
|
|
|
|
|
containing a SQL string followed by parameter values.
|
2021-04-10 07:13:04 +00:00
|
|
|
* `format-entity` -- intended to format SQL entities; returns a string
|
|
|
|
|
representing the SQL entity.
|
2021-02-01 20:00:42 +00:00
|
|
|
* `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.
|
|
|
|
|
* `register-clause!` -- register a new statement/clause formatter.
|
|
|
|
|
* `register-fn!` -- register a new function call (or special syntax)
|
|
|
|
|
formatter.
|
2021-04-10 07:13:04 +00:00
|
|
|
* `register-op!` -- register a new operator formatter.
|
2021-09-26 00:25:44 +00:00
|
|
|
* `set-dialect!` -- set the default dialect to be used for formatting,
|
|
|
|
|
and optionally set a global `:quoted` option.
|
2021-04-10 07:13:04 +00:00
|
|
|
* `sql-kw` -- turns a Clojure keyword (or symbol) into SQL code (makes
|
|
|
|
|
it uppercase and replaces - with space). "
|
2020-09-21 01:48:07 +00:00
|
|
|
(:refer-clojure :exclude [format])
|
2022-08-07 04:49:49 +00:00
|
|
|
(:require [clojure.string :as str]
|
2023-08-23 19:52:07 +00:00
|
|
|
[honey.sql.protocols :as p]))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
;; 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):
|
2022-07-29 22:40:45 +00:00
|
|
|
:alter-table :add-column :drop-column
|
|
|
|
|
:alter-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
|
2023-08-23 19:52:07 +00:00
|
|
|
:create-view :create-or-replace-view :create-materialized-view
|
|
|
|
|
:create-extension
|
2021-03-12 19:43:21 +00:00
|
|
|
: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:
|
2021-03-15 21:48:28 +00:00
|
|
|
:raw :nest :with :with-recursive :intersect :union :union-all :except :except-all
|
2022-03-26 23:58:28 +00:00
|
|
|
:table
|
2021-03-13 02:54:19 +00:00
|
|
|
:select :select-distinct :select-distinct-on :select-top :select-distinct-top
|
2021-03-13 21:42:08 +00:00
|
|
|
:into :bulk-collect-into
|
2023-01-14 23:27:42 +00:00
|
|
|
:insert-into :replace-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
|
|
|
|
2022-05-01 05:03:36 +00:00
|
|
|
(defn add-clause-before
|
2021-04-09 22:58:56 +00:00
|
|
|
"Low-level helper just to insert a new clause.
|
|
|
|
|
|
2022-03-19 22:16:20 +00:00
|
|
|
If the clause is already in the list, this moves it to the end."
|
2020-09-23 19:55:02 +00:00
|
|
|
[order clause before]
|
2021-04-09 22:58:56 +00:00
|
|
|
(let [clauses (set order)
|
|
|
|
|
order (if (contains? clauses clause)
|
|
|
|
|
(filterv #(not= % clause) order)
|
|
|
|
|
order)]
|
|
|
|
|
(if before
|
|
|
|
|
(do
|
|
|
|
|
(when-not (contains? clauses 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-23 19:55:02 +00:00
|
|
|
|
2022-05-01 05:03:36 +00:00
|
|
|
(defn strop
|
2022-04-01 00:34:51 +00:00
|
|
|
"Escape any embedded closing strop characters."
|
|
|
|
|
[s x e]
|
|
|
|
|
(str s (str/replace x (str e) (str e e)) e))
|
|
|
|
|
|
2022-09-04 04:34:09 +00:00
|
|
|
(declare register-clause!)
|
|
|
|
|
|
2020-09-21 01:48:07 +00:00
|
|
|
(def ^:private dialects
|
2022-05-01 05:03:36 +00:00
|
|
|
(atom
|
|
|
|
|
(reduce-kv (fn [m k v]
|
|
|
|
|
(assoc m k (assoc v :dialect k)))
|
|
|
|
|
{}
|
|
|
|
|
{:ansi {:quote #(strop \" % \")}
|
|
|
|
|
:sqlserver {:quote #(strop \[ % \])}
|
|
|
|
|
:mysql {:quote #(strop \` % \`)
|
2022-09-04 04:34:09 +00:00
|
|
|
:clause-order-fn
|
2023-01-14 23:27:42 +00:00
|
|
|
#(add-clause-before % :set :where)}
|
2022-05-01 05:03:36 +00:00
|
|
|
:oracle {:quote #(strop \" % \") :as false}})))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
; should become defonce
|
2022-05-01 05:03:36 +00:00
|
|
|
(def ^:private default-dialect (atom (:ansi @dialects)))
|
2021-09-26 00:25:44 +00:00
|
|
|
(def ^:private default-quoted (atom nil))
|
2022-09-11 21:21:39 +00:00
|
|
|
(def ^:private default-quoted-snake (atom nil))
|
|
|
|
|
(def ^:private default-inline (atom nil))
|
|
|
|
|
(def ^:private default-checking (atom :none))
|
2022-12-18 00:02:02 +00:00
|
|
|
(def ^:private default-numbered (atom false))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
(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)
|
2022-09-11 21:21:39 +00:00
|
|
|
(def ^:private ^:dynamic *quoted* @default-quoted)
|
|
|
|
|
(def ^:private ^:dynamic *quoted-snake* @default-quoted-snake)
|
|
|
|
|
(def ^:private ^:dynamic *inline* @default-inline)
|
2021-01-31 13:08:21 +00:00
|
|
|
(def ^:private ^:dynamic *params* nil)
|
2021-10-04 05:32:05 +00:00
|
|
|
(def ^:private ^:dynamic *values-default-columns* nil)
|
2021-04-10 06:41:59 +00:00
|
|
|
;; there is no way, currently, to enable suspicious characters
|
|
|
|
|
;; in entities; if someone complains about this check, an option
|
|
|
|
|
;; can be added to format to turn this on:
|
|
|
|
|
(def ^:private ^:dynamic *allow-suspicious-entities* false)
|
2021-04-23 05:13:32 +00:00
|
|
|
;; "linting" mode (:none, :basic, :strict):
|
2022-09-11 21:21:39 +00:00
|
|
|
(def ^:private ^:dynamic *checking* @default-checking)
|
2021-07-18 00:57:17 +00:00
|
|
|
;; the current DSL hash map being formatted (for contains-clause?):
|
|
|
|
|
(def ^:private ^:dynamic *dsl* nil)
|
2022-04-23 22:54:24 +00:00
|
|
|
;; caching data to detect expressions that cannot be cached:
|
|
|
|
|
(def ^:private ^:dynamic *caching* nil)
|
2022-12-17 07:11:45 +00:00
|
|
|
(def ^:private ^:dynamic *numbered* nil)
|
2020-09-21 01:48:07 +00:00
|
|
|
|
|
|
|
|
;; clause helpers
|
|
|
|
|
|
2021-07-18 00:57:17 +00:00
|
|
|
(defn contains-clause?
|
|
|
|
|
"Returns true if the current DSL expression being formatted
|
|
|
|
|
contains the specified clause (as a keyword or symbol)."
|
|
|
|
|
[clause]
|
|
|
|
|
(or (contains? *dsl* clause)
|
|
|
|
|
(contains? *dsl*
|
|
|
|
|
(if (keyword? clause)
|
|
|
|
|
(symbol (name clause))
|
|
|
|
|
(keyword (name clause))))))
|
|
|
|
|
|
2021-08-13 01:26:39 +00:00
|
|
|
(defn- mysql?
|
|
|
|
|
"Helper to detect if MySQL is the current dialect."
|
|
|
|
|
[]
|
|
|
|
|
(= :mysql (:dialect *dialect*)))
|
|
|
|
|
|
2021-07-18 00:57:17 +00:00
|
|
|
(defn- sql-server?
|
|
|
|
|
"Helper to detect if SQL Server is the current dialect."
|
|
|
|
|
[]
|
|
|
|
|
(= :sqlserver (:dialect *dialect*)))
|
|
|
|
|
|
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.
|
2022-05-02 00:34:31 +00:00
|
|
|
#?(:clj
|
|
|
|
|
(defn upper-case
|
|
|
|
|
"Upper-case a string in Locale/US to avoid locale-specific capitalization."
|
|
|
|
|
[^String s]
|
|
|
|
|
(.. s toString (toUpperCase (java.util.Locale/US))))
|
2023-06-02 18:24:52 +00:00
|
|
|
:cljr
|
|
|
|
|
(defn upper-case
|
|
|
|
|
"Upper-case a string in Locale/US to avoid locale-specific capitalization."
|
|
|
|
|
[^String s]
|
|
|
|
|
(.ToUpper s (System.Globalization.CultureInfo. "en-Us")))
|
2022-05-02 00:34:31 +00:00
|
|
|
;; TODO - not sure if there's a JavaScript equivalent here we should be using as well
|
2023-05-21 23:05:30 +00:00
|
|
|
:default
|
2022-05-02 00:34:31 +00:00
|
|
|
(defn upper-case
|
|
|
|
|
"In ClojureScript, just an alias for cljs.string/upper-case."
|
|
|
|
|
[s]
|
|
|
|
|
(str/upper-case s)))
|
2020-09-23 19:55:02 +00:00
|
|
|
|
2021-07-23 02:49:25 +00:00
|
|
|
(defn- dehyphen
|
2022-02-22 03:09:49 +00:00
|
|
|
"Replace _embedded_ hyphens with spaces.
|
|
|
|
|
|
|
|
|
|
Hyphens at the start or end of a string should not be touched."
|
2021-07-23 02:49:25 +00:00
|
|
|
[s]
|
2022-02-23 05:27:10 +00:00
|
|
|
(str/replace s #"(\w)-(?=\w)" "$1 "))
|
2021-03-13 21:13:35 +00:00
|
|
|
|
2021-06-17 19:54:15 +00:00
|
|
|
(defn- namespace-_
|
|
|
|
|
"Return the namespace portion of a symbol, with dashes converted."
|
|
|
|
|
[x]
|
|
|
|
|
(try
|
|
|
|
|
(some-> (namespace x) (str/replace "-" "_"))
|
2023-05-21 23:05:30 +00:00
|
|
|
(catch #?(:cljs :default :default Exception) t
|
2021-06-17 19:54:15 +00:00
|
|
|
(throw (ex-info (str "expected symbol, found: "
|
|
|
|
|
(type x))
|
|
|
|
|
{:symbol x
|
2021-06-17 20:05:49 +00:00
|
|
|
:failure (str t)})))))
|
2021-06-17 19:54:15 +00:00
|
|
|
|
|
|
|
|
(defn- name-_
|
|
|
|
|
"Return the name portion of a symbol, with dashes converted."
|
|
|
|
|
[x]
|
|
|
|
|
(try
|
|
|
|
|
(str/replace (name x) "-" "_")
|
2023-05-21 23:05:30 +00:00
|
|
|
(catch #?(:cljs :default :default Exception) t
|
2021-06-17 19:54:15 +00:00
|
|
|
(throw (ex-info (str "expected symbol, found: "
|
|
|
|
|
(type x))
|
|
|
|
|
{:symbol x
|
2021-06-17 20:05:49 +00:00
|
|
|
:failure (str t)})))))
|
2020-10-13 05:54:46 +00:00
|
|
|
|
2023-02-26 12:20:09 +00:00
|
|
|
(defn- ensure-sequential [xs]
|
|
|
|
|
(if (sequential? xs) xs [xs]))
|
|
|
|
|
|
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."
|
2021-05-09 04:01:28 +00:00
|
|
|
[e & [{:keys [aliased drop-ns]}]]
|
2023-08-12 02:06:30 +00:00
|
|
|
(let [e (if (and aliased (keyword? e) (= \' (first (name e))))
|
|
|
|
|
;; #497 quoted alias support (should behave like string)
|
|
|
|
|
(subs (name e) 1)
|
|
|
|
|
e)
|
|
|
|
|
col-fn (if (or *quoted* (string? e))
|
2021-05-09 04:01:28 +00:00
|
|
|
(if *quoted-snake* name-_ name)
|
|
|
|
|
name-_)
|
2022-08-23 22:41:38 +00:00
|
|
|
col-e (col-fn e)
|
|
|
|
|
dialect-q (:quote *dialect* identity)
|
|
|
|
|
quote-fn (cond (or *quoted* (string? e))
|
|
|
|
|
dialect-q
|
|
|
|
|
;; #422: if default quoting and "unusual"
|
|
|
|
|
;; characters in entity, then quote it:
|
|
|
|
|
(nil? *quoted*)
|
|
|
|
|
(fn opt-quote [part]
|
|
|
|
|
(if (re-find #"^[A-Za-z0-9_]+$" part)
|
|
|
|
|
part
|
|
|
|
|
(dialect-q part)))
|
|
|
|
|
:else
|
|
|
|
|
identity)
|
2021-09-09 17:41:37 +00:00
|
|
|
parts (if-let [n (when-not (or drop-ns (string? e))
|
2021-05-09 04:01:28 +00:00
|
|
|
(namespace-_ e))]
|
2022-08-23 22:41:38 +00:00
|
|
|
[n col-e]
|
2021-05-09 04:01:28 +00:00
|
|
|
(if aliased
|
2022-08-23 22:41:38 +00:00
|
|
|
[col-e]
|
|
|
|
|
(str/split col-e #"\.")))
|
2021-09-09 17:41:37 +00:00
|
|
|
entity (str/join "." (map #(cond-> % (not= "*" %) (quote-fn)) parts))
|
2021-04-10 06:41:59 +00:00
|
|
|
suspicious #";"]
|
|
|
|
|
(when-not *allow-suspicious-entities*
|
|
|
|
|
(when (re-find suspicious entity)
|
|
|
|
|
(throw (ex-info (str "suspicious character found in entity: " entity)
|
|
|
|
|
{:disallowed suspicious}))))
|
|
|
|
|
entity))
|
2020-10-02 06:30:18 +00:00
|
|
|
|
2021-03-15 23:45:26 +00:00
|
|
|
(comment
|
2021-05-09 04:01:28 +00:00
|
|
|
(for [v [:foo-bar "foo-bar" ; symbol is the same as keyword
|
|
|
|
|
:f-o.b-r :f-o/b-r]
|
2021-03-15 23:54:13 +00:00
|
|
|
a [true false] d [true false] q [true false]]
|
2022-05-01 05:03:36 +00:00
|
|
|
(binding [*dialect* (:mysql @dialects) *quoted* q]
|
2021-05-09 04:01:28 +00:00
|
|
|
(if q
|
|
|
|
|
[v a d (format-entity v {:aliased a :drop-ns d})
|
|
|
|
|
(binding [*quoted-snake* true]
|
|
|
|
|
(format-entity v {:aliased a :drop-ns d}))]
|
|
|
|
|
[v a d (format-entity v {:aliased a :drop-ns d})])))
|
2022-05-20 03:29:32 +00:00
|
|
|
)
|
2021-03-15 23:45:26 +00:00
|
|
|
|
2022-02-03 07:51:01 +00:00
|
|
|
(defn sql-kw
|
|
|
|
|
"Given a keyword, return a SQL representation of it as a string.
|
|
|
|
|
|
|
|
|
|
A keyword whose name begins with a single quote is left exactly as-is
|
|
|
|
|
(with the `:` and `'` removed), otherwise a `:kebab-case` keyword
|
|
|
|
|
becomes a `KEBAB CASE` (uppercase) string with hyphens replaced
|
|
|
|
|
by spaces, e.g., `:insert-into` => `INSERT INTO`.
|
|
|
|
|
|
2022-03-26 04:48:00 +00:00
|
|
|
Any namespace qualifier is ignored.
|
|
|
|
|
|
|
|
|
|
Any ? is escaped to ??."
|
2022-02-03 07:51:01 +00:00
|
|
|
[k]
|
2022-03-26 04:48:00 +00:00
|
|
|
(let [n (str/replace (name k) "?" "??")]
|
2022-02-03 07:51:01 +00:00
|
|
|
(if (= \' (first n))
|
2022-11-18 06:39:48 +00:00
|
|
|
(let [ident (subs n 1 (count n))
|
|
|
|
|
ident-l (str/lower-case ident)]
|
|
|
|
|
(binding [*quoted* (when-not (contains? #{"array"} ident-l) *quoted*)]
|
|
|
|
|
(format-entity (keyword ident))))
|
2022-02-03 07:51:01 +00:00
|
|
|
(-> n (dehyphen) (upper-case)))))
|
|
|
|
|
|
|
|
|
|
(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))
|
|
|
|
|
|
2022-08-07 04:49:49 +00:00
|
|
|
(extend-protocol p/InlineValue
|
|
|
|
|
nil
|
|
|
|
|
(sqlize [_] "NULL")
|
2023-05-21 23:05:30 +00:00
|
|
|
#?(:cljs string :default String)
|
2022-08-07 04:49:49 +00:00
|
|
|
(sqlize [x] (str \' (str/replace x "'" "''") \'))
|
2023-05-21 23:05:30 +00:00
|
|
|
#?(:cljs Keyword :default clojure.lang.Keyword)
|
2022-08-07 04:49:49 +00:00
|
|
|
(sqlize [x] (sql-kw x))
|
2023-05-21 23:05:30 +00:00
|
|
|
#?(:cljs Symbol :default clojure.lang.Symbol)
|
2022-08-07 04:49:49 +00:00
|
|
|
(sqlize [x] (sql-kw x))
|
2023-05-21 23:05:30 +00:00
|
|
|
#?(:cljs PersistentVector :default clojure.lang.IPersistentVector)
|
2022-08-07 04:49:49 +00:00
|
|
|
(sqlize [x] (str "[" (str/join ", " (map p/sqlize x)) "]"))
|
|
|
|
|
#?@(:clj [java.util.UUID
|
|
|
|
|
;; issue 385: quoted UUIDs for PostgreSQL/ANSI
|
|
|
|
|
(sqlize [x] (str \' x \'))])
|
2023-05-21 23:05:30 +00:00
|
|
|
#?(:cljs default :default Object)
|
2022-08-07 04:49:49 +00:00
|
|
|
(sqlize [x] (str x)))
|
|
|
|
|
|
|
|
|
|
(defn- sqlize-value [x] (p/sqlize x))
|
2022-02-03 07:51:01 +00:00
|
|
|
|
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
|
|
|
|
2022-12-17 07:11:45 +00:00
|
|
|
(defn ->numbered [v]
|
|
|
|
|
(let [n (count (swap! *numbered* conj v))]
|
|
|
|
|
[(str "$" n) (with-meta (constantly (dec n))
|
|
|
|
|
{::wrapper
|
2022-12-17 07:20:17 +00:00
|
|
|
(fn [fk _] (get @*numbered* (fk)))})]))
|
2022-12-17 07:11:45 +00:00
|
|
|
|
|
|
|
|
(defn ->numbered-param [k]
|
|
|
|
|
(let [n (count (swap! *numbered* conj k))]
|
2022-12-17 07:56:53 +00:00
|
|
|
[(str "$" n) (with-meta (constantly (dec n))
|
2022-12-17 07:11:45 +00:00
|
|
|
{::wrapper
|
2022-12-17 07:20:17 +00:00
|
|
|
(fn [fk _] (param-value (get @*numbered* (fk))))})]))
|
2022-12-17 07:11:45 +00:00
|
|
|
|
2022-09-12 03:52:53 +00:00
|
|
|
(def ^:private ^:dynamic *formatted-column* (atom false))
|
|
|
|
|
|
2020-10-02 06:30:18 +00:00
|
|
|
(defn- format-var [x & [opts]]
|
2021-05-10 19:37:07 +00:00
|
|
|
;; rather than name/namespace, we want to allow
|
|
|
|
|
;; for multiple / in the %fun.call case so that
|
|
|
|
|
;; qualified column names can be used:
|
|
|
|
|
(let [c (cond-> (str x) (keyword? x) (subs 1))]
|
2020-10-02 06:30:18 +00:00
|
|
|
(cond (= \% (first c))
|
2021-05-09 05:18:39 +00:00
|
|
|
(let [[f & args] (str/split (subs c 1) #"\.")
|
2021-05-10 13:07:34 +00:00
|
|
|
quoted-args (map #(format-entity (keyword %) opts) args)]
|
2021-05-10 19:37:07 +00:00
|
|
|
[(str (upper-case (str/replace f "-" "_"))
|
|
|
|
|
"(" (str/join ", " quoted-args) ")")])
|
2020-10-02 06:30:18 +00:00
|
|
|
(= \? (first c))
|
2021-05-10 19:37:07 +00:00
|
|
|
(let [k (keyword (subs c 1))]
|
2022-12-17 07:11:45 +00:00
|
|
|
(cond *inline*
|
|
|
|
|
[(sqlize-value (param-value k))]
|
|
|
|
|
*numbered*
|
|
|
|
|
(->numbered-param k)
|
|
|
|
|
:else
|
|
|
|
|
["?" (->param k)]))
|
2022-09-12 03:52:53 +00:00
|
|
|
(= \' (first c))
|
|
|
|
|
(do
|
|
|
|
|
(reset! *formatted-column* true)
|
|
|
|
|
[(subs c 1)])
|
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})))
|
2022-04-23 22:12:55 +00:00
|
|
|
(let [[sql & params] (format-expr s)]
|
|
|
|
|
(into [(cond-> sql
|
|
|
|
|
pair?
|
|
|
|
|
(str (if (and (contains? *dialect* :as) (not (:as *dialect*))) " " " AS ")
|
|
|
|
|
(format-entity (second x) {:aliased true})))]
|
|
|
|
|
params)))
|
2020-09-23 07:14:25 +00:00
|
|
|
|
|
|
|
|
:else
|
2022-04-23 22:12:55 +00:00
|
|
|
[(format-entity x)]))
|
|
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
(format-expr :a)
|
|
|
|
|
(format-expr [:raw "My String"])
|
|
|
|
|
(format-entity-alias [[:raw "My String"]])
|
|
|
|
|
)
|
2020-09-23 07:14:25 +00:00
|
|
|
|
2022-01-07 07:02:20 +00:00
|
|
|
(declare format-selects-common)
|
|
|
|
|
|
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)
|
|
|
|
|
a (second x)
|
2022-01-07 07:02:20 +00:00
|
|
|
pair? (= 2 (count x))
|
2022-01-07 19:35:08 +00:00
|
|
|
big? (and (ident? s) (or (= "*" (name s)) (str/ends-with? (name s) ".*"))
|
2022-01-07 07:02:20 +00:00
|
|
|
(ident? a) (#{"except" "replace"} (name a)))
|
|
|
|
|
more? (and (< 2 (count x)) (not big?))
|
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))
|
2022-01-07 07:02:20 +00:00
|
|
|
[sql' & params'] (when (or pair? big?)
|
|
|
|
|
(cond (sequential? a)
|
|
|
|
|
(let [[sqls params] (format-expr-list a {:aliased true})]
|
2023-08-23 19:45:19 +00:00
|
|
|
(println "sequential alias expression:" a)
|
2022-01-07 07:02:20 +00:00
|
|
|
(into [(str/join " " sqls)] params))
|
|
|
|
|
big? ; BigQuery support #281
|
|
|
|
|
(reduce (fn [[sql & params] [k arg]]
|
|
|
|
|
(let [[sql' params']
|
|
|
|
|
(cond (and (ident? k) (= "except" (name k)) arg)
|
|
|
|
|
(let [[sqls params]
|
|
|
|
|
(format-expr-list arg {:aliased true})]
|
|
|
|
|
[(str (sql-kw k) " (" (str/join ", " sqls) ")")
|
|
|
|
|
params])
|
|
|
|
|
(and (ident? k) (= "replace" (name k)) arg)
|
|
|
|
|
(let [[sql & params] (format-selects-common nil true arg)]
|
|
|
|
|
[(str (sql-kw k) " (" sql ")")
|
|
|
|
|
params])
|
|
|
|
|
:else
|
|
|
|
|
(throw (ex-info "bigquery * only supports except and replace"
|
|
|
|
|
{:clause k :arg arg})))]
|
|
|
|
|
(-> [(cond->> sql' sql (str sql " "))]
|
|
|
|
|
(into params)
|
|
|
|
|
(into params'))))
|
|
|
|
|
[]
|
|
|
|
|
(partition-all 2 (rest x)))
|
|
|
|
|
:else
|
|
|
|
|
(format-selectable-dsl a {:aliased true})))]
|
|
|
|
|
(-> [(cond pair?
|
|
|
|
|
(str sql
|
|
|
|
|
(if as
|
|
|
|
|
(if (and (contains? *dialect* :as)
|
|
|
|
|
(not (:as *dialect*)))
|
|
|
|
|
" "
|
|
|
|
|
" AS ")
|
|
|
|
|
" ") sql')
|
|
|
|
|
big?
|
|
|
|
|
(str sql " " sql')
|
|
|
|
|
more?
|
|
|
|
|
(throw (ex-info "illegal syntax in select expression"
|
|
|
|
|
{:symbol s :alias a :unexpected (nnext x)}))
|
|
|
|
|
:else
|
|
|
|
|
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
|
|
|
|
2022-04-23 22:12:55 +00:00
|
|
|
(defn- reduce-sql [xs]
|
|
|
|
|
(reduce (fn [[sql params] [sql' & params']]
|
|
|
|
|
[(conj sql sql') (if params' (into params params') params)])
|
|
|
|
|
[[] []]
|
|
|
|
|
xs))
|
|
|
|
|
|
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]
|
2022-04-23 22:12:55 +00:00
|
|
|
(let [[sqls params] (reduce-sql (map #(format-dsl %) xs))]
|
2020-09-23 07:14:25 +00:00
|
|
|
(into [(str/join (str " " (sql-kw k) " ") sqls)] params)))
|
|
|
|
|
|
2023-03-01 01:38:13 +00:00
|
|
|
(defn- inline-kw?
|
|
|
|
|
"Return true if the expression should be treated as an inline SQL keeyword."
|
|
|
|
|
[expr]
|
|
|
|
|
(and (ident? expr)
|
|
|
|
|
(nil? (namespace expr))
|
2023-03-02 17:56:30 +00:00
|
|
|
(re-find #"^![a-zA-Z]" (name expr))))
|
2023-03-01 01:38:13 +00:00
|
|
|
|
|
|
|
|
(defn format-interspersed-expr-list
|
|
|
|
|
"If there are inline (SQL) keywords, use them to join the formatted
|
|
|
|
|
expressions together. Otherwise behaves like plain format-expr-list.
|
|
|
|
|
|
|
|
|
|
This allows for argument lists like:
|
|
|
|
|
* [:overlay :foo :*placing :?subs :*from 3 :*for 4]
|
|
|
|
|
* [:trim :*leading-from :bar]"
|
|
|
|
|
[args & [opts]]
|
|
|
|
|
(loop [exprs (map #(format-expr % opts) (remove inline-kw? args))
|
|
|
|
|
args args
|
|
|
|
|
prev-in false
|
|
|
|
|
result []]
|
|
|
|
|
(if (seq args)
|
|
|
|
|
(let [[arg & args'] args]
|
|
|
|
|
(if (inline-kw? arg)
|
|
|
|
|
(let [sql (sql-kw (keyword (subs (name arg) 1)))]
|
|
|
|
|
(if (seq result)
|
|
|
|
|
(let [[cur & params] (peek result)]
|
|
|
|
|
(recur exprs args' true (conj (pop result)
|
|
|
|
|
(into [(str cur " " sql)] params))))
|
|
|
|
|
(recur exprs args' true (conj result [sql]))))
|
|
|
|
|
(if prev-in
|
|
|
|
|
(let [[cur & params] (peek result)
|
|
|
|
|
[sql & params'] (first exprs)]
|
|
|
|
|
(recur (rest exprs) args' false (conj (pop result)
|
|
|
|
|
(-> [(str cur " " sql)]
|
|
|
|
|
(into params)
|
|
|
|
|
(into params')))))
|
|
|
|
|
(recur (rest exprs) args' false (conj result (first exprs))))))
|
|
|
|
|
(reduce-sql result))))
|
|
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
(format-interspersed-expr-list [:foo :*placing :?subs :*from 3 :*for 4]
|
|
|
|
|
{:params {:subs "bar"}})
|
|
|
|
|
(format-interspersed-expr-list [:*leading-from " foo "] {})
|
|
|
|
|
)
|
|
|
|
|
|
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]]
|
2022-04-23 20:40:34 +00:00
|
|
|
(when-not (sequential? exprs)
|
|
|
|
|
(throw (ex-info (str "format-expr-list expects a sequence of expressions, found: "
|
|
|
|
|
(type exprs))
|
|
|
|
|
{:exprs exprs})))
|
2022-04-23 22:12:55 +00:00
|
|
|
(reduce-sql (map #(format-expr % opts) exprs)))
|
2020-09-24 01:15:20 +00:00
|
|
|
|
2022-04-23 20:40:34 +00:00
|
|
|
(comment
|
|
|
|
|
(format-expr-list :?tags)
|
|
|
|
|
)
|
|
|
|
|
|
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)
|
2022-04-23 22:12:55 +00:00
|
|
|
(let [[sqls params] (reduce-sql (map #(format-selectable-dsl % {:as as}) xs))]
|
2021-12-03 21:06:34 +00:00
|
|
|
(when-not (= :none *checking*)
|
|
|
|
|
(when (empty? xs)
|
|
|
|
|
(throw (ex-info (str prefix " empty column list is illegal")
|
|
|
|
|
{:clause (into [prefix] xs)}))))
|
2022-01-07 07:02:20 +00:00
|
|
|
(into [(str (when prefix (str prefix " ")) (str/join ", " sqls))] params))
|
2021-02-13 23:58:56 +00:00
|
|
|
(let [[sql & params] (format-selectable-dsl xs {:as as})]
|
2022-01-07 07:02:20 +00:00
|
|
|
(into [(str (when prefix (str prefix " ")) sql)] params))))
|
2021-02-13 23:58:56 +00:00
|
|
|
|
|
|
|
|
(defn- format-selects [k xs]
|
|
|
|
|
(format-selects-common
|
|
|
|
|
(sql-kw k)
|
2021-09-01 17:20:19 +00:00
|
|
|
(#{:select :select-distinct :from :window :delete-from
|
|
|
|
|
'select 'select-distinct 'from 'window 'delete-from}
|
2021-02-13 23:58:56 +00:00
|
|
|
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'))))
|
|
|
|
|
|
2021-03-13 21:42:08 +00:00
|
|
|
(defn- format-select-into [k xs]
|
2023-02-26 12:20:09 +00:00
|
|
|
(let [[v e] (ensure-sequential xs)
|
2021-03-13 21:42:08 +00:00
|
|
|
[sql & params] (when e (format-expr e))]
|
|
|
|
|
(into [(str (sql-kw k) " " (format-entity v)
|
|
|
|
|
(when sql
|
|
|
|
|
(str " "
|
|
|
|
|
(sql-kw (if (= :into k) :in :limit))
|
|
|
|
|
" "
|
|
|
|
|
sql)))]
|
|
|
|
|
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)]))
|
|
|
|
|
|
2022-08-16 22:53:34 +00:00
|
|
|
(defn- format-with [k xs as-fn]
|
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]
|
2022-08-16 22:53:34 +00:00
|
|
|
(reduce-sql
|
2022-09-23 13:54:30 +00:00
|
|
|
(map
|
|
|
|
|
(fn [[x expr :as with]]
|
|
|
|
|
(let [[sql & params] (format-with-part x)
|
|
|
|
|
non-query-expr? (or (ident? expr) (string? expr))
|
|
|
|
|
[sql' & params'] (if non-query-expr?
|
|
|
|
|
(format-expr expr)
|
|
|
|
|
(format-dsl expr))]
|
|
|
|
|
(if non-query-expr?
|
|
|
|
|
(cond-> [(str sql' " AS " sql)]
|
|
|
|
|
params' (into params')
|
|
|
|
|
params (into params))
|
2022-09-22 16:40:15 +00:00
|
|
|
;; according to docs, CTE should _always_ be wrapped:
|
2022-09-23 13:54:30 +00:00
|
|
|
(cond-> [(str sql " " (as-fn with) " " (str "(" sql' ")"))]
|
2022-09-22 16:40:15 +00:00
|
|
|
params (into params)
|
2022-09-23 13:54:30 +00:00
|
|
|
params' (into params')))))
|
|
|
|
|
xs))]
|
2021-02-10 19:50:00 +00:00
|
|
|
(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])
|
2022-04-23 22:12:55 +00:00
|
|
|
[sql & params] (format-dsl statement)
|
|
|
|
|
[t-sql & t-params] (format-entity-alias table)
|
|
|
|
|
[c-sqls c-params] (reduce-sql (map #'format-entity-alias cols))]
|
|
|
|
|
(-> [(str (sql-kw k) " " t-sql
|
|
|
|
|
" "
|
|
|
|
|
(when (seq cols)
|
|
|
|
|
(str "("
|
|
|
|
|
(str/join ", " c-sqls)
|
|
|
|
|
") "))
|
|
|
|
|
sql)]
|
|
|
|
|
(into t-params)
|
|
|
|
|
(into c-params)
|
|
|
|
|
(into params)))
|
2021-02-10 23:07:16 +00:00
|
|
|
(sequential? (second table))
|
2022-04-23 22:12:55 +00:00
|
|
|
(let [[table cols] table
|
|
|
|
|
[t-sql & t-params] (format-entity-alias table)
|
|
|
|
|
[c-sqls c-params] (reduce-sql (map #'format-entity-alias cols))]
|
|
|
|
|
(-> [(str (sql-kw k) " " t-sql
|
|
|
|
|
" ("
|
|
|
|
|
(str/join ", " c-sqls)
|
|
|
|
|
")")]
|
|
|
|
|
(into t-params)
|
|
|
|
|
(into c-params)))
|
2020-09-28 19:12:40 +00:00
|
|
|
:else
|
2022-04-23 22:12:55 +00:00
|
|
|
(let [[sql & params] (format-entity-alias table)]
|
|
|
|
|
(into [(str (sql-kw k) " " sql)] params)))
|
|
|
|
|
(let [[sql & params] (format-entity-alias table)]
|
|
|
|
|
(into [(str (sql-kw k) " " sql)] params))))
|
|
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
(format-insert :insert-into [[[:raw ":foo"]] {:select :bar}])
|
|
|
|
|
)
|
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)))
|
2022-04-23 22:12:55 +00:00
|
|
|
(let [[u-sqls u-params]
|
|
|
|
|
(reduce-sql (map #'format-entity-alias (rest e)))]
|
|
|
|
|
[(conj sqls
|
|
|
|
|
"USING"
|
|
|
|
|
(str "("
|
|
|
|
|
(str/join ", " u-sqls)
|
|
|
|
|
")"))
|
|
|
|
|
(-> params (into params-j) (into u-params))])
|
2021-01-30 19:00:34 +00:00
|
|
|
(let [[sql & params'] (when e (format-expr e))]
|
|
|
|
|
[(cond-> sqls e (conj "ON" sql))
|
2021-05-10 18:55:28 +00:00
|
|
|
(-> params
|
|
|
|
|
(into params-j)
|
|
|
|
|
(into params'))]))))
|
2020-10-13 05:54:59 +00:00
|
|
|
[[] []]
|
2021-05-10 19:05:07 +00:00
|
|
|
(partition-all 2 clauses))]
|
2020-10-13 05:54:59 +00:00
|
|
|
(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
|
2021-04-11 11:18:14 +00:00
|
|
|
by their table and condition, or a sequence of join
|
|
|
|
|
clauses, so that you can construct a series of joins
|
|
|
|
|
in a specific order."
|
2021-03-08 03:21:13 +00:00
|
|
|
[_ clauses]
|
2021-04-11 11:18:14 +00:00
|
|
|
(let [joins (if (every? map? clauses)
|
|
|
|
|
(into []
|
|
|
|
|
(comp (mapcat #(mapcat (juxt key val) %))
|
|
|
|
|
(map vector))
|
|
|
|
|
clauses)
|
|
|
|
|
(partition-by ident? clauses))]
|
2021-03-08 03:21:13 +00:00
|
|
|
(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]
|
2023-02-26 12:20:09 +00:00
|
|
|
(let [[sqls params] (format-expr-list (ensure-sequential xs))]
|
2020-09-21 01:48:07 +00:00
|
|
|
(into [(str (sql-kw k) " " (str/join ", " sqls))] params)))
|
|
|
|
|
|
|
|
|
|
(defn- format-order-by [k xs]
|
2023-02-26 12:20:09 +00:00
|
|
|
(let [xs (ensure-sequential xs)
|
|
|
|
|
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]
|
2023-02-26 12:20:09 +00:00
|
|
|
(let [[strength tables nowait] (ensure-sequential xs)]
|
2020-09-29 03:45:43 +00:00
|
|
|
[(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]
|
2022-09-02 05:54:00 +00:00
|
|
|
(let [first-xs (when (sequential? xs) (first (drop-while ident? xs)))]
|
|
|
|
|
(cond (contains? #{:default 'default} xs)
|
|
|
|
|
[(str (sql-kw xs) " " (sql-kw k))]
|
|
|
|
|
(empty? xs)
|
|
|
|
|
[(str (sql-kw k) " ()")]
|
|
|
|
|
(sequential? first-xs)
|
|
|
|
|
;; [[1 2 3] [4 5 6]]
|
|
|
|
|
(let [n-1 (map count (filter sequential? xs))
|
|
|
|
|
;; issue #291: ensure all value sequences are the same length
|
|
|
|
|
xs' (if (apply = n-1)
|
|
|
|
|
xs
|
|
|
|
|
(let [n-n (when (seq n-1) (apply max n-1))]
|
|
|
|
|
(map (fn [x]
|
|
|
|
|
(if (sequential? x)
|
|
|
|
|
(take n-n (concat x (repeat nil)))
|
|
|
|
|
x))
|
|
|
|
|
xs)))
|
|
|
|
|
[sqls params]
|
|
|
|
|
(reduce (fn [[sql params] [sqls' params']]
|
|
|
|
|
[(conj sql
|
|
|
|
|
(if (sequential? sqls')
|
|
|
|
|
(str "(" (str/join ", " sqls') ")")
|
|
|
|
|
sqls'))
|
|
|
|
|
(into params params')])
|
|
|
|
|
[[] []]
|
|
|
|
|
(map #(if (sequential? %)
|
|
|
|
|
(format-expr-list %)
|
|
|
|
|
[(sql-kw %)])
|
|
|
|
|
xs'))]
|
|
|
|
|
(into [(str (sql-kw k) " " (str/join ", " sqls))] params))
|
|
|
|
|
|
|
|
|
|
(map? first-xs)
|
|
|
|
|
;; [{:a 1 :b 2 :c 3}]
|
|
|
|
|
(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
|
2022-09-03 05:35:17 +00:00
|
|
|
cols-n (into #{} (mapcat keys) (filter map? xs))
|
2022-09-02 05:54:00 +00:00
|
|
|
cols (if (= (set cols-1) cols-n) cols-1 cols-n)
|
|
|
|
|
[sqls params]
|
|
|
|
|
(reduce (fn [[sql params] [sqls' params']]
|
2022-09-03 05:35:17 +00:00
|
|
|
[(conj sql
|
|
|
|
|
(if (sequential? sqls')
|
|
|
|
|
(str "(" (str/join ", " sqls') ")")
|
|
|
|
|
sqls'))
|
2022-09-02 05:54:00 +00:00
|
|
|
(if params' (into params params') params')])
|
|
|
|
|
[[] []]
|
|
|
|
|
(map (fn [m]
|
2022-09-03 05:35:17 +00:00
|
|
|
(if (map? m)
|
|
|
|
|
(format-expr-list
|
|
|
|
|
(map #(get m
|
|
|
|
|
%
|
|
|
|
|
;; issue #366: use NULL or DEFAULT
|
|
|
|
|
;; for missing column values:
|
|
|
|
|
(if (contains? *values-default-columns* %)
|
|
|
|
|
[:default]
|
|
|
|
|
nil))
|
|
|
|
|
cols))
|
|
|
|
|
[(sql-kw m)]))
|
2022-09-02 05:54:00 +00:00
|
|
|
xs))]
|
|
|
|
|
(into [(str "("
|
|
|
|
|
(str/join ", "
|
|
|
|
|
(map #(format-entity % {:drop-ns true}) cols))
|
|
|
|
|
") "
|
|
|
|
|
(sql-kw k)
|
|
|
|
|
" "
|
|
|
|
|
(str/join ", " sqls))]
|
|
|
|
|
params))
|
2020-09-24 01:15:20 +00:00
|
|
|
|
2022-09-02 05:54:00 +00:00
|
|
|
:else
|
2022-11-17 16:31:27 +00:00
|
|
|
(throw (ex-info ":values expects a sequence of rows (maps) or column values (sequences)"
|
2022-09-02 05:54:00 +00:00
|
|
|
{: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)]
|
2021-08-13 01:26:39 +00:00
|
|
|
[(conj sql (str (format-entity v {:drop-ns (not (mysql?))}) " = " sql'))
|
2020-09-24 01:15:20 +00:00
|
|
|
(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-04-23 02:16:30 +00:00
|
|
|
(if (sequential? x)
|
2023-06-20 19:11:00 +00:00
|
|
|
(let [exprs (take-while (complement map?) x)
|
|
|
|
|
n (count exprs)
|
2021-04-23 02:16:30 +00:00
|
|
|
[clause & more] (drop n x)
|
2023-06-20 19:11:00 +00:00
|
|
|
_ (when (seq more)
|
2021-04-23 02:16:30 +00:00
|
|
|
(throw (ex-info "unsupported :on-conflict format"
|
|
|
|
|
{:clause x})))
|
2023-06-20 19:11:00 +00:00
|
|
|
[sqls expr-params]
|
|
|
|
|
(when (seq exprs)
|
2023-06-20 20:03:19 +00:00
|
|
|
(format-expr-list (map (fn [e] (if (sequential? e) [:nest e] e)) exprs)))
|
2023-06-20 19:11:00 +00:00
|
|
|
[sql & clause-params]
|
|
|
|
|
(when clause
|
|
|
|
|
(format-dsl clause))]
|
|
|
|
|
(-> [(str (sql-kw k)
|
|
|
|
|
(when (pos? n)
|
|
|
|
|
(str " (" (str/join ", " sqls) ")"))
|
|
|
|
|
(when sql
|
|
|
|
|
(str " " sql)))]
|
|
|
|
|
(into expr-params)
|
|
|
|
|
(into clause-params)))
|
2021-04-23 02:16:30 +00:00
|
|
|
(format-on-conflict k [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))
|
2023-03-13 00:40:53 +00:00
|
|
|
(let [fields (or (:fields x) ('fields x))
|
|
|
|
|
[sets & set-params]
|
|
|
|
|
(if (map? fields)
|
|
|
|
|
(format-set-exprs k fields)
|
2023-03-13 21:24:11 +00:00
|
|
|
[(str (sql-kw k) " "
|
|
|
|
|
(str/join ", "
|
|
|
|
|
(map (fn [e]
|
|
|
|
|
(let [e (format-entity e {:drop-ns true})]
|
|
|
|
|
(str e " = EXCLUDED." e)))
|
|
|
|
|
fields)))])
|
2021-02-14 03:08:40 +00:00
|
|
|
where (or (:where x) ('where x))
|
|
|
|
|
[sql & params] (when where (format-dsl {:where where}))]
|
2023-03-13 21:24:11 +00:00
|
|
|
(-> [(str sets (when sql (str " " sql)))]
|
2023-03-13 00:40:53 +00:00
|
|
|
(into set-params)
|
|
|
|
|
(into params)))
|
2021-02-14 03:08:40 +00:00
|
|
|
(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
|
|
|
|
2022-11-06 00:15:32 +00:00
|
|
|
(def ^:private special-ddl-keywords
|
|
|
|
|
"If these are found in DDL, they should map to the given
|
|
|
|
|
SQL string instead of what sql-kw would do."
|
|
|
|
|
{:auto-increment "AUTO_INCREMENT"})
|
|
|
|
|
|
|
|
|
|
(defn- sql-kw-ddl
|
|
|
|
|
"Handle SQL keywords in DDL (allowing for special/exceptions)."
|
|
|
|
|
[id]
|
|
|
|
|
(or (get special-ddl-keywords (sym->kw id))
|
|
|
|
|
(sql-kw id)))
|
|
|
|
|
|
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)
|
2022-11-06 00:15:32 +00:00
|
|
|
(sql-kw-ddl e)
|
2021-03-12 19:43:21 +00:00
|
|
|
(format-simple-expr e context)))
|
|
|
|
|
opt))
|
2022-11-06 00:15:32 +00:00
|
|
|
(ident? opt)
|
|
|
|
|
(sql-kw-ddl opt)
|
2021-03-12 19:43:21 +00:00
|
|
|
:else
|
2022-11-06 00:15:32 +00:00
|
|
|
(throw (ex-info "expected symbol or keyword"
|
|
|
|
|
{:unexpected opt})))))
|
2021-03-12 19:43:21 +00:00
|
|
|
|
2022-11-05 06:40:30 +00:00
|
|
|
(defn- destructure-ddl-item [table context]
|
2021-03-12 19:43:21 +00:00
|
|
|
(let [params
|
2021-03-08 06:13:25 +00:00
|
|
|
(if (sequential? table)
|
|
|
|
|
table
|
|
|
|
|
[table])
|
2021-09-09 17:41:37 +00:00
|
|
|
tab? #(or (ident? %) (string? %))
|
|
|
|
|
coll (take-while tab? params)
|
2022-11-05 06:40:30 +00:00
|
|
|
opts (filter some? (drop-while tab? params))
|
2021-03-12 19:43:21 +00:00
|
|
|
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))
|
2022-11-06 00:15:32 +00:00
|
|
|
(when table (format-entity table))
|
2021-03-12 19:43:21 +00:00
|
|
|
(when ine (sql-kw ine))]
|
2022-11-05 06:40:30 +00:00
|
|
|
(when opts
|
|
|
|
|
(format-ddl-options opts context)))))
|
|
|
|
|
|
2023-04-10 21:09:28 +00:00
|
|
|
(defn- format-truncate [_ xs]
|
2023-02-26 12:20:09 +00:00
|
|
|
(let [[table & options] (ensure-sequential xs)
|
2022-11-05 06:40:30 +00:00
|
|
|
[pre table ine options] (destructure-ddl-item [table options] "truncate")]
|
|
|
|
|
(when (seq pre) (throw (ex-info "TRUNCATE syntax error" {:unexpected pre})))
|
|
|
|
|
(when (seq ine) (throw (ex-info "TRUNCATE syntax error" {:unexpected ine})))
|
2023-04-10 21:09:28 +00:00
|
|
|
[(str/join " " (cond-> ["TRUNCATE TABLE" table]
|
2022-11-05 06:40:30 +00:00
|
|
|
(seq options)
|
|
|
|
|
(conj options)))]))
|
|
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
(destructure-ddl-item [:foo [:abc [:continue :wibble] :identity]] "test")
|
|
|
|
|
(destructure-ddl-item [:foo] "test")
|
2022-11-06 00:15:32 +00:00
|
|
|
(destructure-ddl-item [:id [:int :unsigned :auto-increment]] "test")
|
|
|
|
|
(destructure-ddl-item [[[:foreign-key :bar]] :quux [[:wibble :wobble]]] "test")
|
|
|
|
|
(format-truncate :truncate [:foo])
|
|
|
|
|
)
|
2021-03-12 19:43:21 +00:00
|
|
|
|
|
|
|
|
(defn- format-create [q k item as]
|
|
|
|
|
(let [[pre entity ine & more]
|
2022-11-05 06:40:30 +00:00
|
|
|
(destructure-ddl-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]
|
2022-11-06 00:15:32 +00:00
|
|
|
(let [[col & options] (if (ident? (first xs)) xs (cons nil xs))
|
|
|
|
|
[pre col ine & options]
|
|
|
|
|
(destructure-ddl-item [col options] "column operation")]
|
|
|
|
|
(when (seq pre) (throw (ex-info "column syntax error" {:unexpected pre})))
|
|
|
|
|
(when (seq ine) (throw (ex-info "column syntax error" {:unexpected ine})))
|
|
|
|
|
(str/join " " (filter seq (cons col options)))))
|
|
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
(destructure-ddl-item [:foo [:abc [:continue :wibble] :identity]] "test")
|
|
|
|
|
(destructure-ddl-item [:foo] "test")
|
|
|
|
|
(destructure-ddl-item [:id [:int :unsigned :auto-increment]] "test")
|
|
|
|
|
(format-single-column [:id :int :unsigned :auto-increment])
|
|
|
|
|
(format-single-column [[:constraint :code_title] [:primary-key :code :title]])
|
|
|
|
|
(destructure-ddl-item [[[:foreign-key :bar]] :quux [[:wibble :wobble]]] "test")
|
|
|
|
|
|
|
|
|
|
(format-truncate :truncate [:foo])
|
|
|
|
|
|
|
|
|
|
(destructure-ddl-item [:address [:text]] "test")
|
|
|
|
|
(format-single-column [:address :text])
|
|
|
|
|
(format-single-column [:did :uuid [:default [:gen_random_uuid]]])
|
|
|
|
|
)
|
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
|
|
|
|
2022-09-13 01:17:26 +00:00
|
|
|
(defn- format-add-single-item [k spec]
|
2021-11-27 23:27:12 +00:00
|
|
|
(if (contains? #{:if-not-exists 'if-not-exists} (last spec))
|
2022-09-13 01:17:26 +00:00
|
|
|
(str (sql-kw k) " " (sql-kw :if-not-exists) " " (format-single-column (butlast spec)))
|
|
|
|
|
(str (sql-kw k) " " (format-single-column spec))))
|
|
|
|
|
|
|
|
|
|
(defn- format-add-item [k spec]
|
|
|
|
|
(let [items (if (and (sequential? spec) (sequential? (first spec))) spec [spec])]
|
|
|
|
|
[(str/join ", " (for [item items] (format-add-single-item k item)))]))
|
2021-02-13 01:08:37 +00:00
|
|
|
|
2022-11-06 00:15:32 +00:00
|
|
|
(comment
|
|
|
|
|
(format-add-item :add-column [:address :text])
|
|
|
|
|
(format-add-single-item :add-column [:address :text])
|
|
|
|
|
(format-single-column [:address :text])
|
|
|
|
|
)
|
|
|
|
|
|
2021-02-13 01:08:37 +00:00
|
|
|
(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
|
|
|
|
2021-03-15 22:26:22 +00:00
|
|
|
(defn- raw-render [s]
|
2021-03-15 21:48:28 +00:00
|
|
|
(if (sequential? s)
|
|
|
|
|
(let [[sqls params]
|
|
|
|
|
(reduce (fn [[sqls params] s]
|
|
|
|
|
(if (sequential? s)
|
|
|
|
|
(let [[sql & params'] (format-expr s)]
|
|
|
|
|
[(conj sqls sql)
|
|
|
|
|
(into params params')])
|
|
|
|
|
[(conj sqls s) params]))
|
|
|
|
|
[[] []]
|
|
|
|
|
s)]
|
|
|
|
|
(into [(str/join sqls)] params))
|
|
|
|
|
[s]))
|
|
|
|
|
|
2022-09-12 20:51:54 +00:00
|
|
|
(defn- destructure-drop-columns [tables]
|
|
|
|
|
(let [params
|
|
|
|
|
(if (sequential? tables)
|
|
|
|
|
tables
|
|
|
|
|
[tables])
|
|
|
|
|
_ (when-not (every? ident? params)
|
|
|
|
|
(throw (ex-info "DROP COLUMNS expects just column names"
|
|
|
|
|
{:tables tables})))]
|
|
|
|
|
(loop [if-exists false coll params sqls []]
|
|
|
|
|
(if (seq coll)
|
|
|
|
|
(if (#{:if-exists 'if-exists} (first coll))
|
|
|
|
|
(recur true (rest coll) sqls)
|
|
|
|
|
(recur false (rest coll)
|
|
|
|
|
(conj sqls (cond->> (format-entity (first coll))
|
|
|
|
|
if-exists
|
|
|
|
|
(str (sql-kw :if-exists) " ")))))
|
|
|
|
|
(if if-exists
|
|
|
|
|
(throw (ex-info (str "DROP COLUMNS: missing column name after IF EXISTS")
|
|
|
|
|
{:tables tables}))
|
|
|
|
|
sqls)))))
|
|
|
|
|
|
|
|
|
|
(defn- format-drop-columns
|
|
|
|
|
[k params]
|
|
|
|
|
(let [tables (destructure-drop-columns params)]
|
|
|
|
|
[(str/join ", " (mapv #(str (sql-kw k) " " %) tables))]))
|
|
|
|
|
|
2022-08-07 04:11:08 +00:00
|
|
|
(defn- check-where
|
|
|
|
|
"Given a formatter function, performs a pre-flight check that there is
|
|
|
|
|
a non-empty where clause if at least basic checking is enabled."
|
|
|
|
|
[formatter]
|
|
|
|
|
(fn [k xs]
|
|
|
|
|
(when-not (= :none *checking*)
|
|
|
|
|
(when-not (seq (:where *dsl*))
|
|
|
|
|
(throw (ex-info (str (sql-kw k) " without a non-empty WHERE clause is dangerous")
|
|
|
|
|
{:clause k :where (:where *dsl*)}))))
|
|
|
|
|
(formatter k xs)))
|
|
|
|
|
|
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
|
2022-09-12 20:51:54 +00:00
|
|
|
:drop-column #'format-drop-columns
|
2022-07-29 22:40:45 +00:00
|
|
|
:alter-column (fn [k spec]
|
|
|
|
|
(format-add-item
|
|
|
|
|
(if (mysql?) :modify-column k)
|
|
|
|
|
spec))
|
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))
|
2023-08-23 19:45:19 +00:00
|
|
|
;; postgresql lacks if not exists:
|
|
|
|
|
:create-or-replace-view (fn [_ x] (format-create :create :or-replace-view x :as))
|
2021-03-12 19:43:21 +00:00
|
|
|
: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-03-15 22:26:22 +00:00
|
|
|
:raw (fn [_ x] (raw-render x))
|
2021-11-27 06:40:51 +00:00
|
|
|
:nest (fn [_ x]
|
2021-12-22 06:54:02 +00:00
|
|
|
(let [[sql & params] (format-dsl x {:nested true})]
|
|
|
|
|
(into [sql] params)))
|
2022-08-16 22:53:34 +00:00
|
|
|
:with (let [as-fn
|
|
|
|
|
(fn [[_ _ materialization]]
|
|
|
|
|
(condp = materialization
|
|
|
|
|
:materialized "AS MATERIALIZED"
|
|
|
|
|
:not-materialized "AS NOT MATERIALIZED"
|
|
|
|
|
"AS"))]
|
|
|
|
|
(fn [k xs] (format-with k xs as-fn)))
|
|
|
|
|
:with-recursive (let [as-fn (constantly "AS")]
|
|
|
|
|
(fn [k xs] (format-with k xs as-fn)))
|
2020-10-10 06:05:05 +00:00
|
|
|
: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
|
2022-03-26 23:58:28 +00:00
|
|
|
:table #'format-selector
|
2020-10-10 06:05:05 +00:00
|
|
|
: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
|
2021-03-13 21:42:08 +00:00
|
|
|
:into #'format-select-into
|
|
|
|
|
:bulk-collect-into #'format-select-into
|
2020-10-10 06:05:05 +00:00
|
|
|
:insert-into #'format-insert
|
2023-01-14 23:27:42 +00:00
|
|
|
:replace-into #'format-insert
|
2022-08-07 04:11:08 +00:00
|
|
|
:update (check-where #'format-selector)
|
|
|
|
|
:delete (check-where #'format-selects)
|
|
|
|
|
:delete-from (check-where #'format-selector)
|
2022-11-05 06:40:30 +00:00
|
|
|
:truncate #'format-truncate
|
2020-10-10 06:05:05 +00:00
|
|
|
: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
|
2021-07-18 00:57:17 +00:00
|
|
|
:offset (fn [_ x]
|
|
|
|
|
(if (or (contains-clause? :fetch) (sql-server?))
|
|
|
|
|
(let [[sql & params] (format-on-expr :offset x)
|
|
|
|
|
rows (if (and (number? x) (== 1 x)) :row :rows)]
|
|
|
|
|
(into [(str sql " " (sql-kw rows))] params))
|
|
|
|
|
;; format in the old style:
|
|
|
|
|
(format-on-expr :offset x)))
|
2021-07-17 20:32:43 +00:00
|
|
|
:fetch (fn [_ x]
|
2021-07-18 00:57:17 +00:00
|
|
|
(let [which (if (contains-clause? :offset) :fetch-next :fetch-first)
|
|
|
|
|
rows (if (and (number? x) (== 1 x)) :row-only :rows-only)
|
|
|
|
|
[sql & params] (format-on-expr which x)]
|
|
|
|
|
(into [(str sql " " (sql-kw rows))] params)))
|
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]}]]
|
2021-07-18 00:57:17 +00:00
|
|
|
(binding [*dsl* statement-map]
|
|
|
|
|
(let [[sqls params leftover]
|
|
|
|
|
(reduce (fn [[sql params leftover] k]
|
|
|
|
|
(if-some [xs (if-some [xs (k leftover)]
|
|
|
|
|
xs
|
|
|
|
|
(let [s (kw->sym k)]
|
|
|
|
|
(get leftover s)))]
|
|
|
|
|
(let [formatter (k @clause-format)
|
|
|
|
|
[sql' & params'] (formatter k xs)]
|
|
|
|
|
[(conj sql sql')
|
|
|
|
|
(if params' (into params params') params)
|
|
|
|
|
(dissoc leftover k (kw->sym k))])
|
|
|
|
|
[sql params leftover]))
|
|
|
|
|
[[] [] statement-map]
|
|
|
|
|
*clause-order*)]
|
|
|
|
|
(if (seq leftover)
|
|
|
|
|
(throw (ex-info (str "These SQL clauses are unknown or have nil values: "
|
|
|
|
|
(str/join ", " (keys leftover)))
|
|
|
|
|
leftover))
|
|
|
|
|
(into [(cond-> (str/join (if pretty "\n" " ") (filter seq sqls))
|
|
|
|
|
pretty
|
|
|
|
|
(as-> s (str "\n" s "\n"))
|
|
|
|
|
(and nested (not aliased))
|
|
|
|
|
(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."
|
2021-12-23 21:32:47 +00:00
|
|
|
{:not= :<>
|
2020-09-21 01:48:07 +00:00
|
|
|
:!= :<>
|
|
|
|
|
:regex :regexp})
|
|
|
|
|
|
|
|
|
|
(def ^:private infix-ops
|
2021-03-13 21:13:35 +00:00
|
|
|
(-> #{"mod" "and" "or" "xor" "<>" "<=" ">=" "||" "<->"
|
2023-02-16 06:10:45 +00:00
|
|
|
"like" "not-like" "regexp" "~" "&&"
|
2020-10-14 18:00:39 +00:00
|
|
|
"ilike" "not-ilike" "similar-to" "not-similar-to"
|
2023-04-15 18:37:20 +00:00
|
|
|
"is" "is-not" "not=" "!=" "regex"
|
|
|
|
|
"with-ordinality"}
|
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}))
|
2023-02-16 06:10:45 +00:00
|
|
|
(def ^:private op-can-be-unary
|
|
|
|
|
"The operators that can be unary. This is a fixed set until someone
|
|
|
|
|
identifies any new ones."
|
|
|
|
|
(atom (into #{} (map (comp keyword str) "+-~"))))
|
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-04-12 22:04:17 +00:00
|
|
|
(let [[sql-x & params-x] (format-expr x {:nested true})
|
|
|
|
|
[sql-y & params-y] (format-expr y {:nested true})
|
2022-12-17 07:56:53 +00:00
|
|
|
[v1 :as values] (map #(unwrap % {}) params-y)]
|
2022-04-23 23:03:21 +00:00
|
|
|
;; #396: prevent caching IN () when named parameter is used:
|
|
|
|
|
(when (and (meta (first params-y))
|
|
|
|
|
(::wrapper (meta (first params-y)))
|
|
|
|
|
*caching*)
|
2022-04-23 22:54:24 +00:00
|
|
|
(throw (ex-info "SQL that includes IN () expressions cannot be cached" {})))
|
2021-04-23 05:13:32 +00:00
|
|
|
(when-not (= :none *checking*)
|
2022-12-17 07:56:53 +00:00
|
|
|
(when (or (and (sequential? y) (empty? y))
|
|
|
|
|
(and (sequential? v1) (empty? v1)))
|
2021-04-23 05:13:32 +00:00
|
|
|
(throw (ex-info "IN () empty collection is illegal"
|
|
|
|
|
{:clause [in x y]})))
|
|
|
|
|
(when (and (= :strict *checking*)
|
2022-12-17 07:56:53 +00:00
|
|
|
(or (and (sequential? y) (some nil? y))
|
|
|
|
|
(and (sequential? v1) (some nil? v1))))
|
2021-04-23 05:13:32 +00:00
|
|
|
(throw (ex-info "IN (NULL) does not match"
|
|
|
|
|
{:clause [in x y]}))))
|
2022-12-17 07:56:53 +00:00
|
|
|
(cond (and (not *numbered*)
|
|
|
|
|
(= "?" sql-y)
|
|
|
|
|
(= 1 (count params-y))
|
|
|
|
|
(coll? v1))
|
|
|
|
|
(let [sql (str "(" (str/join ", " (repeat (count v1) "?")) ")")]
|
|
|
|
|
(-> [(str sql-x " " (sql-kw in) " " sql)]
|
|
|
|
|
(into params-x)
|
|
|
|
|
(into v1)))
|
|
|
|
|
(and *numbered*
|
|
|
|
|
(= (str "$" (count @*numbered*)) sql-y)
|
|
|
|
|
(= 1 (count params-y))
|
|
|
|
|
(coll? v1))
|
|
|
|
|
(let [vs (for [v v1] (->numbered v))
|
|
|
|
|
sql (str "(" (str/join ", " (map first vs)) ")")]
|
|
|
|
|
(-> [(str sql-x " " (sql-kw in) " " sql)]
|
|
|
|
|
(into params-x)
|
|
|
|
|
(conj nil)
|
|
|
|
|
(into (map second vs))))
|
|
|
|
|
:else
|
|
|
|
|
(-> [(str sql-x " " (sql-kw in) " " sql-y)]
|
|
|
|
|
(into params-x)
|
|
|
|
|
(into (if *numbered* values params-y))))))
|
2021-01-31 13:08:21 +00:00
|
|
|
|
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
|
|
|
|
2021-04-11 21:32:48 +00:00
|
|
|
(defn- expr-clause-pairs
|
|
|
|
|
"For FILTER and WITHIN GROUP that have an expression
|
|
|
|
|
followed by a SQL clause."
|
|
|
|
|
[k pairs]
|
|
|
|
|
(let [[sqls params]
|
|
|
|
|
(reduce (fn [[sqls params] [e c]]
|
|
|
|
|
(let [[sql-e & params-e] (format-expr e)
|
|
|
|
|
[sql-c & params-c] (format-dsl c {:nested true})]
|
|
|
|
|
[(conj sqls (str sql-e " " (sql-kw k) " " sql-c))
|
|
|
|
|
(-> params (into params-e) (into params-c))]))
|
|
|
|
|
[[] []]
|
|
|
|
|
(partition 2 pairs))]
|
|
|
|
|
(into [(str/join ", " sqls)] params)))
|
|
|
|
|
|
2022-01-30 01:19:17 +00:00
|
|
|
(defn- case-clauses
|
|
|
|
|
"For both :case and :case-expr."
|
|
|
|
|
[k clauses]
|
|
|
|
|
(let [case-expr? (= :case-expr k)
|
|
|
|
|
[sqlx & paramsx] (when case-expr? (format-expr (first clauses)))
|
|
|
|
|
[sqls params]
|
|
|
|
|
(reduce (fn [[sqls params] [condition value]]
|
|
|
|
|
(let [[sqlc & paramsc] (when-not (= :else condition)
|
|
|
|
|
(format-expr condition))
|
|
|
|
|
[sqlv & paramsv] (format-expr value)]
|
|
|
|
|
[(if (or (= :else condition)
|
|
|
|
|
(= 'else condition))
|
|
|
|
|
(conj sqls (sql-kw :else) sqlv)
|
|
|
|
|
(conj sqls (sql-kw :when) sqlc (sql-kw :then) sqlv))
|
|
|
|
|
(-> params (into paramsc) (into paramsv))]))
|
|
|
|
|
[[] []]
|
|
|
|
|
(partition 2 (if case-expr? (rest clauses) clauses)))]
|
|
|
|
|
(-> [(str (sql-kw :case) " "
|
|
|
|
|
(when case-expr?
|
|
|
|
|
(str sqlx " "))
|
|
|
|
|
(str/join " " sqls)
|
|
|
|
|
" " (sql-kw :end))]
|
|
|
|
|
(into paramsx)
|
|
|
|
|
(into params))))
|
|
|
|
|
|
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
|
2023-03-03 23:26:22 +00:00
|
|
|
:. (fn [_ [expr col subcol]]
|
2023-03-02 00:55:54 +00:00
|
|
|
(let [[sql & params] (format-expr expr)]
|
2023-03-03 23:26:22 +00:00
|
|
|
(into [(str sql "." (format-entity col)
|
|
|
|
|
(when subcol
|
|
|
|
|
(str "." (format-entity subcol))))]
|
|
|
|
|
params)))
|
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)])
|
2023-08-12 02:06:30 +00:00
|
|
|
;; #497 used to force rendering as an alias:
|
|
|
|
|
:alias (fn [_ [e]] [(format-entity e {:aliased true})])
|
2022-01-07 20:53:30 +00:00
|
|
|
;; bigquery column types:
|
|
|
|
|
:bigquery/array (fn [_ spec]
|
|
|
|
|
[(str "ARRAY<"
|
2022-11-06 00:15:32 +00:00
|
|
|
(str/join " " (map #(sql-kw %) spec))
|
2022-01-07 20:53:30 +00:00
|
|
|
">")])
|
|
|
|
|
:bigquery/struct (fn [_ spec]
|
|
|
|
|
[(str "STRUCT<"
|
|
|
|
|
(str/join ", " (map format-single-column spec))
|
|
|
|
|
">")])
|
2021-02-13 05:50:22 +00:00
|
|
|
:array
|
2023-02-26 10:59:53 +00:00
|
|
|
(fn [_ [arr type]]
|
2022-05-20 17:34:10 +00:00
|
|
|
;; allow for (unwrap arr) here?
|
2023-02-26 10:59:53 +00:00
|
|
|
(let [[sqls params] (format-expr-list arr)
|
|
|
|
|
type-str (when type (str "::" (sql-kw type) "[]"))]
|
|
|
|
|
(into [(str "ARRAY[" (str/join ", " sqls) "]" type-str)] params)))
|
2020-09-29 21:40:03 +00:00
|
|
|
: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))))
|
2022-01-30 01:19:17 +00:00
|
|
|
:case #'case-clauses
|
|
|
|
|
:case-expr #'case-clauses
|
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)
|
2022-11-19 21:51:43 +00:00
|
|
|
[sql' & params'] (if (ident? type)
|
|
|
|
|
[(sql-kw type)]
|
|
|
|
|
(format-expr type))]
|
2020-10-02 20:24:44 +00:00
|
|
|
(-> [(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-06-13 01:15:59 +00:00
|
|
|
:distinct
|
|
|
|
|
(fn [_ [x]]
|
|
|
|
|
(let [[sql & params] (format-expr x {:nested true})]
|
|
|
|
|
(into [(str "DISTINCT " sql)] 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))))
|
2021-04-11 21:32:48 +00:00
|
|
|
:filter expr-clause-pairs
|
2020-09-29 21:40:03 +00:00
|
|
|
:inline
|
|
|
|
|
(fn [_ [x]]
|
2021-09-26 00:06:48 +00:00
|
|
|
(binding [*inline* true]
|
|
|
|
|
(format-expr x)))
|
2020-09-29 21:40:03 +00:00
|
|
|
:interval
|
|
|
|
|
(fn [_ [n units]]
|
2023-04-14 05:46:37 +00:00
|
|
|
(if units
|
|
|
|
|
(let [[sql & params] (format-expr n)]
|
|
|
|
|
(into [(str "INTERVAL " sql " " (sql-kw units))] params))
|
|
|
|
|
(binding [*inline* true]
|
|
|
|
|
(let [[sql & params] (format-expr n)]
|
|
|
|
|
(into [(str "INTERVAL " sql)] params)))))
|
2023-04-07 05:49:13 +00:00
|
|
|
:join
|
|
|
|
|
(fn [_ [e & js]]
|
|
|
|
|
(let [[sqls params] (reduce-sql (cons (format-expr e)
|
|
|
|
|
(map format-dsl js)))]
|
|
|
|
|
(into [(str "(" (str/join " " sqls) ")")] params)))
|
2021-03-13 21:13:35 +00:00
|
|
|
:lateral
|
|
|
|
|
(fn [_ [clause-or-expr]]
|
|
|
|
|
(if (map? clause-or-expr)
|
|
|
|
|
(let [[sql & params] (format-dsl clause-or-expr)]
|
|
|
|
|
(into [(str "LATERAL (" sql ")")] params))
|
|
|
|
|
(let [[sql & params] (format-expr clause-or-expr)]
|
|
|
|
|
(into [(str "LATERAL " sql)] params))))
|
2020-10-02 20:43:44 +00:00
|
|
|
:lift
|
|
|
|
|
(fn [_ [x]]
|
2022-12-17 07:11:45 +00:00
|
|
|
(cond *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)]
|
|
|
|
|
*numbered*
|
|
|
|
|
(->numbered x)
|
|
|
|
|
:else
|
|
|
|
|
["?" (with-meta (constantly x)
|
|
|
|
|
{::wrapper (fn [fx _] (fx))})]))
|
2020-10-02 20:24:44 +00:00
|
|
|
:nest
|
|
|
|
|
(fn [_ [x]]
|
2021-11-27 06:40:51 +00:00
|
|
|
(let [[sql & params] (format-expr x)]
|
|
|
|
|
(into [(str "(" sql ")")] params)))
|
2020-09-29 21:40:03 +00:00
|
|
|
:not
|
|
|
|
|
(fn [_ [x]]
|
2021-11-27 06:30:36 +00:00
|
|
|
(let [[sql & params] (format-expr x {:nested true})]
|
2020-10-02 06:30:18 +00:00
|
|
|
(into [(str "NOT " sql)] params)))
|
2021-04-11 21:32:48 +00:00
|
|
|
:order-by
|
2023-02-26 10:22:12 +00:00
|
|
|
(fn [k [e & qs]]
|
2021-04-11 21:32:48 +00:00
|
|
|
(let [[sql-e & params-e] (format-expr e)
|
2023-02-26 10:22:12 +00:00
|
|
|
[sql-q & params-q] (format-dsl {k qs})]
|
2021-04-11 21:32:48 +00:00
|
|
|
(-> [(str sql-e " " sql-q)]
|
|
|
|
|
(into params-e)
|
|
|
|
|
(into params-q))))
|
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]]
|
2022-12-17 07:11:45 +00:00
|
|
|
(cond *inline*
|
|
|
|
|
[(sqlize-value (param-value k))]
|
|
|
|
|
*numbered*
|
|
|
|
|
(->numbered-param k)
|
|
|
|
|
:else
|
|
|
|
|
["?" (->param k)]))
|
2020-10-02 06:30:18 +00:00
|
|
|
:raw
|
2023-03-04 22:21:40 +00:00
|
|
|
(fn [_ [& xs]]
|
|
|
|
|
;; #476 : preserve existing single-argument behavior...
|
|
|
|
|
(if (= 1 (count xs))
|
|
|
|
|
(raw-render (first xs))
|
|
|
|
|
;; ...but allow for multiple arguments now:
|
|
|
|
|
(raw-render xs)))
|
2021-04-11 21:32:48 +00:00
|
|
|
:within-group expr-clause-pairs}))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2023-03-01 01:38:13 +00:00
|
|
|
(defn- format-equality-expr [op' op expr nested]
|
|
|
|
|
(let [[_ a b & y] expr
|
|
|
|
|
_ (when (seq y)
|
|
|
|
|
(throw (ex-info (str "only binary "
|
|
|
|
|
op'
|
|
|
|
|
" is supported")
|
|
|
|
|
{:expr expr})))
|
|
|
|
|
[s1 & p1] (format-expr a {:nested true})
|
|
|
|
|
[s2 & p2] (format-expr b {:nested true})]
|
|
|
|
|
(-> (if (or (nil? a) (nil? b))
|
|
|
|
|
(str (if (nil? a)
|
|
|
|
|
(if (nil? b) "NULL" s2)
|
|
|
|
|
s1)
|
|
|
|
|
(if (= := op) " IS NULL" " IS NOT NULL"))
|
|
|
|
|
(str s1 " " (sql-kw op) " " s2))
|
|
|
|
|
(cond-> nested
|
|
|
|
|
(as-> s (str "(" s ")")))
|
|
|
|
|
(vector)
|
|
|
|
|
(into p1)
|
|
|
|
|
(into p2))))
|
|
|
|
|
|
|
|
|
|
(defn- format-infix-expr [op' op expr nested]
|
|
|
|
|
(let [args (cond->> (rest expr)
|
|
|
|
|
(contains? @op-ignore-nil op)
|
|
|
|
|
(remove nil?))
|
|
|
|
|
args (cond (seq args)
|
|
|
|
|
args
|
|
|
|
|
(= :and op)
|
|
|
|
|
[true]
|
|
|
|
|
(= :or op)
|
|
|
|
|
[false]
|
|
|
|
|
:else ; args is empty and not a special case
|
|
|
|
|
[])
|
|
|
|
|
[sqls params]
|
|
|
|
|
(reduce-sql (map #(format-expr % {:nested true}) args))]
|
|
|
|
|
(when-not (pos? (count sqls))
|
|
|
|
|
(throw (ex-info (str "no operands found for " op')
|
|
|
|
|
{:expr expr})))
|
|
|
|
|
(into [(cond-> (str/join (str " " (sql-kw op) " ") sqls)
|
|
|
|
|
(and (contains? @op-can-be-unary op)
|
|
|
|
|
(= 1 (count sqls)))
|
|
|
|
|
(as-> s (str (sql-kw op) " " s))
|
|
|
|
|
nested
|
|
|
|
|
(as-> s (str "(" s ")")))]
|
|
|
|
|
params)))
|
|
|
|
|
|
|
|
|
|
(defn- format-fn-call-expr [op expr]
|
|
|
|
|
(let [args (rest expr)
|
|
|
|
|
[sqls params] (format-interspersed-expr-list args)]
|
|
|
|
|
(into [(str (sql-kw op)
|
|
|
|
|
(if (and (= 1 (count args))
|
|
|
|
|
(map? (first args))
|
|
|
|
|
(= 1 (count sqls)))
|
|
|
|
|
(str " " (first sqls))
|
|
|
|
|
(str "(" (str/join ", " sqls) ")")))]
|
|
|
|
|
params)))
|
|
|
|
|
|
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)
|
2023-02-11 21:35:55 +00:00
|
|
|
(let [op' (sym->kw (first expr))
|
|
|
|
|
op (get infix-aliases op' op')]
|
|
|
|
|
(if (keyword? op')
|
|
|
|
|
(cond (contains? @infix-ops op')
|
|
|
|
|
(if (contains? #{:= :<>} op)
|
2023-03-01 01:38:13 +00:00
|
|
|
(format-equality-expr op' op expr nested)
|
|
|
|
|
(format-infix-expr op' op expr nested))
|
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
|
2023-03-01 01:38:13 +00:00
|
|
|
(format-fn-call-expr op expr))
|
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
|
2022-12-17 07:11:45 +00:00
|
|
|
(cond *inline*
|
|
|
|
|
[(sqlize-value expr)]
|
|
|
|
|
*numbered*
|
|
|
|
|
(->numbered expr)
|
|
|
|
|
:else
|
|
|
|
|
["?" expr])))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2020-09-23 19:55:02 +00:00
|
|
|
(defn- check-dialect [dialect]
|
2022-05-01 05:03:36 +00:00
|
|
|
(when-not (contains? @dialects dialect)
|
2020-09-23 19:55:02 +00:00
|
|
|
(throw (ex-info (str "Invalid dialect: " dialect)
|
2022-05-01 05:03:36 +00:00
|
|
|
{:valid-dialects (vec (sort (keys @dialects)))})))
|
2020-09-23 19:55:02 +00:00
|
|
|
dialect)
|
|
|
|
|
|
2022-01-10 19:12:47 +00:00
|
|
|
(def through-opts
|
2022-01-20 21:02:17 +00:00
|
|
|
"If org.clojure/core.cache is available, resolves to a function that
|
|
|
|
|
calls core.cache.wrapped/lookup-or-miss, otherwise to a function that
|
|
|
|
|
throws an exception.
|
|
|
|
|
|
|
|
|
|
In ClojureScript, a resolves to a function that throws an exception
|
|
|
|
|
because core.cache relies on JVM machinery and is Clojure-only."
|
2022-01-10 12:41:09 +00:00
|
|
|
#?(:clj (try (require 'clojure.core.cache.wrapped)
|
2022-01-10 19:12:47 +00:00
|
|
|
(let [lookup-or-miss (deref (resolve 'clojure.core.cache.wrapped/lookup-or-miss))]
|
2022-01-10 12:41:09 +00:00
|
|
|
(fn [_opts cache data f]
|
2022-01-10 19:12:47 +00:00
|
|
|
(lookup-or-miss cache data f)))
|
2022-01-10 12:41:09 +00:00
|
|
|
(catch Throwable _
|
|
|
|
|
(fn [opts _cache _data _f]
|
|
|
|
|
(throw (ex-info "include core.cached on the classpath to use the :cache option" opts)))))
|
|
|
|
|
:cljs (fn [opts _cache _data _f]
|
|
|
|
|
(throw (ex-info "cached queries are not supported in ClojureScript" opts)))))
|
|
|
|
|
|
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,
|
2021-04-11 18:39:06 +00:00
|
|
|
and named parameters.
|
|
|
|
|
|
2023-02-02 06:20:14 +00:00
|
|
|
If the data DSL is a hash map, it will be treated as a SQL statement
|
|
|
|
|
and formatted via `format-dsl`, otherwise it will be treated as a SQL
|
|
|
|
|
expression and formatted via `format-expr`.
|
|
|
|
|
|
2021-04-11 18:39:06 +00:00
|
|
|
`format` accepts options as either a single hash map argument or
|
|
|
|
|
as named arguments (alternating keys and values). If you are using
|
|
|
|
|
Clojure 1.11 (or later) you can mix'n'match, providing some options
|
|
|
|
|
as named arguments followed by other options in a hash map."
|
2020-09-21 17:56:05 +00:00
|
|
|
([data] (format data {}))
|
|
|
|
|
([data opts]
|
2023-02-02 06:20:14 +00:00
|
|
|
(let [cache (:cache opts)
|
|
|
|
|
dialect? (contains? opts :dialect)
|
|
|
|
|
dialect (when dialect? (get @dialects (check-dialect (:dialect opts))))
|
|
|
|
|
numbered (if (contains? opts :numbered)
|
|
|
|
|
(:numbered opts)
|
|
|
|
|
@default-numbered)
|
|
|
|
|
formatter (if (map? data) #'format-dsl #'format-expr)]
|
2020-09-23 19:55:02 +00:00
|
|
|
(binding [*dialect* (if dialect? dialect @default-dialect)
|
2022-04-23 22:54:24 +00:00
|
|
|
*caching* cache
|
2021-04-23 05:13:32 +00:00
|
|
|
*checking* (if (contains? opts :checking)
|
|
|
|
|
(:checking opts)
|
2022-09-11 21:21:39 +00:00
|
|
|
@default-checking)
|
2020-09-23 19:55:02 +00:00
|
|
|
*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)
|
2022-09-11 21:21:39 +00:00
|
|
|
*inline* (if (contains? opts :inline)
|
|
|
|
|
(:inline opts)
|
|
|
|
|
@default-inline)
|
2022-12-18 00:02:02 +00:00
|
|
|
*numbered* (when numbered
|
2022-12-17 07:11:45 +00:00
|
|
|
(atom []))
|
2021-09-26 00:25:44 +00:00
|
|
|
*quoted* (cond (contains? opts :quoted)
|
|
|
|
|
(:quoted opts)
|
|
|
|
|
dialect?
|
|
|
|
|
true
|
|
|
|
|
:else
|
|
|
|
|
@default-quoted)
|
2022-09-11 21:21:39 +00:00
|
|
|
*quoted-snake* (if (contains? opts :quoted-snake)
|
|
|
|
|
(:quoted-snake opts)
|
|
|
|
|
@default-quoted-snake)
|
2021-10-04 05:32:05 +00:00
|
|
|
*params* (:params opts)
|
|
|
|
|
*values-default-columns* (:values-default-columns opts)]
|
2022-01-08 08:41:21 +00:00
|
|
|
(if cache
|
2023-02-02 06:20:14 +00:00
|
|
|
(->> (through-opts opts cache data (fn [_] (formatter data (dissoc opts :cache))))
|
2022-01-10 12:41:09 +00:00
|
|
|
(mapv #(unwrap % opts)))
|
2023-02-02 06:20:14 +00:00
|
|
|
(mapv #(unwrap % opts) (formatter data opts))))))
|
2021-04-10 00:04:48 +00:00
|
|
|
([data k v & {:as opts}] (format data (assoc opts k v))))
|
2020-09-21 01:48:07 +00:00
|
|
|
|
2023-06-23 23:05:41 +00:00
|
|
|
(defn formatf
|
|
|
|
|
"Experimental implementation of https://github.com/seancorfield/honeysql/issues/495
|
|
|
|
|
|
|
|
|
|
Currently, does not support options."
|
|
|
|
|
[dsl & params]
|
|
|
|
|
(format dsl {:params (zipmap (map (comp keyword str inc) (range)) params)}))
|
|
|
|
|
|
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
|
|
|
|
2021-09-26 00:25:44 +00:00
|
|
|
Can optionally accept `:quoted true` (or `:quoted false`) to set the
|
2022-09-11 22:09:12 +00:00
|
|
|
default global quoting strategy. Without `:quoted`, the default global
|
|
|
|
|
quoting strategy will be reset (only quoting unusual entity names).
|
|
|
|
|
|
|
|
|
|
Note that calling `set-options!` can override this default.
|
2021-09-26 00:25:44 +00:00
|
|
|
|
2020-09-24 18:13:08 +00:00
|
|
|
Dialects are always applied to the base order to create the current order."
|
2022-09-11 22:09:12 +00:00
|
|
|
[dialect & {:keys [quoted]}]
|
2022-05-01 05:03:36 +00:00
|
|
|
(reset! default-dialect (get @dialects (check-dialect dialect)))
|
2020-09-23 19:55:02 +00:00
|
|
|
(when-let [f (:clause-order-fn @default-dialect)]
|
2021-09-26 00:25:44 +00:00
|
|
|
(reset! current-clause-order (f @base-clause-order)))
|
2022-09-11 22:09:12 +00:00
|
|
|
(reset! default-quoted quoted))
|
2022-09-11 21:21:39 +00:00
|
|
|
|
|
|
|
|
(defn set-options!
|
|
|
|
|
"Set default values for any or all of the following options:
|
|
|
|
|
* :checking
|
|
|
|
|
* :inline
|
2022-12-18 00:02:02 +00:00
|
|
|
* :numbered
|
2022-09-11 21:21:39 +00:00
|
|
|
* :quoted
|
|
|
|
|
* :quoted-snake
|
|
|
|
|
Note that calling `set-dialect!` can override the default for `:quoted`."
|
|
|
|
|
[opts]
|
2022-12-18 00:02:02 +00:00
|
|
|
(let [unknowns (dissoc opts :checking :inline :numbered :quoted :quoted-snake)]
|
2022-09-11 21:21:39 +00:00
|
|
|
(when (seq unknowns)
|
|
|
|
|
(throw (ex-info (str (str/join ", " (keys unknowns))
|
|
|
|
|
" are not options that can be set globally.")
|
|
|
|
|
unknowns)))
|
|
|
|
|
(when (contains? opts :checking)
|
|
|
|
|
(reset! default-checking (:checking opts)))
|
|
|
|
|
(when (contains? opts :inline)
|
2022-12-18 00:02:02 +00:00
|
|
|
(reset! default-inline (:inline opts)))
|
|
|
|
|
(when (contains? opts :numbered)
|
|
|
|
|
(reset! default-numbered (:numbered opts)))
|
2022-09-11 21:21:39 +00:00
|
|
|
(when (contains? opts :quoted)
|
2022-12-18 00:02:02 +00:00
|
|
|
(reset! default-quoted (:quoted opts)))
|
2022-09-11 21:21:39 +00:00
|
|
|
(when (contains? opts :quoted-snake)
|
2022-12-18 00:02:02 +00:00
|
|
|
(reset! default-quoted-snake (:quoted-snake opts)))))
|
2020-09-23 19:55:02 +00:00
|
|
|
|
2021-04-09 23:31:16 +00:00
|
|
|
(defn clause-order
|
|
|
|
|
"Return the current order that known clauses will be applied when
|
|
|
|
|
formatting a data structure into SQL. This may be useful when you are
|
|
|
|
|
figuring out the `before` argument of `register-clause!` as well as
|
|
|
|
|
for debugging new clauses you have registered."
|
|
|
|
|
[]
|
|
|
|
|
@current-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
|
2021-04-09 23:31:16 +00:00
|
|
|
MySQL.
|
|
|
|
|
|
|
|
|
|
Use `clause-order` to see the full ordering of existing clauses."
|
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)]
|
2022-01-21 21:05:15 +00:00
|
|
|
(when-not (and f (or (fn? f) (and (var? f) (fn? (deref f)))) )
|
2021-02-02 20:25:26 +00:00
|
|
|
(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
|
|
|
|
2023-02-11 20:54:43 +00:00
|
|
|
(defn registered-clause?
|
|
|
|
|
"Return true if the clause is known to HoneySQL."
|
|
|
|
|
[clause]
|
|
|
|
|
(contains? @clause-format (sym->kw clause)))
|
|
|
|
|
|
2022-05-01 05:03:36 +00:00
|
|
|
(defn register-dialect!
|
|
|
|
|
"Register a new dialect. Accepts a dialect name (keyword) and a hash
|
|
|
|
|
map that must contain at least a `:quoted` key whose value is a unary
|
|
|
|
|
function that accepts a string and returns it quoted per the dialect.
|
|
|
|
|
|
|
|
|
|
It may also contain a `:clause-order-fn` key whose value is a unary
|
|
|
|
|
function that accepts a list of SQL clauses (keywords) in order of
|
|
|
|
|
precedence and returns an updated list of SQL clauses in order. It
|
|
|
|
|
may use `add-clause-before` to achieve this. Currently, the only
|
|
|
|
|
dialect that does this is MySQL, whose `SET` clause (`:set`) has a
|
|
|
|
|
non-standard precedence, compared to other SQL dialects."
|
|
|
|
|
[dialect dialect-spec]
|
|
|
|
|
(when-not (keyword? dialect)
|
|
|
|
|
(throw (ex-info "Dialect must be a keyword" {:dialect dialect})))
|
|
|
|
|
(when-not (map? dialect-spec)
|
2022-05-02 00:34:31 +00:00
|
|
|
(throw (ex-info "Dialect spec must be a hash map containing at least a :quote function"
|
2022-05-01 05:03:36 +00:00
|
|
|
{:dialect-spec dialect-spec})))
|
2022-05-02 00:34:31 +00:00
|
|
|
(when-not (fn? (:quote dialect-spec))
|
|
|
|
|
(throw (ex-info "Dialect spec is missing a :quote function"
|
2022-05-01 05:03:36 +00:00
|
|
|
{:dialect-spec dialect-spec})))
|
|
|
|
|
(when-let [cof (:clause-order-fn dialect-spec)]
|
|
|
|
|
(when-not (fn? cof)
|
|
|
|
|
(throw (ex-info "Dialect spec contains :clause-order-fn but it is not a function"
|
|
|
|
|
{:dialect-spec dialect-spec}))))
|
2022-05-02 00:34:31 +00:00
|
|
|
(when-some [as (:as dialect-spec)]
|
|
|
|
|
(when-not (boolean? as)
|
|
|
|
|
(throw (ex-info "Dialect spec contains :as but it is not a boolean"
|
|
|
|
|
{:dialect-spec dialect-spec}))))
|
2022-05-01 05:03:36 +00:00
|
|
|
(swap! dialects assoc dialect (assoc dialect-spec :dialect dialect)))
|
|
|
|
|
|
2023-02-11 20:54:43 +00:00
|
|
|
(defn registered-dialect?
|
|
|
|
|
"Return true if the dialect is known to HoneySQL."
|
|
|
|
|
[dialect]
|
|
|
|
|
(contains? @dialects dialect))
|
|
|
|
|
|
2022-05-01 05:03:36 +00:00
|
|
|
(defn get-dialect
|
|
|
|
|
"Given a dialect name (keyword), return its definition.
|
|
|
|
|
Returns `nil` if the dialect is unknown."
|
|
|
|
|
[dialect]
|
|
|
|
|
(get @dialects dialect))
|
|
|
|
|
|
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)]
|
2022-01-21 21:05:15 +00:00
|
|
|
(when-not (and f (or (fn? f) (and (var? f) (fn? (deref f)))))
|
2021-02-02 20:25:26 +00:00
|
|
|
(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
|
|
|
|
2023-02-11 20:54:43 +00:00
|
|
|
(defn registered-fn?
|
|
|
|
|
"Return true if the function is known to HoneySQL."
|
|
|
|
|
[function]
|
|
|
|
|
(contains? @special-syntax (sym->kw function)))
|
|
|
|
|
|
2020-09-29 21:40:03 +00:00
|
|
|
(defn register-op!
|
2023-02-11 21:35:55 +00:00
|
|
|
"Register a new infix operator. All operators are variadic and may choose
|
|
|
|
|
to ignore `nil` arguments (this can make it easier to programmatically
|
|
|
|
|
construct the DSL)."
|
|
|
|
|
[op & {:keys [ignore-nil]}]
|
2021-02-02 20:25:26 +00:00
|
|
|
(let [op (sym->kw op)]
|
|
|
|
|
(assert (keyword? op))
|
|
|
|
|
(swap! infix-ops conj op)
|
|
|
|
|
(when ignore-nil
|
|
|
|
|
(swap! op-ignore-nil conj op))))
|
2020-09-29 21:40:03 +00:00
|
|
|
|
2023-02-11 20:54:43 +00:00
|
|
|
(defn registered-op?
|
|
|
|
|
"Return true if the operator is known to HoneySQL."
|
|
|
|
|
[op]
|
|
|
|
|
(contains? @infix-ops (sym->kw op)))
|
|
|
|
|
|
2022-01-08 07:16:56 +00:00
|
|
|
;; helper functions to create HoneySQL data structures from other things
|
|
|
|
|
|
|
|
|
|
(defn map=
|
|
|
|
|
"Given a hash map, return a condition structure that can be used in a
|
|
|
|
|
WHERE clause to test for equality:
|
|
|
|
|
|
|
|
|
|
{:select :* :from :table :where (sql/map= {:id 1})}
|
|
|
|
|
|
|
|
|
|
will produce: SELECT * FROM table WHERE id = ? (and a parameter of 1)"
|
|
|
|
|
[data]
|
|
|
|
|
(let [clauses (reduce-kv (fn [where col val]
|
|
|
|
|
(conj where [:= col val]))
|
|
|
|
|
[]
|
|
|
|
|
data)]
|
|
|
|
|
(if (= 1 (count clauses))
|
|
|
|
|
(first clauses)
|
|
|
|
|
(into [:and] clauses))))
|
|
|
|
|
|
2021-06-13 01:15:41 +00:00
|
|
|
;; aids to migration from HoneySQL 1.x -- these are deliberately undocumented
|
|
|
|
|
;; so as not to encourage their use for folks starting fresh with 2.x!
|
|
|
|
|
|
|
|
|
|
(defn ^:no-doc call [f & args] (apply vector f args))
|
|
|
|
|
|
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])
|
2023-04-14 05:46:37 +00:00
|
|
|
(format {:select [:*] :from [:table]
|
|
|
|
|
:where [:< [:date_add :expiry [:interval "30 Days"]] [:now]]} {})
|
|
|
|
|
(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]})
|
2023-02-11 21:35:55 +00:00
|
|
|
(sql/register-op! :<=> :ignore-nil true)
|
2021-02-02 20:25:26 +00:00
|
|
|
;; 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]]})
|
2023-06-23 23:05:41 +00:00
|
|
|
(sql/formatf '{select * from table where (foo (+ a 1))})
|
|
|
|
|
(sql/formatf '{select * from table where (foo (+ a ?1))} 42)
|
2023-08-11 02:31:17 +00:00
|
|
|
|
|
|
|
|
(sql/format {:update [:user :u]
|
|
|
|
|
:set {:email :u2.email
|
|
|
|
|
:first_name :u2.first_name
|
|
|
|
|
:last_name :u2.last_name}
|
|
|
|
|
:from [[[:values [1 "hollis@weiman.biz" "Hollis" "Connell"]
|
|
|
|
|
[2 "robert@duncan.info" "Robert" "Duncan"]]
|
|
|
|
|
[[:'u2 :id :email :first_name :last_name]]]]
|
|
|
|
|
:where [:= :u.id :u2.id]}
|
|
|
|
|
{:inline true})
|
|
|
|
|
|
|
|
|
|
(sql/register-clause! :output :select :values)
|
|
|
|
|
(sql/format {:insert-into :foo :output [:inserted.*] :values [{:bar 1}]})
|
|
|
|
|
(sql/format {:insert-into :foo :columns [:bar] :output [:inserted.*] :values [[1]]})
|
|
|
|
|
|
2023-08-12 02:06:30 +00:00
|
|
|
;; issue-497
|
|
|
|
|
(honey.sql/format {:select [[:column-name "some-alias"]]
|
|
|
|
|
:from :b
|
|
|
|
|
:order-by [[[:raw "\"some-alias\""]]]})
|
|
|
|
|
(honey.sql/format {:select [[:column-name "some-alias"]]
|
|
|
|
|
:from :b
|
|
|
|
|
:order-by [[[:alias "some-alias"]]]})
|
|
|
|
|
(honey.sql/format {:select [[:column-name "some-alias"]]
|
|
|
|
|
:from :b
|
|
|
|
|
:order-by [[[:alias "some-alias"]]]}
|
|
|
|
|
{:quoted true})
|
2023-08-23 19:45:19 +00:00
|
|
|
(honey.sql/format {:select [[:column-name "some-alias"]]
|
|
|
|
|
:from :b
|
|
|
|
|
:order-by [[[:alias "some-alias"]]]}
|
|
|
|
|
{:dialect :mysql})
|
|
|
|
|
(sql/format {:select :f.* :from [[:foo [:f :FOR :SYSTEM-TIME]]] :where [:= :f.id 1]})
|
|
|
|
|
(sql/format {:using [[:source [:= :table.id :source.id]]]})
|
2023-03-13 00:40:53 +00:00
|
|
|
)
|