From 9a76e4c25a409bb5b7c2a45971c525226e1d8ca3 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 21 Aug 2019 14:47:55 -0700 Subject: [PATCH] Add as-arrays-adapter --- CHANGELOG.md | 2 +- doc/result-set-builders.md | 2 +- src/next/jdbc/result_set.clj | 37 +++++++++++++++++++++++++++++++++--- test/next/jdbc_test.clj | 17 +++++++++++++++++ 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c05c9fc..f09be76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 #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"`. ## Stable Builds diff --git a/doc/result-set-builders.md b/doc/result-set-builders.md index 6de7948..2225bc1 100644 --- a/doc/result-set-builders.md +++ b/doc/result-set-builders.md @@ -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. -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. diff --git a/src/next/jdbc/result_set.clj b/src/next/jdbc/result_set.clj index 6309010..a778775 100644 --- a/src/next/jdbc/result_set.clj +++ b/src/next/jdbc/result_set.clj @@ -201,9 +201,9 @@ (as-unqualified-modified-maps rs (assoc opts :label-fn lower-case))) (defn as-maps-adapter - "Given a builder function (e.g., `as-maps`) and a column reading function, - return a new builder function that uses the column reading function - instead of `.getObject` so you can override the default behavior. + "Given a map builder function (e.g., `as-lower-maps`) 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: @@ -303,6 +303,37 @@ [rs opts] (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) (defprotocol DatafiableRow diff --git a/test/next/jdbc_test.clj b/test/next/jdbc_test.clj index e90322a..63fa905 100644 --- a/test/next/jdbc_test.clj +++ b/test/next/jdbc_test.clj @@ -58,6 +58,23 @@ ;; 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! ; 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! (ds) ["select * from fruit order by id"]