Add as-arrays-adapter

This commit is contained in:
Sean Corfield 2019-08-21 14:47:55 -07:00
parent 57f146ee41
commit 9a76e4c25a
4 changed files with 53 additions and 5 deletions

View file

@ -8,7 +8,7 @@ The following changes have been committed to the **master** branch since the 1.0
* Fix #54 by improving documentation around data type conversions (and the `ReadableColumn` and `SettableParameter` protocols). * Fix #54 by improving documentation around data type conversions (and the `ReadableColumn` and `SettableParameter` protocols).
* Fix #52 by replacing `clojure.string/lower-case` with a US-locale function to avoid breakage in locales such as Turkish. * Fix #52 by replacing `clojure.string/lower-case` with a US-locale function to avoid breakage in locales such as Turkish.
* Add `next.jdbc.result-set/as-maps-adapter` to provide a way to override the default result set reading behavior of using `.getObject`. * Add `next.jdbc.result-set/as-maps-adapter` and `next.jdbc.result-set/as-arrays-adapter` to provide a way to override the default result set reading behavior of using `.getObject`.
* Update `org.clojure/test.check` to `"0.10.0"`. * Update `org.clojure/test.check` to `"0.10.0"`.
## Stable Builds ## Stable Builds

View file

@ -69,7 +69,7 @@ This namespace contains variants of the six `as-maps`-style builders above that
As mentioned above, when `with-column` is called, the expectation is that the row builder will call `.getObject` on the current state of the `ResultSet` object with the column index and will then call `read-column-by-index`, passing the column value, the `ResultSetMetaData`, and the column index. That function is part of the `ReadableColumn` protocol that you can extend to handle conversion of arbitrary database-specific types to Clojure values. As mentioned above, when `with-column` is called, the expectation is that the row builder will call `.getObject` on the current state of the `ResultSet` object with the column index and will then call `read-column-by-index`, passing the column value, the `ResultSetMetaData`, and the column index. That function is part of the `ReadableColumn` protocol that you can extend to handle conversion of arbitrary database-specific types to Clojure values.
If you need more control over how values are read from the `ResultSet` object, you can use `next.jdbc.result-set/as-maps-adapter` which takes an existing builder function and a column reading function and returns a new builder function that calls your column reading function (with the `ResultSet` object, the `ResultSetMetaData` object, and the column index) instead of calling `.getObject` directly. If you need more control over how values are read from the `ResultSet` object, you can use `next.jdbc.result-set/as-maps-adapter` (or `next.jdbc.result-set/as-arrays-adapter`) which takes an existing builder function and a column reading function and returns a new builder function that calls your column reading function (with the `ResultSet` object, the `ResultSetMetaData` object, and the column index) instead of calling `.getObject` directly.
In addition, inside `plan`, as each value is looked up by name in the current state of the `ResultSet` object, the `read-column-by-label` function is called, again passing the column value and the column label (the name used in the SQL to identify that column). This function is also part of the `ReadableColumn` protocol. In addition, inside `plan`, as each value is looked up by name in the current state of the `ResultSet` object, the `read-column-by-label` function is called, again passing the column value and the column label (the name used in the SQL to identify that column). This function is also part of the `ReadableColumn` protocol.

View file

@ -201,9 +201,9 @@
(as-unqualified-modified-maps rs (assoc opts :label-fn lower-case))) (as-unqualified-modified-maps rs (assoc opts :label-fn lower-case)))
(defn as-maps-adapter (defn as-maps-adapter
"Given a builder function (e.g., `as-maps`) and a column reading function, "Given a map builder function (e.g., `as-lower-maps`) and a column reading
return a new builder function that uses the column reading function function, return a new builder function that uses that column reading
instead of `.getObject` so you can override the default behavior. function instead of `.getObject` so you can override the default behavior.
The default column-reader behavior would be equivalent to: The default column-reader behavior would be equivalent to:
@ -303,6 +303,37 @@
[rs opts] [rs opts]
(as-unqualified-modified-arrays rs (assoc opts :label-fn lower-case))) (as-unqualified-modified-arrays rs (assoc opts :label-fn lower-case)))
(defn as-arrays-adapter
"Given an array builder function (e.g., `as-unqualified-arrays`) and a column
reading function, return a new builder function that uses that column reading
function instead of `.getObject` so you can override the default behavior.
The default column-reader behavior would be equivalent to:
(defn default-column-reader
[^ResultSet rs ^ResultSetMetaData rsmeta ^Integer i]
(.getObject rs i))
Your column-reader can use the result set metadata to determine whether
to call `.getObject` or some other method to read the column's value."
[builder-fn column-reader]
(fn [rs opts]
(let [arsb (builder-fn rs opts)]
(reify
RowBuilder
(->row [this] (->row arsb))
(column-count [this] (column-count arsb))
(with-column [this row i]
(conj! row
(read-column-by-index (column-reader rs (:rsmeta arsb) i)
(:rsmeta arsb)
i)))
(row! [this row] (row! arsb row))
ResultSetBuilder
(->rs [this] (->rs arsb))
(with-row [this mrs row] (with-row arsb mrs row))
(rs! [this mrs] (rs! arsb mrs))))))
(declare navize-row) (declare navize-row)
(defprotocol DatafiableRow (defprotocol DatafiableRow

View file

@ -58,6 +58,23 @@
;; and all its corresponding values should be ints ;; and all its corresponding values should be ints
(is (every? int? (map first (rest rs)))) (is (every? int? (map first (rest rs))))
(is (every? string? (map second (rest rs))))) (is (every? string? (map second (rest rs)))))
(let [rs (jdbc/execute! ; test again, with adapter and lower columns
(ds)
["select * from fruit order by id"]
{:builder-fn (rs/as-arrays-adapter
rs/as-lower-arrays
(fn [^ResultSet rs _ ^Integer i]
(.getObject rs i)))})]
(is (every? vector? rs))
(is (= 5 (count rs)))
(is (every? #(= 5 (count %)) rs))
;; columns come first
(is (every? qualified-keyword? (first rs)))
;; :fruit/id should be first column
(is (= :fruit/id (ffirst rs)))
;; and all its corresponding values should be ints
(is (every? int? (map first (rest rs))))
(is (every? string? (map second (rest rs)))))
(let [rs (jdbc/execute! (let [rs (jdbc/execute!
(ds) (ds)
["select * from fruit order by id"] ["select * from fruit order by id"]