fixes #459 by making all operators variadic
except for := and the various :<> variants some operators only make sense in binary usage and will produce invalid SQL if used in a non-binary manner
This commit is contained in:
parent
6324eca4fc
commit
762252b660
8 changed files with 55 additions and 40 deletions
|
|
@ -1,6 +1,7 @@
|
|||
# Changes
|
||||
|
||||
* 2.4.next in progress
|
||||
* Address [#459](https://github.com/seancorfield/honeysql/issues/459) by making all operators variadic (except `:=` and `:<>`).
|
||||
* Address [#458](https://github.com/seancorfield/honeysql/issues/458) by adding `registered-*?` predicates.
|
||||
|
||||
* 2.4.972 -- 2023-02-02
|
||||
|
|
|
|||
|
|
@ -893,11 +893,9 @@ If your database supports `<=>` as an operator, you can tell HoneySQL about it u
|
|||
|
||||
```clojure
|
||||
(sql/register-op! :<=>)
|
||||
;; default is a binary operator:
|
||||
;; all operators are assumed to be variadic:
|
||||
(-> (select :a) (where [:<=> :a "foo"]) sql/format)
|
||||
=> ["SELECT a WHERE a <=> ?" "foo"]
|
||||
;; you can declare that an operator is variadic:
|
||||
(sql/register-op! :<=> :variadic true)
|
||||
(-> (select :a) (where [:<=> "food" :a "fool"]) sql/format)
|
||||
=> ["SELECT a WHERE ? <=> a <=> ?" "food" "fool"]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ The protocols and multimethods in 1.x have all gone away. The primary extension
|
|||
|
||||
You can also register new "functions" that can implement special syntax (such as `:array`, `:inline`, `:raw` etc above) via `honey.sql/register-fn!`. This accepts a "function" name as a keyword and a formatter which will generally be a function of two arguments: the function name (so formatters can be reused across different names) and a vector of the arguments the function should accept.
|
||||
|
||||
And, finally, you can register new operators that will be recognized in expressions via `honey.sql/register-op!`. This accepts an operator name as a keyword and optional named parameters to indicate whether the operator is `:variadic` (the default is strictly binary) and whether it should ignore operands that evaluate to `nil` (via `:ignore-nil`). The latter can make it easier to construct complex expressions programmatically without having to worry about conditionally removing "optional" (`nil`) values.
|
||||
And, finally, you can register new operators that will be recognized in expressions via `honey.sql/register-op!`. This accepts an operator name as a keyword and an optional named parameter to indicate whether it should ignore operands that evaluate to `nil` (via `:ignore-nil`). That can make it easier to construct complex expressions programmatically without having to worry about conditionally removing "optional" (`nil`) values.
|
||||
|
||||
> Note: because of the changes in the extension machinery between 1.x and 2.x, it is not possible to use the [nilenso/honeysql-postgress](https://github.com/nilenso/honeysql-postgres) library with HoneySQL 2.x but the goal is to incorporate all of the syntax from that library into the core of HoneySQL.
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,8 @@ Some "functions" are considered to be operators. In general,
|
|||
|
||||
> 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).
|
||||
Operators are all treated as variadic (except for `:=` and
|
||||
`:<>` / `:!=` / `:not=` which are binary and require exactly two operands).
|
||||
Special syntax can have zero or more arguments and each form is
|
||||
described in the [Special Syntax](special-syntax.md) section.
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ can simply evaluate to `nil` instead).
|
|||
|
||||
## in
|
||||
|
||||
Binary predicate for checking an expression is
|
||||
Predicate for checking an expression is
|
||||
is a member of a specified set of values.
|
||||
|
||||
The two most common forms are:
|
||||
|
|
@ -73,16 +73,21 @@ This produces `(col1, col2) IN ...`
|
|||
|
||||
> Note: This is a change from HoneySQL 1.x which accepted a sequence of column names but required more work for arbitrary expressions.
|
||||
|
||||
## = <> < > <= >=
|
||||
## = <>
|
||||
|
||||
Binary comparison operators. These expect exactly
|
||||
two arguments.
|
||||
|
||||
`not=` and `!=` are accepted as aliases for `<>`.
|
||||
|
||||
## < > <= >=
|
||||
|
||||
Comparison operators. These expect exactly
|
||||
two arguments.
|
||||
|
||||
## is, is-not
|
||||
|
||||
Binary predicates for `NULL` and Boolean values:
|
||||
Predicates for `NULL` and Boolean values:
|
||||
|
||||
```clojure
|
||||
{...
|
||||
|
|
@ -106,16 +111,15 @@ Binary predicates for `NULL` and Boolean values:
|
|||
|
||||
## mod, xor, + - * / % | & ^
|
||||
|
||||
Mathematical and bitwise operators. `+` and `*` are
|
||||
variadic; the rest are strictly binary operators.
|
||||
Mathematical and bitwise operators.
|
||||
|
||||
## like, not like, ilike, not ilike, regexp
|
||||
|
||||
Pattern matching binary operators. `regex` is accepted
|
||||
Pattern matching operators. `regex` is accepted
|
||||
as an alias for `regexp`.
|
||||
|
||||
`similar-to` and `not-similar-to` are also supported.
|
||||
|
||||
## ||
|
||||
|
||||
Variadic string concatenation operator.
|
||||
String concatenation operator.
|
||||
|
|
|
|||
|
|
@ -1279,7 +1279,6 @@
|
|||
(atom)))
|
||||
|
||||
(def ^:private op-ignore-nil (atom #{:and :or}))
|
||||
(def ^:private op-variadic (atom #{:and :or :+ :* :- :|| :&&}))
|
||||
|
||||
(defn- unwrap [x opts]
|
||||
(if-let [m (meta x)]
|
||||
|
|
@ -1558,30 +1557,20 @@
|
|||
(format-dsl expr (assoc opts :nested true))
|
||||
|
||||
(sequential? expr)
|
||||
(let [op (sym->kw (first expr))]
|
||||
(if (keyword? op)
|
||||
(cond (contains? @infix-ops op)
|
||||
(if (contains? @op-variadic op) ; no aliases here, no special semantics
|
||||
(let [x (if (contains? @op-ignore-nil op)
|
||||
(remove nil? expr)
|
||||
expr)
|
||||
[sqls params]
|
||||
(reduce-sql (map #(format-expr % {:nested true})
|
||||
(rest x)))]
|
||||
(into [(cond-> (str/join (str " " (sql-kw op) " ") sqls)
|
||||
nested
|
||||
(as-> s (str "(" s ")")))]
|
||||
params))
|
||||
(let [op' (sym->kw (first expr))
|
||||
op (get infix-aliases op' op')]
|
||||
(if (keyword? op')
|
||||
(cond (contains? @infix-ops op')
|
||||
(if (contains? #{:= :<>} op)
|
||||
(let [[_ a b & y] expr
|
||||
_ (when (seq y)
|
||||
(throw (ex-info (str "only binary "
|
||||
op
|
||||
op'
|
||||
" is supported")
|
||||
{:expr expr})))
|
||||
[s1 & p1] (format-expr a {:nested true})
|
||||
[s2 & p2] (format-expr b {:nested true})
|
||||
op (get infix-aliases op op)]
|
||||
(-> (if (and (#{:= :<>} op) (or (nil? a) (nil? b)))
|
||||
[s2 & p2] (format-expr b {:nested true})]
|
||||
(-> (if (or (nil? a) (nil? b))
|
||||
(str (if (nil? a)
|
||||
(if (nil? b) "NULL" s2)
|
||||
s1)
|
||||
|
|
@ -1591,7 +1580,22 @@
|
|||
(as-> s (str "(" s ")")))
|
||||
(vector)
|
||||
(into p1)
|
||||
(into p2))))
|
||||
(into p2)))
|
||||
(let [x (if (contains? @op-ignore-nil op)
|
||||
(remove nil? expr)
|
||||
expr)
|
||||
[sqls params]
|
||||
(reduce-sql (map #(format-expr % {:nested true})
|
||||
(rest x)))]
|
||||
(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)
|
||||
(= 1 (count sqls))
|
||||
(as-> s (str (sql-kw op) " " s))
|
||||
nested
|
||||
(as-> s (str "(" s ")")))]
|
||||
params)))
|
||||
(contains? #{:in :not-in} op)
|
||||
(let [[sql & params] (format-in op (rest expr))]
|
||||
(into [(if nested (str "(" sql ")") sql)] params))
|
||||
|
|
@ -1855,15 +1859,13 @@
|
|||
(contains? @special-syntax (sym->kw function)))
|
||||
|
||||
(defn register-op!
|
||||
"Register a new infix operator. Operators can be defined to be variadic (the
|
||||
default is that they are binary) and may choose to ignore `nil` arguments
|
||||
(this can make it easier to programmatically construct the DSL)."
|
||||
[op & {:keys [variadic ignore-nil]}]
|
||||
"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]}]
|
||||
(let [op (sym->kw op)]
|
||||
(assert (keyword? op))
|
||||
(swap! infix-ops conj op)
|
||||
(when variadic
|
||||
(swap! op-variadic conj op))
|
||||
(when ignore-nil
|
||||
(swap! op-ignore-nil conj op))))
|
||||
|
||||
|
|
@ -1955,7 +1957,7 @@
|
|||
(sql/format-expr [:primary-key])
|
||||
(sql/register-op! 'y)
|
||||
(sql/format {:where '[y 2 3]})
|
||||
(sql/register-op! :<=> :variadic true :ignore-nil true)
|
||||
(sql/register-op! :<=> :ignore-nil true)
|
||||
;; 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)]))
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
(def !regex !tilde)
|
||||
(def !iregex !tilde*)
|
||||
|
||||
(sql/register-op! :-> :variadic true)
|
||||
(sql/register-op! :->)
|
||||
(sql/register-op! :->>)
|
||||
(sql/register-op! :#>)
|
||||
(sql/register-op! :#>>)
|
||||
|
|
|
|||
|
|
@ -1062,3 +1062,12 @@ ORDER BY id = ? DESC
|
|||
(deftest issue-456-format-expr
|
||||
(is (= ["`x` + ?" 1]
|
||||
(sut/format [:+ :x 1] {:dialect :mysql}))))
|
||||
|
||||
(deftest issue-459-variadic-ops
|
||||
(sut/register-op! :op)
|
||||
(is (= ["SELECT OP a"]
|
||||
(sut/format {:select [[[:op :a]]]})))
|
||||
(is (= ["SELECT a OP b"]
|
||||
(sut/format {:select [[[:op :a :b]]]})))
|
||||
(is (= ["SELECT a OP b OP c"]
|
||||
(sut/format {:select [[[:op :a :b :c]]]}))))
|
||||
|
|
|
|||
Loading…
Reference in a new issue