fix MS SQL table hints in FROM clause (e.g. NOLOCK) #522
Signed-off-by: Sean Corfield <sean@corfield.org>
This commit is contained in:
parent
35f4c674e9
commit
d64177bde5
4 changed files with 88 additions and 33 deletions
|
|
@ -1,6 +1,7 @@
|
|||
# Changes
|
||||
|
||||
* 2.5.next in progress
|
||||
* Address [#522](https://github.com/seancorfield/honeysql/issues/522) by supporting metadata on table specifications in `:from` and `:join` clauses to provide index hints (SQL Server).
|
||||
* Address [#520](https://github.com/seancorfield/honeysql/issues/520) by expanding how `:inline` works, to support a sequence of arguments.
|
||||
* Fix [#518](https://github.com/seancorfield/honeysql/issues/518) by moving temporal clause before alias.
|
||||
* Address [#495](https://github.com/seancorfield/honeysql/issues/495) by adding (experimental) `format&` and `formatv` macros (`.clj` only!) -- purely for discussion: may be removed in a subsequent release!
|
||||
|
|
|
|||
|
|
@ -828,6 +828,23 @@ user=> (sql/format {:select [:u.username]
|
|||
["SELECT u.username FROM user FOR SYSTEM_TIME FROM '2019-08-01 15:23:00' TO '2019-08-01 15:24:00' AS u WHERE u.id = ?" 9]
|
||||
```
|
||||
|
||||
As of 2.5.next, HoneySQL supports metadata on a table expression to provide
|
||||
database-specific hints, such as SQL Server's `WITH (..)` clause:
|
||||
|
||||
```clojure
|
||||
user=> (sql/format {:select [:col]
|
||||
:from [^:nolock [:table]]
|
||||
:where [:= :id 9]})
|
||||
["SELECT col FROM table WITH (NOLOCK) WHERE id = ?" 9]
|
||||
user=> (sql/format {:select [:col]
|
||||
:from [^:nolock [:table :t]]
|
||||
:where [:= :id 9]})
|
||||
["SELECT col FROM table AS t WITH (NOLOCK) WHERE id = ?" 9]
|
||||
```
|
||||
|
||||
Since you cannot put metadata on a keyword, the table name must be written as
|
||||
a vector even when you have no alias.
|
||||
|
||||
> Note: the actual formatting of a `:from` clause is currently identical to the formatting of a `:select` clause.
|
||||
|
||||
If you are using inheritance, you can specify `ONLY(table)` as a function
|
||||
|
|
@ -918,6 +935,25 @@ user=> (sql/format {:select [:t.ref :pp.code]
|
|||
["SELECT t.ref, pp.code FROM transaction AS t LEFT JOIN paypal_tx AS pp USING (id) WHERE ? = pp.status" "settled"]
|
||||
```
|
||||
|
||||
As of 2.5.next, HoneySQL supports metadata on a table expression to provide
|
||||
database-specific hints, such as SQL Server's `WITH (..)` clause:
|
||||
|
||||
```clojure
|
||||
user=> (sql/format {:select [:col]
|
||||
:from [:table]
|
||||
:join [^:nolock [:extra] [:= :table.extra_id :extra.id]]
|
||||
:where [:= :id 9]})
|
||||
["SELECT col FROM table INNER JOIN extra WITH (NOLOCK) ON table.extra_id = extra.id WHERE id = ?" 9]
|
||||
user=> (sql/format {:select [:col]
|
||||
:from [[:table :t]]
|
||||
:join [^:nolock [:extra :x] [:= :t.extra_id :x.id]]
|
||||
:where [:= :id 9]})
|
||||
["SELECT col FROM table AS t INNER JOIN extra AS x WITH (NOLOCK) ON t.extra_id = x.id WHERE id = ?" 9]
|
||||
```
|
||||
|
||||
Since you cannot put metadata on a keyword, the table name must be written as
|
||||
a vector even when you have no alias.
|
||||
|
||||
If you are using inheritance, you can specify `ONLY(table)` as a function
|
||||
call: `[:only :table]`.
|
||||
|
||||
|
|
|
|||
|
|
@ -548,12 +548,43 @@
|
|||
(format-temporal [:for :system-time :between [:inline "2000-12-16"] :and [:inline "2000-12-17"]])
|
||||
)
|
||||
|
||||
(defn- format-meta
|
||||
"If the expression has metadata, format it as a sequence of keywords,
|
||||
treating `:foo true` as `FOO` and `:foo :bar` as `FOO BAR`.
|
||||
Return nil if there is no metadata."
|
||||
[x & [sep]]
|
||||
(when-let [data (meta x)]
|
||||
(let [items (reduce-kv (fn [acc k v]
|
||||
(if (true? v)
|
||||
(conj acc k)
|
||||
(conj acc k v)))
|
||||
[]
|
||||
(reduce dissoc
|
||||
data
|
||||
(into [; remove the somewhat "standard" metadata:
|
||||
:line :column :file
|
||||
:end-line :end-column]
|
||||
*ignored-metadata*)))]
|
||||
(when (seq items)
|
||||
(str/join (str sep " ") (mapv sql-kw items))))))
|
||||
|
||||
(comment
|
||||
(format-meta ^{:foo true :bar :baz} [])
|
||||
|
||||
(binding [*ignored-metadata* [:bar]]
|
||||
(format-meta ^{:foo true :bar :baz} []))
|
||||
|
||||
(format-meta [])
|
||||
(format-meta ^:nolock ^:uncommited [] ",")
|
||||
)
|
||||
|
||||
(defn- format-item-selection
|
||||
"Format all the possible ways to represent a table/column selection."
|
||||
[x as]
|
||||
(if (bigquery-*-except-replace? x)
|
||||
(format-bigquery-*-except-replace x)
|
||||
(let [[selectable alias temporal] (split-alias-temporal x)
|
||||
(let [hints (format-meta x ",")
|
||||
[selectable alias temporal] (split-alias-temporal x)
|
||||
_ (when (= ::too-many! temporal)
|
||||
(throw (ex-info "illegal syntax in select expression"
|
||||
{:symbol selectable :alias alias :unexpected (nnext x)})))
|
||||
|
|
@ -569,16 +600,18 @@
|
|||
(format-temporal temporal))]
|
||||
|
||||
(-> [(str sql
|
||||
(when sql''
|
||||
(when sql'' ; temporal
|
||||
(str " " sql''))
|
||||
(when sql'
|
||||
(when sql' ; alias
|
||||
(str (if as
|
||||
(if (and (contains? *dialect* :as)
|
||||
(not (:as *dialect*)))
|
||||
" "
|
||||
" AS ")
|
||||
" ")
|
||||
sql')))]
|
||||
sql'))
|
||||
(when hints
|
||||
(str " WITH (" hints ")")))]
|
||||
(into params)
|
||||
(into params')
|
||||
(into params'')))))
|
||||
|
|
@ -689,35 +722,6 @@
|
|||
(let [[sqls params] (format-expr-list xs {:drop-ns true})]
|
||||
(into [(str "(" (str/join ", " sqls) ")")] params))))
|
||||
|
||||
(defn- format-meta
|
||||
"If the expression has metadata, format it as a sequence of keywords,
|
||||
treating `:foo true` as `FOO` and `:foo :bar` as `FOO BAR`.
|
||||
Return nil if there is no metadata."
|
||||
[x]
|
||||
(when-let [data (meta x)]
|
||||
(let [items (reduce-kv (fn [acc k v]
|
||||
(if (true? v)
|
||||
(conj acc k)
|
||||
(conj acc k v)))
|
||||
[]
|
||||
(reduce dissoc
|
||||
data
|
||||
(into [; remove the somewhat "standard" metadata:
|
||||
:line :column :file
|
||||
:end-line :end-column]
|
||||
*ignored-metadata*)))]
|
||||
(when (seq items)
|
||||
(str/join " " (mapv sql-kw items))))))
|
||||
|
||||
(comment
|
||||
(format-meta ^{:foo true :bar :baz} [])
|
||||
|
||||
(binding [*ignored-metadata* [:bar]]
|
||||
(format-meta ^{:foo true :bar :baz} []))
|
||||
|
||||
(format-meta [])
|
||||
)
|
||||
|
||||
(defn- format-selects-common [prefix as xs]
|
||||
(let [qualifier (format-meta xs)
|
||||
prefix (if prefix
|
||||
|
|
|
|||
|
|
@ -1320,6 +1320,20 @@ ORDER BY id = ? DESC
|
|||
(is (= ["SELECT ['a', 'b', 'c'] AS x"]
|
||||
(sut/format '{select [[[inline [lift ["a" "b" "c"]]] x]]})))))
|
||||
|
||||
(deftest issue-522
|
||||
(testing "from with metadata"
|
||||
(is (= ["SELECT * FROM table WITH (HINT)"]
|
||||
(sut/format {:select [:*] :from [^:hint [:table]]})))
|
||||
;; hash map (metadata) is unordered:
|
||||
(is (or (= ["SELECT * FROM table WITH (ABC, DEF)"]
|
||||
(sut/format {:select [:*] :from [^:abc ^:def [:table]]}))
|
||||
(= ["SELECT * FROM table WITH (DEF, ABC)"]
|
||||
(sut/format {:select [:*] :from [^:abc ^:def [:table]]}))))
|
||||
(is (or (= ["SELECT * FROM table WITH (ABC, DEF)"]
|
||||
(sut/format {:select [:*] :from [^{:abc true :def true} [:table]]}))
|
||||
(= ["SELECT * FROM table WITH (DEF, ABC)"]
|
||||
(sut/format {:select [:*] :from [^{:abc true :def true} [:table]]}))))))
|
||||
|
||||
(comment
|
||||
;; partial (incorrect!) workaround for #407:
|
||||
(sut/format {:select :f.* :from [[:foo [:f :for :system-time]]] :where [:= :f.id 1]})
|
||||
|
|
|
|||
Loading…
Reference in a new issue