/
simple_jdbc.clj
82 lines (72 loc) · 3.22 KB
/
simple_jdbc.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
(ns db.simple-jdbc
(:import (java.sql Connection
PreparedStatement
ResultSet
ResultSetMetaData
Statement)))
(defn- read-row
[^ResultSet rs labels]
(loop [row (transient {}) i 1 labels (seq labels)]
(if labels
(recur (assoc! row (first labels) (.getObject rs i)) (inc i) (next labels))
(persistent! row))))
(defn- read-rows
([^ResultSet rs]
(let [^ResultSetMetaData md (.getMetaData rs)
n (.getColumnCount md)]
(loop [i 1 labels (transient []) counts (transient {})]
(if (<= i n)
(let [base (-> (.getColumnName md i) clojure.string/lower-case (.replace \_ \-))
count (get counts base 1)]
(recur (inc i) (conj! labels (keyword (if (= count 1) base (str base "-" count))))
(assoc! counts base (inc count))))
(read-rows rs (persistent! labels))))))
([^ResultSet rs labels]
(loop [rows (transient [])]
(if (.next rs)
(recur (conj! rows (read-row rs labels)))
(persistent! rows)))))
(defn- read-results
[rtype ^Statement st]
;; loop binding order is important here, .getMoreResults implicitly
;; closes the ResultSet returned by .getResultSet.
(loop [results (transient []) rtype rtype]
(if rtype ;; true = resultset, false = update-count
(recur (conj! results (read-rows (.getResultSet st))) (.getMoreResults st))
(let [update-count (.getUpdateCount st)]
(if (<= 0 update-count) ;; update-count -1 = done.
(recur (conj! results update-count) (.getMoreResults st))
(case (count results)
0 nil
1 (results 0)
(persistent! results)))))))
(defn execute
"Executes a SQL statement on the provided JDBC connection. If the
statement is a query, the return value of this function is a vector
of row maps in a format described below. If the statement is an
update, the update count (an integer) is returned. If the SQL
contains more than one statement, a vector of results is returned.
The maps created from rows use the column labels as generated by the
SQL statement. The column labels are converted to keywords
following clojure conventions: they are made lower-case, hypens are
replaced by underscores, and duplicates have a number appended.
Duplicates can (and probably should) be avoided by using the SQL
'AS' keyword to provide a label. Thus, a select in the form:
SELECT A.x, A.y_z, B.x, C.x as c_x FROM ...
Will have result rows in the form:
{:x ..., :y-z ..., :x-2 ..., :c_x ...}
[conn sql] When only connection and a SQL string are supplied, the
statement is executed via Statement/execute.
[conn sql params] When additional parameters collection is supplied,
the parameters are bound in order to a PreparedStatement using
.setObject."
([^Connection conn ^String sql]
(with-open [^Statement st (.createStatement conn)]
(read-results (.execute st sql) st)))
([^Connection conn ^String sql params]
(with-open [^PreparedStatement ps (.prepareStatement conn sql)]
(loop [i 1 p (seq params)]
(when p
(.setObject ps i (first p))
(recur (inc i) (next p))))
(read-results (.execute ps) ps))))