aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/clojure/contrib/sql.clj65
-rw-r--r--src/clojure/contrib/sql/internal.clj69
-rw-r--r--src/clojure/contrib/sql/test.clj31
3 files changed, 98 insertions, 67 deletions
diff --git a/src/clojure/contrib/sql.clj b/src/clojure/contrib/sql.clj
index b8aa64ef..172cd190 100644
--- a/src/clojure/contrib/sql.clj
+++ b/src/clojure/contrib/sql.clj
@@ -16,59 +16,33 @@
;; Created 2 April 2008
(ns clojure.contrib.sql
- (:use clojure.contrib.except
- clojure.contrib.sql.internal))
+ (:use clojure.contrib.sql.internal))
(defmacro with-connection
"Evaluates body in the context of a new connection to a database then
- closes it. db-spec is a map containing string values for these required
- keys:
+ closes the connection. db-spec is a map containing string values for
+ these required keys:
:classname the jdbc driver class name
:subprotocol the jdbc subprotocol
:subname the jdbc subname
db-spec may contain additional key-value pairs that are passed along to
the driver as properties such as :user, :password, etc."
[db-spec & body]
- `(do
- (Class/forName (:classname ~db-spec))
- (with-open [con#
- (java.sql.DriverManager/getConnection
- (format "jdbc:%s:%s" (:subprotocol ~db-spec) (:subname ~db-spec))
- (properties (dissoc ~db-spec :classname :subprotocol :subname)))]
- (binding [*db* (assoc *db* :connection con# :level 0)]
- ~@body))))
+ `(with-connection* ~db-spec (fn [] ~@body)))
(defmacro transaction
- "Evaluates body as a transaction on the open database connection. Updates
- are committed together as a group after evaluating body or rolled back on
- any uncaught exception. Any nested transactions will be absorbed into the
- outermost transaction."
+ "Evaluates body as a transaction on the open database connection. Any
+ database updates are committed together as a group after evaluating, or
+ rolled back on any uncaught exception. Any nested transactions are
+ absorbed into the outermost transaction."
[& body]
- `(let [con# (connection)
- level# (:level *db*)]
- (binding [*db* (assoc *db* :level (inc level#))]
- (let [auto-commit# (.getAutoCommit con#)]
- (when (zero? level#)
- (.setAutoCommit con# false))
- (try
- (let [value# (do ~@body)]
- (when (zero? level#)
- (.commit con#))
- value#)
- (catch Exception e#
- (.rollback con#)
- (throw (Exception.
- (format "transaction rolled back: %s"
- (.getMessage e#)) e#)))
- (finally
- (when (zero? level#)
- (.setAutoCommit con# auto-commit#))))))))
+ `(transaction* (fn [] ~@body)))
(defn do-commands
"Executes SQL commands that don't return results on the open database
connection"
[& commands]
- (with-open [stmt (.createStatement (connection))]
+ (with-open [stmt (create-statement)]
(doseq [cmd commands]
(.addBatch stmt cmd))
(.executeBatch stmt)))
@@ -77,7 +51,7 @@
"Executes a prepared statement on the open database connection with
parameter sets"
[sql & sets]
- (with-open [stmt (.prepareStatement (connection) sql)]
+ (with-open [stmt (prepare-statement sql)]
(doseq [set sets]
(doseq [[index value] (map vector (iterate inc 1) set)]
(.setObject stmt index value))
@@ -113,10 +87,11 @@
[table column-names & values]
(let [count (count (first values))
template (apply str (interpose "," (replicate count "?")))
- columns (if (seq column-names)
- (format "(%s)"
- (apply str (interpose "," (map the-str column-names))))
- "")]
+ columns
+ (if (seq column-names)
+ (format "(%s)"
+ (apply str (interpose "," (map the-str column-names))))
+ "")]
(apply do-prepared
(format "insert into %s %s values (%s)"
(the-str table) columns template)
@@ -132,7 +107,7 @@
"Executes a query and then evaluates body with results bound to a seq of
the results"
[results sql & body]
- `(with-open [stmt# (.prepareStatement (connection) ~sql)]
- (with-open [rset# (.executeQuery stmt#)]
- (let [~results (resultset-seq rset#)]
- ~@body))))
+ `(with-open [stmt# (prepare-statement ~sql)
+ rset# (.executeQuery stmt#)]
+ (let [~results (resultset-seq rset#)]
+ ~@body)))
diff --git a/src/clojure/contrib/sql/internal.clj b/src/clojure/contrib/sql/internal.clj
index 42e0ba5f..8d6e8414 100644
--- a/src/clojure/contrib/sql/internal.clj
+++ b/src/clojure/contrib/sql/internal.clj
@@ -16,11 +16,21 @@
(def *db* {:connection nil :level 0})
(defn connection
- "Returns the current database connection or throws an exception."
+ "Returns the current database connection or throws"
[]
(or (:connection *db*)
(throw (Exception. "no current database connection"))))
+(defn create-statement
+ "Creates a statement object on the current connection"
+ []
+ (.createStatement (connection)))
+
+(defn prepare-statement
+ "Creates a prepared statement object on the current connection"
+ [sql]
+ (.prepareStatement (connection) sql))
+
(defn the-str
"Returns the name or string representation of x"
[x]
@@ -29,15 +39,56 @@
(str x)))
(defn properties
- "Converts a map from keywords or symbols to values into a
- java.util.Properties object that maps the same keys to the values with
+ "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.)]
- (when m
- (loop [[key & keys] (keys m)
- [val & vals] (vals m)]
- (.setProperty p (the-str key) (the-str val))
- (when keys
- (recur keys vals))))
+ (doseq [[key val] (seq m)]
+ (.setProperty p (the-str key) (the-str val)))
p))
+
+(defn with-connection*
+ "Evaluates thunk in the context of a new connection to a database then
+ closes the connection. db-spec is a map containing string values for
+ these required keys:
+ :classname the jdbc driver class name
+ :subprotocol the jdbc subprotocol
+ :subname the jdbc subname
+ db-spec may contain additional key-value pairs that are passed along to
+ the driver as properties such as :user, :password, etc."
+ [db-spec thunk]
+ (clojure.lang.RT/loadClassForName (:classname db-spec))
+ (with-open
+ [con
+ (java.sql.DriverManager/getConnection
+ (format "jdbc:%s:%s" (:subprotocol db-spec) (:subname db-spec))
+ (properties (dissoc db-spec :classname :subprotocol :subname)))]
+ (binding [*db* (assoc *db* :connection con :level 0)]
+ (thunk))))
+
+(defn transaction*
+ "Evaluates thunk as a transaction on the open database connection.
+ Any database updates are committed together as a group after evaluating,
+ or rolled back on any uncaught exception. Any nested transactions are
+ absorbed into the outermost transaction."
+ [thunk]
+ (let [con (connection)
+ outermost (zero? (:level *db*))
+ auto-commit (when outermost (.getAutoCommit con))]
+ (binding [*db* (update-in *db* [:level] inc)]
+ (when outermost
+ (.setAutoCommit con false))
+ (try
+ (let [value (thunk)]
+ (when outermost
+ (.commit con))
+ value)
+ (catch Exception e
+ (.rollback con)
+ (throw (Exception.
+ (format "transaction rolled back: %s"
+ (.getMessage e)) e)))
+ (finally
+ (when outermost
+ (.setAutoCommit con auto-commit)))))))
diff --git a/src/clojure/contrib/sql/test.clj b/src/clojure/contrib/sql/test.clj
index 26a8a4a2..314a1c40 100644
--- a/src/clojure/contrib/sql/test.clj
+++ b/src/clojure/contrib/sql/test.clj
@@ -27,24 +27,27 @@
(catch Exception e)))
(defn create-fruit []
- (sql/create-table :fruit
+ (sql/transaction
+ (sql/create-table :fruit
[:name "varchar(32)" "NOT NULL"]
[:appearance "varchar(32)"]
[:cost :int]
- [:grade :real]))
+ [:grade :real])))
(defn insert-rows-fruit []
- (sql/insert-rows :fruit
+ (sql/transaction
+ (sql/insert-rows :fruit
["Apple" "red" 59 87]
["Banana" "yellow" 29 92.2]
["Peach" "fuzzy" 139 90.0]
- ["Orange" "juicy" 89 88.6]))
+ ["Orange" "juicy" 89 88.6])))
(defn insert-values-fruit []
- (sql/insert-values :fruit
+ (sql/transaction
+ (sql/insert-values :fruit
[:name :cost]
["Mango" 722]
- ["Feijoa" 441]))
+ ["Feijoa" 441])))
(defn db-write []
(sql/with-connection db
@@ -64,16 +67,18 @@
(defn db-read-all []
(sql/with-connection db
- (sql/with-results res
- "select * from fruit"
- (into [] res))))
+ (sql/transaction
+ (sql/with-results res
+ "select * from fruit"
+ (into [] res)))))
(defn db-grade-a []
(sql/with-connection db
- (sql/with-results res
- "select name, cost from fruit where grade >= 90"
- (doseq [rec res]
- (println rec)))))
+ (sql/transaction
+ (sql/with-results res
+ "select name, cost from fruit where grade >= 90"
+ (doseq [rec res]
+ (println rec))))))
(defn db-exception []
(sql/with-connection db