Fix #26 by adding datafiable-result-set
This makes handling metadata result sets much easier.
This commit is contained in:
parent
b4331146ff
commit
22a3f2bb5f
7 changed files with 53 additions and 27 deletions
|
|
@ -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:
|
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
|
## Stable Builds
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ The latest versions on Clojars and on cljdoc:
|
||||||
|
|
||||||
[](https://clojars.org/seancorfield/next.jdbc) [](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT)
|
[](https://clojars.org/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)
|
* [Getting Started](/doc/getting-started.md)
|
||||||
* [Migrating from `clojure.java.jdbc`](/doc/migration-from-clojure-java-jdbc.md)
|
* [Migrating from `clojure.java.jdbc`](/doc/migration-from-clojure-java-jdbc.md)
|
||||||
|
|
|
||||||
|
|
@ -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).
|
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)
|
[<: All The Options](/doc/all-the-options.md) | [Migration from `clojure.java.jdbc` :>](/doc/migration-from-clojure-java-jdbc.md)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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
|
## 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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -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!`).
|
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`
|
## `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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -354,6 +354,22 @@
|
||||||
(with-meta this
|
(with-meta this
|
||||||
{`core-p/datafy (navize-row connectable opts)})))
|
{`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
|
(defn- stmt->result-set
|
||||||
"Given a `PreparedStatement` and options, execute it and return a `ResultSet`
|
"Given a `PreparedStatement` and options, execute it and return a `ResultSet`
|
||||||
if possible."
|
if possible."
|
||||||
|
|
@ -413,14 +429,7 @@
|
||||||
(rest sql-params)
|
(rest sql-params)
|
||||||
opts)]
|
opts)]
|
||||||
(if-let [rs (stmt->result-set stmt opts)]
|
(if-let [rs (stmt->result-set stmt opts)]
|
||||||
(let [builder-fn (get opts :builder-fn as-maps)
|
(datafiable-result-set rs this opts)
|
||||||
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'))))
|
|
||||||
[{:next.jdbc/update-count (.getUpdateCount stmt)}])))
|
[{:next.jdbc/update-count (.getUpdateCount stmt)}])))
|
||||||
|
|
||||||
javax.sql.DataSource
|
javax.sql.DataSource
|
||||||
|
|
@ -452,14 +461,7 @@
|
||||||
(rest sql-params)
|
(rest sql-params)
|
||||||
opts)]
|
opts)]
|
||||||
(if-let [rs (stmt->result-set stmt opts)]
|
(if-let [rs (stmt->result-set stmt opts)]
|
||||||
(let [builder-fn (get opts :builder-fn as-maps)
|
(datafiable-result-set rs this opts)
|
||||||
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'))))
|
|
||||||
[{:next.jdbc/update-count (.getUpdateCount stmt)}]))))
|
[{:next.jdbc/update-count (.getUpdateCount stmt)}]))))
|
||||||
|
|
||||||
java.sql.PreparedStatement
|
java.sql.PreparedStatement
|
||||||
|
|
@ -480,15 +482,7 @@
|
||||||
{:next.jdbc/update-count (.getUpdateCount this)}))
|
{:next.jdbc/update-count (.getUpdateCount this)}))
|
||||||
(-execute-all [this _ opts]
|
(-execute-all [this _ opts]
|
||||||
(if-let [rs (stmt->result-set this opts)]
|
(if-let [rs (stmt->result-set this opts)]
|
||||||
(let [builder-fn (get opts :builder-fn as-maps)
|
(datafiable-result-set rs (.getConnection this) opts)
|
||||||
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'))))
|
|
||||||
[{:next.jdbc/update-count (.getUpdateCount this)}]))
|
[{:next.jdbc/update-count (.getUpdateCount this)}]))
|
||||||
|
|
||||||
Object
|
Object
|
||||||
|
|
|
||||||
|
|
@ -191,3 +191,18 @@
|
||||||
(is (every? #(instance? Fruit %) rs))
|
(is (every? #(instance? Fruit %) rs))
|
||||||
(is (= 1 (count rs)))
|
(is (= 1 (count rs)))
|
||||||
(is (= 1 (:id (first 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))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue