Merge pull request #113 from seancorfield/java.data-next

Expand datafication; leverage new exception handling in java.data
This commit is contained in:
Sean Corfield 2020-05-31 18:24:42 -07:00 committed by GitHub
commit d24dd892dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 36 deletions

View file

@ -1,6 +1,6 @@
{:paths ["src"] {:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.10.1"} :deps {org.clojure/clojure {:mvn/version "1.10.1"}
org.clojure/java.data {:mvn/version "1.0.73"}} org.clojure/java.data {:mvn/version "1.0.78"}}
:aliases :aliases
{:test {:extra-paths ["test"] {:test {:extra-paths ["test"]
:extra-deps {org.clojure/test.check {:mvn/version "1.0.0"} :extra-deps {org.clojure/test.check {:mvn/version "1.0.0"}

View file

@ -1,13 +1,33 @@
;; copyright (c) 2020 Sean Corfield, all rights reserved ;; copyright (c) 2020 Sean Corfield, all rights reserved
(ns next.jdbc.datafy (ns next.jdbc.datafy
"This namespace provides datafication of several JDBC object types: "This namespace provides datafication of several JDBC object types,
all within the `java.sql` package:
* `java.sql.Connection` -- datafies as a bean; `:metaData` is navigable * `Connection` -- datafies as a bean; `:metaData` is navigable
and produces `java.sql.DatabaseMetaData`. and produces `java.sql.DatabaseMetaData`.
* `java.sql.DatabaseMetaData` -- datafies as a bean; five properties * `DatabaseMetaData` -- datafies as a bean; five properties
are navigable to produce fully-realized datafiable result sets. are navigable to produce fully-realized datafiable result sets.
* `java.sql.ResultSetMetaData` -- datafies as a vector of column descriptions." * `ParameterMetaData` -- datafies as a vector of parameter descriptions.
* `ResultSet` -- datafies as a bean; if the `ResultSet` has an associated
`Statement` and that in turn has an associated `Connection` then an
additional key of `:rows` is provided which is a datafied result set,
from `next.jdbc.result-set/datafiable-result-set` with default options.
This is provided as a convenience, purely for datafication of other
JDBC data types -- in normal `next.jdbc` usage, result sets are
datafied under full user control.
* `ResultSetMetaData` -- datafies as a vector of column descriptions.
* `Statement` -- datafies as a bean.
Because different database drivers may throw `SQLException` for various
unimplemented or unavailable propertiies on objects in various states,
the default behavior is to return those exceptions using the `:qualify`
option for `clojure.java.data/from-java-shallow`, so for a property
`:foo`, if its corresponding getter throws an exception, it would instead
be returned as `:foo/exception`. This behavior can be overridden by
`binding` `next.jdbc.datafy/*datafy-failure*` to any of the other options
supported: `:group`, `:omit`, or `:return`. See the `clojure.java.data`
documentation for more details."
(:require [clojure.core.protocols :as core-p] (:require [clojure.core.protocols :as core-p]
[clojure.java.data :as j] [clojure.java.data :as j]
[next.jdbc.result-set :as rs]) [next.jdbc.result-set :as rs])
@ -64,9 +84,15 @@
:unknown)) :unknown))
:signed (fn [^ParameterMetaData o i] (.isSigned o i))}) :signed (fn [^ParameterMetaData o i] (.isSigned o i))})
(def ^:dynamic *datafy-failure*
"How datafication failures should be handled, based on `clojure.java.data`.
Defaults to `:qualify`, but can be `:group`, `:omit`, `:qualify`, or `:return`."
:qualify)
(defn- safe-bean [o opts] (defn- safe-bean [o opts]
(try (try
(j/from-java-shallow o (assoc opts :add-class true)) (j/from-java-shallow o (assoc opts :add-class true :exceptions *datafy-failure*))
(catch Throwable t (catch Throwable t
(let [dex (juxt type (comp str ex-message)) (let [dex (juxt type (comp str ex-message))
cause (ex-cause t)] cause (ex-cause t)]
@ -96,7 +122,8 @@
(with-meta (let [data (safe-bean this {})] (with-meta (let [data (safe-bean this {})]
(cond-> data (cond-> data
(not (:exception (meta data))) (not (:exception (meta data)))
(assoc :all-tables []))) ;; add an opaque object that nav will "replace"
(assoc :all-tables (Object.))))
{`core-p/nav (fn [_ k v] {`core-p/nav (fn [_ k v]
(condp = k (condp = k
:all-tables :all-tables
@ -124,8 +151,6 @@
(.getConnection this) (.getConnection this)
{}) {})
v))})) v))}))
ResultSetMetaData
(datafy [this] (datafy-result-set-meta-data this))
ParameterMetaData ParameterMetaData
(datafy [this] (datafy-parameter-meta-data this)) (datafy [this] (datafy-parameter-meta-data this))
ResultSet ResultSet
@ -137,5 +162,7 @@
c (when s (.getConnection s))] c (when s (.getConnection s))]
(cond-> (safe-bean this {}) (cond-> (safe-bean this {})
c (assoc :rows (rs/datafiable-result-set this c {})))))) c (assoc :rows (rs/datafiable-result-set this c {}))))))
ResultSetMetaData
(datafy [this] (datafy-result-set-meta-data this))
Statement Statement
(datafy [this] (safe-bean this {:omit #{:moreResults}}))) (datafy [this] (safe-bean this {:omit #{:moreResults}})))

View file

@ -24,20 +24,20 @@
:networkTimeout :schema :transactionIsolation :typeMap :warnings :networkTimeout :schema :transactionIsolation :typeMap :warnings
;; boolean properties ;; boolean properties
:closed :readOnly :closed :readOnly
;; added by bean itself ;; configured to be added as if by clojure.core/bean
:class}) :class})
(deftest connection-datafy-tests (deftest connection-datafy-tests
(testing "connection datafication" (testing "connection datafication"
(with-open [con (jdbc/get-connection (ds))] (with-open [con (jdbc/get-connection (ds))]
(if (derby?) (let [reference-keys (cond-> basic-connection-keys
(is (= #{:exception :cause} ; at least one property not supported (derby?) (-> (disj :networkTimeout)
(set (keys (d/datafy con))))) (conj :networkTimeout/exception)))
(let [data (set (keys (d/datafy con)))] data (set (keys (d/datafy con)))]
(when-let [diff (seq (set/difference data basic-connection-keys))] (when-let [diff (seq (set/difference data reference-keys))]
(println (:dbtype (db)) :connection (sort diff))) (println (:dbtype (db)) :connection (sort diff)))
(is (= basic-connection-keys (is (= reference-keys
(set/intersection basic-connection-keys data)))))))) (set/intersection reference-keys data)))))))
(def ^:private basic-database-metadata-keys (def ^:private basic-database-metadata-keys
"Generic JDBC Connection fields." "Generic JDBC Connection fields."
@ -63,28 +63,33 @@
:typeInfo :userName :typeInfo :userName
;; boolean properties ;; boolean properties
:catalogAtStart :readOnly :catalogAtStart :readOnly
;; added by bean itself ;; configured to be added as if by clojure.core/bean
:class}) :class
;; added by next.jdbc.datafy if the datafication succeeds
:all-tables})
(deftest database-metadata-datafy-tests (deftest database-metadata-datafy-tests
(testing "database metadata datafication" (testing "database metadata datafication"
(with-open [con (jdbc/get-connection (ds))] (with-open [con (jdbc/get-connection (ds))]
(if (or (postgres?) (sqlite?)) (let [reference-keys (cond-> basic-database-metadata-keys
(is (= #{:exception :cause} ; at least one property not supported (postgres?) (-> (disj :rowIdLifetime)
(set (keys (d/datafy (.getMetaData con)))))) (conj :rowIdLifetime/exception))
(let [data (set (keys (d/datafy (.getMetaData con))))] (sqlite?) (-> (disj :clientInfoProperties :rowIdLifetime)
(when-let [diff (seq (set/difference data basic-database-metadata-keys))] (conj :clientInfoProperties/exception
(println (:dbtype (db)) :db-meta (sort diff))) :rowIdLifetime/exception)))
(is (= basic-database-metadata-keys data (set (keys (d/datafy (.getMetaData con))))]
(set/intersection basic-database-metadata-keys data))))))) (when-let [diff (seq (set/difference data reference-keys))]
(println (:dbtype (db)) :db-meta (sort diff)))
(is (= reference-keys
(set/intersection reference-keys data))))))
(testing "nav to catalogs yields object" (testing "nav to catalogs yields object"
(when-not (or (postgres?) (sqlite?)) (with-open [con (jdbc/get-connection (ds))]
(with-open [con (jdbc/get-connection (ds))] (let [data (d/datafy (.getMetaData con))]
(let [data (d/datafy (.getMetaData con))] (doseq [k (cond-> #{:catalogs :clientInfoProperties :schemas :tableTypes :typeInfo}
(doseq [k [:catalogs :clientInfoProperties :schemas :tableTypes :typeInfo]] (sqlite?) (disj :clientInfoProperties))]
(let [rs (d/nav data k nil)] (let [rs (d/nav data k nil)]
(is (vector? rs)) (is (vector? rs))
(is (every? map? rs))))))))) (is (every? map? rs))))))))
(deftest result-set-metadata-datafy-tests (deftest result-set-metadata-datafy-tests
(testing "result set metadata datafication" (testing "result set metadata datafication"
@ -100,7 +105,10 @@
(comment (comment
(def con (jdbc/get-connection (ds))) (def con (jdbc/get-connection (ds)))
(rs/datafiable-result-set (.getTables (.getMetaData con) nil nil nil nil) con {}) (rs/datafiable-result-set (.getTables (.getMetaData con) nil nil nil nil) con {})
(def ps (jdbc/prepare con ["SELECT * FROM fruit"])) (def ps (jdbc/prepare con ["SELECT * FROM fruit WHERE grade > ?"]))
(require '[next.jdbc.prepare :as prep])
(prep/set-parameters ps [30])
(.execute ps) (.execute ps)
(.getResultSet ps) (.getResultSet ps)
(.close ps)
(.close con)) (.close con))