Add tests for map-like reducible! result set

Ensures basic associative/lookup access do not cause row building; 
ensures assoc/seq produce real maps; ensure datafiable-row produces a 
real map with metadata.
This commit is contained in:
Sean Corfield 2019-04-19 21:53:55 -07:00
parent bfda745a70
commit 12e396683c
3 changed files with 63 additions and 43 deletions

View file

@ -165,14 +165,19 @@
map. Note that a result set is mutable and the current row will change behind map. Note that a result set is mutable and the current row will change behind
this wrapper so operations need to be eager (and fairly limited). this wrapper so operations need to be eager (and fairly limited).
In particular, this does not satisfy `map?` because it does not implement
all of IPersistentMap.
Supports ILookup (keywords are treated as strings). Supports ILookup (keywords are treated as strings).
Supports Associative (again, keywords are treated as strings). If you assoc, Supports Associative (again, keywords are treated as strings). If you assoc,
a full row will be realized (via `row-builder` above). a full row will be realized (via `row-builder` above).
Supports Seqable which realizes a full row of the data." Supports Seqable which realizes a full row of the data.
Supports DatafiableRow (which realizes a full row of the data)."
[^ResultSet rs opts] [^ResultSet rs opts]
(let [gen (delay ((get :gen-fn opts as-maps) rs opts))] (let [gen (delay ((get opts :gen-fn as-maps) rs opts))]
(reify (reify
clojure.lang.ILookup clojure.lang.ILookup

View file

@ -4,17 +4,16 @@
"Stub test namespace for the result set functions. "Stub test namespace for the result set functions.
There's so much that should be tested here: There's so much that should be tested here:
* column name generation functions
* ReadableColumn protocol extension point * ReadableColumn protocol extension point
* RowBuilder and ResultSetBuilder machinery
* ResultSet-as-map for reducible! / -execute protocol
* -execute-one and -execute-all implementations" * -execute-one and -execute-all implementations"
(:require [clojure.datafy :as d] (:require [clojure.core.protocols :as core-p]
[clojure.datafy :as d]
[clojure.string :as str] [clojure.string :as str]
[clojure.test :refer [deftest is testing use-fixtures]] [clojure.test :refer [deftest is testing use-fixtures]]
[next.jdbc.protocols :as p] [next.jdbc.protocols :as p]
[next.jdbc.result-set :as rs] [next.jdbc.result-set :as rs]
[next.jdbc.test-fixtures :refer [with-test-db ds]])) [next.jdbc.test-fixtures :refer [with-test-db ds]])
(:import (java.sql ResultSet ResultSetMetaData)))
(use-fixtures :once with-test-db) (use-fixtures :once with-test-db)
@ -59,16 +58,14 @@
(is (= 3 (:FRUIT/ID (first object)))) (is (= 3 (:FRUIT/ID (first object))))
(is (= "Peach" (:FRUIT/NAME (first object)))))))) (is (= "Peach" (:FRUIT/NAME (first object))))))))
(defn lower-case-cols [^ResultSetMetaData rsmeta opts]
(mapv (fn [^Integer i]
(keyword (str/lower-case (.getColumnLabel rsmeta i))))
(range 1 (inc (.getColumnCount rsmeta)))))
(defn get-lower-column-names [^java.sql.ResultSetMetaData rsmeta opts] (defn as-lower-case [^ResultSet rs opts]
(let [idxs (range 1 (inc (.getColumnCount rsmeta)))]
(mapv (fn [^Integer i]
(keyword (str/lower-case (.getColumnLabel rsmeta i))))
idxs)))
(defn as-lower-maps [^java.sql.ResultSet rs opts]
(let [rsmeta (.getMetaData rs) (let [rsmeta (.getMetaData rs)
cols (get-lower-column-names rsmeta opts)] cols (lower-case-cols rsmeta opts)]
(rs/->MapResultSetBuilder rs rsmeta cols))) (rs/->MapResultSetBuilder rs rsmeta cols)))
(deftest test-map-row-builder (deftest test-map-row-builder
@ -89,7 +86,52 @@
(testing "lower-case row builder" (testing "lower-case row builder"
(let [row (p/-execute-one (ds) (let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 3] ["select * from fruit where id = ?" 3]
{:gen-fn as-lower-maps})] {:gen-fn as-lower-case})]
(is (map? row)) (is (map? row))
(is (= 3 (:id row))) (is (= 3 (:id row)))
(is (= "Peach" (:name row)))))) (is (= "Peach" (:name row))))))
(deftest test-mapify
(testing "no row builder is used"
(is (= [false]
(into [] (map map?) ; it is not a real map
(p/-execute (ds) ["select * from fruit where id = ?" 1]
{:gen-fn (constantly nil)}))))
(is (= ["Apple"]
(into [] (map :name) ; but keyword selection works
(p/-execute (ds) ["select * from fruit where id = ?" 1]
{:gen-fn (constantly nil)}))))
(is (= [[2 [:name "Banana"]]]
(into [] (map (juxt #(get % "id") ; get by string key works
#(find % :name))) ; get MapEntry works
(p/-execute (ds) ["select * from fruit where id = ?" 2]
{:gen-fn (constantly nil)}))))
(is (= [{:id 3 :name "Peach"}]
(into [] (map #(select-keys % [:id :name])) ; select-keys works
(p/-execute (ds) ["select * from fruit where id = ?" 3]
{:gen-fn (constantly nil)}))))
(is (= [[:orange 4]]
(into [] (map #(vector (if (contains? % :name) ; contains works
(keyword (str/lower-case (:name %)))
:unnamed)
(get % :id 0))) ; get with not-found works
(p/-execute (ds) ["select * from fruit where id = ?" 4]
{:gen-fn (constantly nil)})))))
(testing "assoc and seq build maps"
(is (map? (reduce (fn [_ row] (reduced (assoc row :x 1)))
nil
(p/-execute (ds) ["select * from fruit"] {}))))
(is (seq? (reduce (fn [_ row] (reduced (seq row)))
nil
(p/-execute (ds) ["select * from fruit"] {}))))
(is (every? map-entry? (reduce (fn [_ row] (reduced (seq row)))
nil
(p/-execute (ds) ["select * from fruit"] {})))))
(testing "datafiable-row builds map; with metadata"
(is (map? (reduce (fn [_ row] (reduced (rs/datafiable-row row (ds) {})))
nil
(p/-execute (ds) ["select * from fruit"] {}))))
(is (contains? (meta (reduce (fn [_ row] (reduced (rs/datafiable-row row (ds) {})))
nil
(p/-execute (ds) ["select * from fruit"] {})))
`core-p/datafy))))

View file

@ -85,33 +85,6 @@
(quick-bench ; simple keys, arrays -- 4.34-4.4 (quick-bench ; simple keys, arrays -- 4.34-4.4
(execute! con ["select * from fruit"] {:gen-fn rs/as-unqualified-arrays})) (execute! con ["select * from fruit"] {:gen-fn rs/as-unqualified-arrays}))
(defn get-lower-column-names [^java.sql.ResultSetMetaData rsmeta opts]
(let [idxs (range 1 (inc (.getColumnCount rsmeta)))]
(mapv (fn [^Integer i]
(keyword (str/lower-case (.getColumnLabel rsmeta i))))
idxs)))
(defn as-lower-maps [^java.sql.ResultSet rs opts]
(let [rsmeta (.getMetaData rs)
cols (get-lower-column-names rsmeta opts)]
(next.jdbc.result-set/->MapResultSetBuilder rs rsmeta cols)))
(quick-bench ; simple keys -- 4.55-4.57
(execute! con ["select * from fruit"] {:gen-fn as-lower-maps}))
(defn lower-case-cols [^ResultSetMetaData rsmeta opts]
(mapv (fn [^Integer i]
(keyword (str/lower-case (.getColumnLabel rsmeta i))))
(range 1 (inc (.getColumnCount rsmeta)))))
(defn as-lower-case [^ResultSet rs opts]
(let [rsmeta (.getMetaData rs)
cols (lower-case-cols rsmeta opts)]
(next.jdbc.result-set/->MapResultSetBuilder rs rsmeta cols)))
(quick-bench
(execute! con ["SELECT * FROM fruit"] {:gen-fn as-lower-case}))
(quick-bench ; 9.5 -- 2x (quick-bench ; 9.5 -- 2x
(jdbc/query {:connection con} ["select * from fruit"])) (jdbc/query {:connection con} ["select * from fruit"]))