diff --git a/CHANGELOG.md b/CHANGELOG.md index c759ce9..8d9e560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index a34dfd6..c0e0067 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/doc/datafy-nav-and-schema.md b/doc/datafy-nav-and-schema.md index 5b09cc0..58587bc 100644 --- a/doc/datafy-nav-and-schema.md +++ b/doc/datafy-nav-and-schema.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). +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) diff --git a/doc/migration-from-clojure-java-jdbc.md b/doc/migration-from-clojure-java-jdbc.md index 6ef5f7a..d2ee5f5 100644 --- a/doc/migration-from-clojure-java-jdbc.md +++ b/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. +### 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. diff --git a/doc/result-set-builders.md b/doc/result-set-builders.md index 3a1bc09..73c232d 100644 --- a/doc/result-set-builders.md +++ b/doc/result-set-builders.md @@ -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. diff --git a/src/next/jdbc/result_set.clj b/src/next/jdbc/result_set.clj index ed080f2..06bf02d 100644 --- a/src/next/jdbc/result_set.clj +++ b/src/next/jdbc/result_set.clj @@ -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 diff --git a/test/next/jdbc/result_set_test.clj b/test/next/jdbc/result_set_test.clj index 89248e4..ad592f1 100644 --- a/test/next/jdbc/result_set_test.clj +++ b/test/next/jdbc/result_set_test.clj @@ -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))))