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
|
# Changes
|
||||||
|
|
||||||
* 2.5.next in progress
|
* 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.
|
* 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.
|
* 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!
|
* 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]
|
["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.
|
> 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
|
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"]
|
["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
|
If you are using inheritance, you can specify `ONLY(table)` as a function
|
||||||
call: `[:only :table]`.
|
call: `[:only :table]`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -548,12 +548,43 @@
|
||||||
(format-temporal [:for :system-time :between [:inline "2000-12-16"] :and [:inline "2000-12-17"]])
|
(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
|
(defn- format-item-selection
|
||||||
"Format all the possible ways to represent a table/column selection."
|
"Format all the possible ways to represent a table/column selection."
|
||||||
[x as]
|
[x as]
|
||||||
(if (bigquery-*-except-replace? x)
|
(if (bigquery-*-except-replace? x)
|
||||||
(format-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)
|
_ (when (= ::too-many! temporal)
|
||||||
(throw (ex-info "illegal syntax in select expression"
|
(throw (ex-info "illegal syntax in select expression"
|
||||||
{:symbol selectable :alias alias :unexpected (nnext x)})))
|
{:symbol selectable :alias alias :unexpected (nnext x)})))
|
||||||
|
|
@ -569,16 +600,18 @@
|
||||||
(format-temporal temporal))]
|
(format-temporal temporal))]
|
||||||
|
|
||||||
(-> [(str sql
|
(-> [(str sql
|
||||||
(when sql''
|
(when sql'' ; temporal
|
||||||
(str " " sql''))
|
(str " " sql''))
|
||||||
(when sql'
|
(when sql' ; alias
|
||||||
(str (if as
|
(str (if as
|
||||||
(if (and (contains? *dialect* :as)
|
(if (and (contains? *dialect* :as)
|
||||||
(not (:as *dialect*)))
|
(not (:as *dialect*)))
|
||||||
" "
|
" "
|
||||||
" AS ")
|
" AS ")
|
||||||
" ")
|
" ")
|
||||||
sql')))]
|
sql'))
|
||||||
|
(when hints
|
||||||
|
(str " WITH (" hints ")")))]
|
||||||
(into params)
|
(into params)
|
||||||
(into params')
|
(into params')
|
||||||
(into params'')))))
|
(into params'')))))
|
||||||
|
|
@ -689,35 +722,6 @@
|
||||||
(let [[sqls params] (format-expr-list xs {:drop-ns true})]
|
(let [[sqls params] (format-expr-list xs {:drop-ns true})]
|
||||||
(into [(str "(" (str/join ", " sqls) ")")] params))))
|
(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]
|
(defn- format-selects-common [prefix as xs]
|
||||||
(let [qualifier (format-meta xs)
|
(let [qualifier (format-meta xs)
|
||||||
prefix (if prefix
|
prefix (if prefix
|
||||||
|
|
|
||||||
|
|
@ -1320,6 +1320,20 @@ ORDER BY id = ? DESC
|
||||||
(is (= ["SELECT ['a', 'b', 'c'] AS x"]
|
(is (= ["SELECT ['a', 'b', 'c'] AS x"]
|
||||||
(sut/format '{select [[[inline [lift ["a" "b" "c"]]] 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
|
(comment
|
||||||
;; partial (incorrect!) workaround for #407:
|
;; partial (incorrect!) workaround for #407:
|
||||||
(sut/format {:select :f.* :from [[:foo [:f :for :system-time]]] :where [:= :f.id 1]})
|
(sut/format {:select :f.* :from [[:foo [:f :for :system-time]]] :where [:= :f.id 1]})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue