Out of the box, HoneySQL supports most standard ANSI SQL clauses and expressions but where it doesn't support something you need you can add new clauses, new operators, and new "functions" (or "special syntax").
There are three extension points in honey.sql
that let you
register formatters or behavior corresponding to clauses,
operators, and functions.
Built in clauses include: :select
, :from
, :where
and
many more. Built in operators include: :=
, :+
, :mod
.
Built in functions (special syntax) include: :array
, :case
,
:cast
, :inline
, :raw
and many more.
honey.sql/register-clause!
accepts a keyword (or a symbol)
that should be treated as a new clause in a SQL statement,
a "formatter", and a keyword (or a symbol) that identifies
an existing clause that this new one should be ordered before.
The formatter can either be a function of two arguments or a previously registered clause (so that you can easily reuse formatters).
The formatter function will be called with:
The third argument to register-clause!
allows you to
insert your new clause formatter so that clauses are
formatted in the correct order for your SQL dialect.
For example, :select
comes before :from
which comes
before :where
. You can call clause-order
to see what the
current ordering of clauses is.
Note: if you call
register-clause!
more than once for the same clause, the last call "wins". This allows you to correct an incorrect clause order insertion by simply callingregister-clause!
again with a different third argument.
Having registered a new clause, you might also want a helper function
for it, just as the built-in clauses have helpers in honey.sql.helpers
.
Two functions exist in that namespace to make it easier for you to
define your own helpers:
generic-helper-variadic
-- most clauses accept an arbitrary number of items in a sequence and multiple calls in a DSL expression will merge so this is the helper you will use for most clauses,generic-helper-unary
-- a handful of clauses only accept a single item and cannot be merged (they behave as "last one wins"), so this helper supports that semantic.Each of these helper support functions should be called with the keyword that identifies your new clause and the sequence of arguments passed to it. See the docstrings for more detail.
You might have:
(sql/register-clause! :my-clause my-formatter :where)
(defn my-clause [& args] (h/generic-helper-variadic :my-clause args))
honey.sql/register-op!
accepts a keyword (or a symbol) that
should be treated as a new infix operator.
By default, operators are treated as strictly binary --
accepting just two arguments -- and an exception will be
thrown if they are provided less than two or more than
two arguments. You can optionally specify that an operator
can take any number of arguments with :variadic true
:
(require '[honey.sql :as sql])
(sql/register-op! :<=> :variadic true)
;; and then use the new operator:
(sql/format {:select [:*], :from [:table], :where [:<=> 13 :x 42]})
;; will produce:
;;=> ["SELECT * FROM table WHERE ? <=> x <=> ?" 13 42]
If you are building expressions programmatically, you
may want your new operator to ignore "empty" expressions,
i.e., where your expression-building code might produce
nil
. The built-in operators :and
and :or
ignore
such nil
expressions. You can specify :ignore-nil true
to achieve that:
(sql/register-op! :<=> :variadic true :ignore-nil true)
;; and then use the new operator:
(sql/format {:select [:*], :from [:table], :where [:<=> nil :x 42]})
;; will produce:
;;=> ["SELECT * FROM table WHERE x <=> ?" 42]
A number of PostgreSQL operators contain @
which is not legal in a Clojure keyword or symbol (as literal syntax). The recommendation is to def
your own name for these
operators, using at
in place of @
, with an explicit call to keyword
(or symbol
), and then use that def
'd name when registering new operators and when writing
your DSL expressions:
(def <at (keyword "<@"))
(sql/register-op! <at)
;; and use it in expressions: [<at :submitted [:range :begin :end]]
honey.sql/register-fn!
accepts a keyword (or a symbol)
that should be treated as new syntax (as a function call),
and a "formatter". The formatter can either be a function
of two arguments or a previously registered "function" (so
that you can easily reuse formatters).
The formatter function will be called with:
For example:
(sql/register-fn! :foo (fn [f args] ..))
(sql/format {:select [:*], :from [:table], :where [:foo 1 2 3]})
Your formatter function will be called with :foo
and (1 2 3)
.
It should return a vector containing a SQL string followed by
any parameters:
(sql/register-fn! :foo (fn [f args] ["FOO(?)" (first args)]))
(sql/format {:select [:*], :from [:table], :where [:foo 1 2 3]})
;; produces:
;;=> ["SELECT * FROM table WHERE FOO(?)" 1]
In practice, it is likely that your formatter would call
sql/sql-kw
on the function name to produce a SQL representation
of it and would call sql/format-expr
on each argument:
(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]]})
;; produces:
;;=> ["SELECT * FROM table WHERE FOO(a + ?)" 1]
Can you improve this documentation? These fine people already did:
Sean Corfield & lreadEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close