From 16d04a1dfd820e9329aeaa19a391f6a1d0450f2c Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 13 Mar 2021 13:42:08 -0800 Subject: [PATCH] Fixes #297 by adding into/bulk-collect-into --- CHANGELOG.md | 1 + doc/clause-reference.md | 22 ++++++++++++++++++++++ src/honey/sql.cljc | 14 ++++++++++++++ src/honey/sql/helpers.cljc | 32 +++++++++++++++++++++++--------- test/honey/sql/helpers_test.cljc | 23 ++++++++++++++++++++++- 5 files changed, 82 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 658c011..ea35cc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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). diff --git a/doc/clause-reference.md b/doc/clause-reference.md index 78e153d..21a6047 100644 --- a/doc/clause-reference.md +++ b/doc/clause-reference.md @@ -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`. diff --git a/src/honey/sql.cljc b/src/honey/sql.cljc index e3d4573..a6cadb4 100644 --- a/src/honey/sql.cljc +++ b/src/honey/sql.cljc @@ -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 diff --git a/src/honey/sql/helpers.cljc b/src/honey/sql/helpers.cljc index 9534845..9eeb1db 100644 --- a/src/honey/sql/helpers.cljc +++ b/src/honey/sql/helpers.cljc @@ -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 diff --git a/test/honey/sql/helpers_test.cljc b/test/honey/sql/helpers_test.cljc index b5d158e..21b2e38 100644 --- a/test/honey/sql/helpers_test.cljc +++ b/test/honey/sql/helpers_test.cljc @@ -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]