From 82ee465820e7893eeabf8256a8e621798643806c Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 13 Mar 2021 12:36:25 -0800 Subject: [PATCH] Fixes #292 by supporting offset/fetch --- CHANGELOG.md | 2 +- doc/clause-reference.md | 21 ++++++++++++++------- doc/differences-from-1-x.md | 2 +- doc/getting-started.md | 3 +-- src/honey/sql.cljc | 11 ++++------- src/honey/sql/helpers.cljc | 24 ++++++++++++++++++++++-- 6 files changed, 43 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b72a7e1..f0b0627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ * Fix #301 by adding support for `CREATE`/`DROP`/`REFRESH` on `MATERIALIZED VIEW`. * Add tests to confirm #299 does not affect v2. * Confirm the whole of the [nilenso/honeysql-postgres](https://github.com/nilenso/honeysql-postgres) is implemented out-of-the-box (#293). - * Add support for `SELECT TOP` (#292). + * Fix #292 by adding support for `SELECT TOP` and `OFFSET`/`FETCH`. * Reconcile `where` behavior with recent 1.0 changes (porting #283 to v2). * Fix #280 by adding `:escape` as special syntax for regular expression patterns. * Fix #277 by adding `:join-by`/`join-by` so that you can have multiple `JOIN`'s in a specific order. diff --git a/doc/clause-reference.md b/doc/clause-reference.md index 21a0458..78e153d 100644 --- a/doc/clause-reference.md +++ b/doc/clause-reference.md @@ -570,18 +570,27 @@ user=> (sql/format {:select [:*] :from :table ["SELECT * FROM table ORDER BY status ASC, YEAR(created_date) ASC"] ``` -## limit, offset (MySQL) +## limit, offset, fetch -Both `:limit` and `:offset` expect a single SQL expression: +Some databases, including MySQL, support `:limit` and `:offset` +for paginated queries, other databases support `:offset` and +`fetch` for that (which is ANSI-compliant and should be +preferred if your database supports it). All three expect a +single SQL expression: ```clojure user=> (sql/format {:select [:id :name] :from [:table] - :limit 20 :offset 20}) -["SELECT id, name FROM table LIMIT ? OFFSET ?" 20 20] + :limit 10 :offset 20}) +["SELECT id, name FROM table LIMIT ? OFFSET ?" 10 20] +user=> (sql/format {:select [:id :name] + :from [:table] + :offset 20 :fetch 10}) +["SELECT id, name FROM table OFFSET ? FETCH ? ONLY" 20 10] ``` -> Note: In the prerelease, these MySQL-specific clauses are in the default dialect but these will be moved to the `:mysql` dialect. +All three are available in all dialects for HoneySQL so it +is up to you to choose the correct pair for your database. ## for @@ -627,8 +636,6 @@ expected to be just one of those three mentioned above). The syntax accepted for MySQL's `:lock` is exactly the same as the `:for` clause above. -> Note: In the prerelease, this MySQL-specific clauses is in the default dialect but this will be moved to the `:mysql` dialect. - ## values `:values` accepts either a sequence of hash maps representing diff --git a/doc/differences-from-1-x.md b/doc/differences-from-1-x.md index 3277b6d..4f63c8d 100644 --- a/doc/differences-from-1-x.md +++ b/doc/differences-from-1-x.md @@ -74,7 +74,7 @@ Other `honeysql.core` functions that no longer exist include: `build`, `qualify` You can now select a non-ANSI dialect of SQL using the new `honey.sql/set-dialect!` function (which sets a default dialect for all `format` operations) or by passing the new `:dialect` option to the `format` function. `:ansi` is the default dialect (which will mostly incorporate PostgreSQL usage over time). Other dialects supported are `:mysql` (which has a different quoting strategy and uses a different ranking for the `:set` clause), `:oracle` (which is essentially the `:ansi` dialect but will control other things over time), and `:sqlserver` (which is essentially the `:ansi` dialect but with a different quoting strategy). Other dialects and changes may be added over time. -> Note: `:limit` and `:offset` are currently in the default `:ansi` dialect even though they are MySQL-specific. This will change as the dialects are fleshed out. I plan to add `:top` for `:sqlserver` and `:offset` / `:fetch` for `:ansi`, at which point `:limit` / `:offset` will become MySQL-only. +> Note: in general, all clauses are available in all dialects in HoneySQL unless the syntax of the clauses conflict between dialects (currently, no such clauses exist). The `:mysql` dialect is the only one so far that changes the priority ordering of a few clauses. ## Option Changes diff --git a/doc/getting-started.md b/doc/getting-started.md index f49781c..5455c3a 100644 --- a/doc/getting-started.md +++ b/doc/getting-started.md @@ -259,8 +259,7 @@ The `:sqlserver` dialect uses `[`..`]` and the `:mysql` dialect uses ```..```. In addition, the `:oracle` dialect disables `AS` in aliases. Currently, the only dialect that has substantive differences from -the others is `:mysql` which has a `:lock` clause (that is very -similar to the ANSI `:for` clause) and for which the `:set` clause +the others is `:mysql` for which the `:set` clause has a different precedence than ANSI SQL. You can change the dialect globally using the `set-dialect!` function, diff --git a/src/honey/sql.cljc b/src/honey/sql.cljc index a193595..0f1277e 100644 --- a/src/honey/sql.cljc +++ b/src/honey/sql.cljc @@ -53,7 +53,7 @@ :cross-join :where :group-by :having :window :partition-by - :order-by :limit :offset :for :values + :order-by :limit :offset :fetch :for :lock :values :on-conflict :on-constraint :do-nothing :do-update-set :on-duplicate-key-update :returning :with-data]) @@ -79,14 +79,9 @@ :sqlserver {:quote #(str \[ % \])} :mysql {:quote #(str \` % \`) :clause-order-fn (fn [order] - ;; :lock is like :for - (swap! clause-format assoc :lock - (get @clause-format :for)) ;; MySQL :set has different priority - ;; and :lock is between :for and :values (-> (filterv (complement #{:set}) order) - (add-clause-before :set :where) - (add-clause-before :lock :values)))} + (add-clause-before :set :where)))} :oracle {:quote #(str \" % \") :as false}}) ; should become defonce @@ -778,7 +773,9 @@ :order-by #'format-order-by :limit #'format-on-expr :offset #'format-on-expr + :fetch #'format-on-expr :for #'format-lock-strength + :lock #'format-lock-strength :values #'format-values :on-conflict #'format-on-conflict :on-constraint #'format-selector diff --git a/src/honey/sql/helpers.cljc b/src/honey/sql/helpers.cljc index 6a90f04..28d1633 100644 --- a/src/honey/sql/helpers.cljc +++ b/src/honey/sql/helpers.cljc @@ -527,13 +527,18 @@ (limit 40) Produces: LIMIT ? - Parameters: 40" + Parameters: 40 + + The two-argument syntax is not supported: use `offset` + instead: + + `LIMIT 20,10` is equivalent to `LIMIT 10 OFFSET 20`" {:arglists '([limit])} [& args] (generic-1 :limit args)) (defn offset - "Specific to MySQL, accepts a single SQL expression: + "Accepts a single SQL expression: (offset 10) @@ -543,10 +548,25 @@ [& args] (generic-1 :offset args)) +(defn fetch + "Accepts a single SQL expression: + + (fetch 10) + + Produces: FETCH ? ONLY + Parameters: 10" + {:arglists '([offset])} + [& args] + (generic-1 :offset args)) + (defn for [& args] (generic-1 :for args)) +(defn lock + [& args] + (generic-1 :lock args)) + (defn values "Accepts a single argument: a collection of row values. Each row value can be either a sequence of column values