Merge pull request #206 from rschmukler/rs/hash-map-and-batch-multi-insert
insert-multi! hashmap support + batch mode
This commit is contained in:
commit
68cff61e94
5 changed files with 99 additions and 23 deletions
|
|
@ -180,7 +180,10 @@
|
||||||
:opts (s/? ::opts-map)))
|
:opts (s/? ::opts-map)))
|
||||||
|
|
||||||
(s/fdef sql/insert-multi!
|
(s/fdef sql/insert-multi!
|
||||||
:args (s/and (s/cat :connectable ::connectable
|
:args
|
||||||
|
(s/or
|
||||||
|
:with-rows-and-columns
|
||||||
|
(s/and (s/cat :connectable ::connectable
|
||||||
:table keyword?
|
:table keyword?
|
||||||
:cols (s/coll-of keyword?
|
:cols (s/coll-of keyword?
|
||||||
:kind sequential?
|
:kind sequential?
|
||||||
|
|
@ -189,7 +192,14 @@
|
||||||
:kind sequential?)
|
:kind sequential?)
|
||||||
:opts (s/? ::opts-map))
|
:opts (s/? ::opts-map))
|
||||||
#(apply = (count (:cols %))
|
#(apply = (count (:cols %))
|
||||||
(map count (:rows %)))))
|
(map count (:rows %))))
|
||||||
|
:with-hash-maps
|
||||||
|
(s/cat :connectable ::connectable
|
||||||
|
:table keyword?
|
||||||
|
:hash-maps (s/coll-of map?
|
||||||
|
:kind sequential?
|
||||||
|
:min-count 1)
|
||||||
|
:opts (s/? ::opts-map))))
|
||||||
|
|
||||||
(s/fdef sql/query
|
(s/fdef sql/query
|
||||||
:args (s/cat :connectable ::connectable
|
:args (s/cat :connectable ::connectable
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
In addition, `find-by-keys` supports `:order-by` to add an `ORDER BY`
|
In addition, `find-by-keys` supports `:order-by` to add an `ORDER BY`
|
||||||
clause to the generated SQL."
|
clause to the generated SQL."
|
||||||
(:require [next.jdbc :refer [execute! execute-one!]]
|
(:require [next.jdbc :refer [execute! execute-one! execute-batch!]]
|
||||||
[next.jdbc.sql.builder
|
[next.jdbc.sql.builder
|
||||||
:refer [for-delete for-insert for-insert-multi
|
:refer [for-delete for-insert for-insert-multi
|
||||||
for-query for-update]]))
|
for-query for-update]]))
|
||||||
|
|
@ -50,18 +50,41 @@
|
||||||
multiple rows in the database and attempts to return a vector of maps of
|
multiple rows in the database and attempts to return a vector of maps of
|
||||||
generated keys.
|
generated keys.
|
||||||
|
|
||||||
Note: this expands to a single SQL statement with placeholders for every
|
Also supports a sequence of hash maps with keys corresponding to column
|
||||||
value being inserted -- for large sets of rows, this may exceed the limits
|
names.
|
||||||
|
|
||||||
|
If called with `:batch` true will call `execute-batch!` - see its documentation
|
||||||
|
for situations in which the generated keys may or may not be returned as well as
|
||||||
|
additional options that can be passed.
|
||||||
|
|
||||||
|
Note: without `:batch` this expands to a single SQL statement with placeholders for
|
||||||
|
every value being inserted -- for large sets of rows, this may exceed the limits
|
||||||
on SQL string size and/or number of parameters for your JDBC driver or your
|
on SQL string size and/or number of parameters for your JDBC driver or your
|
||||||
database!"
|
database!"
|
||||||
([connectable table cols rows]
|
{:arglists '([connectable table hash-maps]
|
||||||
(insert-multi! connectable table cols rows {}))
|
[connectable table hash-maps opts]
|
||||||
|
[connectable table cols rows]
|
||||||
|
[connectable table cols rows opts])}
|
||||||
|
([connectable table hash-maps]
|
||||||
|
(insert-multi! connectable table hash-maps {}))
|
||||||
|
([connectable table hash-maps-or-cols opts-or-rows]
|
||||||
|
(if-not (-> hash-maps-or-cols first map?)
|
||||||
|
(insert-multi! connectable table hash-maps-or-cols opts-or-rows {})
|
||||||
|
(let [cols (keys (first hash-maps-or-cols))
|
||||||
|
->row (fn ->row [m]
|
||||||
|
(map (partial get m) cols))]
|
||||||
|
(insert-multi! connectable table cols (map ->row hash-maps-or-cols) opts-or-rows))))
|
||||||
([connectable table cols rows opts]
|
([connectable table cols rows opts]
|
||||||
(if (seq rows)
|
(if (seq rows)
|
||||||
(let [opts (merge (:options connectable) opts)]
|
(let [opts (merge (:options connectable) opts)
|
||||||
|
batch? (:batch opts)]
|
||||||
|
(if batch?
|
||||||
|
(let [[sql & param-groups] (for-insert-multi table cols rows opts)]
|
||||||
|
(execute-batch! connectable sql param-groups
|
||||||
|
(merge {:return-keys true :return-generated-keys true} opts)))
|
||||||
(execute! connectable
|
(execute! connectable
|
||||||
(for-insert-multi table cols rows opts)
|
(for-insert-multi table cols rows opts)
|
||||||
(merge {:return-keys true} opts)))
|
(merge {:return-keys true} opts))))
|
||||||
[])))
|
[])))
|
||||||
|
|
||||||
(defn query
|
(defn query
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,10 @@
|
||||||
|
|
||||||
Applies any `:table-fn` / `:column-fn` supplied in the options.
|
Applies any `:table-fn` / `:column-fn` supplied in the options.
|
||||||
|
|
||||||
|
If `:batch` is set to `true` in `opts` the INSERT statement will be prepared
|
||||||
|
using a single set of placeholders and remaining parameters in the vector will
|
||||||
|
be grouped at the row level.
|
||||||
|
|
||||||
If `:suffix` is provided in `opts`, that string is appended to the
|
If `:suffix` is provided in `opts`, that string is appended to the
|
||||||
`INSERT ...` statement."
|
`INSERT ...` statement."
|
||||||
[table cols rows opts]
|
[table cols rows opts]
|
||||||
|
|
@ -147,15 +151,16 @@
|
||||||
(assert (seq rows) "rows may not be empty")
|
(assert (seq rows) "rows may not be empty")
|
||||||
(let [table-fn (:table-fn opts identity)
|
(let [table-fn (:table-fn opts identity)
|
||||||
column-fn (:column-fn opts identity)
|
column-fn (:column-fn opts identity)
|
||||||
|
batch? (:batch opts)
|
||||||
params (str/join ", " (map (comp column-fn name) cols))
|
params (str/join ", " (map (comp column-fn name) cols))
|
||||||
places (as-? (first rows) opts)]
|
places (as-? (first rows) opts)]
|
||||||
(into [(str "INSERT INTO " (table-fn (safe-name table))
|
(into [(str "INSERT INTO " (table-fn (safe-name table))
|
||||||
" (" params ")"
|
" (" params ")"
|
||||||
" VALUES "
|
" VALUES "
|
||||||
(str/join ", " (repeat (count rows) (str "(" places ")")))
|
(str/join ", " (repeat (if batch? 1 (count rows)) (str "(" places ")")))
|
||||||
(when-let [suffix (:suffix opts)]
|
(when-let [suffix (:suffix opts)]
|
||||||
(str " " suffix)))]
|
(str " " suffix)))]
|
||||||
cat
|
(if batch? identity cat)
|
||||||
rows)))
|
rows)))
|
||||||
|
|
||||||
(defn for-order-col
|
(defn for-order-col
|
||||||
|
|
|
||||||
|
|
@ -146,11 +146,19 @@
|
||||||
{:id 9 :status 42 :opt nil}
|
{:id 9 :status 42 :opt nil}
|
||||||
{:table-fn sql-server :column-fn mysql})
|
{:table-fn sql-server :column-fn mysql})
|
||||||
["INSERT INTO [user] (`id`, `status`, `opt`) VALUES (?, ?, ?)" 9 42 nil])))
|
["INSERT INTO [user] (`id`, `status`, `opt`) VALUES (?, ?, ?)" 9 42 nil])))
|
||||||
(testing "multi-row insert"
|
(testing "multi-row insert (normal mode)"
|
||||||
(is (= (builder/for-insert-multi :user
|
(is (= (builder/for-insert-multi :user
|
||||||
[:id :status]
|
[:id :status]
|
||||||
[[42 "hello"]
|
[[42 "hello"]
|
||||||
[35 "world"]
|
[35 "world"]
|
||||||
[64 "dollars"]]
|
[64 "dollars"]]
|
||||||
{:table-fn sql-server :column-fn mysql})
|
{:table-fn sql-server :column-fn mysql})
|
||||||
["INSERT INTO [user] (`id`, `status`) VALUES (?, ?), (?, ?), (?, ?)" 42 "hello" 35 "world" 64 "dollars"]))))
|
["INSERT INTO [user] (`id`, `status`) VALUES (?, ?), (?, ?), (?, ?)" 42 "hello" 35 "world" 64 "dollars"])))
|
||||||
|
(testing "multi-row insert (batch mode)"
|
||||||
|
(is (= (builder/for-insert-multi :user
|
||||||
|
[:id :status]
|
||||||
|
[[42 "hello"]
|
||||||
|
[35 "world"]
|
||||||
|
[64 "dollars"]]
|
||||||
|
{:table-fn sql-server :column-fn mysql :batch true})
|
||||||
|
["INSERT INTO [user] (`id`, `status`) VALUES (?, ?)" [42 "hello"] [35 "world"] [64 "dollars"]]))))
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,36 @@
|
||||||
(is (= {:next.jdbc/update-count 2}
|
(is (= {:next.jdbc/update-count 2}
|
||||||
(sql/delete! (ds) :fruit ["id > ?" 4])))
|
(sql/delete! (ds) :fruit ["id > ?" 4])))
|
||||||
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
|
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
|
||||||
|
(testing "multiple insert/delete with maps"
|
||||||
|
(is (= (cond (derby?)
|
||||||
|
[nil] ; WTF Apache Derby?
|
||||||
|
(mssql?)
|
||||||
|
[14M]
|
||||||
|
(sqlite?)
|
||||||
|
[14]
|
||||||
|
:else
|
||||||
|
[12 13 14])
|
||||||
|
(mapv new-key
|
||||||
|
(sql/insert-multi! (ds) :fruit
|
||||||
|
[{:name "Kiwi"
|
||||||
|
:appearance "green & fuzzy"
|
||||||
|
:cost 100
|
||||||
|
:grade 99.9}
|
||||||
|
{:name "Grape"
|
||||||
|
:appearance "black"
|
||||||
|
:cost 10
|
||||||
|
:grade 50}
|
||||||
|
{:name "Lemon"
|
||||||
|
:appearance "yellow"
|
||||||
|
:cost 20
|
||||||
|
:grade 9.9}]))))
|
||||||
|
(is (= 7 (count (sql/query (ds) ["select * from fruit"]))))
|
||||||
|
(is (= {:next.jdbc/update-count 1}
|
||||||
|
(sql/delete! (ds) :fruit {:id 12})))
|
||||||
|
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
|
||||||
|
(is (= {:next.jdbc/update-count 2}
|
||||||
|
(sql/delete! (ds) :fruit ["id > ?" 10])))
|
||||||
|
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
|
||||||
(testing "empty insert-multi!" ; per #44
|
(testing "empty insert-multi!" ; per #44
|
||||||
(is (= [] (sql/insert-multi! (ds) :fruit
|
(is (= [] (sql/insert-multi! (ds) :fruit
|
||||||
[:name :appearance :cost :grade]
|
[:name :appearance :cost :grade]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue