From e5529ddfb5c1caac14e2e6ff3d0a38bdb187f424 Mon Sep 17 00:00:00 2001
From: Gary Fredericks <fredericksgary@gmail.com>
Date: Tue, 22 Jan 2013 10:53:41 -0600
Subject: [PATCH] Create a IResultsetReadColumn protocol for transforming
 types out of the database

By default is the identity, but can be extended by users to deal with
special types (like arrays).
---
 src/main/clojure/clojure/java/jdbc.clj      |   16 +++++++++++++++-
 src/test/clojure/clojure/java/test_jdbc.clj |   23 +++++++++++++++++++++++
 2 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj
index 24a3156..4376aa7 100644
--- a/src/main/clojure/clojure/java/jdbc.clj
+++ b/src/main/clojure/clojure/java/jdbc.clj
@@ -239,6 +239,20 @@ generated keys are returned (as a map)." }
     cols
     (reduce (fn [unique-cols col-name] (conj unique-cols (make-name-unique unique-cols col-name 1))) []  cols)))
 
+(defprotocol IResultsetReadColumn
+  "Protocol for reading objects from the java.sql.ResultSet. Default
+   implementations (for Object and nil) return the argument, but it can
+   be extended to provide custom behavior for special types."
+  (resultset-read-column [_] "Function for transforming values after reading them
+                              from the database"))
+
+(extend-protocol IResultsetReadColumn
+  Object
+  (resultset-read-column [x] x)
+
+  nil
+  (resultset-read-column [_] nil))
+
 (defn resultset-seq
   "Creates and returns a lazy sequence of maps corresponding to
    the rows in the java.sql.ResultSet rs. Based on clojure.core/resultset-seq
@@ -253,7 +267,7 @@ generated keys are returned (as a map)." }
                  (map (fn [^Integer i] (.getColumnLabel rsmeta i)))
                  make-cols-unique
                  (map (comp keyword identifiers)))
-          row-values (fn [] (map (fn [^Integer i] (.getObject rs i)) idxs))
+          row-values (fn [] (map (fn [^Integer i] (resultset-read-column (.getObject rs i))) idxs))
           ;; This used to use create-struct (on keys) and then struct to populate each row.
           ;; That had the side effect of preserving the order of columns in each row. As
           ;; part of JDBC-15, this was changed because structmaps are deprecated. We don't
diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj
index 062444c..3db373a 100644
--- a/src/test/clojure/clojure/java/test_jdbc.clj
+++ b/src/test/clojure/clojure/java/test_jdbc.clj
@@ -526,3 +526,26 @@
        (is (= 1 (sql/query db ["SELECT * FROM fruit"] :result-set-fn count)))))
     (is (= 0 (sql/query db ["SELECT * FROM fruit"] :result-set-fn count)))))
 
+(deftest test-resultset-read-column
+  (extend-protocol sql/IResultsetReadColumn
+    String
+    (resultset-read-column [s] ::FOO))
+
+  (doseq [db (test-specs)]
+    (sql/with-connection db
+      (create-test-table :fruit db)
+      (sql/insert! db
+                   :fruit
+                   [:name :cost]
+                   ["Crepes" 12]
+                   ["Vegetables" -88]
+                   ["Teenage Mutant Ninja Turtles" 0])
+      (is (= {:name ::FOO, :cost -88}
+             (sql/with-query-results res ["SELECT name, cost FROM fruit WHERE name = ?"
+                                          "Vegetables"]
+               (first res))))))
+
+  ;; somewhat "undo" the first extension
+  (extend-protocol sql/IResultsetReadColumn
+    String
+    (resultset-read-column [s] s)))
\ No newline at end of file
-- 
1.7.9.5

