update docs; fix set-options!

This commit is contained in:
Sean Corfield 2022-12-17 16:02:02 -08:00
parent d0ae02a6ef
commit bf96f034c3
8 changed files with 106 additions and 11 deletions

View file

@ -1,10 +1,12 @@
# Changes
* 2.4.next in progress
* Fix `set-options!` (only `:checking` worked in 2.4.947).
* Fix `:cast` formatting when quoting is enabled, via PR [#443](https://github.com/seancorfield/honeysql/pull/443) [duddlf23](https://github.com/duddlf23).
* Fix [#441](https://github.com/seancorfield/honeysql/issues/441) by adding `:replace-into` to in-flight clause order (as well as registering it for the `:mysql` dialect).
* Fix [#434](https://github.com/seancorfield/honeysql/issues/434) by special-casing `:'ARRAY`.
* Fix [#433](https://github.com/seancorfield/honeysql/issues/433) by supporting additional `WITH` syntax, via PR [#432](https://github.com/seancorfield/honeysql/issues/432), [@MawiraIke](https://github.com/MawiraIke). _[Technically, this was in 2.4.947, but I kept the issue open while I wordsmithed the documentation]_
* Address [#405](https://github.com/seancorfield/honeysql/issues/405) by adding `:numbered` option, which can also be set globally using `set-options!`.
* 2.4.947 -- 2022-11-05
* Fix [#439](https://github.com/seancorfield/honeysql/issues/439) by rewriting how DDL options are processed; also fixes [#386](https://github.com/seancorfield/honeysql/issues/386) and [#437](https://github.com/seancorfield/honeysql/issues/437); **Whilst this is intended to be purely a bug fix, it has the potential to be a breaking change -- hence the version jump to 2.4!**

View file

@ -116,6 +116,13 @@ If you want to format the query as a string with no parameters (e.g. to use the
=> ["SELECT a, b, c FROM foo WHERE foo.a = 'baz'"]
```
As seen above, the default parameterization uses positional parameters (`?`) with the order of values in the generated vector matching the order of those placeholders in the SQL. As of 2.4.next, you can specified `:numbered true` as an option to produce numbered parameters (`$1`, `$2`, etc):
```clojure
(sql/format sqlmap {:numbered true})
=> ["SELECT a, b, c FROM foo WHERE foo.a = $1" "baz"]
```
Namespace-qualified keywords (and symbols) are generally treated as table-qualified columns: `:foo/bar` becomes `foo.bar`, except in contexts where that would be illegal (such as the list of columns in an `INSERT` statement). This approach is likely to be more compatible with code that uses libraries like [`next.jdbc`](https://github.com/seancorfield/next-jdbc) and [`seql`](https://github.com/exoscale/seql), as well as being more convenient in a world of namespace-qualified keywords, following the example of `clojure.spec` etc.
```clojure
@ -388,6 +395,19 @@ INSERT INTO comp_table
VALUES (?, (?, ?)), (?, (?, ?))
"
"small" 1 "inch" "large" 10 "feet"]
;; with numbered parameters:
(-> (insert-into :comp_table)
(columns :name :comp_column)
(values
[["small" (composite 1 "inch")]
["large" (composite 10 "feet")]])
(sql/format {:pretty true :numbered true}))
=> ["
INSERT INTO comp_table
(name, comp_column)
VALUES ($1, ($2, $3)), ($4, ($5, $6))
"
"small" 1 "inch" "large" 10 "feet"]
;; or as pure data DSL:
(-> {:insert-into [:comp_table],
:columns [:name :comp_column],
@ -608,6 +628,12 @@ Keywords that begin with `?` are interpreted as bindable parameters:
(where [:= :a :?baz])
(sql/format {:params {:baz "BAZ"}}))
=> ["SELECT id FROM foo WHERE a = ?" "BAZ"]
;; or with numbered parameters:
(-> (select :id)
(from :foo)
(where [:= :a :?baz])
(sql/format {:params {:baz "BAZ"} :numbered true}))
=> ["SELECT id FROM foo WHERE a = $1" "BAZ"]
;; or as pure data DSL:
(-> {:select [:id], :from [:foo], :where [:= :a :?baz]}
(sql/format {:params {:baz "BAZ"}}))
@ -832,6 +858,24 @@ LIMIT ?
OFFSET ?
"
"bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10]
;; with numbered parameters:
(sql/format big-complicated-map
{:params {:param1 "gabba" :param2 2}
:pretty true :numbered true})
=> ["
SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS \"bla-bla\", NOW(), @x := 10
FROM foo AS f, baz AS b
INNER JOIN draq ON f.b = draq.x INNER JOIN eldr ON f.e = eldr.t
LEFT JOIN clod AS c ON f.a = c.d
RIGHT JOIN bock ON bock.z = c.e
WHERE ((f.a = $1) AND (b.baz <> $2)) OR (($3 < $4) AND ($5 < $6)) OR (f.e IN ($7, $8, $9)) OR f.e BETWEEN $10 AND $11
GROUP BY f.a, c.e
HAVING $12 < f.e
ORDER BY b.baz DESC, c.quux ASC, f.a NULLS FIRST
LIMIT $13
OFFSET $14
"
"bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10]
```
```clojure
;; Printable and readable
@ -882,8 +926,13 @@ Or perhaps your database supports syntax like `a BETWIXT b AND c`, in which case
;; example usage:
(-> (select :a) (where [:betwixt :a 1 10]) sql/format)
=> ["SELECT a WHERE a BETWIXT ? AND ?" 1 10]
;; with numbered parameters:
(-> (select :a) (where [:betwixt :a 1 10]) (sql/format {:numbered true}))
=> ["SELECT a WHERE a BETWIXT $1 AND $2" 1 10]
```
> Note: the generation of positional placeholders (`?`) or numbered placeholders (`$1`, `$2`, etc) is handled automatically by `format-expr` so you get this behavior "for free" in your extensions, as long as you use the public API for `honey.sql`. You should avoid writing extensions that generate placeholders directly if you want them to work with numbered parameters.
You can also register SQL clauses, specifying the keyword, the formatting function, and an existing clause that this new clause should be processed before:
```clojure
@ -909,6 +958,6 @@ If you find yourself registering an operator, a function (syntax), or a new clau
## License
Copyright (c) 2020-2021 Sean Corfield. HoneySQL 1.x was copyright (c) 2012-2020 Justin Kramer and Sean Corfield.
Copyright (c) 2020-2022 Sean Corfield. HoneySQL 1.x was copyright (c) 2012-2020 Justin Kramer and Sean Corfield.
Distributed under the Eclipse Public License, the same as Clojure.

View file

@ -104,7 +104,7 @@ You can now select a non-ANSI dialect of SQL using the new `honey.sql/set-dialec
## Option Changes
The `:quoting <dialect>` option has superseded by the new dialect machinery and a new `:quoted` option that turns quoting on or off. You either use `:dialect <dialect>` instead or set a default dialect (via `set-dialect!`) and then use `:quoted true` in `format` calls where you want quoting.
The `:quoting <dialect>` option has been superseded by the new dialect machinery and a new `:quoted` option that turns quoting on or off. You either use `:dialect <dialect>` instead (which turns on quoting by default) or set a default dialect (via `set-dialect!`) and then use `:quoted true` in `format` calls where you want quoting.
SQL entity names are automatically quoted if you specify a `:dialect` option to `format`, unless you also specify `:quoted false`.
@ -112,7 +112,7 @@ The following options are no longer supported:
* `:allow-dashed-names?` -- if you provide dashed-names in 2.x, they will be left as-is if quoting is enabled, else they will be converted to snake_case (so you will either get `"dashed-names"` with quoting or `dashed_names` without). If you want dashed-names to be converted to snake_case when `:quoted true`, you also need to specify `:quoted-snake true`.
* `:allow-namespaced-names?` -- this supported `foo/bar` column names in SQL which I'd like to discourage.
* `:namespace-as-table?` -- this is the default in 2.x: `:foo/bar` will be treated as `foo.bar` which is more in keeping with `next.jdbc`.
* `:parameterizer` -- this would add a lot of complexity to the formatting engine and I do not know how widely it was used (especially in its arbitrarily extensible form).
* `:parameterizer` -- this would add a lot of complexity to the formatting engine and I do not know how widely it was used (especially in its arbitrarily extensible form). _[As of 2.4.next, the ability to generated SQL with numbered parameters, i.e., `$1` instead of positional parameters, `?`, has been added via the `:numbered true` option]_
* `:return-param-names` -- this was added to 1.x back in 2013 without an associated issue or PR so I've no idea what use case this was intended to support.
> Note: I expect some push back on those first three options and the associated behavior changes.

View file

@ -110,6 +110,8 @@ Some "functions" are considered to be operators. In general,
`42` and `"c"` lifted out into the overall vector result
(with a SQL string followed by all its parameters).
> Note: you can use the `:numbered true` option to `format` to produce SQL containing numbered placeholders, like `FOO(a, $1, $2)`, instead of positional placeholders (`?`).
Operators can be strictly binary or variadic (most are strictly binary).
Special syntax can have zero or more arguments and each form is
described in the [Special Syntax](special-syntax.md) section.
@ -179,7 +181,7 @@ expression requires an extra level of nesting:
As indicated in the preceding sections, values found in the DSL data structure
that are not keywords or symbols are lifted out as positional parameters.
They are replaced by `?` in the generated SQL string and added to the
By default, they are replaced by `?` in the generated SQL string and added to the
parameter list in order:
<!-- :test-doc-blocks/skip -->
@ -187,6 +189,14 @@ parameter list in order:
[:between :size 10 20] ;=> "size BETWEEN ? AND ?" with parameters 10 and 20
```
If you specify the `:numbered true` option to `format`, numbered placeholders (`$1`, `$2`, etc) will be used instead of positional placeholders (`?`).
<!-- :test-doc-blocks/skip -->
```clojure
;; with :numbered true option:
[:between :size 10 20] ;=> "size BETWEEN $1 AND $2" with parameters 10 and 20
```
HoneySQL also supports named parameters. There are two ways
of identifying a named parameter:
* a keyword or symbol that begins with `?`
@ -206,6 +216,18 @@ call as the `:params` key of the options hash map.
;;=> ["SELECT * FROM table WHERE a = ?" 42]
```
Or with `:numbered true`:
```clojure
(sql/format {:select [:*] :from [:table]
:where [:= :a :?x]}
{:params {:x 42} :numbered true})
;;=> ["SELECT * FROM table WHERE a = $1" 42]
(sql/format {:select [:*] :from [:table]
:where [:= :a [:param :x]]}
{:params {:x 42} :numbered true})
;;=> ["SELECT * FROM table WHERE a = $1" 42]
```
## Functional Helpers
In addition to the hash map (and sequences) approach of building

View file

@ -19,6 +19,7 @@ All options may be omitted. The default behavior of each option is described in
* `:checking` -- `:none` (default), `:basic`, or `:strict` to control the amount of lint-like checking that HoneySQL performs,
* `:dialect` -- a keyword that identifies a dialect to be used for this specific call to `format`; the default is to use what was specified in `set-dialect!` or `:ansi` if no other dialect has been set,
* `:inline` -- a Boolean indicating whether or not to inline parameter values, rather than use `?` placeholders and a sequence of parameter values; the default is `false` -- values are not inlined,
* `:numbered` -- a Boolean indicating whether to generate numbered placeholders in the generated SQL (`$1`, `$2`, etc) or positional placeholders (`?`); the default is `false` (positional placeholders); this option was added in 2.4.next,
* `:params` -- a hash map providing values for named parameters, identified by names (keywords or symbols) that start with `?` in the DSL; the default is that any such named parameters will have `nil` values,
* `:quoted` -- a Boolean indicating whether or not to quote (strop) SQL entity names (table and column names); the default is `nil` -- alphanumeric SQL entity names are not quoted but (as of 2.3.928) "unusual" SQL entity names are quoted; a `false` value turns off all quoting,
* `:quoted-snake` -- a Boolean indicating whether or not quoted and string SQL entity names should have `-` replaced by `_`; the default is `false` -- quoted and string SQL entity names are left exactly as-is,
@ -29,6 +30,7 @@ global defaults of certain options:
* `:checking` -- can be `:basic` or `:strict`; specify `:none` to reset to the default,
* `:inline` -- can be `true` but consider the security issues this causes by not using parameterized SQL statements; specify `false` (or `nil`) to reset to the default,
* `:numbered` -- can be `true` or `false`; specify `false` to reset to the default,
* `:quoted` -- can be `true` or `false`; specify `nil` to reset to the default; calling `set-dialect!` or providing a `:dialect` option to `format` will override the global default,
* `:quoted-snake` -- can be `true`; specify `false` (or `nil`) to reset to the default.
@ -96,6 +98,13 @@ was wrapped in `[:inline `..`]`:
> Note: you can provide additional inline formatting by extending the `InlineValue` protocol from `honey.sql.protocols` to new types.
## `:numbered`
By default, HoneySQL generates SQL using positional placeholders (`?`).
Specifying `:numbered true` tells HoneySQL to generate SQL using
numbered placeholders instead (`$1`, `$2`, etc). This can be set
globally using `set-options!`.
## `:params`
The `:params` option provides a mapping from named parameters

View file

@ -10,6 +10,12 @@ Everything that the nilenso library provided (in 0.4.112) is implemented
directly in HoneySQL 2.x although a few things have a
slightly different syntax.
If you are using HoneySQL with the Node.js PostgreSQL driver, it
only accepts numbered placeholders, not positional placeholders,
so you will need to specify the `:numbered true` option that was
added in 2.4.next. You may find it convenient to set this option
globally, via `set-options!`.
## Code Examples
The code examples herein assume:

View file

@ -120,6 +120,7 @@
(def ^:private default-quoted-snake (atom nil))
(def ^:private default-inline (atom nil))
(def ^:private default-checking (atom :none))
(def ^:private default-numbered (atom false))
(def ^:private ^:dynamic *dialect* nil)
;; nil would be a better default but that makes testing individual
@ -1667,7 +1668,10 @@
([data opts]
(let [cache (:cache opts)
dialect? (contains? opts :dialect)
dialect (when dialect? (get @dialects (check-dialect (:dialect opts))))]
dialect (when dialect? (get @dialects (check-dialect (:dialect opts))))
numbered (if (contains? opts :numbered)
(:numbered opts)
@default-numbered)]
(binding [*dialect* (if dialect? dialect @default-dialect)
*caching* cache
*checking* (if (contains? opts :checking)
@ -1681,7 +1685,7 @@
*inline* (if (contains? opts :inline)
(:inline opts)
@default-inline)
*numbered* (when (:numbered opts)
*numbered* (when numbered
(atom []))
*quoted* (cond (contains? opts :quoted)
(:quoted opts)
@ -1722,11 +1726,12 @@
"Set default values for any or all of the following options:
* :checking
* :inline
* :numbered
* :quoted
* :quoted-snake
Note that calling `set-dialect!` can override the default for `:quoted`."
[opts]
(let [unknowns (dissoc opts :checking :inline :quoted :quoted-snake)]
(let [unknowns (dissoc opts :checking :inline :numbered :quoted :quoted-snake)]
(when (seq unknowns)
(throw (ex-info (str (str/join ", " (keys unknowns))
" are not options that can be set globally.")
@ -1734,11 +1739,13 @@
(when (contains? opts :checking)
(reset! default-checking (:checking opts)))
(when (contains? opts :inline)
(reset! default-checking (:inline opts)))
(reset! default-inline (:inline opts)))
(when (contains? opts :numbered)
(reset! default-numbered (:numbered opts)))
(when (contains? opts :quoted)
(reset! default-checking (:quoted opts)))
(reset! default-quoted (:quoted opts)))
(when (contains? opts :quoted-snake)
(reset! default-checking (:quoted-snake opts)))))
(reset! default-quoted-snake (:quoted-snake opts)))))
(defn clause-order
"Return the current order that known clauses will be applied when

View file

@ -1,4 +1,4 @@
;; copyright (c) 2020-2021 sean corfield, all rights reserved
;; copyright (c) 2020-2022 sean corfield, all rights reserved
(ns honey.sql.helpers-test
(:refer-clojure :exclude [filter for group-by partition-by set update])