address #471
This commit is contained in:
parent
a610f256dd
commit
0936095040
3 changed files with 127 additions and 51 deletions
|
|
@ -1,6 +1,7 @@
|
|||
# Changes
|
||||
|
||||
* 2.4.next in progress
|
||||
* Address [#471](https://github.com/seancorfield/honeysql/issues/471) by supported interspersed SQL keywords in function calls. Documentation TBD!
|
||||
* Fix [#467](https://github.com/seancorfield/honeysql/issues/467) by allowing single keywords (symbols) as a short hand for a single-element sequence in more constructs via PR [#470](https://github.com/seancorfield/honeysql/pull/470) [@p-himik](https://github.com/p-himik).
|
||||
* Address [#466](https://github.com/seancorfield/honeysql/issues/466) by treating `[:and]` as `TRUE` and `[:or]` as `FALSE`.
|
||||
* Fix [#465](https://github.com/seancorfield/honeysql/issues/465) to allow multiple columns in `:order-by` special syntax via PR [#468](https://github.com/seancorfield/honeysql/pull/468) [@p-himik](https://github.com/p-himik).
|
||||
|
|
|
|||
|
|
@ -465,6 +465,50 @@
|
|||
(let [[sqls params] (reduce-sql (map #(format-dsl %) xs))]
|
||||
(into [(str/join (str " " (sql-kw k) " ") sqls)] params)))
|
||||
|
||||
(defn- inline-kw?
|
||||
"Return true if the expression should be treated as an inline SQL keeyword."
|
||||
[expr]
|
||||
(and (ident? expr)
|
||||
(nil? (namespace expr))
|
||||
(re-find #"^\*[a-zA-Z]" (name expr))))
|
||||
|
||||
(defn format-interspersed-expr-list
|
||||
"If there are inline (SQL) keywords, use them to join the formatted
|
||||
expressions together. Otherwise behaves like plain format-expr-list.
|
||||
|
||||
This allows for argument lists like:
|
||||
* [:overlay :foo :*placing :?subs :*from 3 :*for 4]
|
||||
* [:trim :*leading-from :bar]"
|
||||
[args & [opts]]
|
||||
(loop [exprs (map #(format-expr % opts) (remove inline-kw? args))
|
||||
args args
|
||||
prev-in false
|
||||
result []]
|
||||
(if (seq args)
|
||||
(let [[arg & args'] args]
|
||||
(if (inline-kw? arg)
|
||||
(let [sql (sql-kw (keyword (subs (name arg) 1)))]
|
||||
(if (seq result)
|
||||
(let [[cur & params] (peek result)]
|
||||
(recur exprs args' true (conj (pop result)
|
||||
(into [(str cur " " sql)] params))))
|
||||
(recur exprs args' true (conj result [sql]))))
|
||||
(if prev-in
|
||||
(let [[cur & params] (peek result)
|
||||
[sql & params'] (first exprs)]
|
||||
(recur (rest exprs) args' false (conj (pop result)
|
||||
(-> [(str cur " " sql)]
|
||||
(into params)
|
||||
(into params')))))
|
||||
(recur (rest exprs) args' false (conj result (first exprs))))))
|
||||
(reduce-sql result))))
|
||||
|
||||
(comment
|
||||
(format-interspersed-expr-list [:foo :*placing :?subs :*from 3 :*for 4]
|
||||
{:params {:subs "bar"}})
|
||||
(format-interspersed-expr-list [:*leading-from " foo "] {})
|
||||
)
|
||||
|
||||
(defn format-expr-list
|
||||
"Given a sequence of expressions represented as data, return a pair
|
||||
where the first element is a sequence of SQL fragments and the second
|
||||
|
|
@ -1551,26 +1595,7 @@
|
|||
(raw-render xs))
|
||||
:within-group expr-clause-pairs}))
|
||||
|
||||
(defn format-expr
|
||||
"Given a data structure that represents a SQL expression and a hash
|
||||
map of options, return a vector containing a string -- the formatted
|
||||
SQL statement -- followed by any parameter values that SQL needs.
|
||||
|
||||
This is intended to be used when writing your own formatters to
|
||||
extend the DSL supported by HoneySQL."
|
||||
[expr & [{:keys [nested] :as opts}]]
|
||||
(cond (ident? expr)
|
||||
(format-var expr opts)
|
||||
|
||||
(map? expr)
|
||||
(format-dsl expr (assoc opts :nested true))
|
||||
|
||||
(sequential? expr)
|
||||
(let [op' (sym->kw (first expr))
|
||||
op (get infix-aliases op' op')]
|
||||
(if (keyword? op')
|
||||
(cond (contains? @infix-ops op')
|
||||
(if (contains? #{:= :<>} op)
|
||||
(defn- format-equality-expr [op' op expr nested]
|
||||
(let [[_ a b & y] expr
|
||||
_ (when (seq y)
|
||||
(throw (ex-info (str "only binary "
|
||||
|
|
@ -1589,7 +1614,9 @@
|
|||
(as-> s (str "(" s ")")))
|
||||
(vector)
|
||||
(into p1)
|
||||
(into p2)))
|
||||
(into p2))))
|
||||
|
||||
(defn- format-infix-expr [op' op expr nested]
|
||||
(let [args (cond->> (rest expr)
|
||||
(contains? @op-ignore-nil op)
|
||||
(remove nil?))
|
||||
|
|
@ -1613,15 +1640,10 @@
|
|||
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))
|
||||
(contains? @special-syntax op)
|
||||
(let [formatter (get @special-syntax op)]
|
||||
(formatter op (rest expr)))
|
||||
:else
|
||||
|
||||
(defn- format-fn-call-expr [op expr]
|
||||
(let [args (rest expr)
|
||||
[sqls params] (format-expr-list args)]
|
||||
[sqls params] (format-interspersed-expr-list args)]
|
||||
(into [(str (sql-kw op)
|
||||
(if (and (= 1 (count args))
|
||||
(map? (first args))
|
||||
|
|
@ -1629,6 +1651,37 @@
|
|||
(str " " (first sqls))
|
||||
(str "(" (str/join ", " sqls) ")")))]
|
||||
params)))
|
||||
|
||||
(defn format-expr
|
||||
"Given a data structure that represents a SQL expression and a hash
|
||||
map of options, return a vector containing a string -- the formatted
|
||||
SQL statement -- followed by any parameter values that SQL needs.
|
||||
|
||||
This is intended to be used when writing your own formatters to
|
||||
extend the DSL supported by HoneySQL."
|
||||
[expr & [{:keys [nested] :as opts}]]
|
||||
(cond (ident? expr)
|
||||
(format-var expr opts)
|
||||
|
||||
(map? expr)
|
||||
(format-dsl expr (assoc opts :nested true))
|
||||
|
||||
(sequential? expr)
|
||||
(let [op' (sym->kw (first expr))
|
||||
op (get infix-aliases op' op')]
|
||||
(if (keyword? op')
|
||||
(cond (contains? @infix-ops op')
|
||||
(if (contains? #{:= :<>} op)
|
||||
(format-equality-expr op' op expr nested)
|
||||
(format-infix-expr op' op expr nested))
|
||||
(contains? #{:in :not-in} op)
|
||||
(let [[sql & params] (format-in op (rest expr))]
|
||||
(into [(if nested (str "(" sql ")") sql)] params))
|
||||
(contains? @special-syntax op)
|
||||
(let [formatter (get @special-syntax op)]
|
||||
(formatter op (rest expr)))
|
||||
:else
|
||||
(format-fn-call-expr op expr))
|
||||
(let [[sqls params] (format-expr-list expr)]
|
||||
(into [(str "(" (str/join ", " sqls) ")")] params))))
|
||||
|
||||
|
|
|
|||
|
|
@ -1108,3 +1108,25 @@ ORDER BY id = ? DESC
|
|||
(sut/format {:select [[[:- 1 2]]]})))
|
||||
(is (= ["SELECT ? ~ ?" "a" "b"] ; regex op
|
||||
(sut/format {:select [[[(keyword "~") "a" "b"]]]}))))
|
||||
|
||||
(deftest issue-471-interspersed-kws
|
||||
(testing "overlay"
|
||||
(is (= ["SELECT OVERLAY(foo PLACING ? FROM ? FOR ?)"
|
||||
"bar" 3 4]
|
||||
(sut/format {:select [[[:overlay :foo :*placing "bar" :*from 3 :*for 4]]]}))))
|
||||
(testing "position"
|
||||
(is (= ["SELECT POSITION(? IN bar)" "foo"]
|
||||
(sut/format {:select [[[:position "foo" :*in :bar]]]}))))
|
||||
(testing "trim"
|
||||
(is (= ["SELECT TRIM(LEADING FROM bar)"]
|
||||
(sut/format {:select [[[:trim :*leading :*from :bar]]]})))
|
||||
(is (= ["SELECT TRIM(LEADING FROM bar)"]
|
||||
(sut/format {:select [[[:trim :*leading-from :bar]]]}))))
|
||||
(testing "extract"
|
||||
(is (= ["SELECT EXTRACT(CENTURY FROM TIMESTAMP '2000-12-16 12:21:13')"]
|
||||
(sut/format {:select [[[:extract :*century :*from
|
||||
:*timestamp [:inline "2000-12-16 12:21:13"]]]]}))))
|
||||
(testing "xmlelement"
|
||||
(is (= ["SELECT XMLELEMENT(NAME \"foo$bar\", XMLATTRIBUTES('xyz' AS \"a&b\"))"]
|
||||
(sut/format {:select [[[:xmlelement :*name :foo$bar
|
||||
[:xmlattributes [:inline "xyz"] :*as :a&b]]]]})))))
|
||||
|
|
|
|||
Loading…
Reference in a new issue