Fixes #297 by adding into/bulk-collect-into

This commit is contained in:
Sean Corfield 2021-03-13 13:42:08 -08:00
parent cff1e5b43c
commit 16d04a1dfd
5 changed files with 82 additions and 10 deletions

View file

@ -7,6 +7,7 @@
* Fix #303 by supporting MySQL's `ON DUPLICATE KEY UPDATE`.
* Fix #301 by adding support for `CREATE`/`DROP`/`REFRESH` on `MATERIALIZED VIEW`.
* Add tests to confirm #299 does not affect v2.
* Fix #297 by adding both `SELECT .. INTO ..` and `SELECT .. BULK COLLECT INTO ..`.
* Confirm the whole of the [nilenso/honeysql-postgres](https://github.com/nilenso/honeysql-postgres) is implemented out-of-the-box (#293).
* Fix #292 by adding support for `SELECT TOP` and `OFFSET`/`FETCH`.
* Fix #284 by adding support for `LATERAL` (as special syntax, with a helper).

View file

@ -247,6 +247,28 @@ user=> (sql/format '{select-distinct-on [[a b] c d]
["SELECT DISTINCT ON(a, b) c, d FROM table"]
```
## into
Used for selecting rows into a new table, optional in another database:
```clojure
user=> (sql/format '{select * into newtable from mytable})
["SELECT * INTO newtable FROM mytable"]
user=> (sql/format '{select * into [newtable otherdb] from mytable})
["SELECT * INTO newtable IN otherdb FROM mytable"]
```
## bulk-collect-into
Used for selecting rows into an array variable, with an optional limit:
```clojure
user=> (sql/format '{select * bulk-collect-into arrv from mytable})
["SELECT * BULK COLLECT INTO arrv FROM mytable"]
user=> (sql/format '{select * bulk-collect-into [arrv 100] from mytable})
["SELECT * BULK COLLECT INTO arrv LIMIT ? FROM mytable" 100]
```
## insert-into
There are three use cases with `:insert-into`.

View file

@ -46,6 +46,7 @@
;; then SQL clauses in priority order:
:nest :with :with-recursive :intersect :union :union-all :except :except-all
:select :select-distinct :select-distinct-on :select-top :select-distinct-top
:into :bulk-collect-into
:insert-into :update :delete :delete-from :truncate
:columns :set :from :using
:join-by
@ -336,6 +337,17 @@
cols)]
(-> [sql'] (into params) (into params'))))
(defn- format-select-into [k xs]
(let [[v e] (if (sequential? xs) xs [xs])
[sql & params] (when e (format-expr e))]
(into [(str (sql-kw k) " " (format-entity v)
(when sql
(str " "
(sql-kw (if (= :into k) :in :limit))
" "
sql)))]
params)))
(defn- format-with-part [x]
(if (sequential? x)
(let [[sql & params] (format-dsl (second x))]
@ -752,6 +764,8 @@
:select-distinct-on #'format-selects-on
:select-top #'format-select-top
:select-distinct-top #'format-select-top
:into #'format-select-into
:bulk-collect-into #'format-select-into
:insert-into #'format-insert
:update #'format-selector
:delete #'format-selects

View file

@ -2,13 +2,14 @@
(ns honey.sql.helpers
"Helper functions for the built-in clauses in honey.sql."
(:refer-clojure :exclude [update set group-by for partition-by])
(:require [honey.sql]))
(:refer-clojure :exclude [into update set group-by for partition-by])
(:require [clojure.core :as c]
[honey.sql]))
;; implementation helpers:
(defn- default-merge [current args]
(into (vec current) args))
(c/into (vec current) args))
(defn- and-merge
[current arg]
@ -16,11 +17,11 @@
(ident? (first arg))
(#{:and :or} (keyword (first arg))))]
(cond (= conj' (first current))
(into (vec current) (rest arg))
(c/into (vec current) (rest arg))
(seq current)
(into [conj' current] (rest arg))
(c/into [conj' current] (rest arg))
:else
(into [conj'] (rest arg)))
(c/into [conj'] (rest arg)))
(cond (#{:and 'and} (first current))
(conj (vec current) arg)
(seq current)
@ -346,6 +347,19 @@
[& args]
(generic :select-distinct-top args))
(defn into
"Accepts table name, optionally followed a database name."
{:arglist '([table] [table dbname])}
[& args]
(generic :into args))
(defn bulk-collect-into
"Accepts a variable name, optionally followed by a limit
expression."
{:arglist '([varname] [varname n])}
[& args]
(generic :bulk-collect-into args))
(defn insert-into
"Accepts a table name or a table/alias pair. That
can optionally be followed by a collection of
@ -634,7 +648,7 @@
Produces: (a, ?)
Parameters: 42"
[& args]
(into [:composite] args))
(c/into [:composite] args))
(defn lateral
"Accepts a SQL clause or a SQL expression:
@ -647,7 +661,7 @@
LATERAL CALC_VALUE(bar)"
{:arglists '([clause-or-expression])}
[& args]
(into [:lateral] args))
(c/into [:lateral] args))
;; to make this easy to use in a select, wrap it so it becomes a function:
(defn over
@ -660,7 +674,7 @@
Produces: SELECT id, AVG(salary) OVER ()PARTITION BY department)"
[& args]
[(into [:over] args)])
[(c/into [:over] args)])
;; this helper is intended to ease the migration from nilenso:
(defn upsert

View file

@ -5,9 +5,10 @@
(:require #?(:clj [clojure.test :refer [deftest is testing]]
:cljs [cljs.test :refer-macros [deftest is testing]])
[honey.sql :as sql]
[honey.sql.helpers
[honey.sql.helpers :as h
:refer [add-column add-index alter-table columns create-table create-table-as create-view
create-materialized-view drop-view drop-materialized-view
bulk-collect-into
cross-join do-update-set drop-column drop-index drop-table from full-join
group-by having insert-into
join-by join lateral left-join limit offset on-conflict
@ -95,6 +96,26 @@
(from :bar)
(order-by :quux)))))))
(deftest select-into-tests
(testing "SELECT INTO"
(is (= ["SELECT * INTO foo FROM bar"]
(sql/format {:select :* :into :foo :from :bar})))
(is (= ["SELECT * INTO foo IN otherdb FROM bar"]
(sql/format {:select :* :into [:foo :otherdb] :from :bar})))
(is (= ["SELECT * INTO foo FROM bar"]
(sql/format (-> (select '*) (h/into 'foo) (from 'bar)))))
(is (= ["SELECT * INTO foo IN otherdb FROM bar"]
(sql/format (-> (select :*) (h/into :foo :otherdb) (from :bar))))))
(testing "SELECT BULK COLLECT INTO"
(is (= ["SELECT * BULK COLLECT INTO foo FROM bar"]
(sql/format {:select :* :bulk-collect-into :foo :from :bar})))
(is (= ["SELECT * BULK COLLECT INTO foo LIMIT ? FROM bar" 100]
(sql/format {:select :* :bulk-collect-into [:foo 100] :from :bar})))
(is (= ["SELECT * BULK COLLECT INTO foo FROM bar"]
(sql/format (-> (select :*) (bulk-collect-into :foo) (from :bar)))))
(is (= ["SELECT * BULK COLLECT INTO foo LIMIT ? FROM bar" 100]
(sql/format (-> (select :*) (bulk-collect-into :foo 100) (from :bar)))))))
(deftest from-expression-tests
(testing "FROM can be a function invocation"
(is (= ["SELECT foo, bar FROM F(?) AS x" 1]