Fixes #44
Also adds `assert`s in SQL-generating functions, instead of just producing illegal SQL.
This commit is contained in:
parent
2131136c0d
commit
821f9b1a5a
4 changed files with 40 additions and 13 deletions
|
|
@ -7,9 +7,12 @@ Only accretive/fixative changes will be made from now on.
|
|||
The following changes have been committed to the **master** branch since the 1.0.1 release:
|
||||
|
||||
* Fix #45 by adding TimesTen driver support.
|
||||
* Fix #44 so that `insert-multi!` with an empty `rows` vector returns `[]`.
|
||||
* Fix #42 by adding specs for `execute-batch!` and `set-parameters` in `next.jdbc.prepare`.
|
||||
* Fix #41 by improving docstrings and documentation, especially around prepared statement handling.
|
||||
* Fix #40 by adding `next.jdbc.prepare/execute-batch!`.
|
||||
* Added `assert`s in `next.jdbc.sql` as more informative errors for cases that would generate SQL exceptions (from malformed SQL).
|
||||
* Added spec for `:order-by` to reflect what is actually permitted.
|
||||
* Expose `next.jdbc.connect/dbtypes` as a table of known database types and aliases, along with their class name(s), port, and other JDBC string components.
|
||||
|
||||
## Stable Builds
|
||||
|
|
|
|||
|
|
@ -47,7 +47,13 @@
|
|||
(s/def ::connectable any?)
|
||||
(s/def ::key-map (s/map-of keyword? any?))
|
||||
(s/def ::example-map (s/map-of keyword? any? :min-count 1))
|
||||
(s/def ::opts-map (s/map-of keyword? any?))
|
||||
|
||||
(s/def ::order-by-col (s/or :col keyword?
|
||||
:dir (s/cat :col keyword?
|
||||
:dir #{:asc :desc})))
|
||||
(s/def ::order-by (s/coll-of ::order-by-col :kind vector? :min-count 1))
|
||||
(s/def ::opts-map (s/and (s/map-of keyword? any?)
|
||||
(s/keys :opt-un [::order-by])))
|
||||
|
||||
(s/def ::transactable any?)
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
[(conj conds (str e " = ?")) (conj params v)])))
|
||||
[[] []]
|
||||
key-map)]
|
||||
(assert (seq where) "key-map may not be empty")
|
||||
(into [(str (str/upper-case (name clause)) " "
|
||||
(str/join (if (= :where clause) " AND " ", ") where))]
|
||||
params)))
|
||||
|
|
@ -79,10 +80,11 @@
|
|||
(defn- for-order
|
||||
"Given an `:order-by` vector, return an `ORDER BY` clause."
|
||||
[order-by opts]
|
||||
(if (vector? order-by)
|
||||
(str "ORDER BY "
|
||||
(str/join ", " (map #(for-order-col % opts) order-by)))
|
||||
(throw (IllegalArgumentException. ":order-by must be a vector"))))
|
||||
(when-not (vector? order-by)
|
||||
(throw (IllegalArgumentException. ":order-by must be a vector")))
|
||||
(assert (seq order-by) ":order-by may not be empty")
|
||||
(str "ORDER BY "
|
||||
(str/join ", " (map #(for-order-col % opts) order-by))))
|
||||
|
||||
(defn- for-query
|
||||
"Given a table name and either a hash map of column names and values or a
|
||||
|
|
@ -147,6 +149,7 @@
|
|||
(let [entity-fn (:table-fn opts identity)
|
||||
params (as-keys key-map opts)
|
||||
places (as-? key-map opts)]
|
||||
(assert (seq key-map) "key-map may not be empty")
|
||||
(into [(str "INSERT INTO " (entity-fn (name table))
|
||||
" (" params ")"
|
||||
" VALUES (" places ")")]
|
||||
|
|
@ -159,7 +162,10 @@
|
|||
|
||||
Applies any `:table-fn` / `:column-fn` supplied in the options."
|
||||
[table cols rows opts]
|
||||
(assert (apply = (count cols) (map count rows)))
|
||||
(assert (apply = (count cols) (map count rows))
|
||||
"column counts are not consistent across cols and rows")
|
||||
;; to avoid generating bad SQL
|
||||
(assert (seq rows) "rows may not be empty")
|
||||
(let [table-fn (:table-fn opts identity)
|
||||
column-fn (:column-fn opts identity)
|
||||
params (str/join ", " (map (comp column-fn name) cols))
|
||||
|
|
@ -199,9 +205,11 @@
|
|||
([connectable table cols rows]
|
||||
(insert-multi! connectable table cols rows {}))
|
||||
([connectable table cols rows opts]
|
||||
(execute! connectable
|
||||
(for-insert-multi table cols rows opts)
|
||||
(merge {:return-keys true} opts))))
|
||||
(if (seq rows)
|
||||
(execute! connectable
|
||||
(for-insert-multi table cols rows opts)
|
||||
(merge {:return-keys true} opts))
|
||||
[])))
|
||||
|
||||
(defn query
|
||||
"Syntactic sugar over `execute!` to provide a query alias.
|
||||
|
|
|
|||
|
|
@ -55,9 +55,9 @@
|
|||
["DELETE FROM [user] WHERE id = ? and opt is null" 9]))))
|
||||
|
||||
(deftest test-for-update
|
||||
(testing "empty example (SQL error)"
|
||||
(is (= (#'sql/for-update :user {:status 42} {} {:table-fn sql-server :column-fn mysql})
|
||||
["UPDATE [user] SET `status` = ? WHERE " 42])))
|
||||
(testing "empty example (would be a SQL error)"
|
||||
(is (thrown? AssertionError ; changed in #44
|
||||
(#'sql/for-update :user {:status 42} {} {:table-fn sql-server :column-fn mysql}))))
|
||||
(testing "by example"
|
||||
(is (= (#'sql/for-update :user {:status 42} {:id 9} {:table-fn sql-server :column-fn mysql})
|
||||
["UPDATE [user] SET `status` = ? WHERE `id` = ?" 42 9])))
|
||||
|
|
@ -154,7 +154,11 @@
|
|||
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
|
||||
(is (= {:next.jdbc/update-count 2}
|
||||
(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 "empty insert-multi!" ; per #44
|
||||
(is (= [] (sql/insert-multi! (ds) :fruit
|
||||
[:name :appearance :cost :grade]
|
||||
[])))))
|
||||
|
||||
(deftest no-empty-example-maps
|
||||
(is (thrown? clojure.lang.ExceptionInfo
|
||||
|
|
@ -163,3 +167,9 @@
|
|||
(sql/update! (ds) :fruit {} {})))
|
||||
(is (thrown? clojure.lang.ExceptionInfo
|
||||
(sql/delete! (ds) :fruit {}))))
|
||||
|
||||
(deftest no-empty-order-by
|
||||
(is (thrown? clojure.lang.ExceptionInfo
|
||||
(sql/find-by-keys (ds) :fruit
|
||||
{:name "Apple"}
|
||||
{:order-by []}))))
|
||||
|
|
|
|||
Loading…
Reference in a new issue