Fixes #338 properly by making offset/fetch smarter
This commit is contained in:
parent
52e2a57fca
commit
50bbfef07f
4 changed files with 93 additions and 43 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -1,13 +1,11 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
* 2.0.next (gold) in progress
|
* 2.0.0-rc5 in progress
|
||||||
* Address #332 by improving `:cross-join` documentation.
|
* Fix #338 by producing `OFFSET n ROWS` (or `ROW` if `n` is 1) if `:fetch` is present or `:sqlserver` dialect is specified; and by producing `FETCH NEXT n ROWS ONLY` (or `ROW` is `n` is 1; or `FIRST` instead of `NEXT` if `:offset` is not present).
|
||||||
* Fix `fetch` helper.
|
|
||||||
|
|
||||||
* 2.0.0-rc4 (for testing; 2021-07-17)
|
|
||||||
* Fix #338 by adding `ONLY` to `:fetch`.
|
|
||||||
* Fix #337 by switching to `clojure.test` even for ClojureScript.
|
* Fix #337 by switching to `clojure.test` even for ClojureScript.
|
||||||
|
* Address #332 by improving `:cross-join` documentation.
|
||||||
* Address #330 by improving exception when a non-entity is encountered where an entity is expected.
|
* Address #330 by improving exception when a non-entity is encountered where an entity is expected.
|
||||||
|
* Fix `fetch` helper (it previously returned an `:offset` clause).
|
||||||
* Fix bug in unrolling nested argument to `with-columns` helper.
|
* Fix bug in unrolling nested argument to `with-columns` helper.
|
||||||
|
|
||||||
* 2.0.0-rc3 (for testing; 2021-06-16)
|
* 2.0.0-rc3 (for testing; 2021-06-16)
|
||||||
|
|
|
||||||
4
pom.xml
4
pom.xml
|
|
@ -3,7 +3,7 @@
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.github.seancorfield</groupId>
|
<groupId>com.github.seancorfield</groupId>
|
||||||
<artifactId>honeysql</artifactId>
|
<artifactId>honeysql</artifactId>
|
||||||
<version>2.0.0-rc4</version>
|
<version>2.0.0-rc5</version>
|
||||||
<name>honeysql</name>
|
<name>honeysql</name>
|
||||||
<description>SQL as Clojure data structures.</description>
|
<description>SQL as Clojure data structures.</description>
|
||||||
<url>https://github.com/seancorfield/honeysql</url>
|
<url>https://github.com/seancorfield/honeysql</url>
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
<url>https://github.com/seancorfield/honeysql</url>
|
<url>https://github.com/seancorfield/honeysql</url>
|
||||||
<connection>scm:git:git://github.com/seancorfield/honeysql.git</connection>
|
<connection>scm:git:git://github.com/seancorfield/honeysql.git</connection>
|
||||||
<developerConnection>scm:git:ssh://git@github.com/seancorfield/honeysql.git</developerConnection>
|
<developerConnection>scm:git:ssh://git@github.com/seancorfield/honeysql.git</developerConnection>
|
||||||
<tag>v2.0.0-rc4</tag>
|
<tag>v2.0.0-rc5</tag>
|
||||||
</scm>
|
</scm>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
||||||
|
|
@ -86,11 +86,14 @@
|
||||||
(conj order clause))))
|
(conj order clause))))
|
||||||
|
|
||||||
(def ^:private dialects
|
(def ^:private dialects
|
||||||
|
(reduce-kv (fn [m k v]
|
||||||
|
(assoc m k (assoc v :dialect k)))
|
||||||
|
{}
|
||||||
{:ansi {:quote #(str \" % \")}
|
{:ansi {:quote #(str \" % \")}
|
||||||
:sqlserver {:quote #(str \[ % \])}
|
:sqlserver {:quote #(str \[ % \])}
|
||||||
:mysql {:quote #(str \` % \`)
|
:mysql {:quote #(str \` % \`)
|
||||||
:clause-order-fn #(add-clause-before % :set :where)}
|
:clause-order-fn #(add-clause-before % :set :where)}
|
||||||
:oracle {:quote #(str \" % \") :as false}})
|
:oracle {:quote #(str \" % \") :as false}}))
|
||||||
|
|
||||||
; should become defonce
|
; should become defonce
|
||||||
(def ^:private default-dialect (atom (:ansi dialects)))
|
(def ^:private default-dialect (atom (:ansi dialects)))
|
||||||
|
|
@ -109,9 +112,26 @@
|
||||||
(def ^:private ^:dynamic *allow-suspicious-entities* false)
|
(def ^:private ^:dynamic *allow-suspicious-entities* false)
|
||||||
;; "linting" mode (:none, :basic, :strict):
|
;; "linting" mode (:none, :basic, :strict):
|
||||||
(def ^:private ^:dynamic *checking* :none)
|
(def ^:private ^:dynamic *checking* :none)
|
||||||
|
;; the current DSL hash map being formatted (for contains-clause?):
|
||||||
|
(def ^:private ^:dynamic *dsl* nil)
|
||||||
|
|
||||||
;; clause helpers
|
;; clause helpers
|
||||||
|
|
||||||
|
(defn contains-clause?
|
||||||
|
"Returns true if the current DSL expression being formatted
|
||||||
|
contains the specified clause (as a keyword or symbol)."
|
||||||
|
[clause]
|
||||||
|
(or (contains? *dsl* clause)
|
||||||
|
(contains? *dsl*
|
||||||
|
(if (keyword? clause)
|
||||||
|
(symbol (name clause))
|
||||||
|
(keyword (name clause))))))
|
||||||
|
|
||||||
|
(defn- sql-server?
|
||||||
|
"Helper to detect if SQL Server is the current dialect."
|
||||||
|
[]
|
||||||
|
(= :sqlserver (:dialect *dialect*)))
|
||||||
|
|
||||||
;; String.toUpperCase() or `str/upper-case` for that matter converts the
|
;; String.toUpperCase() or `str/upper-case` for that matter converts the
|
||||||
;; string to uppercase for the DEFAULT LOCALE. Normally this does what you'd
|
;; string to uppercase for the DEFAULT LOCALE. Normally this does what you'd
|
||||||
;; expect but things like `inner join` get converted to `İNNER JOİN` (dot over
|
;; expect but things like `inner join` get converted to `İNNER JOİN` (dot over
|
||||||
|
|
@ -869,10 +889,18 @@
|
||||||
:partition-by #'format-selects
|
:partition-by #'format-selects
|
||||||
:order-by #'format-order-by
|
:order-by #'format-order-by
|
||||||
:limit #'format-on-expr
|
:limit #'format-on-expr
|
||||||
:offset #'format-on-expr
|
:offset (fn [_ x]
|
||||||
|
(if (or (contains-clause? :fetch) (sql-server?))
|
||||||
|
(let [[sql & params] (format-on-expr :offset x)
|
||||||
|
rows (if (and (number? x) (== 1 x)) :row :rows)]
|
||||||
|
(into [(str sql " " (sql-kw rows))] params))
|
||||||
|
;; format in the old style:
|
||||||
|
(format-on-expr :offset x)))
|
||||||
:fetch (fn [_ x]
|
:fetch (fn [_ x]
|
||||||
(let [[sql & params] (format-on-expr :fetch x)]
|
(let [which (if (contains-clause? :offset) :fetch-next :fetch-first)
|
||||||
(into [(str sql " " (sql-kw :only))] params)))
|
rows (if (and (number? x) (== 1 x)) :row-only :rows-only)
|
||||||
|
[sql & params] (format-on-expr which x)]
|
||||||
|
(into [(str sql " " (sql-kw rows))] params)))
|
||||||
:for #'format-lock-strength
|
:for #'format-lock-strength
|
||||||
:lock #'format-lock-strength
|
:lock #'format-lock-strength
|
||||||
:values #'format-values
|
:values #'format-values
|
||||||
|
|
@ -907,6 +935,7 @@
|
||||||
This is intended to be used when writing your own formatters to
|
This is intended to be used when writing your own formatters to
|
||||||
extend the DSL supported by HoneySQL."
|
extend the DSL supported by HoneySQL."
|
||||||
[statement-map & [{:keys [aliased nested pretty]}]]
|
[statement-map & [{:keys [aliased nested pretty]}]]
|
||||||
|
(binding [*dsl* statement-map]
|
||||||
(let [[sqls params leftover]
|
(let [[sqls params leftover]
|
||||||
(reduce (fn [[sql params leftover] k]
|
(reduce (fn [[sql params leftover] k]
|
||||||
(if-some [xs (if-some [xs (k leftover)]
|
(if-some [xs (if-some [xs (k leftover)]
|
||||||
|
|
@ -929,7 +958,7 @@
|
||||||
pretty
|
pretty
|
||||||
(as-> s (str "\n" s "\n"))
|
(as-> s (str "\n" s "\n"))
|
||||||
(and nested (not aliased))
|
(and nested (not aliased))
|
||||||
(as-> s (str "(" s ")")))] params))))
|
(as-> s (str "(" s ")")))] params)))))
|
||||||
|
|
||||||
(def ^:private infix-aliases
|
(def ^:private infix-aliases
|
||||||
"Provided for backward compatibility with earlier HoneySQL versions."
|
"Provided for backward compatibility with earlier HoneySQL versions."
|
||||||
|
|
|
||||||
|
|
@ -783,7 +783,30 @@ ORDER BY id = ? DESC
|
||||||
:join [[{:select :a :from :b :where [:= :id 123]} :x] :y]
|
:join [[{:select :a :from :b :where [:= :id 123]} :x] :y]
|
||||||
:where [:= :id 456]})))))
|
:where [:= :id 456]})))))
|
||||||
|
|
||||||
(deftest fetch-offset-issue338
|
(deftest fetch-offset-issue-338
|
||||||
(is (= ["SELECT foo FROM bar OFFSET ? FETCH ? ONLY" 20 10]
|
(testing "default offset (with and without limit)"
|
||||||
|
(is (= ["SELECT foo FROM bar LIMIT ? OFFSET ?" 10 20]
|
||||||
(format {:select :foo :from :bar
|
(format {:select :foo :from :bar
|
||||||
:fetch 10 :offset 20}))))
|
:limit 10 :offset 20})))
|
||||||
|
(is (= ["SELECT foo FROM bar OFFSET ?" 20]
|
||||||
|
(format {:select :foo :from :bar
|
||||||
|
:offset 20}))))
|
||||||
|
(testing "default offset / fetch"
|
||||||
|
(is (= ["SELECT foo FROM bar OFFSET ? ROWS FETCH NEXT ? ROWS ONLY" 20 10]
|
||||||
|
(format {:select :foo :from :bar
|
||||||
|
:fetch 10 :offset 20})))
|
||||||
|
(is (= ["SELECT foo FROM bar OFFSET ? ROW FETCH NEXT ? ROW ONLY" 1 1]
|
||||||
|
(format {:select :foo :from :bar
|
||||||
|
:fetch 1 :offset 1})))
|
||||||
|
(is (= ["SELECT foo FROM bar FETCH FIRST ? ROWS ONLY" 2]
|
||||||
|
(format {:select :foo :from :bar
|
||||||
|
:fetch 2}))))
|
||||||
|
(testing "SQL Server offset"
|
||||||
|
(is (= ["SELECT [foo] FROM [bar] OFFSET ? ROWS FETCH NEXT ? ROWS ONLY" 20 10]
|
||||||
|
(format {:select :foo :from :bar
|
||||||
|
:fetch 10 :offset 20}
|
||||||
|
{:dialect :sqlserver})))
|
||||||
|
(is (= ["SELECT [foo] FROM [bar] OFFSET ? ROWS" 20]
|
||||||
|
(format {:select :foo :from :bar
|
||||||
|
:offset 20}
|
||||||
|
{:dialect :sqlserver})))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue