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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
;; Copyright (c) Stephen C. Gilardi. All rights reserved. The use and
;; distribution terms for this software are covered by the Eclipse Public
;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
;; be found in the file epl-v10.html at the root of this distribution. By
;; using this software in any fashion, you are agreeing to be bound by the
;; terms of this license. You must not remove this notice, or any other,
;; from this software.
;;
;; internal definitions for clojure.contrib.sql
;;
;; scgilardi (gmail)
;; Created 3 October 2008
(ns clojure.contrib.sql.internal
(:use (clojure.contrib
[except :only (throw-arg)]
[java-utils :only (as-str)])))
(def *db* {:connection nil :level 0})
; TODO: test and move to clojure.contrib.java-utils?
(defn properties
"Converts a map from keywords, symbols, or strings to values into a
java.util.Properties object that maps the key names to the values with
all represented as strings."
[m]
(let [p (java.util.Properties.)]
(doseq [[key val] (seq m)]
(.setProperty p (as-str key) (as-str val)))
p))
(defn find-connection*
"Returns the current database connection (or nil if there is none)"
[]
(:connection *db*))
(defn connection*
"Returns the current database connection (or throws if there is none)"
[]
(or (find-connection*)
(throw (Exception. "no current database connection"))))
(defn rollback
"Accessor for the rollback flag on the current connection"
([]
(deref (:rollback *db*)))
([val]
(swap! (:rollback *db*) (fn [_] val))))
(defn get-connection
"Creates a connection to a database. db-spec is a map containing values
for one of the following parameter sets:
DataSource:
:datasource (required) a javax.sql.DataSource
:username (optional) a String
:password (optional) a String
DriverManager:
:classname (required) a String, the jdbc driver class name
:subprotocol (required) a String, the jdbc subprotocol
:subname (required) a String, the jdbc subname
(others) (optional) passed to the driver as properties."
[{:keys [datasource username password classname subprotocol subname]
:as db-spec}]
(when classname
(clojure.lang.RT/loadClassForName classname))
(if datasource
(if username
(.getConnection datasource username password)
(.getConnection datasource))
(java.sql.DriverManager/getConnection
(format "jdbc:%s:%s" subprotocol subname)
(properties (dissoc db-spec :classname :subprotocol :subname)))))
(defn with-connection*
"Evaluates func in the context of a new connection to a database then
closes the connection."
[db-spec func]
(with-open [con (get-connection db-spec)]
(binding [*db* (assoc *db*
:connection con :level 0 :rollback (atom false))]
(func))))
(defn transaction*
"Evaluates func as a transaction on the open database connection. Any
nested transactions are absorbed into the outermost transaction. By
default, all database updates are committed together as a group after
evaluating the outermost body, or rolled back on any uncaught
exception. If rollback is set within scope of the outermost transaction,
the entire transaction will be rolled back rather than committed when
complete."
[func]
(binding [*db* (update-in *db* [:level] inc)]
(if (= (:level *db*) 1)
(let [con (connection*)
auto-commit (.getAutoCommit con)]
(io!
(.setAutoCommit con false)
(try
(func)
(catch Exception e
(rollback true)
(throw
(Exception. (format "transaction rolled back: %s"
(.getMessage e)) e)))
(finally
(if (rollback)
(.rollback con)
(.commit con))
(rollback false)
(.setAutoCommit con auto-commit)))))
(func))))
(defn with-query-results*
"Executes a query, then evaluates func passing in a seq of the results as
an argument. The first argument is a vector containing the (optionally
parameterized) sql query string followed by values for any parameters."
[[sql & params :as sql-params] func]
(when-not (vector? sql-params)
(throw-arg "\"%s\" expected %s %s, found %s %s"
"sql-params"
"vector"
"[sql param*]"
(.getName (class sql-params))
(pr-str sql-params)))
(with-open [stmt (.prepareStatement (connection*) sql)]
(doseq [[index value] (map vector (iterate inc 1) params)]
(.setObject stmt index value))
(with-open [rset (.executeQuery stmt)]
(func (resultset-seq rset)))))
|