Fix #26 by adding datafiable-result-set

This makes handling metadata result sets much easier.
This commit is contained in:
Sean Corfield 2019-06-08 15:09:42 -07:00
parent b4331146ff
commit 22a3f2bb5f
7 changed files with 53 additions and 27 deletions

View file

@ -6,7 +6,7 @@ Only accretive/fixative changes will be made from now on.
The following changes have been committed to the **master** branch and will be in the next release:
* None.
* Fix #26 by exposing `next.jdbc.result-set/datafiable-result-set` so that various `java.sql.DatabaseMetaData` methods that result metadata information in `ResultSet`s can be easily turned into a fully realized result set.
## Stable Builds

View file

@ -8,7 +8,7 @@ The latest versions on Clojars and on cljdoc:
[![Clojars Project](https://clojars.org/seancorfield/next.jdbc/latest-version.svg)](https://clojars.org/seancorfield/next.jdbc) [![cljdoc badge](https://cljdoc.org/badge/seancorfield/next.jdbc)](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT)
This documentation is for the 1.0.0-rc1 release -- [see the CHANGELOG](CHANGELOG.md).
This documentation is for the upcoming 1.0.0 release -- [see the CHANGELOG](CHANGELOG.md).
* [Getting Started](/doc/getting-started.md)
* [Migrating from `clojure.java.jdbc`](/doc/migration-from-clojure-java-jdbc.md)

View file

@ -45,4 +45,6 @@ When that is called (`nav` on a row, column name, and column value), if a `:sche
The protocol `next.jdbc.result-set/DatafiableRow` has a default implementation of `datafiable-row` for `clojure.lang.IObj` that just adds the metadata to support `datafy`. There is also an implementation baked into the result set handling behind `plan` so that you can call `datafiable-row` directly during reduction and get a fully-realized row that can be `datafy`'d (and then `nav`igated).
In addition, you can call `next.jdbc.result-set/datafiable-result-set` on any `ResultSet` object and get a fully realized, datafiable result set created using any of the result set builders.
[<: All The Options](/doc/all-the-options.md) | [Migration from `clojure.java.jdbc` :>](/doc/migration-from-clojure-java-jdbc.md)

View file

@ -67,6 +67,19 @@ If you are using `:result-set-fn` and/or `:row-fn`, you will need to change to e
Note: this means that result sets are never exposed lazily in `next.jdbc` -- in `clojure.java.jdbc` you had to be careful that your `:result-set-fn` was eager, but in `next.jdbc` you either reduce the result set eagerly (via `plan`) or you get a fully-realized result set data structure back (from `execute!` and `execute-one!`). As with `clojure.java.jdbc` however, you can still stream result sets from the database and process them via reduction (was `reducible-query`, now `plan`). Remember that you can terminate a reduction early by using the `reduced` function to wrap the final value you produce.
### Processing Database Metadata
There are no metadata-specific functions in `next.jdbc` but those in `clojure.java.jdbc` are only a very thin layer over the raw Java calls. Here's how metadata can be handled in `next.jdbc`:
```clojure
(with-open [con (p/get-connection ds opts)]
(-> (.getMetaData con) ; produces java.sql.DatabaseMetaData
(.getTables nil nil nil (into-array ["TABLE" "VIEW"]))
(rs/datafiable-result-set ds opts)))
```
Several methods on `DatabaseMetaData` return a `ResultSet` object. All of those can be handled similarly.
## Further Minor differences
These are mostly drawn from [Issue #5](https://github.com/seancorfield/next-jdbc/issues/5) although most of the bullets in that issue are described in more detail above.

View file

@ -59,6 +59,8 @@ The options hash map for any `next.jdbc` function can contain a `:builder-fn` ke
The options hash map passed to the builder function will contain a `:next.jdbc/sql-params` key, whose value is the SQL + parameters vector passed into the top-level `next.jdbc` functions (`plan`, `execute!`, and `execute-one!`).
There is also a convenience function, `datafiable-result-set`, that accepts a `ResultSet` object (and a connectable and an options hash map) and returns a fully realized result set, per the `:builder-fn` option (or `as-maps` if that option is omitted).
## `next.jdbc.optional`
This namespace contains variants of the six `as-maps`-style builders above that omit keys from the row hash maps if the corresponding column is `NULL`. This is in keeping with Clojure's views of "optionality" -- that optional elements should simply be omitted -- and is provided as an "opt-in" style of rows and result sets.

View file

@ -354,6 +354,22 @@
(with-meta this
{`core-p/datafy (navize-row connectable opts)})))
(defn datafiable-result-set
"Given a ResultSet, a connectable, and an options hash map, return a fully
realized, datafiable result set per the `:builder-fn` option passed in.
If no `:builder-fn` option is provided, `as-maps` is used as the default.
This can be used to process regular result sets or metadata result sets."
[^java.sql.ResultSet rs connectable opts]
(let [builder-fn (get opts :builder-fn as-maps)
builder (builder-fn rs opts)]
(loop [rs' (->rs builder) more? (.next rs)]
(if more?
(recur (with-row builder rs'
(datafiable-row (row-builder builder) connectable opts))
(.next rs))
(rs! builder rs')))))
(defn- stmt->result-set
"Given a `PreparedStatement` and options, execute it and return a `ResultSet`
if possible."
@ -413,14 +429,7 @@
(rest sql-params)
opts)]
(if-let [rs (stmt->result-set stmt opts)]
(let [builder-fn (get opts :builder-fn as-maps)
builder (builder-fn rs opts)]
(loop [rs' (->rs builder) more? (.next rs)]
(if more?
(recur (with-row builder rs'
(datafiable-row (row-builder builder) this opts))
(.next rs))
(rs! builder rs'))))
(datafiable-result-set rs this opts)
[{:next.jdbc/update-count (.getUpdateCount stmt)}])))
javax.sql.DataSource
@ -452,14 +461,7 @@
(rest sql-params)
opts)]
(if-let [rs (stmt->result-set stmt opts)]
(let [builder-fn (get opts :builder-fn as-maps)
builder (builder-fn rs opts)]
(loop [rs' (->rs builder) more? (.next rs)]
(if more?
(recur (with-row builder rs'
(datafiable-row (row-builder builder) this opts))
(.next rs))
(rs! builder rs'))))
(datafiable-result-set rs this opts)
[{:next.jdbc/update-count (.getUpdateCount stmt)}]))))
java.sql.PreparedStatement
@ -480,15 +482,7 @@
{:next.jdbc/update-count (.getUpdateCount this)}))
(-execute-all [this _ opts]
(if-let [rs (stmt->result-set this opts)]
(let [builder-fn (get opts :builder-fn as-maps)
builder (builder-fn rs opts)]
(loop [rs' (->rs builder) more? (.next rs)]
(if more?
(recur (with-row builder rs'
(datafiable-row (row-builder builder)
(.getConnection this) opts))
(.next rs))
(rs! builder rs'))))
(datafiable-result-set rs (.getConnection this) opts)
[{:next.jdbc/update-count (.getUpdateCount this)}]))
Object

View file

@ -191,3 +191,18 @@
(is (every? #(instance? Fruit %) rs))
(is (= 1 (count rs)))
(is (= 1 (:id (first rs))))))
(deftest metadata-result-set
(let [metadata (with-open [con (p/get-connection (ds) {})]
(-> (.getMetaData con)
(.getTables nil nil nil (into-array ["TABLE" "VIEW"]))
(rs/datafiable-result-set (ds) {})))]
(is (vector? metadata))
(is (map? (first metadata)))
;; we should find :something/table_name with a value of "fruit"
;; may be upper/lower-case, could have any qualifier
(is (some (fn [row]
(some #(and (= "table_name" (-> % key name str/lower-case))
(= "fruit" (-> % val name str/lower-case)))
row))
metadata))))