Fixes #51 by implementing IPersistentMap in full
* `dissoc`, `cons`, `=` -- both realize a full row. * `count`, `empty` -- do not realize rows, `empty` doesn't use the builder at all. * `str` -- attempts to realize a row (else returns the same "helpful" string as before).
This commit is contained in:
parent
22b7e6df61
commit
0fd8bf1a88
3 changed files with 120 additions and 42 deletions
|
|
@ -6,6 +6,7 @@ Only accretive/fixative changes will be made from now on.
|
||||||
|
|
||||||
The following changes have been committed to the **master** branch since the 1.0.4 release:
|
The following changes have been committed to the **master** branch since the 1.0.4 release:
|
||||||
|
|
||||||
|
* Fix #51 by implementing `IPersistentMap` fully for the "mapified" result set inside `plan`. This adds support for `dissoc` and `cons` (which will both realize a row), `count` (which returns the column count but does not realize a row), `empty` (returns an empty hash map without realizing a row), etc.
|
||||||
* Improved documentation around connection pooling (HikariCP caveats).
|
* Improved documentation around connection pooling (HikariCP caveats).
|
||||||
|
|
||||||
## Stable Builds
|
## Stable Builds
|
||||||
|
|
|
||||||
|
|
@ -288,36 +288,37 @@
|
||||||
(range 1 (inc (column-count builder))))
|
(range 1 (inc (column-count builder))))
|
||||||
(row! builder)))
|
(row! builder)))
|
||||||
|
|
||||||
|
(definterface MapifiedResultSet)
|
||||||
|
|
||||||
(defn- mapify-result-set
|
(defn- mapify-result-set
|
||||||
"Given a `ResultSet`, return an object that wraps the current row as a hash
|
"Given a `ResultSet`, return an object that wraps the current row as a hash
|
||||||
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
|
Supports `IPersistentMap` in full. Any operation that requires a full hash
|
||||||
all of `IPersistentMap`.
|
map (`assoc`, `dissoc`, `cons`, `seq`, etc) will cause a full row to be
|
||||||
|
realized (via `row-builder` above). The result will be a regular map: if
|
||||||
Supports `ILookup` (keywords are treated as strings).
|
you want the row to be datafiable/navigable, use `datafiable-row` to
|
||||||
|
realize the full row explicitly before performing other
|
||||||
Supports `Associative` (again, keywords are treated as strings). If you `assoc`,
|
(metadata-preserving) operations on it."
|
||||||
a full row will be realized (via `row-builder` above).
|
|
||||||
|
|
||||||
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 [builder (delay ((get opts :builder-fn as-maps) rs opts))]
|
(let [builder (delay ((get opts :builder-fn as-maps) rs opts))]
|
||||||
(reify
|
(reify
|
||||||
|
|
||||||
clojure.lang.ILookup
|
MapifiedResultSet
|
||||||
(valAt [this k]
|
;; marker, just for printing resolution
|
||||||
(try
|
|
||||||
(read-column-by-label (.getObject rs (name k)) (name k))
|
clojure.lang.IPersistentMap
|
||||||
(catch SQLException _)))
|
(assoc [this k v]
|
||||||
(valAt [this k not-found]
|
(assoc (row-builder @builder) k v))
|
||||||
(try
|
(assocEx [this k v]
|
||||||
(read-column-by-label (.getObject rs (name k)) (name k))
|
(.assocEx ^clojure.lang.IPersistentMap (row-builder @builder) k v))
|
||||||
(catch SQLException _
|
(without [this k]
|
||||||
not-found)))
|
(dissoc (row-builder @builder) k))
|
||||||
|
|
||||||
|
java.lang.Iterable ; Java 7 compatible: no forEach / spliterator
|
||||||
|
(iterator [this]
|
||||||
|
(.iterator ^java.lang.Iterable (row-builder @builder)))
|
||||||
|
|
||||||
clojure.lang.Associative
|
clojure.lang.Associative
|
||||||
(containsKey [this k]
|
(containsKey [this k]
|
||||||
|
|
@ -332,8 +333,29 @@
|
||||||
(.getObject rs (name k))
|
(.getObject rs (name k))
|
||||||
(name k)))
|
(name k)))
|
||||||
(catch SQLException _)))
|
(catch SQLException _)))
|
||||||
(assoc [this k v]
|
|
||||||
(assoc (row-builder @builder) k v))
|
clojure.lang.Counted
|
||||||
|
(count [this]
|
||||||
|
(column-count @builder))
|
||||||
|
|
||||||
|
clojure.lang.IPersistentCollection
|
||||||
|
(cons [this obj]
|
||||||
|
(cons obj (seq (row-builder @builder))))
|
||||||
|
(empty [this]
|
||||||
|
{})
|
||||||
|
(equiv [this obj]
|
||||||
|
(.equiv ^clojure.lang.IPersistentCollection (row-builder @builder) obj))
|
||||||
|
|
||||||
|
clojure.lang.ILookup
|
||||||
|
(valAt [this k]
|
||||||
|
(try
|
||||||
|
(read-column-by-label (.getObject rs (name k)) (name k))
|
||||||
|
(catch SQLException _)))
|
||||||
|
(valAt [this k not-found]
|
||||||
|
(try
|
||||||
|
(read-column-by-label (.getObject rs (name k)) (name k))
|
||||||
|
(catch SQLException _
|
||||||
|
not-found)))
|
||||||
|
|
||||||
clojure.lang.Seqable
|
clojure.lang.Seqable
|
||||||
(seq [this]
|
(seq [this]
|
||||||
|
|
@ -345,7 +367,21 @@
|
||||||
(row-builder @builder)
|
(row-builder @builder)
|
||||||
{`core-p/datafy (navize-row connectable opts)}))
|
{`core-p/datafy (navize-row connectable opts)}))
|
||||||
|
|
||||||
(toString [_] "{row} from `plan` -- missing `map` or `reduce`?"))))
|
(toString [_]
|
||||||
|
(try
|
||||||
|
(str (row-builder @builder))
|
||||||
|
(catch Throwable _
|
||||||
|
"{row} from `plan` -- missing `map` or `reduce`?"))))))
|
||||||
|
|
||||||
|
(defmethod print-dup MapifiedResultSet [_ ^java.io.Writer w]
|
||||||
|
(.write w "{row} from `plan` -- missing `map` or `reduce`?"))
|
||||||
|
|
||||||
|
(prefer-method print-dup MapifiedResultSet clojure.lang.IPersistentMap)
|
||||||
|
|
||||||
|
(defmethod print-method MapifiedResultSet [_ ^java.io.Writer w]
|
||||||
|
(.write w "{row} from `plan` -- missing `map` or `reduce`?"))
|
||||||
|
|
||||||
|
(prefer-method print-method MapifiedResultSet clojure.lang.IPersistentMap)
|
||||||
|
|
||||||
(extend-protocol
|
(extend-protocol
|
||||||
DatafiableRow
|
DatafiableRow
|
||||||
|
|
|
||||||
|
|
@ -113,12 +113,12 @@
|
||||||
|
|
||||||
(deftest test-mapify
|
(deftest test-mapify
|
||||||
(testing "no row builder is used"
|
(testing "no row builder is used"
|
||||||
(is (= [false]
|
(is (= [true]
|
||||||
(into [] (map map?) ; it is not a real map
|
(into [] (map map?) ; it looks like a real map now
|
||||||
(p/-execute (ds) ["select * from fruit where id = ?" 1]
|
(p/-execute (ds) ["select * from fruit where id = ?" 1]
|
||||||
{:builder-fn (constantly nil)}))))
|
{:builder-fn (constantly nil)}))))
|
||||||
(is (= ["Apple"]
|
(is (= ["Apple"]
|
||||||
(into [] (map :name) ; but keyword selection works
|
(into [] (map :name) ; keyword selection works
|
||||||
(p/-execute (ds) ["select * from fruit where id = ?" 1]
|
(p/-execute (ds) ["select * from fruit where id = ?" 1]
|
||||||
{:builder-fn (constantly nil)}))))
|
{:builder-fn (constantly nil)}))))
|
||||||
(is (= [[2 [:name "Banana"]]]
|
(is (= [[2 [:name "Banana"]]]
|
||||||
|
|
@ -136,14 +136,55 @@
|
||||||
:unnamed)
|
:unnamed)
|
||||||
(get % :id 0))) ; get with not-found works
|
(get % :id 0))) ; get with not-found works
|
||||||
(p/-execute (ds) ["select * from fruit where id = ?" 4]
|
(p/-execute (ds) ["select * from fruit where id = ?" 4]
|
||||||
|
{:builder-fn (constantly nil)}))))
|
||||||
|
(is (= [{}]
|
||||||
|
(into [] (map empty) ; return empty map without building
|
||||||
|
(p/-execute (ds) ["select * from fruit where id = ?" 1]
|
||||||
{:builder-fn (constantly nil)})))))
|
{:builder-fn (constantly nil)})))))
|
||||||
(testing "assoc and seq build maps"
|
(testing "count does not build a map"
|
||||||
|
(let [count-builder (fn [_1 _2]
|
||||||
|
(reify rs/RowBuilder
|
||||||
|
(column-count [_] 13)))]
|
||||||
|
(is (= [13]
|
||||||
|
(into [] (map count) ; count relies on columns, not row fields
|
||||||
|
(p/-execute (ds) ["select * from fruit where id = ?" 1]
|
||||||
|
{:builder-fn count-builder}))))))
|
||||||
|
(testing "assoc, dissoc, cons, seq, and = build maps"
|
||||||
(is (map? (reduce (fn [_ row] (reduced (assoc row :x 1)))
|
(is (map? (reduce (fn [_ row] (reduced (assoc row :x 1)))
|
||||||
nil
|
nil
|
||||||
(p/-execute (ds) ["select * from fruit"] {}))))
|
(p/-execute (ds) ["select * from fruit"] {}))))
|
||||||
|
(is (= 6 (count (reduce (fn [_ row] (reduced (assoc row :x 1)))
|
||||||
|
nil
|
||||||
|
(p/-execute (ds) ["select * from fruit"] {})))))
|
||||||
|
(is (map? (reduce (fn [_ row] (reduced
|
||||||
|
(dissoc row
|
||||||
|
(if (postgres?)
|
||||||
|
:fruit/name
|
||||||
|
:FRUIT/NAME))))
|
||||||
|
nil
|
||||||
|
(p/-execute (ds) ["select * from fruit"] {}))))
|
||||||
|
(is (= 4 (count (reduce (fn [_ row] (reduced
|
||||||
|
(dissoc row
|
||||||
|
(if (postgres?)
|
||||||
|
:fruit/name
|
||||||
|
:FRUIT/NAME))))
|
||||||
|
nil
|
||||||
|
(p/-execute (ds) ["select * from fruit"] {})))))
|
||||||
(is (seq? (reduce (fn [_ row] (reduced (seq row)))
|
(is (seq? (reduce (fn [_ row] (reduced (seq row)))
|
||||||
nil
|
nil
|
||||||
(p/-execute (ds) ["select * from fruit"] {}))))
|
(p/-execute (ds) ["select * from fruit"] {}))))
|
||||||
|
(is (seq? (reduce (fn [_ row] (reduced (cons :seq row)))
|
||||||
|
nil
|
||||||
|
(p/-execute (ds) ["select * from fruit"] {}))))
|
||||||
|
(is (= :seq (first (reduce (fn [_ row] (reduced (cons :seq row)))
|
||||||
|
nil
|
||||||
|
(p/-execute (ds) ["select * from fruit"] {})))))
|
||||||
|
(is (false? (reduce (fn [_ row] (reduced (= row {})))
|
||||||
|
nil
|
||||||
|
(p/-execute (ds) ["select * from fruit"] {}))))
|
||||||
|
(is (map-entry? (second (reduce (fn [_ row] (reduced (cons :seq row)))
|
||||||
|
nil
|
||||||
|
(p/-execute (ds) ["select * from fruit"] {})))))
|
||||||
(is (every? map-entry? (reduce (fn [_ row] (reduced (seq row)))
|
(is (every? map-entry? (reduce (fn [_ row] (reduced (seq row)))
|
||||||
nil
|
nil
|
||||||
(p/-execute (ds) ["select * from fruit"] {})))))
|
(p/-execute (ds) ["select * from fruit"] {})))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue