Fix #6 by adding IReadColumn and ISQLParameter protocols

The latter can be extended via metadata but the former cannot (since 
only the latter is coming from Clojure).
This commit is contained in:
Sean Corfield 2019-04-01 23:25:10 -07:00
parent 4b81a42b4d
commit 6d1a42a0a0
2 changed files with 61 additions and 12 deletions

View file

@ -14,6 +14,24 @@
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
(defprotocol ISQLParameter :extend-via-metadata true
"Protocol for setting SQL parameters in statement objects, which
can convert from Clojure values. The default implementation just
calls .setObject on the parameter value. It can be extended to use other
methods of PreparedStatement to convert and set parameter values."
(set-parameter [val stmt ix]
"Convert a Clojure value into a SQL value and store it as the ix'th
parameter in the given SQL statement object."))
(extend-protocol ISQLParameter
Object
(set-parameter [v ^PreparedStatement s ^long i]
(.setObject s i v))
nil
(set-parameter [_ ^PreparedStatement s ^long i]
(.setObject s i nil)))
(defn set-parameters (defn set-parameters
"Given a PreparedStatement and a vector of parameter values, update the "Given a PreparedStatement and a vector of parameter values, update the
PreparedStatement with those parameters and return it. PreparedStatement with those parameters and return it.
@ -23,7 +41,7 @@
[^PreparedStatement ps params] [^PreparedStatement ps params]
(when (seq params) (when (seq params)
(loop [[p & more] params i 1] (loop [[p & more] params i 1]
(.setObject ps i p) (set-parameter p ps i)
(when more (when more
(recur more (inc i))))) (recur more (inc i)))))
ps) ps)

View file

@ -17,9 +17,8 @@
If :identifiers was specified, apply that to both the table qualifier If :identifiers was specified, apply that to both the table qualifier
and the column name." and the column name."
[^ResultSet rs opts] [^ResultSet rs ^ResultSetMetaData rsmeta opts]
(let [^ResultSetMetaData rsmeta (.getMetaData rs) (let [idxs (range 1 (inc (.getColumnCount rsmeta)))]
idxs (range 1 (inc (.getColumnCount rsmeta)))]
(if-let [ident-fn (:identifiers opts)] (if-let [ident-fn (:identifiers opts)]
(mapv (fn [^Integer i] (mapv (fn [^Integer i]
(keyword (when-let [qualifier (not-empty (.getTableName rsmeta i))] (keyword (when-let [qualifier (not-empty (.getTableName rsmeta i))]
@ -32,8 +31,33 @@
idxs)))) idxs))))
(defprotocol ColumnarResultSet (defprotocol ColumnarResultSet
(column-names [this]) "To allow reducing functions to access a result set's column names and
(row-values [this])) row values, such as the as-arrays reducing function in this namespace."
(column-names [this] "Return the column names from a result set.")
(row-values [this] "Return the values from the current row of a result set."))
(defprotocol IReadColumn
"Protocol for reading objects from the java.sql.ResultSet. Default
implementations (for Object and nil) return the argument, and the
Boolean implementation ensures a canonicalized true/false value,
but it can be extended to provide custom behavior for special types."
(read-column-by-label [val label]
"Function for transforming values after reading them via a column label.")
(read-column-by-index [val rsmeta idx]
"Function for transforming values after reading them via a column index."))
(extend-protocol IReadColumn
Object
(read-column-by-label [x _] x)
(read-column-by-index [x _2 _3] x)
Boolean
(read-column-by-label [x _] (if (= true x) true false))
(read-column-by-index [x _2 _3] (if (= true x) true false))
nil
(read-column-by-label [_1 _2] nil)
(read-column-by-index [_1 _2 _3] nil))
(defn- mapify-result-set (defn- mapify-result-set
"Given a result set, return an object that wraps the current row as a hash "Given a result set, return an object that wraps the current row as a hash
@ -47,17 +71,18 @@
Supports Seqable which realizes a full row of the data." Supports Seqable which realizes a full row of the data."
[^ResultSet rs opts] [^ResultSet rs opts]
(let [cols (delay (get-column-names rs opts))] (let [rsmeta (.getMetaData rs)
cols (delay (get-column-names rs rsmeta opts))]
(reify (reify
clojure.lang.ILookup clojure.lang.ILookup
(valAt [this k] (valAt [this k]
(try (try
(.getObject rs (name k)) (read-column-by-label (.getObject rs (name k)) (name k))
(catch SQLException _))) (catch SQLException _)))
(valAt [this k not-found] (valAt [this k not-found]
(try (try
(.getObject rs (name k)) (read-column-by-label (.getObject rs (name k)) (name k))
(catch SQLException _ (catch SQLException _
not-found))) not-found)))
@ -70,7 +95,9 @@
false))) false)))
(entryAt [this k] (entryAt [this k]
(try (try
(clojure.lang.MapEntry. k (.getObject rs (name k))) (clojure.lang.MapEntry. k (read-column-by-label
(.getObject rs (name k))
(name k)))
(catch SQLException _))) (catch SQLException _)))
(assoc [this k v] (assoc [this k v]
(assoc (into {} (seq this)) k v)) (assoc (into {} (seq this)) k v))
@ -79,13 +106,17 @@
(seq [this] (seq [this]
(seq (mapv (fn [^Integer i] (seq (mapv (fn [^Integer i]
(clojure.lang.MapEntry. (nth @cols (dec i)) (clojure.lang.MapEntry. (nth @cols (dec i))
(.getObject rs i))) (read-column-by-index
(.getObject rs i)
rsmeta i)))
(range 1 (inc (count @cols)))))) (range 1 (inc (count @cols))))))
ColumnarResultSet ColumnarResultSet
(column-names [this] @cols) (column-names [this] @cols)
(row-values [this] (row-values [this]
(mapv (fn [^Integer i] (.getObject rs i)) (mapv (fn [^Integer i] (read-column-by-index
(.getObject rs i)
rsmeta i))
(range 1 (inc (count @cols)))))))) (range 1 (inc (count @cols))))))))
(defn as-arrays (defn as-arrays