# SQL Special Syntax

This section lists the function-like expressions that
HoneySQL supports out of the box which are formatted
as special syntactic forms.

The first group are used for SQL expressions. The second (last group) are used primarily in column definitions (as part of `:with-columns` and `:add-column` / `:alter-column`).

## array

Accepts a single argument, which is expected to evaluate to
a sequence, and produces `ARRAY[?, ?, ..]` for the elements
of that sequence (as SQL parameters):

```clojure
(require '[honey.sql :as sql])

(sql/format-expr [:array (range 5)])
;;=> ["ARRAY[?, ?, ?, ?, ?]" 0 1 2 3 4]
```

> Note: you cannot provide a named parameter as the argument for `:array` because the generated SQL depends on the number of elements in the sequence, so the following throws an exception:

<!-- :test-doc-blocks/skip -->
```clojure
(sql/format {:select [[[:array :?tags] :arr]]} {:params {:tags [1 2 3]}})
```

You can do the following instead:

```clojure
(let [tags [1 2 3]]
  (sql/format {:select [[[:array tags] :arr]]} {:inline true}))
;;=> ["SELECT ARRAY[1, 2, 3] AS arr"]
```

In addition, the argument to `:array` is treated as a literal sequence of Clojure values and is **not** interpreted as a HoneySQL expression, so you must use the `{:inline true}` formatting option as shown above rather than try to inline the values like this:

```clojure
(sql/format {:select [[[:array [:inline [1 2 3]]] :arr]]})
;;=> ["SELECT ARRAY[inline, (?, ?, ?)] AS arr" 1 2 3]
```

## between

Accepts three arguments: an expression, a lower bound, and
an upper bound:

```clojure
(sql/format-expr [:between :id 1 100])
;;=> ["id BETWEEN ? AND ?" 1 100]
```

## case

A SQL CASE expression. Expects an even number of arguments:
alternating condition and result expressions. A condition
may be `:else` (or `'else`) to produce `ELSE`, otherwise
`WHEN <condition> THEN <result>` will be produced:

```clojure
(sql/format-expr [:case [:< :a 10] "small" [:> :a 100] "big" :else "medium"])
;; => ["CASE WHEN a < ? THEN ? WHEN a > ? THEN ? ELSE ? END" 10 "small" 100 "big" "medium"]
```

Google BigQuery supports a variant of `CASE` that takes an expression and then the `WHEN`
clauses contain expressions to match against, rather than conditions. HoneySQL supports
this using `:case-expr`:

```clojure
(sql/format-expr [:case-expr :a 10 "small" 100 "big" :else "medium"])
;; => ["CASE a WHEN ? THEN ? WHEN ? THEN ? ELSE ? END" 10 "small" 100 "big" "medium"]
```

## cast

A SQL CAST expression. Expects an expression and something
that produces a SQL type:

```clojure
(sql/format-expr [:cast :a :int])
;;=> ["CAST(a AS INT)"]
```

## composite

Accepts any number of expressions and produces a composite
expression (comma-separated, wrapped in parentheses):

```clojure
(sql/format-expr [:composite :a :b "red" [:+ :x 1]])
;;=> ["(a, b, ?, x + ?)" "red" 1]
```

## distinct

Accepts a single expression and prefixes it with `DISTINCT `:

```clojure
(sql/format {:select [ [[:count [:distinct :status]] :n] ] :from :table})
;;=> ["SELECT COUNT(DISTINCT status) AS n FROM table"]
```

## entity

Accepts a single keyword or symbol argument and produces a
SQL entity. This is intended for use in contexts that would
otherwise produce a sequence of SQL keywords, such as when
constructing DDL statements.

<!-- :test-doc-blocks/skip -->
```clojure
[:tablespace :quux]
;;=> TABLESPACE QUUX
[:tablespace [:entity :quux]]
;;=> TABLESPACE quux
```

## escape

Intended to be used with regular expression patterns to
specify the escape characters (if any).

```clojure
(sql/format {:select :* :from :foo
             :where [:similar-to :foo [:escape "bar" [:inline  "*"]]]})
;;=> ["SELECT * FROM foo WHERE foo SIMILAR TO ? ESCAPE '*'" "bar"]
```

## filter, within-group

Used to produce PostgreSQL's `FILTER` and `WITHIN GROUP` expressions.
See also **order-by** below.

These both accept a SQL expression followed by a SQL clause.
Filter generally expects an aggregate expression and a `WHERE` clause.
Within group generally expects an aggregate expression and an `ORDER BY` clause.

```clojure
(sql/format {:select [:a :b [[:filter :%count.* {:where [:< :x 100]}] :c]
                     [[:within-group [:percentile_disc [:inline 0.25]]
                                     {:order-by [:a]}] :inter_max]
                     [[:within-group [:percentile_cont [:inline 0.25]]
                                     {:order-by [:a]}] :abs_max]]
             :from :aa}
             {:pretty true})
;;=> ["
SELECT a, b, COUNT(*) FILTER (WHERE x < ?) AS c, PERCENTILE_DISC(0.25) WITHIN GROUP (ORDER BY a ASC) AS inter_max, PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY a ASC) AS abs_max
FROM aa
"
100]
```

There are helpers for both `filter` and `within-group`. Be careful with `filter`
since it shadows `clojure.core/filter`:

```clojure
(refer-clojure :exclude '[filter])
(require '[honey.sql.helpers :refer [select filter within-group from order-by where]])

(sql/format (-> (select :a :b [(filter :%count.* (where :< :x 100)) :c]
                        [(within-group [:percentile_disc [:inline 0.25]]
                                       (order-by :a)) :inter_max]
                        [(within-group [:percentile_cont [:inline 0.25]]
                                       (order-by :a)) :abs_max])
                (from :aa))
                {:pretty true})
;;=> ["
SELECT a, b, COUNT(*) FILTER (WHERE x < ?) AS c, PERCENTILE_DISC(0.25) WITHIN GROUP (ORDER BY a ASC) AS inter_max, PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY a ASC) AS abs_max
FROM aa
"
100]
```

## inline

Accepts a single argument and tries to render it as a
SQL value directly in the formatted SQL string rather
than turning it into a positional parameter:
* `nil` becomes `NULL`
* keywords and symbols become upper case entities (with `-` replaced by space)
* strings become inline SQL strings (with single quotes)
* a sequence has each element formatted inline and then joined with spaces
* all other values are just rendered via Clojure's `str` function

```clojure
(sql/format {:where [:= :x [:inline "foo"]]})
;;=> ["WHERE x = 'foo'"]
```

## interval

Accepts two arguments: an expression and a keyword (or a symbol)
that represents a time unit. Produces an `INTERVAL` expression:

```clojure
(sql/format-expr [:date_add [:now] [:interval 30 :days]])
;;=> ["DATE_ADD(NOW(), INTERVAL ? DAYS)" 30]
```

> Note: PostgreSQL has an `INTERVAL` data type which is unrelated to this syntax. In PostgreSQL, the closet equivalent would be `[:cast "30 days" :interval]` which will lift `"30 days"` out as a parameter. In DDL, for PostgreSQL, you can use `:interval` to produce the `INTERVAL` data type (without wrapping it in a vector).

## lateral

Accepts a single argument that can be a (`SELECT`) clause or
a (function call) expression. Produces a `LATERAL` subquery
clause based on the `SELECT` clause or the SQL expression.

## lift

Used to wrap a Clojure value that should be passed as a
SQL parameter but would otherwise be treated as a SQL
expression or statement, i.e., a sequence or hash map.
This can be useful when dealing with JSON types:

```clojure
(sql/format {:where [:= :json-col [:lift {:a 1 :b "two"}]]})
;;=> ["WHERE json_col = ?" {:a 1 :b "two"}]
```

> Note: HoneySQL 1.x used `honeysql.format/value` for this.

## nest

Used to wrap an expression when you want an extra
level of parentheses around it:

```clojure
(sql/format {:where [:= :x 42]})
;;=> ["WHERE x = ?" 42]
(sql/format {:where [:nest [:= :x 42]]})
;;=> ["WHERE (x = ?)" 42]
```

`:nest` is also supported as a SQL clause for the same reason.

```clojure
;; BigQuery requires UNION clauses be parenthesized:
(sql/format {:union-all [{:nest {:select :*}} {:nest {:select :*}}]})
;;=> ["(SELECT *) UNION ALL (SELECT *)"]
```

## not

Accepts a single expression and formats it with `NOT`
in front of it:

```clojure
(sql/format-expr [:not nil])
;;=> ["NOT NULL"]
(sql/format-expr [:not [:= :x 42]])
;;=> ["NOT (x = ?)" 42]
```

## order-by

In addition to the `ORDER BY` clause, HoneySQL also supports `ORDER BY`
in an expression (for PostgreSQL). It accepts a SQL expression followed
by an ordering specifier, which can be an expression or a pair of expression
and direction (`:asc` or `:desc`):

```clojure
(sql/format {:select [[[:array_agg [:order-by :a [:b :desc]]]]] :from :table})
;;=> ["SELECT ARRAY_AGG(a ORDER BY b DESC) FROM table"]
(sql/format (-> (select [[:array_agg [:order-by :a [:b :desc]]]])
                (from :table)))
;;=> ["SELECT ARRAY_AGG(a ORDER BY b DESC) FROM table"]
(sql/format {:select [[[:string_agg :a [:order-by [:inline ","] :a]]]] :from :table})
;;=> ["SELECT STRING_AGG(a, ',' ORDER BY a ASC) FROM table"]
(sql/format (-> (select [[:string_agg :a [:order-by [:inline ","] :a]]])
                (from :table)))
;;=> ["SELECT STRING_AGG(a, ',' ORDER BY a ASC) FROM table"]
```

There is no helper for the `ORDER BY` special syntax: the `order-by` helper
only produces a SQL clause.

## over

This is intended to be used with the `:window` and `:partition-by` clauses.

`:over` takes any number of window expressions which are either pairs or triples
that have an aggregation expression, a window function, and an optional alias.

The window function may either be a SQL entity (named in a `:window` clause)
or a SQL clause that describes the window (e.g., using `:partition-by` and/or `:order-by`).

Since a function call (using `:over`) needs to be wrapped in a sequence for a
`:select` clause, it is usually easier to use the `over` helper function
to construct this expression.

## param

Used to identify a named parameter in a SQL expression
as an alternative to a keyword (or a symbol) that begins
with `?`:

```clojure
(sql/format {:where [:= :x :?foo]} {:params {:foo 42}})
;;=> ["WHERE x = ?" 42]
(sql/format {:where [:= :x [:param :foo]]} {:params {:foo 42}})
;;=> ["WHERE x = ?" 42]
```

## raw

Accepts a single argument and renders it as literal SQL
in the formatted string:

```clojure
(sql/format {:select [:a [[:raw "@var := foo"]]]})
;;=> ["SELECT a, @var := foo"]
```

If the argument is a sequence of expressions, they
will each be rendered literally and joined together
(with no spaces):

```clojure
(sql/format {:select [:a [[:raw ["@var" " := " "foo"]]]]})
;;=> ["SELECT a, @var := foo"]
```

When a sequence of expressions is supplied, any
subexpressions that are, in turn, sequences will be
formatted as regular SQL expressions and that SQL
will be joined into the result, along with any
parameters from them:

```clojure
(sql/format {:select [:a [[:raw ["@var := " [:inline "foo"]]]]]})
;;=> ["SELECT a, @var := 'foo'"]
(sql/format {:select [:a [[:raw ["@var := " ["foo"]]]]]})
;;=> ["SELECT a, @var := (?)" "foo"]
```

`:raw` is also supported as a SQL clause for the same reason.

## Column Descriptors

There are three types of descriptors that vary
in how they treat their first argument. All three
descriptors automatically try to inline any parameters
(and will throw an exception if they can't, since these
descriptors are meant to be used in column or index
specifications).

### foreign-key, primary-key

If no arguments are provided, these render as just SQL
keywords (uppercase):

<!-- :test-doc-blocks/skip -->
```clojure
[:foreign-key] ;=> FOREIGN KEY
[:primary-key] ;=> PRIMARY KEY
```

Otherwise, these render as regular function calls:

<!-- :test-doc-blocks/skip -->
```clojure
[:foreign-key :a]    ;=> FOREIGN KEY(a)
[:primary-key :x :y] ;=> PRIMARY KEY(x, y)
```

### constraint, default, references

Although these are grouped together, they are generally
used differently. This group renders as SQL keywords if
no arguments are provided. If a single argument is
provided, this renders as a SQL keyword followed by the
argument. If two or more arguments are provided, this
renders as a SQL keyword followed by the first argument,
followed by the rest as a regular argument list:

<!-- :test-doc-blocks/skip -->
```clojure
[:default]              ;=> DEFAULT
[:default 42]           ;=> DEFAULT 42
[:default "str"]        ;=> DEFAULT 'str'
[:constraint :name]     ;=> CONSTRAINT name
[:references :foo :bar] ;=> REFERENCES foo(bar)
```

### index, unique

These behave like the group above except that if the
first argument is `nil`, it is omitted:

<!-- :test-doc-blocks/skip -->
```clojure
[:index :foo :bar :quux] ;=> INDEX foo(bar, quux)
[:index nil :bar :quux]  ;=> INDEX(bar, quux)
[:unique :a :b]          ;=> UNIQUE a(b)
```
