diff --git a/doc/extending-honeysql.md b/doc/extending-honeysql.md index 3894636..69dafe9 100644 --- a/doc/extending-honeysql.md +++ b/doc/extending-honeysql.md @@ -26,7 +26,7 @@ of two arguments or a previously registered clause (so that you can easily reuse formatters). The formatter function will be called with: -* The function name (always as a keyword), +* The clause name (always as a keyword), * The sequence of arguments provided. The third argument to `register-clause!` allows you to diff --git a/doc/special-syntax.md b/doc/special-syntax.md index b52600a..d3be25a 100644 --- a/doc/special-syntax.md +++ b/doc/special-syntax.md @@ -5,15 +5,170 @@ HoneySQL supports out of the box which are formatted as special syntactic forms. ## 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 +(sql/format-expr [:array (range 5)]) +;;=> ["ARRAY[?, ?, ?, ?, ?]" 0 1 2 3 4] +``` + ## 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 THEN ` 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"] +``` + ## 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] +``` + ## default + +Takes no arguments and produces the SQL keyword `DEFAULT`. + +_[I expect this to be expanded for PostgreSQL]_ + ## 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] +``` + ## 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"}] +``` + ## 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. + ## 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] +``` + ## 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"] +``` diff --git a/src/honey/sql.cljc b/src/honey/sql.cljc index ac322e3..fb1ff2c 100644 --- a/src/honey/sql.cljc +++ b/src/honey/sql.cljc @@ -608,7 +608,8 @@ (let [[sqlc & paramsc] (when-not (= :else condition) (format-expr condition)) [sqlv & paramsv] (format-expr value)] - [(if (= :else condition) + [(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))])) @@ -892,6 +893,7 @@ :pretty true})) ;; while working on the docs (require '[honey.sql :as sql]) + (sql/format-expr [:array (range 5)]) (sql/format {:where [:and [:= :id 42] [:= :type "match"]]}) (sql/format {:where [:and [:= :type "match"] (when false [:in :status [1 5]])]}) (sql/format {:select [:*] :from [:table] :where [:= :id 1]})