From 0b54d58d1c9a41ae2a55f4c7af827a5f6cd12abc Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 28 Jun 2017 23:10:46 -0700 Subject: [PATCH 001/175] Add reducible queries JDBC-99 for 0.7.0-beta1 (need to drop 1.4) --- CHANGES.md | 4 ++ src/main/clojure/clojure/java/jdbc.clj | 48 +++++++++++++++++++++ src/test/clojure/clojure/java/test_jdbc.clj | 7 ++- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 35f412db..3eb41597 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes coming in 0.7.0-beta1 + +* `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce`): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99) + Changes in 0.7.0-alpha3 * `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](http://dev.clojure.org/jira/browse/JDBC-151). diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 9fa735a9..0e6e0d6f 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -965,6 +965,54 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (result-set-seq rset opts))) opts)))) +(defn reducible-result-set + "Given a java.sql.ResultSet return a reducible collection. + Note: :as-arrays? is not accepted here." + [^ResultSet rs {:keys [identifiers qualifier read-columns] + :or {identifiers str/lower-case + read-columns dft-read-columns}}] + (reify clojure.core.protocols.CollReduce + (coll-reduce [this f] (clojure.core.protocols/coll-reduce this f (f))) + (coll-reduce [this f init] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + identifier-fn (if qualifier + (comp (partial keyword qualifier) identifiers) + (comp keyword identifiers)) + keys (->> idxs + (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) + (make-cols-unique) + (mapv identifier-fn))] + (loop [init' init] + (if (.next rs) + (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] + (if (reduced? result) + @result + (recur result))) + init')))))) + +(defn reducible-query + "Given a database connection, a vector containing SQL and optional parameters, + return a reducible collection. When reduced, it will start the database query + and reduce the result set, and then close the connection. The following options + from query etc are not accepted here: + :as-arrays? :explain :explain-fn :result-set-fn :row-fn + See also prepare-statement for additional options." + ([db sql-params] (reducible-query db sql-params {})) + ([db sql-params opts] + (let [{:keys [reducing-fn] :as opts} + (merge {:identifiers str/lower-case :read-columns dft-read-columns} + (when (map? db) db) + opts) + sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (reify clojure.core.protocols.CollReduce + (coll-reduce [this f] (clojure.core.protocols/coll-reduce this f (f))) + (coll-reduce [this f init] + (db-query-with-resultset + db sql-params-vector + (^{:once true} fn* [rset] + (reduce f init (reducible-result-set rset opts))))))))) + (defn- direction "Given an entities function, a column name, and a direction, return the matching SQL column / order. diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index d14755b6..75628336 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -436,7 +436,12 @@ (is (= "Peach" (:name (sql/get-by-id db :fruit 3 :id)))) (is (= ["Peach"] (map :name (sql/find-by-keys db :fruit {:id 3 :cost 139})))) (is (= ["Peach" "Orange"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [:id]})))) - (is (= ["Orange" "Peach"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [{:appearance :desc}]})))))) + (is (= ["Orange" "Peach"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [{:appearance :desc}]})))) + (is (= 366 (reduce (fn [n r] (+ n (:cost r))) 0 + (sql/reducible-query db "SELECT * FROM fruit")))) + (when-let [transduce-fn (resolve 'clojure.core/transduce)] + (is (= 366 (transduce-fn (map :cost) + + (sql/reducible-query db "SELECT * FROM fruit"))))))) (deftest test-insert-values (doseq [db (test-specs)] From 2c5274b7758fba7de366541fdcadeabe38a61f2d Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 28 Jun 2017 23:13:56 -0700 Subject: [PATCH 002/175] Drop 1.4 locally; bump 1.9 to Alpha 17 --- project.clj | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/project.clj b/project.clj index 4f813347..0da34570 100644 --- a/project.clj +++ b/project.clj @@ -10,7 +10,7 @@ :url "http://www.eclipse.org/legal/epl-v10.html"} :source-paths ["src/main/clojure"] :test-paths ["src/test/clojure"] - :dependencies [[org.clojure/clojure "1.9.0-alpha14"] + :dependencies [[org.clojure/clojure "1.9.0-alpha17"] ;; These are just the versions most recently test against ;; for your own projects, use whatever version is most ;; appropriate for you. Again, note that this project.clj @@ -30,8 +30,7 @@ ;; if you have the MS driver in your local repo [sqljdbc4 "4.0"]] - :profiles {:1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]} - :1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]} + :profiles {:1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]} :1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]} :1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]} @@ -41,6 +40,6 @@ :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/" "ws-archiva" "https://d259tvauhnips9.cloudfront.net/archiva/repository/internal/"} ;; include dev profile with 1.9 to pull in test.check - :aliases {"test-all" ["with-profile" "test,1.4:test,1.5:test,1.6:test,1.7:test,1.8:dev,test,1.9" "test"] - "check-all" ["with-profile" "1.4:1.5:1.6:1.7:1.8:1.9" "check"]} + :aliases {"test-all" ["with-profile" "test,1.5:test,1.6:test,1.7:test,1.8:dev,test,1.9" "test"] + "check-all" ["with-profile" "1.5:1.6:1.7:1.8:1.9" "check"]} :min-lein-version "2.0.0") From 04bdeb5f339a7e1af18d17b516ddbd977d0c2f90 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 28 Jun 2017 23:18:26 -0700 Subject: [PATCH 003/175] Note that Clojure 1.4.0 is no longer supported --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 3eb41597..18072d26 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ Changes coming in 0.7.0-beta1 +* Support for Clojure 1.4.0 has been dropped -- breaking change. * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce`): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99) Changes in 0.7.0-alpha3 From c924876e9fa0ece9631657831fd965dc980bf830 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 28 Jun 2017 23:42:20 -0700 Subject: [PATCH 004/175] 0.7.0 Beta 1 -- still need to update specs for reducible functions --- CHANGES.md | 2 +- README.md | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 18072d26..c8a28adc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes coming in 0.7.0-beta1 +Changes in 0.7.0-beta1 * Support for Clojure 1.4.0 has been dropped -- breaking change. * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce`): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99) diff --git a/README.md b/README.md index d714cef2..40df7a6d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Additional documentation can be found in the [java.jdbc section of clojure-doc.o Releases and Dependency Information ======================================== -Latest stable release: 0.7.0-alpha3 +Latest stable release: 0.7.0-beta1 * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -24,14 +24,14 @@ Latest stable release: 0.7.0-alpha3 [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.0-alpha3"] +[org.clojure/java.jdbc "0.7.0-beta1"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.0-alpha3 + 0.7.0-beta1 ``` You will also need to add dependencies for the JDBC driver you intend to use. Here are links (to Maven Central) for each of the common database drivers that clojure.java.jdbc is known to be used with: @@ -132,6 +132,10 @@ Developer Information Change Log ==================== +Release 0.7.0-beta1 on 2017-06-28 + * Support for Clojure 1.4.0 has been dropped -- breaking change. + * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce`): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99) + Release 0.7.0-alpha3 on 2017-03-23 * `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](http://dev.clojure.org/jira/browse/JDBC-151). * `redshift` has been added as a `dbtype` with `com.amazon.redshift.jdbc.Driver` as the driver name. From 5605da2fca366af1499ddb87e9884179a998c51b Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 29 Jun 2017 00:02:12 -0700 Subject: [PATCH 005/175] Fix tests for spec.alpha --- src/main/clojure/clojure/java/jdbc/spec.clj | 19 +++++++++++++++++-- src/test/clojure/clojure/java/test_jdbc.clj | 6 +++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index c751aa65..70ba78f5 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -1,4 +1,4 @@ -;; Copyright (c) 2016 Sean Corfield. All rights reserved. +;; Copyright (c) 2016-2017 Sean Corfield. 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 @@ -14,7 +14,7 @@ (ns ^{:author "Sean Corfield" :doc "Optional specifications for use with Clojure 1.9 or later."} clojure.java.jdbc.spec - (:require [clojure.spec :as s] + (:require [clojure.spec.alpha :as s] [clojure.java.jdbc :as sql])) ;; basic java.sql types -- cannot be generated! @@ -172,6 +172,10 @@ ::identifiers ::qualifier ::as-arrays? ::read-columns])) +(s/def ::reducible-query-options (s/keys :req-un [] + :opt-un [::identifiers ::qualifier + ::read-columns])) + ;; the function API (s/def ::naming-strategy (s/fspec :args (s/cat :x ::identifier) @@ -279,6 +283,17 @@ :opts (s/? ::query-options)) :ret any?) +(s/fdef sql/reducible-result-set + :args (s/cat :rs ::result-set + :opts (s/? ::reducible-query-options)) + :ret any?) + +(s/fdef sql/reducible-query + :args (s/cat :db ::db-spec + :sql-params ::sql-params + :opts (s/? ::reducible-query-options)) + :ret any?) + (s/fdef sql/find-by-keys :args (s/cat :db ::db-spec :table ::identifier diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index 75628336..1b60d7b3 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -27,11 +27,11 @@ (def with-spec? (try (require 'clojure.java.jdbc.spec) - (require 'clojure.spec.test) + (require 'clojure.spec.test.alpha) ;; require this to workaround rebinding of report multi-fn (require 'clojure.test.check.clojure-test) - (let [syms ((resolve 'clojure.spec.test/enumerate-namespace) 'clojure.java.jdbc)] - ((resolve 'clojure.spec.test/instrument) syms)) + (let [syms ((resolve 'clojure.spec.test.alpha/enumerate-namespace) 'clojure.java.jdbc)] + ((resolve 'clojure.spec.test.alpha/instrument) syms)) (println "Instrumenting clojure.java.jdbc with clojure.spec") true (catch Exception _ From 74249d8899a3b270aeded15f105ce5bc9c317c0e Mon Sep 17 00:00:00 2001 From: puredanger Date: Thu, 29 Jun 2017 11:31:31 -0500 Subject: [PATCH 006/175] set clojure build level --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index 664273ce..9db5bdf5 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,10 @@ HEAD + + 1.5.1 + + From d63e1db19825612c4d3414456f8bbc4df1c23e3b Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 29 Jun 2017 09:36:54 -0700 Subject: [PATCH 007/175] For 1.7+ use clojure.lang.IReduce; else use CollReduce --- src/main/clojure/clojure/java/jdbc.clj | 151 ++++++++++++++------ src/test/clojure/clojure/java/test_jdbc.clj | 13 ++ 2 files changed, 117 insertions(+), 47 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 0e6e0d6f..a9fcfcd4 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -965,53 +965,110 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (result-set-seq rset opts))) opts)))) -(defn reducible-result-set - "Given a java.sql.ResultSet return a reducible collection. - Note: :as-arrays? is not accepted here." - [^ResultSet rs {:keys [identifiers qualifier read-columns] - :or {identifiers str/lower-case - read-columns dft-read-columns}}] - (reify clojure.core.protocols.CollReduce - (coll-reduce [this f] (clojure.core.protocols/coll-reduce this f (f))) - (coll-reduce [this f init] - (let [rsmeta (.getMetaData rs) - idxs (range 1 (inc (.getColumnCount rsmeta))) - identifier-fn (if qualifier - (comp (partial keyword qualifier) identifiers) - (comp keyword identifiers)) - keys (->> idxs - (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) - (make-cols-unique) - (mapv identifier-fn))] - (loop [init' init] - (if (.next rs) - (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] - (if (reduced? result) - @result - (recur result))) - init')))))) - -(defn reducible-query - "Given a database connection, a vector containing SQL and optional parameters, - return a reducible collection. When reduced, it will start the database query - and reduce the result set, and then close the connection. The following options - from query etc are not accepted here: - :as-arrays? :explain :explain-fn :result-set-fn :row-fn - See also prepare-statement for additional options." - ([db sql-params] (reducible-query db sql-params {})) - ([db sql-params opts] - (let [{:keys [reducing-fn] :as opts} - (merge {:identifiers str/lower-case :read-columns dft-read-columns} - (when (map? db) db) - opts) - sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] - (reify clojure.core.protocols.CollReduce - (coll-reduce [this f] (clojure.core.protocols/coll-reduce this f (f))) - (coll-reduce [this f init] - (db-query-with-resultset - db sql-params-vector - (^{:once true} fn* [rset] - (reduce f init (reducible-result-set rset opts))))))))) +(if (and (>= (:major *clojure-version*) 1) (>= (:minor *clojure-version*) 7)) + (do + (defn reducible-result-set + "Given a java.sql.ResultSet return a reducible collection. + Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce + Note: :as-arrays? is not accepted here." + [^ResultSet rs {:keys [identifiers qualifier read-columns] + :or {identifiers str/lower-case + read-columns dft-read-columns}}] + (reify clojure.lang.IReduce + (reduce [this f] (.reduce this f (f))) + (reduce [this f init] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + identifier-fn (if qualifier + (comp (partial keyword qualifier) identifiers) + (comp keyword identifiers)) + keys (->> idxs + (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) + (make-cols-unique) + (mapv identifier-fn))] + (loop [init' init] + (if (.next rs) + (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] + (if (reduced? result) + @result + (recur result))) + init')))))) + + (defn reducible-query + "Given a database connection, a vector containing SQL and optional parameters, + return a reducible collection. When reduced, it will start the database query + and reduce the result set, and then close the connection: + (transduce (map :cost) + (reducible-query db sql-params)) + Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce + The following options from query etc are not accepted here: + :as-arrays? :explain :explain-fn :result-set-fn :row-fn + See also prepare-statement for additional options." + ([db sql-params] (reducible-query db sql-params {})) + ([db sql-params opts] + (let [{:keys [reducing-fn] :as opts} + (merge {:identifiers str/lower-case :read-columns dft-read-columns} + (when (map? db) db) + opts) + sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (reify clojure.lang.IReduce + (reduce [this f] (.reduce this f (f))) + (reduce [this f init] + (db-query-with-resultset + db sql-params-vector + (^{:once true} fn* [rset] + (reduce f init (reducible-result-set rset opts)))))))))) + + (do ;; for backward compatibility thru 1.5.1 use CollReduce + (defn reducible-result-set + "Given a java.sql.ResultSet return a reducible collection. + Compiled with Clojure 1.5 or 1.6 -- uses clojure.core.protocols.CollReduce + Note: :as-arrays? is not accepted here." + [^ResultSet rs {:keys [identifiers qualifier read-columns] + :or {identifiers str/lower-case + read-columns dft-read-columns}}] + (reify clojure.core.protocols.CollReduce + (coll-reduce [this f] (clojure.core.protocols/coll-reduce this f (f))) + (coll-reduce [this f init] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + identifier-fn (if qualifier + (comp (partial keyword qualifier) identifiers) + (comp keyword identifiers)) + keys (->> idxs + (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) + (make-cols-unique) + (mapv identifier-fn))] + (loop [init' init] + (if (.next rs) + (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] + (if (reduced? result) + @result + (recur result))) + init')))))) + + (defn reducible-query + "Given a database connection, a vector containing SQL and optional parameters, + return a reducible collection. When reduced, it will start the database query + and reduce the result set, and then close the connection: + (transduce (map :cost) + (reducible-query db sql-params)) + Compiled with Clojure 1.5 or 1.6 -- uses clojure.core.protocols.CollReduce + The following options from query etc are not accepted here: + :as-arrays? :explain :explain-fn :result-set-fn :row-fn + See also prepare-statement for additional options." + ([db sql-params] (reducible-query db sql-params {})) + ([db sql-params opts] + (let [{:keys [reducing-fn] :as opts} + (merge {:identifiers str/lower-case :read-columns dft-read-columns} + (when (map? db) db) + opts) + sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (reify clojure.core.protocols.CollReduce + (coll-reduce [this f] (clojure.core.protocols/coll-reduce this f (f))) + (coll-reduce [this f init] + (db-query-with-resultset + db sql-params-vector + (^{:once true} fn* [rset] + (reduce f init (reducible-result-set rset opts))))))))))) (defn- direction "Given an entities function, a column name, and a direction, diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index 1b60d7b3..9b22dbae 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -437,9 +437,22 @@ (is (= ["Peach"] (map :name (sql/find-by-keys db :fruit {:id 3 :cost 139})))) (is (= ["Peach" "Orange"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [:id]})))) (is (= ["Orange" "Peach"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [{:appearance :desc}]})))) + ;; reduce with init (is (= 366 (reduce (fn [n r] (+ n (:cost r))) 0 (sql/reducible-query db "SELECT * FROM fruit")))) + ;; reduce without init + (is (= 366 (reduce (fn ([] 0) ([n r] (+ n (:cost r)))) + (sql/reducible-query db "SELECT * FROM fruit")))) + ;; plain old into + (is (= 4 (count (into [] (sql/reducible-query db "SELECT * FROM fruit"))))) (when-let [transduce-fn (resolve 'clojure.core/transduce)] + ;; transducing into + (is (= [29 59 139 139] + (into [] (map :cost) (sql/reducible-query + db + (str "SELECT * FROM fruit" + " ORDER BY cost"))))) + ;; transduce without init (is (= 366 (transduce-fn (map :cost) + (sql/reducible-query db "SELECT * FROM fruit"))))))) From fd6724ffb17ca0969eb2f46ec642268088cbfc0e Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 29 Jun 2017 09:39:21 -0700 Subject: [PATCH 008/175] Update changes for IReduce/CollReduce split --- CHANGES.md | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c8a28adc..8a263a46 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ Changes in 0.7.0-beta1 * Support for Clojure 1.4.0 has been dropped -- breaking change. -* `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce`): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99) +* `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99) Changes in 0.7.0-alpha3 diff --git a/README.md b/README.md index 40df7a6d..1ea5d978 100644 --- a/README.md +++ b/README.md @@ -132,9 +132,9 @@ Developer Information Change Log ==================== -Release 0.7.0-beta1 on 2017-06-28 +Release 0.7.0-beta1 on 2017-06-29 * Support for Clojure 1.4.0 has been dropped -- breaking change. - * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce`): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99) + * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99) Release 0.7.0-alpha3 on 2017-03-23 * `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](http://dev.clojure.org/jira/browse/JDBC-151). From 27ce33ee98fa5898e0427bf1f18150e361decacf Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 29 Jun 2017 11:58:48 -0500 Subject: [PATCH 009/175] [maven-release-plugin] prepare release java.jdbc-0.7.0-beta1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9db5bdf5..24ccb747 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-SNAPSHOT + 0.7.0-beta1 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.0-beta1 From 464cd8d56d65e3fb5cbc59f6e4524722b707b931 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 29 Jun 2017 11:58:48 -0500 Subject: [PATCH 010/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 24ccb747..9db5bdf5 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-beta1 + 0.7.0-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.0-beta1 + HEAD From 43923b58352e9c32489dc7463c8da54ec3a66deb Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 29 Jun 2017 16:58:41 -0700 Subject: [PATCH 011/175] Prep for 0.7.0-beta2 dropping Clojure 1.5 and 1.6; implementing IReduce properly --- CHANGES.md | 7 +- pom.xml | 2 +- project.clj | 8 +- src/main/clojure/clojure/java/jdbc.clj | 172 ++++++++------------ src/test/clojure/clojure/java/test_jdbc.clj | 66 ++++++-- 5 files changed, 129 insertions(+), 126 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8a263a46..3a16fd37 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,12 @@ +Changes coming in 0.7.0-beta2 + +* Support for Clojure 1.6.0 and earlier has been dropped -- breaking change. +* `reducible-query` and `reducible-result-set` use `IReduce` and correctly support the no-`init` arity of `reduce` by using the first row of the `ResultSet`, if present, as the (missing) `init` value, and only calling `f` with no arguments if the `ResultSet` is empty. The `init` arity of `reduce` only ever calls `f` with two arguments. + Changes in 0.7.0-beta1 * Support for Clojure 1.4.0 has been dropped -- breaking change. -* `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99) +* `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99). Changes in 0.7.0-alpha3 diff --git a/pom.xml b/pom.xml index 9db5bdf5..1c5d895c 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ - 1.5.1 + 1.7.0 diff --git a/project.clj b/project.clj index 0da34570..61d8a120 100644 --- a/project.clj +++ b/project.clj @@ -30,9 +30,7 @@ ;; if you have the MS driver in your local repo [sqljdbc4 "4.0"]] - :profiles {:1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]} - :1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]} - :1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} + :profiles {:1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]} :1.9 {:dependencies [[org.clojure/clojure "1.9.0-master-SNAPSHOT"]]} :dev {:dependencies [[org.clojure/test.check "0.9.0"]]}} @@ -40,6 +38,6 @@ :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/" "ws-archiva" "https://d259tvauhnips9.cloudfront.net/archiva/repository/internal/"} ;; include dev profile with 1.9 to pull in test.check - :aliases {"test-all" ["with-profile" "test,1.5:test,1.6:test,1.7:test,1.8:dev,test,1.9" "test"] - "check-all" ["with-profile" "1.5:1.6:1.7:1.8:1.9" "check"]} + :aliases {"test-all" ["with-profile" "test,1.7:test,1.8:dev,test,1.9" "test"] + "check-all" ["with-profile" "1.7:1.8:1.9" "check"]} :min-lein-version "2.0.0") diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index a9fcfcd4..f7d5f474 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -965,110 +965,74 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (result-set-seq rset opts))) opts)))) -(if (and (>= (:major *clojure-version*) 1) (>= (:minor *clojure-version*) 7)) - (do - (defn reducible-result-set - "Given a java.sql.ResultSet return a reducible collection. - Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce - Note: :as-arrays? is not accepted here." - [^ResultSet rs {:keys [identifiers qualifier read-columns] - :or {identifiers str/lower-case - read-columns dft-read-columns}}] - (reify clojure.lang.IReduce - (reduce [this f] (.reduce this f (f))) - (reduce [this f init] - (let [rsmeta (.getMetaData rs) - idxs (range 1 (inc (.getColumnCount rsmeta))) - identifier-fn (if qualifier - (comp (partial keyword qualifier) identifiers) - (comp keyword identifiers)) - keys (->> idxs - (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) - (make-cols-unique) - (mapv identifier-fn))] - (loop [init' init] - (if (.next rs) - (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] - (if (reduced? result) - @result - (recur result))) - init')))))) - - (defn reducible-query - "Given a database connection, a vector containing SQL and optional parameters, - return a reducible collection. When reduced, it will start the database query - and reduce the result set, and then close the connection: - (transduce (map :cost) + (reducible-query db sql-params)) - Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce - The following options from query etc are not accepted here: - :as-arrays? :explain :explain-fn :result-set-fn :row-fn - See also prepare-statement for additional options." - ([db sql-params] (reducible-query db sql-params {})) - ([db sql-params opts] - (let [{:keys [reducing-fn] :as opts} - (merge {:identifiers str/lower-case :read-columns dft-read-columns} - (when (map? db) db) - opts) - sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] - (reify clojure.lang.IReduce - (reduce [this f] (.reduce this f (f))) - (reduce [this f init] - (db-query-with-resultset - db sql-params-vector - (^{:once true} fn* [rset] - (reduce f init (reducible-result-set rset opts)))))))))) - - (do ;; for backward compatibility thru 1.5.1 use CollReduce - (defn reducible-result-set - "Given a java.sql.ResultSet return a reducible collection. - Compiled with Clojure 1.5 or 1.6 -- uses clojure.core.protocols.CollReduce - Note: :as-arrays? is not accepted here." - [^ResultSet rs {:keys [identifiers qualifier read-columns] - :or {identifiers str/lower-case - read-columns dft-read-columns}}] - (reify clojure.core.protocols.CollReduce - (coll-reduce [this f] (clojure.core.protocols/coll-reduce this f (f))) - (coll-reduce [this f init] - (let [rsmeta (.getMetaData rs) - idxs (range 1 (inc (.getColumnCount rsmeta))) - identifier-fn (if qualifier - (comp (partial keyword qualifier) identifiers) - (comp keyword identifiers)) - keys (->> idxs - (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) - (make-cols-unique) - (mapv identifier-fn))] - (loop [init' init] - (if (.next rs) - (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] - (if (reduced? result) - @result - (recur result))) - init')))))) - - (defn reducible-query - "Given a database connection, a vector containing SQL and optional parameters, - return a reducible collection. When reduced, it will start the database query - and reduce the result set, and then close the connection: - (transduce (map :cost) + (reducible-query db sql-params)) - Compiled with Clojure 1.5 or 1.6 -- uses clojure.core.protocols.CollReduce - The following options from query etc are not accepted here: - :as-arrays? :explain :explain-fn :result-set-fn :row-fn - See also prepare-statement for additional options." - ([db sql-params] (reducible-query db sql-params {})) - ([db sql-params opts] - (let [{:keys [reducing-fn] :as opts} - (merge {:identifiers str/lower-case :read-columns dft-read-columns} - (when (map? db) db) - opts) - sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] - (reify clojure.core.protocols.CollReduce - (coll-reduce [this f] (clojure.core.protocols/coll-reduce this f (f))) - (coll-reduce [this f init] - (db-query-with-resultset - db sql-params-vector - (^{:once true} fn* [rset] - (reduce f init (reducible-result-set rset opts))))))))))) +(defn reducible-result-set + "Given a java.sql.ResultSet return a reducible collection. + Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce + Note: :as-arrays? is not accepted here." + [^ResultSet rs {:keys [identifiers qualifier read-columns] + :or {identifiers str/lower-case + read-columns dft-read-columns}}] + (let [identifier-fn (if qualifier + (comp (partial keyword qualifier) identifiers) + (comp keyword identifiers)) + make-keys (fn [idxs rsmeta] + (->> idxs + (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) + (make-cols-unique) + (mapv identifier-fn))) + init-reduce (fn [keys rs rsmeta idxs f init] + (loop [init' init] + (if (.next rs) + (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] + (if (reduced? result) + @result + (recur result))) + init')))] + (reify clojure.lang.IReduce + (reduce [this f] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + keys (make-keys idxs rsmeta)] + (if (.next rs) + ;; reduce init is first row of ResultSet + (init-reduce keys rs rsmeta idxs f + (zipmap keys (read-columns rs rsmeta idxs))) + ;; no rows so call 0-arity f to get result value + ;; per reduce docstring contract + (f)))) + (reduce [this f init] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + keys (make-keys idxs rsmeta)] + (init-reduce keys rs rsmeta idxs f init)))))) + +(defn reducible-query + "Given a database connection, a vector containing SQL and optional parameters, + return a reducible collection. When reduced, it will start the database query + and reduce the result set, and then close the connection: + (transduce (map :cost) + (reducible-query db sql-params)) + Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce + The following options from query etc are not accepted here: + :as-arrays? :explain :explain-fn :result-set-fn :row-fn + See also prepare-statement for additional options." + ([db sql-params] (reducible-query db sql-params {})) + ([db sql-params opts] + (let [{:keys [reducing-fn] :as opts} + (merge {:identifiers str/lower-case :read-columns dft-read-columns} + (when (map? db) db) + opts) + sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (reify clojure.lang.IReduce + (reduce [this f] + (db-query-with-resultset + db sql-params-vector + (^{:once true} fn* [rset] + (reduce f (reducible-result-set rset opts))))) + (reduce [this f init] + (db-query-with-resultset + db sql-params-vector + (^{:once true} fn* [rset] + (reduce f init (reducible-result-set rset opts))))))))) (defn- direction "Given an entities function, a column name, and a direction, diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index 9b22dbae..7c0c4bb0 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -438,23 +438,59 @@ (is (= ["Peach" "Orange"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [:id]})))) (is (= ["Orange" "Peach"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [{:appearance :desc}]})))) ;; reduce with init - (is (= 366 (reduce (fn [n r] (+ n (:cost r))) 0 + (is (= 466 (reduce (fn [n r] (+ n (:cost r))) 100 (sql/reducible-query db "SELECT * FROM fruit")))) - ;; reduce without init - (is (= 366 (reduce (fn ([] 0) ([n r] (+ n (:cost r)))) - (sql/reducible-query db "SELECT * FROM fruit")))) - ;; plain old into + ;; reduce without init -- uses first row as init! + (is (= 366 + (:cost (reduce (fn + ([] (throw (ex-info "I should not be called!" {}))) + ([m r] (update-in m [:cost] + (:cost r)))) + (sql/reducible-query db "SELECT * FROM fruit"))))) + ;; verify reduce without init on empty rs calls 0-arity only + (is (= "Zero-arity!" + (reduce (fn + ([] "Zero-arity!") + ([m r] (throw (ex-info "I should not be called!" + {:m m :r r})))) + (sql/reducible-query db "SELECT * FROM fruit WHERE ID = -99")))) + ;; verify reduce with init does not call f for empty rs + (is (= "Unchanged!" + (reduce (fn + ([] (throw (ex-info "I should not be called!" {}))) + ([m r] (throw (ex-info "I should not be called!" + {:m m :r r})))) + "Unchanged!" + (sql/reducible-query db "SELECT * FROM fruit WHERE ID = -99")))) + ;; verify reduce without init does not call f if only one row is in the rs + (is (= "Orange" + (:name (reduce (fn + ([] (throw (ex-info "I should not be called!" {}))) + ([m r] (throw (ex-info "I should not be called!" + {:m m :r r})))) + (sql/reducible-query db "SELECT * FROM fruit WHERE ID = 4"))))) + ;; verify reduce with init does not call 0-arity f and + ;; only calls 2-arity f once if only one row is in the rs + (is (= 239 + (reduce (fn + ([] (throw (ex-info "I should not be called!" {}))) + ;; cannot be called on its own result: + ([m r] (+ (:a m) (:cost r)))) + {:a 100} + (sql/reducible-query db "SELECT * FROM fruit WHERE ID = 4")))) + ;; plain old into (uses (reduce conj coll) behind the scenes) (is (= 4 (count (into [] (sql/reducible-query db "SELECT * FROM fruit"))))) - (when-let [transduce-fn (resolve 'clojure.core/transduce)] - ;; transducing into - (is (= [29 59 139 139] - (into [] (map :cost) (sql/reducible-query - db - (str "SELECT * FROM fruit" - " ORDER BY cost"))))) - ;; transduce without init - (is (= 366 (transduce-fn (map :cost) + - (sql/reducible-query db "SELECT * FROM fruit"))))))) + ;; transducing into + (is (= [29 59 139 139] + (into [] + (map :cost) + (sql/reducible-query db (str "SELECT * FROM fruit" + " ORDER BY cost"))))) + ;; transduce without init (calls (+) to get init value) + (is (= 366 (transduce (map :cost) + + (sql/reducible-query db "SELECT * FROM fruit")))) + ;; transduce with init + (is (= 466 (transduce (map :cost) + 100 + (sql/reducible-query db "SELECT * FROM fruit")))))) (deftest test-insert-values (doseq [db (test-specs)] From 412ff6b6091a833c76334ce6221305b23d6c3d6c Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 30 Jun 2017 14:52:16 -0700 Subject: [PATCH 012/175] Complete specs; prepare 0.7.0-beta2 --- CHANGES.md | 5 +- README.md | 15 +++-- src/main/clojure/clojure/java/jdbc/spec.clj | 55 +++++++++++++++--- src/test/clojure/clojure/java/test_jdbc.clj | 64 ++++++++++++--------- 4 files changed, 99 insertions(+), 40 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3a16fd37..c40a5452 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,14 @@ -Changes coming in 0.7.0-beta2 +Changes in 0.7.0-beta2 * Support for Clojure 1.6.0 and earlier has been dropped -- breaking change. +* Or, put another way, `clojure.java.jdbc` now requires Clojure 1.7 or later! +* All public functions now have specs in the optional `clojure.java.jdbc.spec` namespace (requires `clojure.spec.alpha`). * `reducible-query` and `reducible-result-set` use `IReduce` and correctly support the no-`init` arity of `reduce` by using the first row of the `ResultSet`, if present, as the (missing) `init` value, and only calling `f` with no arguments if the `ResultSet` is empty. The `init` arity of `reduce` only ever calls `f` with two arguments. Changes in 0.7.0-beta1 * Support for Clojure 1.4.0 has been dropped -- breaking change. +* Optional spec support now uses `clojure.spec.alpha`. * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99). Changes in 0.7.0-alpha3 diff --git a/README.md b/README.md index 1ea5d978..937deec0 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Additional documentation can be found in the [java.jdbc section of clojure-doc.o Releases and Dependency Information ======================================== -Latest stable release: 0.7.0-beta1 +Latest stable release: 0.7.0-beta2 * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -24,14 +24,14 @@ Latest stable release: 0.7.0-beta1 [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.0-beta1"] +[org.clojure/java.jdbc "0.7.0-beta2"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.0-beta1 + 0.7.0-beta2 ``` You will also need to add dependencies for the JDBC driver you intend to use. Here are links (to Maven Central) for each of the common database drivers that clojure.java.jdbc is known to be used with: @@ -132,9 +132,16 @@ Developer Information Change Log ==================== +Release 0.7.0-beta2 on 2017-06-30 (a.k.a The Reducible Saga, Part 2) + * Support for Clojure 1.5 and 1.6 has been dropped -- breaking change. + * Or, put another way, `clojure.java.jdbc` now requires Clojure 1.7 or later! + * All public functions now have specs in the optional `clojure.java.jdbc.spec` namespace (requires `clojure.spec.alpha`). + * `reducible-query` and `reducible-result-set` use `IReduce` and correctly support the no-`init` arity of `reduce` by using the first row of the `ResultSet`, if present, as the (missing) `init` value, and only calling `f` with no arguments if the `ResultSet` is empty. The `init` arity of `reduce` only ever calls `f` with two arguments. + Release 0.7.0-beta1 on 2017-06-29 * Support for Clojure 1.4.0 has been dropped -- breaking change. - * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99) + * Optional spec support now uses `clojure.spec.alpha`. + * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99). Release 0.7.0-alpha3 on 2017-03-23 * `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](http://dev.clojure.org/jira/browse/JDBC-151). diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index 70ba78f5..73af9aa4 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -30,7 +30,7 @@ (s/def ::subprotocol-base #{"derby" "h2" "hsqldb" "jtds:sqlserver" "mysql" "oracle:oci" "oracle:thin" "pgsql" "postgresql" - "sqlite" "sqlserver"}) + "redshift" "sqlite" "sqlserver"}) (s/def ::subprotocol-alias #{"hsql" "jtds" "mssql" "oracle" "postgres"}) ;; technically :subprotocol can be any string... (s/def ::subprotocol string?) @@ -269,13 +269,34 @@ :args (s/cat :meta-query any? :opt-args (s/? any?))) -;; db-do-commands +(s/fdef sql/db-do-commands + :args (s/cat :db ::db-spec + :transaction? (s/? boolean?) + :sql-commands (s/or :command string? + :commands (s/coll-of string?))) + :ret any?) -;; db-do-prepared-return-keys -- can have both execute options and query options! +(s/fdef sql/db-do-prepared-return-keys + :args (s/cat :db ::db-spec + :transaction? (s/? boolean?) + :sql-params ::sql-params + :opts (s/? (s/merge ::execute-options ::query-options))) + :ret any?) -;; db-do-prepared +(s/fdef sql/db-do-prepared + :args (s/cat :db ::db-spec + :transaction? (s/? boolean?) + :sql-params ::sql-params + ;; TODO: this set of options needs reviewing: + :opts (s/? (s/merge ::execute-options ::query-options))) + :ret any?) -;; db-query-with-resultset +(s/fdef sql/db-query-with-resultset + :args (s/cat :db ::db-spec + :sql-params ::sql-params + :func ifn? + :opts (s/? ::query-options)) + :ret any?) (s/fdef sql/query :args (s/cat :db ::db-spec @@ -322,9 +343,29 @@ :opts (s/? ::exec-sql-options)) :ret ::execute-result) -;; insert! -- can have both execute options and query options! +(s/fdef sql/insert! + :args (s/or :row (s/cat :db ::db-spec + :table ::identifier + :row (s/map-of ::identifier any?) + :opts (s/? (s/merge ::execute-options ::query-options))) + :cvs (s/cat :db ::db-spec + :table ::identifier + :cols (s/nilable (s/coll-of ::identifier)) + :vals (s/coll-of any?) + :opts (s/? (s/merge ::execute-options ::query-options)))) + :ret any?) -;; insert-multi! -- can have both execute options and query options! +(s/fdef sql/insert-multi! + :args (s/or :rows (s/cat :db ::db-spec + :table ::identifier + :rows (s/coll-of (s/map-of ::identifier any?)) + :opts (s/? (s/merge ::execute-options ::query-options))) + :cvs (s/cat :db ::db-spec + :table ::identifier + :cols (s/nilable (s/coll-of ::identifier)) + :vals (s/coll-of (s/coll-of any?)) + :opts (s/? (s/merge ::execute-options ::query-options)))) + :ret any?) (s/fdef sql/update! :args (s/cat :db ::db-spec diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index 7c0c4bb0..d5792332 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -683,56 +683,64 @@ (catch Exception _ (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))))) +(defmacro illegal-arg-or-spec + "Execute a form in the context of a try/catch that verifies either + IllegalArgumentException was thrown or a spec violation occurred + so that we can test transparently across Clojure 1.7 to 1.9+." + [fn-name & body] + `(try + ~@body + (is false (str "Illegal arguments to " ~fn-name " were not detected!")) + (catch IllegalArgumentException _#) + (catch clojure.lang.ExceptionInfo e# + (is (re-find #"did not conform to spec" (.getMessage e#)))))) + (deftest test-sql-exception (doseq [db (test-specs)] (create-test-table :fruit db) - (try + (illegal-arg-or-spec "insert!" (sql/with-db-transaction [t-conn db] (sql/insert! t-conn :fruit [:name :appearance] - ["Apple" "strange" "whoops"])) - (catch IllegalArgumentException _ - (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) + ["Apple" "strange" "whoops"]))) (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) (deftest test-sql-exception-with-isolation (doseq [db (test-specs)] (create-test-table :fruit db) - (try + (illegal-arg-or-spec "insert!" (sql/with-db-transaction [t-conn db {:isolation :read-uncommitted}] (sql/insert! t-conn :fruit [:name :appearance] - ["Apple" "strange" "whoops"])) - (catch IllegalArgumentException _ - (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) + ["Apple" "strange" "whoops"]))) (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) (deftest test-insert-values-exception (doseq [db (test-specs)] (create-test-table :fruit db) - (is (thrown? IllegalArgumentException - (sql/with-db-transaction [t-conn db] - (sql/insert-multi! t-conn - :fruit - [:name :appearance] - [["Grape" "yummy"] - ["Pear" "bruised"] - ["Apple" "strange" "whoops"]])))) + (illegal-arg-or-spec "insert-multi!" + (sql/with-db-transaction [t-conn db] + (sql/insert-multi! t-conn + :fruit + [:name :appearance] + [["Grape" "yummy"] + ["Pear" "bruised"] + ["Apple" "strange" "whoops"]]))) (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) (deftest test-insert-values-exception-with-isolation (doseq [db (test-specs)] (create-test-table :fruit db) - (is (thrown? IllegalArgumentException - (sql/with-db-transaction [t-conn db {:isolation :read-uncommitted}] - (sql/insert-multi! t-conn - :fruit - [:name :appearance] - [["Grape" "yummy"] - ["Pear" "bruised"] - ["Apple" "strange" "whoops"]])))) + (illegal-arg-or-spec + (sql/with-db-transaction [t-conn db {:isolation :read-uncommitted}] + (sql/insert-multi! t-conn + :fruit + [:name :appearance] + [["Grape" "yummy"] + ["Pear" "bruised"] + ["Apple" "strange" "whoops"]]))) (is (= 0 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))))) (deftest test-rollback @@ -1029,10 +1037,10 @@ (deftest illegal-insert-arguments (doseq [db (test-specs)] - (is (thrown? IllegalArgumentException (sql/insert! db))) - (is (thrown? IllegalArgumentException (sql/insert! db {:name "Apple"} [:name]))) - (is (thrown? IllegalArgumentException (sql/insert! db {:name "Apple"} [:name] {:entities identity}))) - (is (thrown? IllegalArgumentException (sql/insert! db [:name]))) + (illegal-arg-or-spec "insert!" (sql/insert! db)) + (illegal-arg-or-spec "insert!" (sql/insert! db {:name "Apple"} [:name])) + (illegal-arg-or-spec "insert!" (sql/insert! db {:name "Apple"} [:name] {:entities identity})) + (illegal-arg-or-spec "insert!" (sql/insert! db [:name])) (if with-spec? ; clojure.spec catches this differently (is (thrown? clojure.lang.ExceptionInfo (sql/insert! db [:name] {:entities identity}))) (is (thrown? ClassCastException (sql/insert! db [:name] {:entities identity})))))) From 9773355d10ec0418235c4233c1368abcd881d898 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 30 Jun 2017 15:10:54 -0700 Subject: [PATCH 013/175] Clarification of Clojure version support --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 937deec0..dfeeaa04 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Additional documentation can be found in the [java.jdbc section of clojure-doc.o Releases and Dependency Information ======================================== -Latest stable release: 0.7.0-beta2 +Latest stable release: 0.7.0-beta2 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -34,6 +34,8 @@ Latest stable release: 0.7.0-beta2 0.7.0-beta2 ``` +_Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ + You will also need to add dependencies for the JDBC driver you intend to use. Here are links (to Maven Central) for each of the common database drivers that clojure.java.jdbc is known to be used with: * [Apache Derby](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.apache.derby%22%20AND%20a%3A%22derby%22) @@ -46,7 +48,7 @@ You will also need to add dependencies for the JDBC driver you intend to use. He Note: different versions of various database drivers have different Java/JVM version requirements. In particular, recent versions of Apache Derby require at least Java 8 and recent versions of H2 require at least Java 7. Clojure's Continuous Integration system uses older versions so tests can be run on Java 6 (see `pom.xml`); local testing is done with more recent versions on Java 8. -clojure.java.jdbc is also tested against Microsoft's own JDBC4 Driver 4.0 but that +`clojure.java.jdbc` is also tested against Microsoft's own JDBC4 Driver 4.0 but that has to be [downloaded manually](https://www.microsoft.com/en-us/download/details.aspx?id=11774) and placed in a Maven repository accessible to your system. For testing, it was installed locally as: ```clojure ;; Microsoft SQL Server JDBC4 Driver 4.0 From ef0b4cc6cd7c663d16eda2531eda891d3c481a2f Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 30 Jun 2017 17:12:06 -0500 Subject: [PATCH 014/175] [maven-release-plugin] prepare release java.jdbc-0.7.0-beta2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1c5d895c..d964c6ee 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-SNAPSHOT + 0.7.0-beta2 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.0-beta2 From 8f32b6b964d60e03ea78b7759a2f2d141fa30d66 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 30 Jun 2017 17:12:06 -0500 Subject: [PATCH 015/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d964c6ee..1c5d895c 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-beta2 + 0.7.0-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.0-beta2 + HEAD From 877e4e1050e89dbb7c082e343e13a19876a69747 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 3 Jul 2017 10:47:46 -0700 Subject: [PATCH 016/175] Fix reflection warnings JDBC-152 --- CHANGES.md | 4 ++++ project.clj | 3 +-- src/main/clojure/clojure/java/jdbc.clj | 7 ++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c40a5452..3cb7cd19 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes coming in 0.7.0 + +* Reflection warnings removed in `reducible-result-set` [JDBC-152](https://dev.clojure.org/jira/browse/JDBC-152). + Changes in 0.7.0-beta2 * Support for Clojure 1.6.0 and earlier has been dropped -- breaking change. diff --git a/project.clj b/project.clj index 61d8a120..c582843c 100644 --- a/project.clj +++ b/project.clj @@ -38,6 +38,5 @@ :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/" "ws-archiva" "https://d259tvauhnips9.cloudfront.net/archiva/repository/internal/"} ;; include dev profile with 1.9 to pull in test.check - :aliases {"test-all" ["with-profile" "test,1.7:test,1.8:dev,test,1.9" "test"] - "check-all" ["with-profile" "1.7:1.8:1.9" "check"]} + :aliases {"test-all" ["with-profile" "test,1.7:test,1.8:dev,test,1.9" "test"]} :min-lein-version "2.0.0") diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index f7d5f474..13368efc 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -45,7 +45,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} [clojure.walk :as walk]) (:import (java.net URI) (java.sql BatchUpdateException DriverManager - PreparedStatement ResultSet SQLException Statement Types) + PreparedStatement ResultSet ResultSetMetaData + SQLException Statement Types) (java.util Hashtable Map Properties) (javax.sql DataSource))) @@ -975,12 +976,12 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (let [identifier-fn (if qualifier (comp (partial keyword qualifier) identifiers) (comp keyword identifiers)) - make-keys (fn [idxs rsmeta] + make-keys (fn [idxs ^ResultSetMetaData rsmeta] (->> idxs (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) (make-cols-unique) (mapv identifier-fn))) - init-reduce (fn [keys rs rsmeta idxs f init] + init-reduce (fn [keys ^ResultSet rs rsmeta idxs f init] (loop [init' init] (if (.next rs) (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] From 4bc29598e0e23ff768986adfed72e216dd8aa773 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Tue, 4 Jul 2017 19:39:22 -0700 Subject: [PATCH 017/175] Release 0.7.0-beta3 Fix reflection warnings; fix opts in reducible-query; clarify specs. --- CHANGES.md | 4 +++- README.md | 12 +++++++++--- src/main/clojure/clojure/java/jdbc.clj | 6 ++++-- src/main/clojure/clojure/java/jdbc/spec.clj | 18 ++++++++++-------- src/test/clojure/clojure/java/test_jdbc.clj | 5 +++-- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3cb7cd19..606d8b1c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ -Changes coming in 0.7.0 +Changes in 0.7.0-beta3 * Reflection warnings removed in `reducible-result-set` [JDBC-152](https://dev.clojure.org/jira/browse/JDBC-152). +* `opts` are now correctly passed from `reducible-query` to `db-query-with-resultset`. +* Updated the `::query-options` spec to make it clear that `::prepare-options` are also acceptable there. Changes in 0.7.0-beta2 diff --git a/README.md b/README.md index dfeeaa04..418b99ba 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Additional documentation can be found in the [java.jdbc section of clojure-doc.o Releases and Dependency Information ======================================== -Latest stable release: 0.7.0-beta2 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.0-beta3 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -24,14 +24,14 @@ Latest stable release: 0.7.0-beta2 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.0-beta2"] +[org.clojure/java.jdbc "0.7.0-beta3"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.0-beta2 + 0.7.0-beta3 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -134,6 +134,12 @@ Developer Information Change Log ==================== +Release 0.7.0-beta3 on 2017-07-04 + + * Reflection warnings removed in `reducible-result-set` [JDBC-152](https://dev.clojure.org/jira/browse/JDBC-152). + * `opts` are now correctly passed from `reducible-query` to `db-query-with-resultset`. + * Updated the `::query-options` spec to make it clear that `::prepare-options` are also acceptable there. + Release 0.7.0-beta2 on 2017-06-30 (a.k.a The Reducible Saga, Part 2) * Support for Clojure 1.5 and 1.6 has been dropped -- breaking change. * Or, put another way, `clojure.java.jdbc` now requires Clojure 1.7 or later! diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 13368efc..9a31f7f4 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1028,12 +1028,14 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (db-query-with-resultset db sql-params-vector (^{:once true} fn* [rset] - (reduce f (reducible-result-set rset opts))))) + (reduce f (reducible-result-set rset opts))) + opts)) (reduce [this f init] (db-query-with-resultset db sql-params-vector (^{:once true} fn* [rset] - (reduce f init (reducible-result-set rset opts))))))))) + (reduce f init (reducible-result-set rset opts))) + opts)))))) (defn- direction "Given an entities function, a column name, and a direction, diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index 73af9aa4..26d56167 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -167,14 +167,16 @@ (s/def ::transaction-options (s/keys :req-un [] :opt-un [::isolation ::read-only?])) -(s/def ::query-options (s/keys :req-un [] - :opt-un [::result-set-fn ::row-fn - ::identifiers ::qualifier - ::as-arrays? ::read-columns])) - -(s/def ::reducible-query-options (s/keys :req-un [] - :opt-un [::identifiers ::qualifier - ::read-columns])) +(s/def ::query-options (s/merge (s/keys :req-un [] + :opt-un [::result-set-fn ::row-fn + ::identifiers ::qualifier + ::as-arrays? ::read-columns]) + ::prepare-options)) + +(s/def ::reducible-query-options (s/merge (s/keys :req-un [] + :opt-un [::identifiers ::qualifier + ::read-columns]) + ::prepare-options)) ;; the function API diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index d5792332..f6dedf8d 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -437,9 +437,10 @@ (is (= ["Peach"] (map :name (sql/find-by-keys db :fruit {:id 3 :cost 139})))) (is (= ["Peach" "Orange"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [:id]})))) (is (= ["Orange" "Peach"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [{:appearance :desc}]})))) - ;; reduce with init + ;; reduce with init (and ensure we can pass :fetch-size through) (is (= 466 (reduce (fn [n r] (+ n (:cost r))) 100 - (sql/reducible-query db "SELECT * FROM fruit")))) + (sql/reducible-query db "SELECT * FROM fruit" + {:fetch-size 100})))) ;; reduce without init -- uses first row as init! (is (= 366 (:cost (reduce (fn From 424352f39d34c0d225f256e00b9daf4acea92e68 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 4 Jul 2017 21:41:21 -0500 Subject: [PATCH 018/175] [maven-release-plugin] prepare release java.jdbc-0.7.0-beta3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1c5d895c..fde61b35 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-SNAPSHOT + 0.7.0-beta3 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.0-beta3 From cf8e05d16078fba70773d498740a1ee68f162d11 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 4 Jul 2017 21:41:21 -0500 Subject: [PATCH 019/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index fde61b35..1c5d895c 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-beta3 + 0.7.0-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.0-beta3 + HEAD From 897d1707e6bc688bb0af4fd64a006d418ac4bb81 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Tue, 4 Jul 2017 19:43:47 -0700 Subject: [PATCH 020/175] Release 0.7.0-beta4 Corrected what was in beta 3 vs beta 4 --- CHANGES.md | 7 +++++-- README.md | 13 ++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 606d8b1c..2af5650e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,12 @@ -Changes in 0.7.0-beta3 +Changes in 0.7.0-beta4 -* Reflection warnings removed in `reducible-result-set` [JDBC-152](https://dev.clojure.org/jira/browse/JDBC-152). * `opts` are now correctly passed from `reducible-query` to `db-query-with-resultset`. * Updated the `::query-options` spec to make it clear that `::prepare-options` are also acceptable there. +Changes in 0.7.0-beta3 + +* Reflection warnings removed in `reducible-result-set` [JDBC-152](https://dev.clojure.org/jira/browse/JDBC-152). + Changes in 0.7.0-beta2 * Support for Clojure 1.6.0 and earlier has been dropped -- breaking change. diff --git a/README.md b/README.md index 418b99ba..7a8e5a34 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Additional documentation can be found in the [java.jdbc section of clojure-doc.o Releases and Dependency Information ======================================== -Latest stable release: 0.7.0-beta3 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.0-beta4 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -24,14 +24,14 @@ Latest stable release: 0.7.0-beta3 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.0-beta3"] +[org.clojure/java.jdbc "0.7.0-beta4"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.0-beta3 + 0.7.0-beta4 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -134,12 +134,15 @@ Developer Information Change Log ==================== -Release 0.7.0-beta3 on 2017-07-04 +Release 0.7.0-beta4 on 2017-07-04 - * Reflection warnings removed in `reducible-result-set` [JDBC-152](https://dev.clojure.org/jira/browse/JDBC-152). * `opts` are now correctly passed from `reducible-query` to `db-query-with-resultset`. * Updated the `::query-options` spec to make it clear that `::prepare-options` are also acceptable there. +Release 0.7.0-beta3 on 2017-07-04 + + * Reflection warnings removed in `reducible-result-set` [JDBC-152](https://dev.clojure.org/jira/browse/JDBC-152). + Release 0.7.0-beta2 on 2017-06-30 (a.k.a The Reducible Saga, Part 2) * Support for Clojure 1.5 and 1.6 has been dropped -- breaking change. * Or, put another way, `clojure.java.jdbc` now requires Clojure 1.7 or later! From b52b2396a38f599122a80d75b62cb1461be3ba1f Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 4 Jul 2017 22:17:20 -0500 Subject: [PATCH 021/175] [maven-release-plugin] prepare release java.jdbc-0.7.0-beta4 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1c5d895c..36241320 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-SNAPSHOT + 0.7.0-beta4 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.0-beta4 From cf1d153aaefa8fae7eaef0b19bb71d5f32aad47c Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 4 Jul 2017 22:17:20 -0500 Subject: [PATCH 022/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 36241320..1c5d895c 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-beta4 + 0.7.0-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.0-beta4 + HEAD From 5471dd4f3123a460d8ea9a288e2a3d8c96be76ad Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 5 Jul 2017 14:17:54 -0700 Subject: [PATCH 023/175] JDBC-153 support :auto-commit? :read-only? on connections; improve option checking on prepare-statement --- CHANGES.md | 5 + src/main/clojure/clojure/java/jdbc.clj | 229 +++++++++++--------- src/main/clojure/clojure/java/jdbc/spec.clj | 16 +- src/test/clojure/clojure/java/test_jdbc.clj | 13 +- 4 files changed, 158 insertions(+), 105 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2af5650e..2df10054 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +Changes coming in 0.7.0-? + +* `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://dev.clojure.org/jira/browse/JDBC-153). +* Additional validation of options is performed in `prepared-statement` to avoid silently ignoring invalid combinations of `:concurrency`, `:cursors`, `:result-type`, and `:return-keys`. + Changes in 0.7.0-beta4 * `opts` are now correctly passed from `reducible-query` to `db-query-with-resultset`. diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 9a31f7f4..4537183b 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -203,6 +203,17 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (list* 'do body)) (catch ClassNotFoundException _#))) +(defn- modify-connection + "Given a database connection and a map of options, update the connection + as specified by the options." + ^java.sql.Connection + [^java.sql.Connection connection opts] + (when (contains? opts :auto-commit?) + (.setAutoCommit connection (:auto-commit? opts))) + (when (contains? opts :read-only?) + (.setReadOnly connection (:read-only? opts))) + connection) + (defn get-connection "Creates a connection to a database. db-spec is usually a map containing connection parameters but can also be a URI or a String. The various possibilities are described @@ -255,89 +266,100 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} java.lang.String: subprotocol://user:password@host:post/subname An optional prefix of jdbc: is allowed." - ^java.sql.Connection - [{:keys [connection - factory - connection-uri - classname subprotocol subname - dbtype dbname host port - datasource username password user - name environment] - :as db-spec}] - (cond - (string? db-spec) - (get-connection (URI. (strip-jdbc db-spec))) - - (instance? URI db-spec) - (get-connection (parse-properties-uri db-spec)) - - connection - connection - - (or (and datasource username password) - (and datasource user password)) - (.getConnection ^DataSource datasource ^String (or username user) ^String password) - - datasource - (.getConnection ^DataSource datasource) - - factory - (factory (dissoc db-spec :factory)) - - connection-uri - (DriverManager/getConnection connection-uri) - - (and dbtype dbname) - (let [;; allow aliases for dbtype - subprotocol (subprotocols dbtype dbtype) - host (or host "127.0.0.1") - port (or port (condp = subprotocol - "jtds:sqlserver" 1433 - "mysql" 3306 - "postgresql" 5432 - "sqlserver" 1433 - nil)) - db-sep (if (= "sqlserver" subprotocol) ";DATABASENAME=" "/") - url (if (#{"derby" "h2" "hsqldb" "sqlite"} subprotocol) - (str "jdbc:" subprotocol ":" dbname) - (str "jdbc:" subprotocol "://" host - (when port (str ":" port)) - db-sep dbname)) - etc (dissoc db-spec :dbtype :dbname)] - (if-let [class-name (or classname (classnames subprotocol))] - (do - ;; force DriverManager to be loaded - (DriverManager/getLoginTimeout) - (clojure.lang.RT/loadClassForName class-name)) - (throw (ex-info (str "Unknown dbtype: " dbtype) db-spec))) - (DriverManager/getConnection url (as-properties etc))) - - (and subprotocol subname) - (let [;; allow aliases for subprotocols - subprotocol (subprotocols subprotocol subprotocol) - url (format "jdbc:%s:%s" subprotocol subname) - etc (dissoc db-spec :classname :subprotocol :subname)] - (if-let [class-name (or classname (classnames subprotocol))] - (do - ;; force DriverManager to be loaded - (DriverManager/getLoginTimeout) - (clojure.lang.RT/loadClassForName class-name)) - (throw (ex-info (str "Unknown subprotocol: " subprotocol) db-spec))) - (DriverManager/getConnection url (as-properties etc))) - - name - (or (when-available javax.naming.InitialContext - (let [env (and environment (Hashtable. ^Map environment)) - context (javax.naming.InitialContext. env) - ^DataSource datasource (.lookup context ^String name)] - (.getConnection datasource))) - (throw (ex-info (str "javax.naming.InitialContext is not available for: " - name) - db-spec))) - - :else - (let [^String msg (format "db-spec %s is missing a required parameter" db-spec)] - (throw (IllegalArgumentException. msg))))) + (^java.sql.Connection [db-spec] (get-connection db-spec {})) + (^java.sql.Connection + [{:keys [connection + factory + connection-uri + classname subprotocol subname + dbtype dbname host port + datasource username password user + name environment] + :as db-spec} + opts] + (cond + (string? db-spec) + (get-connection (URI. (strip-jdbc db-spec)) opts) + + (instance? URI db-spec) + (get-connection (parse-properties-uri db-spec) opts) + + connection + connection ;; do not apply opts here + + (or (and datasource username password) + (and datasource user password)) + (-> (.getConnection ^DataSource datasource + ^String (or username user) + ^String password) + (modify-connection opts)) + + datasource + (-> (.getConnection ^DataSource datasource) + (modify-connection opts)) + + factory + (-> (factory (dissoc db-spec :factory)) + (modify-connection opts)) + + connection-uri + (-> (DriverManager/getConnection connection-uri) + (modify-connection opts)) + + (and dbtype dbname) + (let [;; allow aliases for dbtype + subprotocol (subprotocols dbtype dbtype) + host (or host "127.0.0.1") + port (or port (condp = subprotocol + "jtds:sqlserver" 1433 + "mysql" 3306 + "postgresql" 5432 + "sqlserver" 1433 + nil)) + db-sep (if (= "sqlserver" subprotocol) ";DATABASENAME=" "/") + url (if (#{"derby" "h2" "hsqldb" "sqlite"} subprotocol) + (str "jdbc:" subprotocol ":" dbname) + (str "jdbc:" subprotocol "://" host + (when port (str ":" port)) + db-sep dbname)) + etc (dissoc db-spec :dbtype :dbname)] + (if-let [class-name (or classname (classnames subprotocol))] + (do + ;; force DriverManager to be loaded + (DriverManager/getLoginTimeout) + (clojure.lang.RT/loadClassForName class-name)) + (throw (ex-info (str "Unknown dbtype: " dbtype) db-spec))) + (-> (DriverManager/getConnection url (as-properties etc)) + (modify-connection opts))) + + (and subprotocol subname) + (let [;; allow aliases for subprotocols + subprotocol (subprotocols subprotocol subprotocol) + url (format "jdbc:%s:%s" subprotocol subname) + etc (dissoc db-spec :classname :subprotocol :subname)] + (if-let [class-name (or classname (classnames subprotocol))] + (do + ;; force DriverManager to be loaded + (DriverManager/getLoginTimeout) + (clojure.lang.RT/loadClassForName class-name)) + (throw (ex-info (str "Unknown subprotocol: " subprotocol) db-spec))) + (-> (DriverManager/getConnection url (as-properties etc)) + (modify-connection opts))) + + name + (or (when-available javax.naming.InitialContext + (let [env (and environment (Hashtable. ^Map environment)) + context (javax.naming.InitialContext. env) + ^DataSource datasource (.lookup context ^String name)] + (-> (.getConnection datasource) + (modify-connection opts)))) + (throw (ex-info (str "javax.naming.InitialContext is not available for: " + name) + db-spec))) + + :else + (let [^String msg (format "db-spec %s is missing a required parameter" db-spec)] + (throw (IllegalArgumentException. msg)))))) (defn- make-name-unique "Given a collection of column names and a new column name, @@ -510,7 +532,9 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} :cursors :fetch-size n :max-rows n - :timeout n" + :timeout n + Note that :result-type and :concurrency must be specified together as the + underlying Java API expects both (or neither)." ([con sql] (prepare-statement con sql {})) ([^java.sql.Connection con ^String sql {:keys [return-keys result-type concurrency cursors @@ -518,6 +542,10 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (let [^PreparedStatement stmt (cond return-keys (try + (when (or result-type concurrency cursors) + (throw (IllegalArgumentException. + (str ":concurrency, :cursors, and :result-type " + "may not be specified with :return-keys.")))) (if (vector? return-keys) (try (.prepareStatement con sql (string-array return-keys)) @@ -539,6 +567,10 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (get result-set-type result-type result-type) (get result-set-concurrency concurrency concurrency))) + (or result-type concurrency cursors) + (throw (IllegalArgumentException. + (str ":concurrency, :cursors, and :result-type " + "may not be specified independently."))) :else (.prepareStatement con sql))] (when fetch-size (.setFetchSize stmt fetch-size)) @@ -692,7 +724,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (try (.setReadOnly con old-readonly) (catch Exception _))))))) - (with-open [con (get-connection db)] + (with-open [con (get-connection db opts)] (db-transaction* (add-connection db con) func opts))) (do (when (and isolation @@ -719,11 +751,11 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defmacro with-db-connection "Evaluates body in the context of an active connection to the database. - (with-db-connection [con-db db-spec] + (with-db-connection [con-db db-spec opts] ... con-db ...)" [binding & body] - `(let [db-spec# ~(second binding)] - (with-open [con# (get-connection db-spec#)] + `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] + (with-open [con# (get-connection db-spec# opts#)] (let [~(first binding) (add-connection db-spec# con#)] ~@body)))) @@ -731,12 +763,13 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} "Evaluates body in the context of an active connection with metadata bound to the specified name. See also metadata-result for dealing with the results of operations that retrieve information from the metadata. - (with-db-metadata [md db-spec] + (with-db-metadata [md db-spec opts] ... md ...)" [binding & body] - `(with-open [con# (get-connection ~(second binding))] - (let [~(first binding) (.getMetaData con#)] - ~@body))) + `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] + (with-open [con# (get-connection db-spec# opts#)] + (let [~(first binding) (.getMetaData con#)] + ~@body)))) (defn metadata-result "If the argument is a java.sql.ResultSet, turn it into a result-set-seq, @@ -843,7 +876,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (db-do-execute-prepared-return-keys db sql params (assoc opts :transaction? transaction?)) (with-open [^PreparedStatement stmt (prepare-statement con sql (assoc opts :return-keys true))] (db-do-execute-prepared-return-keys db stmt params (assoc opts :transaction? transaction?))))) - (with-open [con (get-connection db)] + (with-open [con (get-connection db opts)] (db-do-prepared-return-keys (add-connection db con) transaction? sql-params opts)))))) (defn- db-do-execute-prepared-statement @@ -885,7 +918,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (db-do-execute-prepared-statement db sql params (assoc opts :transaction? transaction?)) (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] (db-do-execute-prepared-statement db stmt params (assoc opts :transaction? transaction?))))) - (with-open [con (get-connection db)] + (with-open [con (get-connection db opts)] (db-do-prepared (add-connection db con) transaction? sql-params opts)))))) (defn db-query-with-resultset @@ -919,7 +952,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (if-let [con (db-find-connection db)] (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] (run-query-with-params stmt)) - (with-open [con (get-connection db)] + (with-open [con (get-connection db opts)] (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] (run-query-with-params stmt)))))))) @@ -1116,7 +1149,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (db-do-prepared db transaction? sql-params opts))] (if-let [con (db-find-connection db)] (execute-helper db) - (with-open [con (get-connection db)] + (with-open [con (get-connection db opts)] (execute-helper (add-connection db con))))))) (defn- delete-sql @@ -1212,7 +1245,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (insert-single-row-sql table row entities)) rows)] (if-let [con (db-find-connection db)] (insert-helper db transaction? sql-params opts) - (with-open [con (get-connection db)] + (with-open [con (get-connection db opts)] (insert-helper (add-connection db con) transaction? sql-params opts))))) (defn- insert-cols! @@ -1225,7 +1258,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} sql-params (insert-multi-row-sql table cols values entities)] (if-let [con (db-find-connection db)] (db-do-prepared db transaction? sql-params (assoc opts :multi? true)) - (with-open [con (get-connection db)] + (with-open [con (get-connection db opts)] (db-do-prepared (add-connection db con) transaction? sql-params (assoc opts :multi? true)))))) diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index 26d56167..d531a857 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -100,6 +100,7 @@ ;; the corresponding options must either be omitted or given valid values (s/def ::as-arrays? (s/or :as-is #{:cols-as-is} :truthy (s/nilable boolean?))) +(s/def ::auto-commit? boolean?) (s/def ::concurrency (set (keys @#'sql/result-set-concurrency))) (s/def ::cursors (set (keys @#'sql/result-set-holdability))) (s/def ::fetch-size nat-int?) @@ -159,10 +160,14 @@ ::identifiers ::qualifier ::as-arrays? ::read-columns])) -(s/def ::prepare-options (s/keys :req-un [] - :opt-un [::return-keys ::result-type - ::concurrency ::cursors ::fetch-size - ::max-rows ::timeout])) +(s/def ::connection-options (s/keys :req-un [] + :opt-un [::auto-commit? ::read-only?])) + +(s/def ::prepare-options (s/merge (s/keys :req-un [] + :opt-un [::return-keys ::result-type + ::concurrency ::cursors ::fetch-size + ::max-rows ::timeout]) + ::connection-options)) (s/def ::transaction-options (s/keys :req-un [] :opt-un [::isolation ::read-only?])) @@ -196,7 +201,8 @@ :ret ::naming-strategy) (s/fdef sql/get-connection - :args (s/cat :db-spec ::db-spec) + :args (s/cat :db-spec ::db-spec + :opts (s/? ::connection-options)) :ret ::connection) (s/fdef sql/result-set-seq diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index f6dedf8d..eb35b68f 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -176,6 +176,11 @@ (re-find #"pgsql") (re-find #"pgsql" (or (:subprotocol db) (:dbtype db))))) +(defn- sqlite? [db] + (if (string? db) + (re-find #"sqlite:" db) + (= "sqlite" (or (:subprotocol db) (:dbtype db))))) + (defmulti create-test-table "Create a standard test table. Uses db-do-commands. For MySQL, ensure table uses an engine that supports transactions!" @@ -437,10 +442,14 @@ (is (= ["Peach"] (map :name (sql/find-by-keys db :fruit {:id 3 :cost 139})))) (is (= ["Peach" "Orange"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [:id]})))) (is (= ["Orange" "Peach"] (map :name (sql/find-by-keys db :fruit {:cost 139} {:order-by [{:appearance :desc}]})))) - ;; reduce with init (and ensure we can pass :fetch-size through) + ;; reduce with init (and ensure we can pass :fetch-size & connection opts through) (is (= 466 (reduce (fn [n r] (+ n (:cost r))) 100 (sql/reducible-query db "SELECT * FROM fruit" - {:fetch-size 100})))) + (cond-> {:fetch-size 100} + (not (sqlite? db)) + (assoc :read-only? true) + (not (derby? db)) + (assoc :auto-commit? false)))))) ;; reduce without init -- uses first row as init! (is (= 366 (:cost (reduce (fn From f67550f3474ecb3a98545fa8ed9e1562cc516dcb Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 5 Jul 2017 20:03:49 -0700 Subject: [PATCH 024/175] Release 0.7.0-beta5 Adds :auto-commit? and :read-only? for connection options. --- CHANGES.md | 2 +- README.md | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2df10054..ca933969 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes coming in 0.7.0-? +Changes in 0.7.0-beta5 * `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://dev.clojure.org/jira/browse/JDBC-153). * Additional validation of options is performed in `prepared-statement` to avoid silently ignoring invalid combinations of `:concurrency`, `:cursors`, `:result-type`, and `:return-keys`. diff --git a/README.md b/README.md index 7a8e5a34..04662d56 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Additional documentation can be found in the [java.jdbc section of clojure-doc.o Releases and Dependency Information ======================================== -Latest stable release: 0.7.0-beta4 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.0-beta5 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -24,14 +24,14 @@ Latest stable release: 0.7.0-beta4 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.0-beta4"] +[org.clojure/java.jdbc "0.7.0-beta5"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.0-beta4 + 0.7.0-beta5 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -134,6 +134,11 @@ Developer Information Change Log ==================== +Release 0.7.0-beta5 on 2017-07-05 + + * `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://dev.clojure.org/jira/browse/JDBC-153). + * Additional validation of options is performed in `prepared-statement` to avoid silently ignoring invalid combinations of `:concurrency`, `:cursors`, `:result-type`, and `:return-keys`. + Release 0.7.0-beta4 on 2017-07-04 * `opts` are now correctly passed from `reducible-query` to `db-query-with-resultset`. From 7871f7bc1ef4d6109170fd1618ac6904281a3747 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 5 Jul 2017 22:05:18 -0500 Subject: [PATCH 025/175] [maven-release-plugin] prepare release java.jdbc-0.7.0-btea5 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1c5d895c..18b5a863 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-SNAPSHOT + 0.7.0-btea5 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.0-btea5 From 9b9ab112fc42632221dbf73d4e3b52b830a98277 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 5 Jul 2017 22:05:18 -0500 Subject: [PATCH 026/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 18b5a863..1c5d895c 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-btea5 + 0.7.0-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.0-btea5 + HEAD From 425b629e91cfc0f7d13a1b80de410b4dc8329ed5 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 5 Jul 2017 22:23:15 -0500 Subject: [PATCH 027/175] [maven-release-plugin] prepare release java.jdbc-0.7.0-beta5 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1c5d895c..fd02594f 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-SNAPSHOT + 0.7.0-beta5 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.0-beta5 From 75141ad7a3fdc028fac4db8e0721742bc166836f Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 5 Jul 2017 22:23:15 -0500 Subject: [PATCH 028/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index fd02594f..1c5d895c 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-beta5 + 0.7.0-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.0-beta5 + HEAD From ddec91da22987a0826706d9bc13ffe722cbc3601 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 13 Jul 2017 23:00:18 -0700 Subject: [PATCH 029/175] Add conditional? to create/drop DDL functions --- src/main/clojure/clojure/java/jdbc.clj | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 4537183b..a42bc6ef 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1360,6 +1360,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} ([table specs] (create-table-ddl table specs {})) ([table specs opts] (let [table-spec (:table-spec opts) + conditional? (:conditional? opts) entities (:entities opts identity) table-spec-str (or (and table-spec (str " " table-spec)) "") spec-to-string (fn [spec] @@ -1369,7 +1370,10 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (catch Exception _ (throw (IllegalArgumentException. "column spec is not a sequence of keywords / strings")))))] - (format "CREATE TABLE %s (%s)%s" + (format "CREATE TABLE%s %s (%s)%s" + (if (boolean? conditional?) + (if conditional? " IF NOT EXISTS" "") + conditional?) (as-sql-name entities table) (str/join ", " (map spec-to-string specs)) table-spec-str)))) @@ -1377,5 +1381,9 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defn drop-table-ddl "Given a table name, return the DDL string for dropping that table." ([name] (drop-table-ddl name {})) - ([name {:keys [entities] :or {entities identity}}] - (format "DROP TABLE %s" (as-sql-name entities name)))) + ([name {:keys [entities conditional?] :or {entities identity}}] + (format "DROP TABLE%s %s" + (if (boolean? conditional?) + (if conditional? " IF EXISTS" "") + conditional?) + (as-sql-name entities name)))) From 8c7bd87c99fe4d1554eca19d82bbce1ccabbf4bc Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 16 Jul 2017 16:03:29 -0700 Subject: [PATCH 030/175] Release 0.7.0 Document and spec `:conditional?`; improve Oracle support. --- CHANGES.md | 5 ++ README.md | 11 ++- src/main/clojure/clojure/java/jdbc.clj | 76 +++++++++++++++------ src/main/clojure/clojure/java/jdbc/spec.clj | 20 ++++-- 4 files changed, 84 insertions(+), 28 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ca933969..c06b6780 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +Changes in 0.7.0 + +* `:conditional?` option for `create-table-ddl` and `drop-table-ddl` to provide for existence check (or a function to manipulate the generated DDL). +* Add better support for Oracle connections (default port to `1521`, support `:dbtype "oracle"` -- as `"oracle:thin"` -- and `:dbtype "oracle:oci"`, with `@` instead of `//` before host). + Changes in 0.7.0-beta5 * `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://dev.clojure.org/jira/browse/JDBC-153). diff --git a/README.md b/README.md index 04662d56..d02c7a91 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Additional documentation can be found in the [java.jdbc section of clojure-doc.o Releases and Dependency Information ======================================== -Latest stable release: 0.7.0-beta5 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.0 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -24,14 +24,14 @@ Latest stable release: 0.7.0-beta5 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.0-beta5"] +[org.clojure/java.jdbc "0.7.0"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.0-beta5 + 0.7.0 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -134,6 +134,11 @@ Developer Information Change Log ==================== +Release 0.7.0 on 2017-07-16 + + * `:conditional?` option for `create-table-ddl` and `drop-table-ddl` to provide for existence check (or a function to manipulate the generated DDL). + * Add better support for Oracle connections (default port to `1521`, support `:dbtype "oracle"` -- as `"oracle:thin"` -- and `:dbtype "oracle:oci"`, with `@` instead of `//` before host). + Release 0.7.0-beta5 on 2017-07-05 * `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://dev.clojure.org/jira/browse/JDBC-153). diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index a42bc6ef..9537ff0f 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -170,21 +170,29 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} "oracle" "oracle:thin" "postgres" "postgresql"}) +(def ^:private host-prefixes + "Map of subprotocols to non-standard host-prefixes. + Anything not listed is assumed to use //." + {"oracle:oci" "@" + "oracle:thin" "@"}) + (defn- parse-properties-uri [^URI uri] (let [host (.getHost uri) port (if (pos? (.getPort uri)) (.getPort uri)) path (.getPath uri) scheme (.getScheme uri) + subprotocol (subprotocols scheme scheme) + host-prefix (host-prefixes subprotocol "//") ^String query (.getQuery uri) query-parts (and query (for [^String kvs (.split query "&")] (vec (.split kvs "="))))] (merge {:subname (if host (if port - (str "//" host ":" port path) - (str "//" host path)) + (str host-prefix host ":" port path) + (str host-prefix host path)) (.getSchemeSpecificPart uri)) - :subprotocol (subprotocols scheme scheme)} + :subprotocol subprotocol} (if-let [user-info (.getUserInfo uri)] {:user (first (str/split user-info #":")) :password (second (str/split user-info #":"))}) @@ -313,13 +321,17 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} port (or port (condp = subprotocol "jtds:sqlserver" 1433 "mysql" 3306 + "oracle:oci" 1521 + "oracle:thin" 1521 "postgresql" 5432 "sqlserver" 1433 nil)) db-sep (if (= "sqlserver" subprotocol) ";DATABASENAME=" "/") url (if (#{"derby" "h2" "hsqldb" "sqlite"} subprotocol) (str "jdbc:" subprotocol ":" dbname) - (str "jdbc:" subprotocol "://" host + (str "jdbc:" subprotocol ":" + (host-prefixes subprotocol "//") + host (when port (str ":" port)) db-sep dbname)) etc (dissoc db-spec :dbtype :dbname)] @@ -1356,12 +1368,18 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} remaining elements are left as-is when converting them to strings. An options map may be provided that can contain: :table-spec -- a string that is appended to the DDL -- and/or - :entities -- a function to specify how column names are transformed." + :entities -- a function to specify how column names are transformed. + :conditional? -- either a boolean, indicating whether to add 'IF NOT EXISTS', + or a string, which is inserted literally before the table name, or a + function of two arguments (table name and the create statement), that can + manipulate the generated statement to better support other databases, e.g., + MS SQL Server which need to wrap create table in an existence query." ([table specs] (create-table-ddl table specs {})) ([table specs opts] (let [table-spec (:table-spec opts) conditional? (:conditional? opts) entities (:entities opts identity) + table-name (as-sql-name entities table) table-spec-str (or (and table-spec (str " " table-spec)) "") spec-to-string (fn [spec] (try @@ -1370,20 +1388,38 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (catch Exception _ (throw (IllegalArgumentException. "column spec is not a sequence of keywords / strings")))))] - (format "CREATE TABLE%s %s (%s)%s" - (if (boolean? conditional?) - (if conditional? " IF NOT EXISTS" "") - conditional?) - (as-sql-name entities table) - (str/join ", " (map spec-to-string specs)) - table-spec-str)))) + (cond->> (format "CREATE TABLE%s %s (%s)%s" + (cond (or (nil? conditional?) + (instance? Boolean conditional?)) + (if conditional? " IF NOT EXISTS" "") + (fn? conditional?) + "" + :else + (str " " conditional?)) + table-name + (str/join ", " (map spec-to-string specs)) + table-spec-str) + (fn? conditional?) (conditional? table-name))))) (defn drop-table-ddl - "Given a table name, return the DDL string for dropping that table." - ([name] (drop-table-ddl name {})) - ([name {:keys [entities conditional?] :or {entities identity}}] - (format "DROP TABLE%s %s" - (if (boolean? conditional?) - (if conditional? " IF EXISTS" "") - conditional?) - (as-sql-name entities name)))) + "Given a table name, return the DDL string for dropping that table. + An options map may be provided that can contain: + :entities -- a function to specify how column names are transformed. + :conditional? -- either a boolean, indicating whether to add 'IF EXISTS', + or a string, which is inserted literally before the table name, or a + function of two arguments (table name and the create statement), that can + manipulate the generated statement to better support other databases, e.g., + MS SQL Server which need to wrap create table in an existence query." + ([table] (drop-table-ddl table {})) + ([table {:keys [entities conditional?] :or {entities identity}}] + (let [table-name (as-sql-name entities table)] + (cond->> (format "DROP TABLE%s %s" + (cond (or (nil? conditional?) + (instance? Boolean conditional?)) + (if conditional? " IF EXISTS" "") + (fn? conditional?) + "" + :else + (str " " conditional?)) + table-name) + (fn? conditional?) (conditional? table-name))))) diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index d531a857..f0593560 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -145,11 +145,12 @@ (s/def ::table-spec string?) (s/def ::timeout nat-int?) (s/def ::transaction? boolean?) +(s/def ::explain? (s/or :b boolean? :s string?)) +(s/def ::explain-fn fn?) +(s/def ::conditional? (s/or :b boolean? :s string? :f fn?)) ;; various types of options -(s/def ::create-options (s/keys :req-un [] :opt-un [::table-spec ::entities])) - (s/def ::exec-sql-options (s/keys :req-un [] :opt-un [::entities ::transaction?])) (s/def ::execute-options (s/keys :req-un [] :opt-un [::transaction? ::multi?])) @@ -309,7 +310,10 @@ (s/fdef sql/query :args (s/cat :db ::db-spec :sql-params ::sql-params - :opts (s/? ::query-options)) + :opts (s/? (s/merge ::query-options + (s/keys :req-un [] + :opt-un [::explain? + ::explain-fn])))) :ret any?) (s/fdef sql/reducible-result-set @@ -388,10 +392,16 @@ (s/fdef sql/create-table-ddl :args (s/cat :table ::identifier :specs (s/coll-of ::column-spec) - :opts (s/? ::create-options)) + :opts (s/? (s/keys :req-un [] + :opt-un [::entities + ::conditional? + ::table-spec]))) + :ret string?) (s/fdef sql/drop-table-ddl :args (s/cat :table ::identifier - :opts (s/? (s/keys :req-un [] :opt-un [::entities]))) + :opts (s/? (s/keys :req-un [] + :opt-un [::entities + ::conditional?]))) :ret string?) From 7f24fdd6d16b13ed998ea3968ae6db5cde5f54a0 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Sun, 16 Jul 2017 18:26:16 -0500 Subject: [PATCH 031/175] [maven-release-plugin] prepare release java.jdbc-0.7.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1c5d895c..ab34936a 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0-SNAPSHOT + 0.7.0 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.0 From e83c0fe64cce32fbff75356ce5e871039a1c0a82 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Sun, 16 Jul 2017 18:26:16 -0500 Subject: [PATCH 032/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ab34936a..08597b14 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.0 + 0.7.1-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.0 + HEAD From 12604f29c36e3c7b51d28d1f77da856f2bcd8f7e Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 12 Aug 2017 17:02:29 -0700 Subject: [PATCH 033/175] JDBC-154 improve prepare-statement docstring --- src/main/clojure/clojure/java/jdbc.clj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 9537ff0f..75c00099 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -541,10 +541,10 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} the generated keys to return, otherwise it should just be true :result-type :forward-only | :scroll-insensitive | :scroll-sensitive :concurrency :read-only | :updatable - :cursors - :fetch-size n - :max-rows n - :timeout n + :cursors :hold | :close + :fetch-size n + :max-rows n + :timeout n Note that :result-type and :concurrency must be specified together as the underlying Java API expects both (or neither)." ([con sql] (prepare-statement con sql {})) From dd77edeb158edd3e46fb01c4648bbc6264b091e6 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 30 Aug 2017 10:16:49 -0700 Subject: [PATCH 034/175] Release 0.7.1 fixes JDBC-155 --- CHANGES.md | 4 ++++ README.md | 11 ++++++++--- project.clj | 2 +- src/main/clojure/clojure/java/jdbc.clj | 5 +++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c06b6780..14a22fca 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes in 0.7.1 + +* Connection strings with empty values were not parsed correctly [JDBC-155](https://dev.clojure.org/jira/browse/JDBC-155). + Changes in 0.7.0 * `:conditional?` option for `create-table-ddl` and `drop-table-ddl` to provide for existence check (or a function to manipulate the generated DDL). diff --git a/README.md b/README.md index d02c7a91..2067a1f3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Additional documentation can be found in the [java.jdbc section of clojure-doc.o Releases and Dependency Information ======================================== -Latest stable release: 0.7.0 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.1 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -24,14 +24,14 @@ Latest stable release: 0.7.0 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.0"] +[org.clojure/java.jdbc "0.7.1"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.0 + 0.7.1 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -134,6 +134,11 @@ Developer Information Change Log ==================== + +Release 0.7.1 on 2017-08-30 + + * Connection strings with empty values were not parsed correctly [JDBC-155](https://dev.clojure.org/jira/browse/JDBC-155). + Release 0.7.0 on 2017-07-16 * `:conditional?` option for `create-table-ddl` and `drop-table-ddl` to provide for existence check (or a function to manipulate the generated DDL). diff --git a/project.clj b/project.clj index c582843c..13cefc0c 100644 --- a/project.clj +++ b/project.clj @@ -2,7 +2,7 @@ ;; develop and test java.jdbc locally. The pom.xml file is the ;; "system of record" as far as the project version is concerned. -(defproject org.clojure/java.jdbc "0.7.0-SNAPSHOT" +(defproject org.clojure/java.jdbc "0.7.2-SNAPSHOT" :description "A low-level Clojure wrapper for JDBC-based access to databases." :parent [org.clojure/pom.contrib "0.1.2"] :url "https://github.com/clojure/java.jdbc" diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 75c00099..a519b857 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -184,8 +184,9 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} subprotocol (subprotocols scheme scheme) host-prefix (host-prefixes subprotocol "//") ^String query (.getQuery uri) - query-parts (and query (for [^String kvs (.split query "&")] - (vec (.split kvs "="))))] + query-parts (and query + (for [^String kvs (.split query "&")] + ((juxt first second) (.split kvs "="))))] (merge {:subname (if host (if port From 75bb2231f5d04895a6ce7fa363b554c455190ac4 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 30 Aug 2017 12:18:38 -0500 Subject: [PATCH 035/175] [maven-release-plugin] prepare release java.jdbc-0.7.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 08597b14..7febda6b 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.1-SNAPSHOT + 0.7.1 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.1 From 636e7049cd25c96e00f86a1abb5384ea8b83d5bd Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Wed, 30 Aug 2017 12:18:38 -0500 Subject: [PATCH 036/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7febda6b..0c2e54e1 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.1 + 0.7.2-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.1 + HEAD From e606cc81857578d8c99fba89523b03fb5dc092eb Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 13 Sep 2017 19:57:50 -0700 Subject: [PATCH 037/175] JDBC-156 fix spec for connection-uri --- CHANGES.md | 4 ++++ src/main/clojure/clojure/java/jdbc/spec.clj | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 14a22fca..e22e9dd9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes coming in 0.7.2 + +* `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://dev.clojure.org/jira/browse/JDBC-156). + Changes in 0.7.1 * Connection strings with empty values were not parsed correctly [JDBC-155](https://dev.clojure.org/jira/browse/JDBC-155). diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index f0593560..ed717d41 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -20,7 +20,6 @@ ;; basic java.sql types -- cannot be generated! (s/def ::connection #(instance? java.sql.Connection %)) -(s/def ::connection-uri #(instance? java.net.URI %)) (s/def ::datasource #(instance? javax.sql.DataSource %)) (s/def ::prepared-statement #(instance? java.sql.PreparedStatement %)) (s/def ::result-set #(instance? java.sql.ResultSet %)) @@ -55,6 +54,8 @@ (s/def ::password string?) (s/def ::name string?) (s/def ::environment (s/nilable map?)) +;; raw connection-uri +(s/def ::connection-uri string?) (s/def ::db-spec-connection (s/keys :req-un [::connection])) (s/def ::db-spec-friendly (s/keys :req-un [::dbtype ::dbname] :opt-un [::host ::port])) From b841efa638fd022a6b86269c0be2b2d11afa8eb4 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 15 Sep 2017 12:58:38 -0700 Subject: [PATCH 038/175] Update Contributing link --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7800bfb8..512917d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ See [Contributing] and the [FAQ] on the Clojure development [wiki] for more information on how to contribute. [Clojure contrib]: http://dev.clojure.org/display/doc/Clojure+Contrib -[Contributing]: http://dev.clojure.org/display/community/Contributing +[Contributing]: https://clojure.org/community/contributing [FAQ]: http://dev.clojure.org/display/community/Contributing+FAQ [JIRA]: http://dev.clojure.org/jira/browse/JDBC [guidelines]: http://dev.clojure.org/display/community/Guidelines+for+Clojure+Contrib+committers From 7177209ba178c16902e7eb9b007156062f1bcd10 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 15 Sep 2017 14:54:29 -0700 Subject: [PATCH 039/175] Enhance connection-uri; clarify docstring --- CHANGES.md | 2 ++ src/main/clojure/clojure/java/jdbc.clj | 23 ++++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e22e9dd9..8b255784 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ Changes coming in 0.7.2 * `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://dev.clojure.org/jira/browse/JDBC-156). +* Allow for `:user` and `:password` to be passed with `:connection-uri`, so credentials can be omitted from the connection string. +* Clarified docstring for `get-connection` to show where `:user` and `:password` can be passed. Changes in 0.7.1 diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index a519b857..f011f2bc 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -236,11 +236,14 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defaults to 127.0.0.1) :port (optional) a Long, the port of the database (defaults to 3306 for mysql, 1433 for mssql/jtds, else nil) - (others) (optional) passed to the driver as properties. + (others) (optional) passed to the driver as properties + (may include :user and :password) Raw: :connection-uri (required) a String Passed directly to DriverManager/getConnection + (both :user and :password may be specified as well, rather + than passing them as part of the connection string) Other formats accepted: @@ -252,7 +255,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} :subprotocol (required) a String, the jdbc subprotocol :subname (required) a String, the jdbc subname :classname (optional) a String, the jdbc driver class name - (others) (optional) passed to the driver as properties. + (others) (optional) passed to the driver as properties + (may include :user and :password) Factory: :factory (required) a function of one argument, a map of params @@ -260,10 +264,9 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} DataSource: :datasource (required) a javax.sql.DataSource - :username (optional) a String - :user (optional) a String - an alternate alias for :username - (added after 0.3.0-beta2 for consistency JDBC-74) - :password (optional) a String, required if :username is supplied + :username (optional) a String - deprecated, use :user instead + :user (optional) a String - preferred + :password (optional) a String, required if :user is supplied JNDI: :name (required) a String or javax.naming.Name @@ -296,8 +299,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} connection connection ;; do not apply opts here - (or (and datasource username password) - (and datasource user password)) + (or (and datasource username password) ; legacy + (and datasource user password)) ; preferred (-> (.getConnection ^DataSource datasource ^String (or username user) ^String password) @@ -312,7 +315,9 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (modify-connection opts)) connection-uri - (-> (DriverManager/getConnection connection-uri) + (-> (if (and user password) + (DriverManager/getConnection connection-uri user password) + (DriverManager/getConnection connection-uri)) (modify-connection opts)) (and dbtype dbname) From b0545dd90e4c68d6ba3ffb503c2da68afcb1e598 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 2 Oct 2017 10:10:46 -0700 Subject: [PATCH 040/175] Release 0.7.2 --- CHANGES.md | 2 +- README.md | 12 +++++++++--- project.clj | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8b255784..e8cbef74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes coming in 0.7.2 +Changes in 0.7.2 * `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://dev.clojure.org/jira/browse/JDBC-156). * Allow for `:user` and `:password` to be passed with `:connection-uri`, so credentials can be omitted from the connection string. diff --git a/README.md b/README.md index 2067a1f3..c92d3da6 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Additional documentation can be found in the [java.jdbc section of clojure-doc.o Releases and Dependency Information ======================================== -Latest stable release: 0.7.1 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.2 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -24,14 +24,14 @@ Latest stable release: 0.7.1 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.1"] +[org.clojure/java.jdbc "0.7.2"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.1 + 0.7.2 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -135,6 +135,12 @@ Change Log ==================== +Release 0.7.2 on 2017-10-02 + + * `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://dev.clojure.org/jira/browse/JDBC-156). + * Allow for `:user` and `:password` to be passed with `:connection-uri`, so credentials can be omitted from the connection string. + * Clarified docstring for `get-connection` to show where `:user` and `:password` can be passed. + Release 0.7.1 on 2017-08-30 * Connection strings with empty values were not parsed correctly [JDBC-155](https://dev.clojure.org/jira/browse/JDBC-155). diff --git a/project.clj b/project.clj index 13cefc0c..c2db39a1 100644 --- a/project.clj +++ b/project.clj @@ -2,7 +2,7 @@ ;; develop and test java.jdbc locally. The pom.xml file is the ;; "system of record" as far as the project version is concerned. -(defproject org.clojure/java.jdbc "0.7.2-SNAPSHOT" +(defproject org.clojure/java.jdbc "0.7.3-SNAPSHOT" :description "A low-level Clojure wrapper for JDBC-based access to databases." :parent [org.clojure/pom.contrib "0.1.2"] :url "https://github.com/clojure/java.jdbc" From 6797c0cf9b08d15298a7e15c11b1159e0322c351 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 2 Oct 2017 12:18:53 -0500 Subject: [PATCH 041/175] [maven-release-plugin] prepare release java.jdbc-0.7.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0c2e54e1..9e51a241 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.2-SNAPSHOT + 0.7.2 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.2 From 6024b5762d187236dd78bbbea0848c2991f130cf Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 2 Oct 2017 12:18:53 -0500 Subject: [PATCH 042/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9e51a241..741b4149 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.2 + 0.7.3-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.2 + HEAD From bbc2042df4ae29022a9fdde6715c89f3fed69b24 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 2 Oct 2017 10:37:38 -0700 Subject: [PATCH 043/175] Bump base Clojure dep to 1.9.0-beta1 --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index c2db39a1..78d0f42a 100644 --- a/project.clj +++ b/project.clj @@ -10,7 +10,7 @@ :url "http://www.eclipse.org/legal/epl-v10.html"} :source-paths ["src/main/clojure"] :test-paths ["src/test/clojure"] - :dependencies [[org.clojure/clojure "1.9.0-alpha17"] + :dependencies [[org.clojure/clojure "1.9.0-beta1"] ;; These are just the versions most recently test against ;; for your own projects, use whatever version is most ;; appropriate for you. Again, note that this project.clj From cd0739ed834b82a7c30d24d0ee3f1a4846b22781 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 5 Oct 2017 20:55:34 -0700 Subject: [PATCH 044/175] JDBC-159 add :keywordize? option --- CHANGES.md | 4 ++ src/main/clojure/clojure/java/jdbc.clj | 57 +++++++++++++++++++------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e8cbef74..f8a62a10 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes coming in 0.7.3 + +* Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://dev.clojure.org/jira/browse/JDBC-159). + Changes in 0.7.2 * `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://dev.clojure.org/jira/browse/JDBC-156). diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index f011f2bc..99c67f5c 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -471,17 +471,29 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} are untouched (the result set maintains column name/value order). The :identifiers option specifies how SQL column names are converted to Clojure keywords. The default is to convert them to lower case. - The :qualifier option specifies the namespace qualifier for those identifiers." + The :keywordize? option can be specified as false to opt-out of the conversion + to keywords. + The :qualifier option specifies the namespace qualifier for those identifiers + (and this may not be specified when :keywordize? is false)." ([rs] (result-set-seq rs {})) - ([^ResultSet rs {:keys [as-arrays? identifiers qualifier read-columns] + ([^ResultSet rs {:keys [as-arrays? identifiers keywordize? + qualifier read-columns] :or {identifiers str/lower-case + keywordize? true read-columns dft-read-columns}}] (let [rsmeta (.getMetaData rs) idxs (range 1 (inc (.getColumnCount rsmeta))) col-name-fn (if (= :cols-as-is as-arrays?) identity make-cols-unique) - identifier-fn (if qualifier - (comp (partial keyword qualifier) identifiers) - (comp keyword identifiers)) + identifier-fn (cond (and qualifier (not keywordize?)) + (throw (IllegalArgumentException. + (str ":qualifier is not allowed unless " + ":keywordize? is true"))) + (and qualifier keywordize?) + (comp (partial keyword qualifier) identifiers) + keywordize? + (comp keyword identifiers) + :else + identifiers) keys (->> idxs (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) col-name-fn @@ -792,13 +804,15 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defn metadata-result "If the argument is a java.sql.ResultSet, turn it into a result-set-seq, else return it as-is. This makes working with metadata easier. - Also accepts an option map containing :identifiers, :qualifier, :as-arrays?, - :row-fn,and :result-set-fn to control how the ResultSet is transformed and - returned. See query for more details." + Also accepts an option map containing :identifiers, :keywordize?, :qualifier, + :as-arrays?, :row-fn,and :result-set-fn to control how the ResultSet is + transformed and returned. See query for more details." ([rs-or-value] (metadata-result rs-or-value {})) ([rs-or-value opts] (let [{:keys [as-arrays? result-set-fn row-fn] :as opts} - (merge {:identifiers str/lower-case :read-columns dft-read-columns + (merge {:identifiers str/lower-case + :keywordize? true + :read-columns dft-read-columns :row-fn identity} opts) result-set-fn (or result-set-fn (if as-arrays? vec doall))] (if (instance? java.sql.ResultSet rs-or-value) @@ -982,6 +996,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} set (and are also passed to prepare-statement as needed): :as-arrays? - return the results as a set of arrays, default false. :identifiers - applied to each column name in the result set, default lower-case + :keywordize? - defaults to true, can be false to opt-out of converting + identifiers to keywords :qualifier - optionally provides the namespace qualifier for identifiers :result-set-fn - applied to the entire result set, default doall / vec if :as-arrays? true, :result-set-fn will default to vec @@ -994,6 +1010,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} ([db sql-params opts] (let [{:keys [as-arrays? explain? explain-fn result-set-fn row-fn] :as opts} (merge {:explain-fn println :identifiers str/lower-case + :keywordize? true :read-columns dft-read-columns :row-fn identity} (when (map? db) db) opts) @@ -1021,12 +1038,20 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} "Given a java.sql.ResultSet return a reducible collection. Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce Note: :as-arrays? is not accepted here." - [^ResultSet rs {:keys [identifiers qualifier read-columns] + [^ResultSet rs {:keys [identifiers keywordize? qualifier read-columns] :or {identifiers str/lower-case + keywordize? true read-columns dft-read-columns}}] - (let [identifier-fn (if qualifier - (comp (partial keyword qualifier) identifiers) - (comp keyword identifiers)) + (let [identifier-fn (cond (and qualifier (not keywordize?)) + (throw (IllegalArgumentException. + (str ":qualifier is not allowed unless " + ":keywordize? is true"))) + (and qualifier keywordize?) + (comp (partial keyword qualifier) identifiers) + keywordize? + (comp keyword identifiers) + :else + identifiers) make-keys (fn [idxs ^ResultSetMetaData rsmeta] (->> idxs (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) @@ -1070,7 +1095,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} ([db sql-params] (reducible-query db sql-params {})) ([db sql-params opts] (let [{:keys [reducing-fn] :as opts} - (merge {:identifiers str/lower-case :read-columns dft-read-columns} + (merge {:identifiers str/lower-case :keywordize? true + :read-columns dft-read-columns} (when (map? db) db) opts) sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] @@ -1254,7 +1280,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} map, insert the rows into the database." [db table rows opts] (let [{:keys [entities transaction?] :as opts} - (merge {:entities identity :identifiers str/lower-case :transaction? true} + (merge {:entities identity :identifiers str/lower-case + :keywordize? true :transaction? true} (when (map? db) db) opts) sql-params (map (fn [row] From 02d05a3c0055d2296a0b514e3c9728162494809f Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 5 Oct 2017 21:15:00 -0700 Subject: [PATCH 045/175] JDBC-158 exposed transaction exception When rollback exception occurs, both exceptions are combined & thrown. --- CHANGES.md | 1 + src/main/clojure/clojure/java/jdbc.clj | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f8a62a10..72b5382b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ Changes coming in 0.7.3 * Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://dev.clojure.org/jira/browse/JDBC-159). +* If an exception occurs during a transaction, and then rollback fails with another exception, both exceptions will now be combined into an `ex-info`. Previously the rollback exception obscured the transaction exception [JDBC-158](https://dev.clojure.org/jira/browse/JDBC-158). Changes in 0.7.2 diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 99c67f5c..a240600a 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -735,7 +735,15 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (.commit con)) result) (catch Throwable t - (.rollback con) + (try + (.rollback con) + (catch Throwable rb + ;; combine both exceptions + (throw (ex-info (str "Rollback failed handling \"" + (.getMessage t) + "\"") + {:rollback rb + :handling t})))) (throw t)) (finally (db-unset-rollback-only! nested-db) From 2063773726771e2a7f58b0b3835555bf4d5f4e8d Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 5 Oct 2017 21:27:55 -0700 Subject: [PATCH 046/175] Release 0.7.3 --- CHANGES.md | 2 +- README.md | 10 +++++++--- project.clj | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 72b5382b..20aec6b3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes coming in 0.7.3 +Changes in 0.7.3 * Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://dev.clojure.org/jira/browse/JDBC-159). * If an exception occurs during a transaction, and then rollback fails with another exception, both exceptions will now be combined into an `ex-info`. Previously the rollback exception obscured the transaction exception [JDBC-158](https://dev.clojure.org/jira/browse/JDBC-158). diff --git a/README.md b/README.md index c92d3da6..09422447 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Additional documentation can be found in the [java.jdbc section of clojure-doc.o Releases and Dependency Information ======================================== -Latest stable release: 0.7.2 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.3 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -24,14 +24,14 @@ Latest stable release: 0.7.2 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.2"] +[org.clojure/java.jdbc "0.7.3"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.2 + 0.7.3 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -134,6 +134,10 @@ Developer Information Change Log ==================== +Release 0.7.3 on 2017-10-05 + + * Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://dev.clojure.org/jira/browse/JDBC-159). + * If an exception occurs during a transaction, and then rollback fails with another exception, both exceptions will now be combined into an `ex-info`. Previously the rollback exception obscured the transaction exception [JDBC-158](https://dev.clojure.org/jira/browse/JDBC-158). Release 0.7.2 on 2017-10-02 diff --git a/project.clj b/project.clj index 78d0f42a..a7d7550a 100644 --- a/project.clj +++ b/project.clj @@ -2,7 +2,7 @@ ;; develop and test java.jdbc locally. The pom.xml file is the ;; "system of record" as far as the project version is concerned. -(defproject org.clojure/java.jdbc "0.7.3-SNAPSHOT" +(defproject org.clojure/java.jdbc "0.7.4-SNAPSHOT" :description "A low-level Clojure wrapper for JDBC-based access to databases." :parent [org.clojure/pom.contrib "0.1.2"] :url "https://github.com/clojure/java.jdbc" From c1e99fbbe0431216e2ad6e13f8730fe6dc170263 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 5 Oct 2017 23:29:27 -0500 Subject: [PATCH 047/175] [maven-release-plugin] prepare release java.jdbc-0.7.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 741b4149..bc58a713 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.3-SNAPSHOT + 0.7.3 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.3 From 0a9e5e91642ac43d19681acbf089bbc636bb20f7 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 5 Oct 2017 23:29:27 -0500 Subject: [PATCH 048/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index bc58a713..16727262 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.3 + 0.7.4-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.3 + HEAD From 2ac6363927c9b14a753fb2dbbd28b13acda10aac Mon Sep 17 00:00:00 2001 From: Shaun Mahood Date: Fri, 6 Oct 2017 11:18:35 -0600 Subject: [PATCH 049/175] JDBC-160 change README.md to make API reference and documentation easier to find. Signed-off-by: Sean Corfield --- README.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 09422447..4931cb89 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,23 @@ clojure.java.jdbc A low-level Clojure wrapper for JDBC-based access to databases. -For DSLs that are compatible with this library, consider: - -* [HoneySQL](https://github.com/jkk/honeysql) -* [SQLingvo](https://github.com/r0man/sqlingvo) -* [Korma](http://sqlkorma.com) +For higher level DSLs and migration libraries that are compatible, see the [documentation](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html). Formerly known as `clojure.contrib.sql`. -Additional documentation can be found in the [java.jdbc section of clojure-doc.org](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) and there is a dedicated [java.jdbc mailing list](https://groups.google.com/forum/#!forum/clojure-java-jdbc) +Documentation +======================================== +* [API Reference](http://clojure.github.com/java.jdbc/) (Autogenerated) + +* [Overview](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) +* [Manipulating Data with SQL](http://clojure-doc.org/articles/ecosystem/java_jdbc/using_sql.html) +* [How to Reuse Database Connections](http://clojure-doc.org/articles/ecosystem/java_jdbc/reusing_connections.html) +* [Using DDL and Metadata](http://clojure-doc.org/articles/ecosystem/java_jdbc/using_ddl.html) + +Support +======================================== +* [Mailing List](https://groups.google.com/forum/#!forum/clojure-java-jdbc) +* #sql on [Clojurians Slack](http://clojurians.net/) Releases and Dependency Information ======================================== @@ -100,7 +108,7 @@ Example Usage {:row-fn :cost}) ;; (24) ``` -For more detail see the [generated documentation on github](http://clojure.github.com/java.jdbc/). +For more detail see the [API reference](http://clojure.github.com/java.jdbc/) or [documentation](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html). Developer Information ======================================== From 110a2dab07be442639d4c6acde2b9d99cb16ec30 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 6 Oct 2017 10:32:33 -0700 Subject: [PATCH 050/175] JDBC-160 improve readme --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 20aec6b3..fbff4644 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes coming in 0.7.4 + +* Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160). + Changes in 0.7.3 * Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://dev.clojure.org/jira/browse/JDBC-159). From 8a6231008bebfa40cb0273624c3f0ae1c3dc8777 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 6 Oct 2017 10:35:53 -0700 Subject: [PATCH 051/175] Tweak release history formatting --- README.md | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 4931cb89..bcf067e3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Documentation * [How to Reuse Database Connections](http://clojure-doc.org/articles/ecosystem/java_jdbc/reusing_connections.html) * [Using DDL and Metadata](http://clojure-doc.org/articles/ecosystem/java_jdbc/using_ddl.html) -Support +Support ======================================== * [Mailing List](https://groups.google.com/forum/#!forum/clojure-java-jdbc) * #sql on [Clojurians Slack](http://clojurians.net/) @@ -142,47 +142,40 @@ Developer Information Change Log ==================== -Release 0.7.3 on 2017-10-05 - +* Release 0.7.3 on 2017-10-05 * Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://dev.clojure.org/jira/browse/JDBC-159). * If an exception occurs during a transaction, and then rollback fails with another exception, both exceptions will now be combined into an `ex-info`. Previously the rollback exception obscured the transaction exception [JDBC-158](https://dev.clojure.org/jira/browse/JDBC-158). -Release 0.7.2 on 2017-10-02 - +* Release 0.7.2 on 2017-10-02 * `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://dev.clojure.org/jira/browse/JDBC-156). * Allow for `:user` and `:password` to be passed with `:connection-uri`, so credentials can be omitted from the connection string. * Clarified docstring for `get-connection` to show where `:user` and `:password` can be passed. -Release 0.7.1 on 2017-08-30 - +* Release 0.7.1 on 2017-08-30 * Connection strings with empty values were not parsed correctly [JDBC-155](https://dev.clojure.org/jira/browse/JDBC-155). -Release 0.7.0 on 2017-07-16 - +* Release 0.7.0 on 2017-07-16 * `:conditional?` option for `create-table-ddl` and `drop-table-ddl` to provide for existence check (or a function to manipulate the generated DDL). * Add better support for Oracle connections (default port to `1521`, support `:dbtype "oracle"` -- as `"oracle:thin"` -- and `:dbtype "oracle:oci"`, with `@` instead of `//` before host). -Release 0.7.0-beta5 on 2017-07-05 - +* Release 0.7.0-beta5 on 2017-07-05 * `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://dev.clojure.org/jira/browse/JDBC-153). * Additional validation of options is performed in `prepared-statement` to avoid silently ignoring invalid combinations of `:concurrency`, `:cursors`, `:result-type`, and `:return-keys`. -Release 0.7.0-beta4 on 2017-07-04 - +* Release 0.7.0-beta4 on 2017-07-04 * `opts` are now correctly passed from `reducible-query` to `db-query-with-resultset`. * Updated the `::query-options` spec to make it clear that `::prepare-options` are also acceptable there. -Release 0.7.0-beta3 on 2017-07-04 - +* Release 0.7.0-beta3 on 2017-07-04 * Reflection warnings removed in `reducible-result-set` [JDBC-152](https://dev.clojure.org/jira/browse/JDBC-152). -Release 0.7.0-beta2 on 2017-06-30 (a.k.a The Reducible Saga, Part 2) +* Release 0.7.0-beta2 on 2017-06-30 (a.k.a The Reducible Saga, Part 2) * Support for Clojure 1.5 and 1.6 has been dropped -- breaking change. * Or, put another way, `clojure.java.jdbc` now requires Clojure 1.7 or later! * All public functions now have specs in the optional `clojure.java.jdbc.spec` namespace (requires `clojure.spec.alpha`). * `reducible-query` and `reducible-result-set` use `IReduce` and correctly support the no-`init` arity of `reduce` by using the first row of the `ResultSet`, if present, as the (missing) `init` value, and only calling `f` with no arguments if the `ResultSet` is empty. The `init` arity of `reduce` only ever calls `f` with two arguments. -Release 0.7.0-beta1 on 2017-06-29 +* Release 0.7.0-beta1 on 2017-06-29 * Support for Clojure 1.4.0 has been dropped -- breaking change. * Optional spec support now uses `clojure.spec.alpha`. * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99). From dcaca43a86498c0fbe303ed17bdf7541ae898f2e Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 6 Oct 2017 10:36:57 -0700 Subject: [PATCH 052/175] Another formatting tweak --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bcf067e3..3bcdb532 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ Change Log * Optional spec support now uses `clojure.spec.alpha`. * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99). -Release 0.7.0-alpha3 on 2017-03-23 +* Release 0.7.0-alpha3 on 2017-03-23 * `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](http://dev.clojure.org/jira/browse/JDBC-151). * `redshift` has been added as a `dbtype` with `com.amazon.redshift.jdbc.Driver` as the driver name. From ee8559819a9387ffb89735680559997d4d9bd5ff Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 6 Oct 2017 11:39:03 -0700 Subject: [PATCH 053/175] Optional specs updated with changes in recent versions. --- CHANGES.md | 1 + src/main/clojure/clojure/java/jdbc/spec.clj | 23 +++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fbff4644..03a1ea99 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ Changes coming in 0.7.4 * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160). +* Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases. Changes in 0.7.3 diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index ed717d41..2b512d97 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -58,12 +58,18 @@ (s/def ::connection-uri string?) (s/def ::db-spec-connection (s/keys :req-un [::connection])) -(s/def ::db-spec-friendly (s/keys :req-un [::dbtype ::dbname] :opt-un [::host ::port])) -(s/def ::db-spec-raw (s/keys :req-un [::connection-uri])) -(s/def ::db-spec-driver-manager (s/keys :req-un [::subprotocol ::subname] :opt-un [::classname])) +(s/def ::db-spec-friendly (s/keys :req-un [::dbtype ::dbname] + :opt-un [::host ::port ::user ::password + ::classname])) +(s/def ::db-spec-raw (s/keys :req-un [::connection-uri] + :opt-un [::user ::password])) +(s/def ::db-spec-driver-manager (s/keys :req-un [::subprotocol ::subname] + :opt-un [::classname ::user ::password])) (s/def ::db-spec-factory (s/keys :req-un [::factory])) -(s/def ::db-spec-data-source (s/keys :req-un [::datasource] :opt-un [::username ::user ::password])) -(s/def ::db-spec-jndi (s/keys :req-un [::name] :opt-un [::environment])) +(s/def ::db-spec-data-source (s/keys :req-un [::datasource] + :opt-un [::username ::user ::password])) +(s/def ::db-spec-jndi (s/keys :req-un [::name] + :opt-un [::environment])) (s/def ::db-spec-string string?) (s/def ::db-spec (s/or :connection ::db-spec-connection :friendly ::db-spec-friendly @@ -115,6 +121,7 @@ (s/def ::isolation (set (keys @#'sql/isolation-levels))) (s/def ::entities (s/fspec :args (s/cat :s string?) :ret ::entity)) +(s/def ::keywordize? boolean?) (s/def ::max-size nat-int?) (s/def ::multi? boolean?) ;; strictly speaking we accept any keyword or string whose upper case name @@ -160,6 +167,7 @@ :opt-un [::entities ::order-by ::result-set-fn ::row-fn ::identifiers ::qualifier + ::keywordize? ::as-arrays? ::read-columns])) (s/def ::connection-options (s/keys :req-un [] @@ -177,11 +185,14 @@ (s/def ::query-options (s/merge (s/keys :req-un [] :opt-un [::result-set-fn ::row-fn ::identifiers ::qualifier + ::keywordize? ::as-arrays? ::read-columns]) ::prepare-options)) (s/def ::reducible-query-options (s/merge (s/keys :req-un [] - :opt-un [::identifiers ::qualifier + :opt-un [::identifiers + ::keywordize? + ::qualifier ::read-columns]) ::prepare-options)) From 4bd4536daa2773fc36bbd5a43680116cdbf066e6 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 13 Oct 2017 15:02:49 -0400 Subject: [PATCH 054/175] Add placeholder notes for possible speedups (mostly for reducible-query) --- src/main/clojure/clojure/java/jdbc.clj | 2946 ++++++++++++------------ 1 file changed, 1480 insertions(+), 1466 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index a240600a..ce51d0d8 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1,1466 +1,1480 @@ -;; Copyright (c) 2008-2017 Sean Corfield, 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. -;; -;; jdbc.clj -;; -;; A Clojure interface to sql databases via jdbc -;; -;; scgilardi (gmail) -;; Created 2 April 2008 -;; -;; seancorfield (gmail) -;; Migrated from clojure.contrib.sql 17 April 2011 - -(ns - ^{:author "Stephen C. Gilardi, Sean Corfield", - :doc "A Clojure interface to SQL databases via JDBC - -clojure.java.jdbc provides a simple abstraction for CRUD (create, read, -update, delete) operations on a SQL database, along with basic transaction -support. Basic DDL operations are also supported (create table, drop table, -access to table metadata). - -Maps are used to represent records, making it easy to store and retrieve -data. Results can be processed using any standard sequence operations. - -For most operations, Java's PreparedStatement is used so your SQL and -parameters can be represented as simple vectors where the first element -is the SQL string, with ? for each parameter, and the remaining elements -are the parameter values to be substituted. In general, operations return -the number of rows affected, except for a single record insert where any -generated keys are returned (as a map). - -For more documentation, see: - -http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} - clojure.java.jdbc - (:require [clojure.set :as set] - [clojure.string :as str] - [clojure.walk :as walk]) - (:import (java.net URI) - (java.sql BatchUpdateException DriverManager - PreparedStatement ResultSet ResultSetMetaData - SQLException Statement Types) - (java.util Hashtable Map Properties) - (javax.sql DataSource))) - -(defn as-sql-name - "Given a naming strategy function and a keyword or string, return - a string per that naming strategy. - A name of the form x.y is treated as multiple names, x, y, etc, - and each are turned into strings via the naming strategy and then - joined back together so x.y might become `x`.`y` if the naming - strategy quotes identifiers with `." - [f x] - (let [n (name x) - i (.indexOf n (int \.))] - (if (= -1 i) - (f n) - (str/join "." (map f (.split n "\\.")))))) - -(defn quoted - "Given a (vector) pair of delimiters (characters or strings), return a naming - strategy function that will quote SQL entities with them. - Given a single delimiter, treat it as a (vector) pair of that delimiter. - ((quoted [\\[ \\]]) \"foo\") will return \"[foo]\" -- for MS SQL Server - ((quoted \\`') \"foo\") will return \"`foo`\" -- for MySQL - Intended to be used with :entities to provide a quoting (naming) strategy that - is appropriate for your database." - [q] - (cond (vector? q) - (fn [x] - (str (first q) x (last q))) - (keyword? q) - (case q - :ansi (quoted \") - :mysql (quoted \`) - :oracle (quoted \") - :sqlserver (quoted [\[ \]])) - :else - (quoted [q q]))) - -(defn- table-str - "Transform a table spec to an entity name for SQL. The table spec may be a - string, a keyword or a map with a single pair - table name and alias." - [table entities] - (let [entities (or entities identity)] - (if (map? table) - (let [[k v] (first table)] - (str (as-sql-name entities k) " " (as-sql-name entities v))) - (as-sql-name entities table)))) - -(defn- kv-sql - "Given a sequence of column name keys and a matching sequence of column - values, and an entities mapping function, return a sequence of SQL fragments - that can be joined for part of an UPDATE SET or a SELECT WHERE clause. - Note that we pass the appropriate operator for NULL since it is different - in each case." - [ks vs entities null-op] - (map (fn [k v] - (str (as-sql-name entities k) - (if (nil? v) null-op " = ?"))) - ks vs)) - -(defn- ^Properties as-properties - "Convert any seq of pairs to a java.utils.Properties instance. - Uses as-sql-name to convert both keys and values into strings." - [m] - (let [p (Properties.)] - (doseq [[k v] m] - (.setProperty p (as-sql-name identity k) - (if (instance? clojure.lang.Named v) - (as-sql-name identity v) - (str v)))) - p)) - -;; convenience for working with different forms of connections -(defprotocol Connectable - (add-connection [db connection]) - (get-level [db])) - -(defn- inc-level - "Increment the nesting level for a transacted database connection. - If we are at the top level, also add in a rollback state." - [db] - (let [nested-db (update-in db [:level] (fnil inc 0))] - (if (= 1 (:level nested-db)) - (assoc nested-db :rollback (atom false)) - nested-db))) - -(extend-protocol Connectable - String - (add-connection [s connection] {:connection connection :level 0 :connection-string s}) - (get-level [_] 0) - - clojure.lang.Associative - (add-connection [m connection] (assoc m :connection connection)) - (get-level [m] (or (:level m) 0)) - - nil - (add-connection [_ connection] {:connection connection :level 0 :legacy true}) - (get-level [_] 0)) - -(def ^:private classnames - "Map of subprotocols to classnames. dbtype specifies one of these keys. - The subprotocols map below provides aliases for dbtype." - {"derby" "org.apache.derby.jdbc.EmbeddedDriver" - "h2" "org.h2.Driver" - "hsqldb" "org.hsqldb.jdbcDriver" - "jtds:sqlserver" "net.sourceforge.jtds.jdbc.Driver" - "mysql" "com.mysql.jdbc.Driver" - "oracle:oci" "oracle.jdbc.OracleDriver" - "oracle:thin" "oracle.jdbc.OracleDriver" - "postgresql" "org.postgresql.Driver" - "pgsql" "com.impossibl.postgres.jdbc.PGDriver" - "redshift" "com.amazon.redshift.jdbc.Driver" - "sqlite" "org.sqlite.JDBC" - "sqlserver" "com.microsoft.sqlserver.jdbc.SQLServerDriver"}) - -(def ^:private subprotocols - "Map of schemes to subprotocols. Used to provide aliases for dbtype." - {"hsql" "hsqldb" - "jtds" "jtds:sqlserver" - "mssql" "sqlserver" - "oracle" "oracle:thin" - "postgres" "postgresql"}) - -(def ^:private host-prefixes - "Map of subprotocols to non-standard host-prefixes. - Anything not listed is assumed to use //." - {"oracle:oci" "@" - "oracle:thin" "@"}) - -(defn- parse-properties-uri [^URI uri] - (let [host (.getHost uri) - port (if (pos? (.getPort uri)) (.getPort uri)) - path (.getPath uri) - scheme (.getScheme uri) - subprotocol (subprotocols scheme scheme) - host-prefix (host-prefixes subprotocol "//") - ^String query (.getQuery uri) - query-parts (and query - (for [^String kvs (.split query "&")] - ((juxt first second) (.split kvs "="))))] - (merge - {:subname (if host - (if port - (str host-prefix host ":" port path) - (str host-prefix host path)) - (.getSchemeSpecificPart uri)) - :subprotocol subprotocol} - (if-let [user-info (.getUserInfo uri)] - {:user (first (str/split user-info #":")) - :password (second (str/split user-info #":"))}) - (walk/keywordize-keys (into {} query-parts))))) - -(defn- strip-jdbc [^String spec] - (if (.startsWith spec "jdbc:") - (.substring spec 5) - spec)) - -;; feature testing macro, based on suggestion from Chas Emerick: -(defmacro when-available - [sym & body] - (try - (when (resolve sym) - (list* 'do body)) - (catch ClassNotFoundException _#))) - -(defn- modify-connection - "Given a database connection and a map of options, update the connection - as specified by the options." - ^java.sql.Connection - [^java.sql.Connection connection opts] - (when (contains? opts :auto-commit?) - (.setAutoCommit connection (:auto-commit? opts))) - (when (contains? opts :read-only?) - (.setReadOnly connection (:read-only? opts))) - connection) - -(defn get-connection - "Creates a connection to a database. db-spec is usually a map containing connection - parameters but can also be a URI or a String. The various possibilities are described - below: - - DriverManager (preferred): - :dbtype (required) a String, the type of the database (the jdbc subprotocol) - :dbname (required) a String, the name of the database - :classname (optional) a String, the jdbc driver class name - :host (optional) a String, the host name/IP of the database - (defaults to 127.0.0.1) - :port (optional) a Long, the port of the database - (defaults to 3306 for mysql, 1433 for mssql/jtds, else nil) - (others) (optional) passed to the driver as properties - (may include :user and :password) - - Raw: - :connection-uri (required) a String - Passed directly to DriverManager/getConnection - (both :user and :password may be specified as well, rather - than passing them as part of the connection string) - - Other formats accepted: - - Existing Connection: - :connection (required) an existing open connection that can be used - but cannot be closed (only the parent connection can be closed) - - DriverManager (alternative / legacy style): - :subprotocol (required) a String, the jdbc subprotocol - :subname (required) a String, the jdbc subname - :classname (optional) a String, the jdbc driver class name - (others) (optional) passed to the driver as properties - (may include :user and :password) - - Factory: - :factory (required) a function of one argument, a map of params - (others) (optional) passed to the factory function in a map - - DataSource: - :datasource (required) a javax.sql.DataSource - :username (optional) a String - deprecated, use :user instead - :user (optional) a String - preferred - :password (optional) a String, required if :user is supplied - - JNDI: - :name (required) a String or javax.naming.Name - :environment (optional) a java.util.Map - - java.net.URI: - Parsed JDBC connection string (see java.lang.String format next) - - java.lang.String: - subprotocol://user:password@host:post/subname - An optional prefix of jdbc: is allowed." - (^java.sql.Connection [db-spec] (get-connection db-spec {})) - (^java.sql.Connection - [{:keys [connection - factory - connection-uri - classname subprotocol subname - dbtype dbname host port - datasource username password user - name environment] - :as db-spec} - opts] - (cond - (string? db-spec) - (get-connection (URI. (strip-jdbc db-spec)) opts) - - (instance? URI db-spec) - (get-connection (parse-properties-uri db-spec) opts) - - connection - connection ;; do not apply opts here - - (or (and datasource username password) ; legacy - (and datasource user password)) ; preferred - (-> (.getConnection ^DataSource datasource - ^String (or username user) - ^String password) - (modify-connection opts)) - - datasource - (-> (.getConnection ^DataSource datasource) - (modify-connection opts)) - - factory - (-> (factory (dissoc db-spec :factory)) - (modify-connection opts)) - - connection-uri - (-> (if (and user password) - (DriverManager/getConnection connection-uri user password) - (DriverManager/getConnection connection-uri)) - (modify-connection opts)) - - (and dbtype dbname) - (let [;; allow aliases for dbtype - subprotocol (subprotocols dbtype dbtype) - host (or host "127.0.0.1") - port (or port (condp = subprotocol - "jtds:sqlserver" 1433 - "mysql" 3306 - "oracle:oci" 1521 - "oracle:thin" 1521 - "postgresql" 5432 - "sqlserver" 1433 - nil)) - db-sep (if (= "sqlserver" subprotocol) ";DATABASENAME=" "/") - url (if (#{"derby" "h2" "hsqldb" "sqlite"} subprotocol) - (str "jdbc:" subprotocol ":" dbname) - (str "jdbc:" subprotocol ":" - (host-prefixes subprotocol "//") - host - (when port (str ":" port)) - db-sep dbname)) - etc (dissoc db-spec :dbtype :dbname)] - (if-let [class-name (or classname (classnames subprotocol))] - (do - ;; force DriverManager to be loaded - (DriverManager/getLoginTimeout) - (clojure.lang.RT/loadClassForName class-name)) - (throw (ex-info (str "Unknown dbtype: " dbtype) db-spec))) - (-> (DriverManager/getConnection url (as-properties etc)) - (modify-connection opts))) - - (and subprotocol subname) - (let [;; allow aliases for subprotocols - subprotocol (subprotocols subprotocol subprotocol) - url (format "jdbc:%s:%s" subprotocol subname) - etc (dissoc db-spec :classname :subprotocol :subname)] - (if-let [class-name (or classname (classnames subprotocol))] - (do - ;; force DriverManager to be loaded - (DriverManager/getLoginTimeout) - (clojure.lang.RT/loadClassForName class-name)) - (throw (ex-info (str "Unknown subprotocol: " subprotocol) db-spec))) - (-> (DriverManager/getConnection url (as-properties etc)) - (modify-connection opts))) - - name - (or (when-available javax.naming.InitialContext - (let [env (and environment (Hashtable. ^Map environment)) - context (javax.naming.InitialContext. env) - ^DataSource datasource (.lookup context ^String name)] - (-> (.getConnection datasource) - (modify-connection opts)))) - (throw (ex-info (str "javax.naming.InitialContext is not available for: " - name) - db-spec))) - - :else - (let [^String msg (format "db-spec %s is missing a required parameter" db-spec)] - (throw (IllegalArgumentException. msg)))))) - -(defn- make-name-unique - "Given a collection of column names and a new column name, - return the new column name made unique, if necessary, by - appending _N where N is some unique integer suffix." - [cols col-name n] - (let [suffixed-name (if (= n 1) col-name (str col-name "_" n))] - (if (apply distinct? suffixed-name cols) - suffixed-name - (recur cols col-name (inc n))))) - -(defn- make-cols-unique - "Given a collection of column names, rename duplicates so - that the result is a collection of unique column names." - [cols] - (if (or (empty? cols) (apply distinct? cols)) - cols - (reduce (fn [unique-cols col-name] - (conj unique-cols (make-name-unique unique-cols col-name 1))) [] cols))) - -(defprotocol ISQLValue - "Protocol for creating SQL values from Clojure values. Default - implementations (for Object and nil) just return the argument, - but it can be extended to provide custom behavior to support - exotic types supported by different databases." - (sql-value [val] "Convert a Clojure value into a SQL value.")) - -(extend-protocol ISQLValue - Object - (sql-value [v] v) - - nil - (sql-value [_] nil)) - -(defprotocol ISQLParameter - "Protocol for setting SQL parameters in statement objects, which - can convert from Clojure values. The default implementation just - delegates the conversion to ISQLValue's sql-value conversion and - uses .setObject on the parameter. 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 (sql-value v))) - - nil - (set-parameter [_ ^PreparedStatement s ^long i] - (.setObject s i (sql-value nil)))) - -(defn- dft-set-parameters - "Default implementation of parameter setting for the given statement." - [stmt params] - (dorun (map-indexed (fn [ix value] - (set-parameter value stmt (inc ix))) - params))) - -(defprotocol IResultSetReadColumn - "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." - (result-set-read-column [val rsmeta idx] - "Function for transforming values after reading them from the database")) - -(extend-protocol IResultSetReadColumn - Object - (result-set-read-column [x _2 _3] x) - - Boolean - (result-set-read-column [x _2 _3] (if (= true x) true false)) - - nil - (result-set-read-column [_1 _2 _3] nil)) - -(defn- dft-read-columns - "Default implementation of reading row values from result set, given the - result set metadata and the indices." - [^ResultSet rs rsmeta idxs] - (mapv (fn [^Integer i] (result-set-read-column (.getObject rs i) rsmeta i)) idxs)) - -(defn result-set-seq - "Creates and returns a lazy sequence of maps corresponding to the rows in the - java.sql.ResultSet rs. Loosely based on clojure.core/resultset-seq but it - respects the specified naming strategy. Duplicate column names are made unique - by appending _N before applying the naming strategy (where N is a unique integer), - unless the :as-arrays? option is :cols-as-is, in which case the column names - are untouched (the result set maintains column name/value order). - The :identifiers option specifies how SQL column names are converted to Clojure - keywords. The default is to convert them to lower case. - The :keywordize? option can be specified as false to opt-out of the conversion - to keywords. - The :qualifier option specifies the namespace qualifier for those identifiers - (and this may not be specified when :keywordize? is false)." - ([rs] (result-set-seq rs {})) - ([^ResultSet rs {:keys [as-arrays? identifiers keywordize? - qualifier read-columns] - :or {identifiers str/lower-case - keywordize? true - read-columns dft-read-columns}}] - (let [rsmeta (.getMetaData rs) - idxs (range 1 (inc (.getColumnCount rsmeta))) - col-name-fn (if (= :cols-as-is as-arrays?) identity make-cols-unique) - identifier-fn (cond (and qualifier (not keywordize?)) - (throw (IllegalArgumentException. - (str ":qualifier is not allowed unless " - ":keywordize? is true"))) - (and qualifier keywordize?) - (comp (partial keyword qualifier) identifiers) - keywordize? - (comp keyword identifiers) - :else - identifiers) - keys (->> idxs - (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) - col-name-fn - (mapv identifier-fn)) - row-values (fn [] (read-columns rs rsmeta 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 - ;; want to switch to records so we're using regular maps instead. We no longer - ;; guarantee column order in rows but using into {} should preserve order for up - ;; to 16 columns (because it will use a PersistentArrayMap). If someone is relying - ;; on the order-preserving behavior of structmaps, we can reconsider... - records (fn thisfn [] - (when (.next rs) - (cons (zipmap keys (row-values)) (lazy-seq (thisfn))))) - rows (fn thisfn [] - (when (.next rs) - (cons (vec (row-values)) (lazy-seq (thisfn)))))] - (if as-arrays? - (cons (vec keys) (rows)) - (records))))) - -(defn- execute-batch - "Executes a batch of SQL commands and returns a sequence of update counts. - (-2) indicates a single operation operating on an unknown number of rows. - Specifically, Oracle returns that and we must call getUpdateCount() to get - the actual number of rows affected. In general, operations return an array - of update counts, so this may not be a general solution for Oracle..." - [^Statement stmt] - (let [result (.executeBatch stmt)] - (if (and (= 1 (count result)) (= -2 (first result))) - (list (.getUpdateCount stmt)) - (seq result)))) - -(def ^{:private true - :doc "Map friendly :concurrency values to ResultSet constants."} - result-set-concurrency - {:read-only ResultSet/CONCUR_READ_ONLY - :updatable ResultSet/CONCUR_UPDATABLE}) - -(def ^{:private true - :doc "Map friendly :cursors values to ResultSet constants."} - result-set-holdability - {:hold ResultSet/HOLD_CURSORS_OVER_COMMIT - :close ResultSet/CLOSE_CURSORS_AT_COMMIT}) - -(def ^{:private true - :doc "Map friendly :type values to ResultSet constants."} - result-set-type - {:forward-only ResultSet/TYPE_FORWARD_ONLY - :scroll-insensitive ResultSet/TYPE_SCROLL_INSENSITIVE - :scroll-sensitive ResultSet/TYPE_SCROLL_SENSITIVE}) - -(defn ^{:tag (class (into-array String []))} string-array - [return-keys] - (into-array String return-keys)) - -(defn prepare-statement - "Create a prepared statement from a connection, a SQL string and a map - of options: - :return-keys truthy | nil - default nil - for some drivers, this may be a vector of column names to identify - the generated keys to return, otherwise it should just be true - :result-type :forward-only | :scroll-insensitive | :scroll-sensitive - :concurrency :read-only | :updatable - :cursors :hold | :close - :fetch-size n - :max-rows n - :timeout n - Note that :result-type and :concurrency must be specified together as the - underlying Java API expects both (or neither)." - ([con sql] (prepare-statement con sql {})) - ([^java.sql.Connection con ^String sql - {:keys [return-keys result-type concurrency cursors - fetch-size max-rows timeout]}] - (let [^PreparedStatement - stmt (cond return-keys - (try - (when (or result-type concurrency cursors) - (throw (IllegalArgumentException. - (str ":concurrency, :cursors, and :result-type " - "may not be specified with :return-keys.")))) - (if (vector? return-keys) - (try - (.prepareStatement con sql (string-array return-keys)) - (catch Exception _ - ;; assume it is unsupported and try regular generated keys: - (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS))) - (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS)) - (catch Exception _ - ;; assume it is unsupported and try basic PreparedStatement: - (.prepareStatement con sql))) - - (and result-type concurrency) - (if cursors - (.prepareStatement con sql - (get result-set-type result-type result-type) - (get result-set-concurrency concurrency concurrency) - (get result-set-holdability cursors cursors)) - (.prepareStatement con sql - (get result-set-type result-type result-type) - (get result-set-concurrency concurrency concurrency))) - - (or result-type concurrency cursors) - (throw (IllegalArgumentException. - (str ":concurrency, :cursors, and :result-type " - "may not be specified independently."))) - :else - (.prepareStatement con sql))] - (when fetch-size (.setFetchSize stmt fetch-size)) - (when max-rows (.setMaxRows stmt max-rows)) - (when timeout (.setQueryTimeout stmt timeout)) - stmt))) - -(defn print-sql-exception - "Prints the contents of an SQLException to *out*" - [^SQLException exception] - (let [^Class exception-class (class exception)] - (println - (format (str "%s:" \newline - " Message: %s" \newline - " SQLState: %s" \newline - " Error Code: %d") - (.getSimpleName exception-class) - (.getMessage exception) - (.getSQLState exception) - (.getErrorCode exception))))) - -(defn print-sql-exception-chain - "Prints a chain of SQLExceptions to *out*" - [^SQLException exception] - (loop [e exception] - (when e - (print-sql-exception e) - (recur (.getNextException e))))) - -(def ^{:private true} special-counts - {Statement/EXECUTE_FAILED "EXECUTE_FAILED" - Statement/SUCCESS_NO_INFO "SUCCESS_NO_INFO"}) - -(defn print-update-counts - "Prints the update counts from a BatchUpdateException to *out*" - [^BatchUpdateException exception] - (println "Update counts:") - (dorun - (map-indexed - (fn [index count] - (println (format " Statement %d: %s" - index - (get special-counts count count)))) - (.getUpdateCounts exception)))) - -;; java.jdbc pieces rewritten to not use dynamic bindings - -(defn db-find-connection - "Returns the current database connection (or nil if there is none)" - ^java.sql.Connection [db] - (and (map? db) - (:connection db))) - -(defn db-connection - "Returns the current database connection (or throws if there is none)" - ^java.sql.Connection [db] - (or (db-find-connection db) - (throw (Exception. "no current database connection")))) - -(defn db-set-rollback-only! - "Marks the outermost transaction such that it will rollback rather than - commit when complete" - [db] - (reset! (:rollback db) true)) - -(defn db-unset-rollback-only! - "Marks the outermost transaction such that it will not rollback when complete" - [db] - (reset! (:rollback db) false)) - -(defn db-is-rollback-only - "Returns true if the outermost transaction will rollback rather than - commit when complete" - [db] - (deref (:rollback db))) - -(def ^:private - isolation-levels - "Transaction isolation levels." - {:none java.sql.Connection/TRANSACTION_NONE - :read-committed java.sql.Connection/TRANSACTION_READ_COMMITTED - :read-uncommitted java.sql.Connection/TRANSACTION_READ_UNCOMMITTED - :repeatable-read java.sql.Connection/TRANSACTION_REPEATABLE_READ - :serializable java.sql.Connection/TRANSACTION_SERIALIZABLE}) - -(def ^:private isolation-kws - "Map transaction isolation constants to our keywords." - (set/map-invert isolation-levels)) - -(defn get-isolation-level - "Given a db-spec (with an optional connection), return the current - transaction isolation level, if known. Return nil if there is no - active connection in the db-spec. Return :unknown if we do not - recognize the isolation level." - [db] - (when-let [con (db-find-connection db)] - (isolation-kws (.getTransactionIsolation con) :unknown))) - -(defn db-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. - The isolation option may be :none, :read-committed, :read-uncommitted, - :repeatable-read, or :serializable. Note that not all databases support - all of those isolation levels, and may either throw an exception or - substitute another isolation level. - The read-only? option puts the transaction in readonly mode (if supported)." - ([db func] (db-transaction* db func {})) - ([db func opts] - (let [{:keys [isolation read-only?] :as opts} - (merge (when (map? db) db) opts)] - (if (zero? (get-level db)) - (if-let [con (db-find-connection db)] - (let [nested-db (inc-level db) - auto-commit (.getAutoCommit con) - old-isolation (.getTransactionIsolation con) - old-readonly (.isReadOnly con)] - (io! - (when isolation - (.setTransactionIsolation con (isolation isolation-levels))) - (when read-only? - (.setReadOnly con true)) - (.setAutoCommit con false) - (try - (let [result (func nested-db)] - (if (db-is-rollback-only nested-db) - (.rollback con) - (.commit con)) - result) - (catch Throwable t - (try - (.rollback con) - (catch Throwable rb - ;; combine both exceptions - (throw (ex-info (str "Rollback failed handling \"" - (.getMessage t) - "\"") - {:rollback rb - :handling t})))) - (throw t)) - (finally - (db-unset-rollback-only! nested-db) - ;; the following can throw SQLExceptions but we do not - ;; want those to replace any exception currently being - ;; handled -- and if the connection got closed, we just - ;; want to ignore exceptions here anyway - (try - (.setAutoCommit con auto-commit) - (catch Exception _)) - (when isolation - (try - (.setTransactionIsolation con old-isolation) - (catch Exception _))) - (when read-only? - (try - (.setReadOnly con old-readonly) - (catch Exception _))))))) - (with-open [con (get-connection db opts)] - (db-transaction* (add-connection db con) func opts))) - (do - (when (and isolation - (let [con (db-find-connection db)] - (not= (isolation isolation-levels) - (.getTransactionIsolation con)))) - (let [msg "Nested transactions may not have different isolation levels"] - (throw (IllegalStateException. msg)))) - (func (inc-level db))))))) - -(defmacro with-db-transaction - "Evaluates body in the context of a transaction on the specified database connection. - The binding provides the database connection for the transaction and the name to which - that is bound for evaluation of the body. The binding may also specify the isolation - level for the transaction, via the :isolation option and/or set the transaction to - readonly via the :read-only? option. - (with-db-transaction [t-con db-spec {:isolation level :read-only? true}] - ... t-con ...) - See db-transaction* for more details." - [binding & body] - `(db-transaction* ~(second binding) - (^{:once true} fn* [~(first binding)] ~@body) - ~@(rest (rest binding)))) - -(defmacro with-db-connection - "Evaluates body in the context of an active connection to the database. - (with-db-connection [con-db db-spec opts] - ... con-db ...)" - [binding & body] - `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] - (with-open [con# (get-connection db-spec# opts#)] - (let [~(first binding) (add-connection db-spec# con#)] - ~@body)))) - -(defmacro with-db-metadata - "Evaluates body in the context of an active connection with metadata bound - to the specified name. See also metadata-result for dealing with the results - of operations that retrieve information from the metadata. - (with-db-metadata [md db-spec opts] - ... md ...)" - [binding & body] - `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] - (with-open [con# (get-connection db-spec# opts#)] - (let [~(first binding) (.getMetaData con#)] - ~@body)))) - -(defn metadata-result - "If the argument is a java.sql.ResultSet, turn it into a result-set-seq, - else return it as-is. This makes working with metadata easier. - Also accepts an option map containing :identifiers, :keywordize?, :qualifier, - :as-arrays?, :row-fn,and :result-set-fn to control how the ResultSet is - transformed and returned. See query for more details." - ([rs-or-value] (metadata-result rs-or-value {})) - ([rs-or-value opts] - (let [{:keys [as-arrays? result-set-fn row-fn] :as opts} - (merge {:identifiers str/lower-case - :keywordize? true - :read-columns dft-read-columns - :row-fn identity} opts) - result-set-fn (or result-set-fn (if as-arrays? vec doall))] - (if (instance? java.sql.ResultSet rs-or-value) - ((^{:once true} fn* [rs] - (result-set-fn (if as-arrays? - (cons (first rs) - (map row-fn (rest rs))) - (map row-fn rs)))) - (result-set-seq rs-or-value opts)) - rs-or-value)))) - -(defmacro metadata-query - "Given a Java expression that extracts metadata (in the context of with-db-metadata), - and a map of options like metadata-result, manage the connection for a single - metadata-based query. Example usage: - - (with-db-metadata [meta db-spec] - (metadata-query (.getTables meta nil nil nil (into-array String [\"TABLE\"])) - {:row-fn ... :result-set-fn ...}))" - [meta-query & opt-args] - `(with-open [rs# ~meta-query] - (metadata-result rs# ~@opt-args))) - -(defn db-do-commands - "Executes SQL commands on the specified database connection. Wraps the commands - in a transaction if transaction? is true. transaction? can be ommitted and it - defaults to true. Accepts a single SQL command (string) or a vector of them. - Uses executeBatch. This may affect what SQL you can run via db-do-commands." - ([db sql-commands] - (db-do-commands db true (if (string? sql-commands) [sql-commands] sql-commands))) - ([db transaction? sql-commands] - (if (string? sql-commands) - (db-do-commands db transaction? [sql-commands]) - (if-let [con (db-find-connection db)] - (with-open [^Statement stmt (.createStatement con)] - (doseq [^String cmd sql-commands] - (.addBatch stmt cmd)) - (if transaction? - (with-db-transaction [t-db (add-connection db (.getConnection stmt))] - (execute-batch stmt)) - (execute-batch stmt))) - (with-open [con (get-connection db)] - (db-do-commands (add-connection db con) transaction? sql-commands)))))) - -(defn- db-do-execute-prepared-return-keys - "Executes a PreparedStatement, optionally in a transaction, and (attempts to) - return any generated keys." - [db ^PreparedStatement stmt param-group opts] - (let [{:keys [transaction?] :as opts} (merge (when (map? db) db) opts) - exec-and-return-keys - (^{:once true} fn* [] - (let [counts (.executeUpdate stmt)] - (try - (let [rs (.getGeneratedKeys stmt) - result (first (result-set-seq rs opts))] - ;; sqlite (and maybe others?) requires - ;; record set to be closed - (.close rs) - result) - (catch Exception _ - ;; assume generated keys is unsupported and return counts instead: - counts))))] - ((:set-parameters opts dft-set-parameters) stmt param-group) - (if transaction? - (with-db-transaction [t-db (add-connection db (.getConnection stmt))] - (exec-and-return-keys)) - (exec-and-return-keys)))) - -(defn- sql-stmt? - "Given an expression, return true if it is either a string (SQL) or a - PreparedStatement." - [expr] - (or (string? expr) (instance? PreparedStatement expr))) - -(defn db-do-prepared-return-keys - "Executes an (optionally parameterized) SQL prepared statement on the - open database connection. The param-group is a seq of values for all of - the parameters. transaction? can be ommitted and will default to true. - Return the generated keys for the (single) update/insert. - A PreparedStatement may be passed in, instead of a SQL string, in which - case :return-keys MUST BE SET on that PreparedStatement!" - ([db sql-params] - (db-do-prepared-return-keys db true sql-params {})) - ([db transaction? sql-params] - (if (map? sql-params) - (db-do-prepared-return-keys db true transaction? sql-params) - (db-do-prepared-return-keys db transaction? sql-params {}))) - ([db transaction? sql-params opts] - (let [opts (merge (when (map? db) db) opts)] - (if-let [con (db-find-connection db)] - (let [[sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] - (if (instance? PreparedStatement sql) - (db-do-execute-prepared-return-keys db sql params (assoc opts :transaction? transaction?)) - (with-open [^PreparedStatement stmt (prepare-statement con sql (assoc opts :return-keys true))] - (db-do-execute-prepared-return-keys db stmt params (assoc opts :transaction? transaction?))))) - (with-open [con (get-connection db opts)] - (db-do-prepared-return-keys (add-connection db con) transaction? sql-params opts)))))) - -(defn- db-do-execute-prepared-statement - "Execute a PreparedStatement, optionally in a transaction." - [db ^PreparedStatement stmt param-groups opts] - (let [{:keys [transaction?] :as opts} (merge (when (map? db) db) opts)] - (if (empty? param-groups) - (if transaction? - (with-db-transaction [t-db (add-connection db (.getConnection stmt))] - (vector (.executeUpdate stmt))) - (vector (.executeUpdate stmt))) - (do - (doseq [param-group param-groups] - ((:set-parameters opts dft-set-parameters) stmt param-group) - (.addBatch stmt)) - (if transaction? - (with-db-transaction [t-db (add-connection db (.getConnection stmt))] - (execute-batch stmt)) - (execute-batch stmt)))))) - -(defn db-do-prepared - "Executes an (optionally parameterized) SQL prepared statement on the - open database connection. Each param-group is a seq of values for all of - the parameters. transaction? can be omitted and defaults to true. - The sql parameter can either be a SQL string or a PreparedStatement. - Return a seq of update counts (one count for each param-group)." - ([db sql-params] - (db-do-prepared db true sql-params {})) - ([db transaction? sql-params] - (if (map? sql-params) - (db-do-prepared db true transaction? sql-params) - (db-do-prepared db transaction? sql-params {}))) - ([db transaction? sql-params opts] - (let [opts (merge (when (map? db) db) opts)] - (if-let [con (db-find-connection db)] - (let [[sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params)) - params (if (or (:multi? opts) (empty? params)) params [params])] - (if (instance? PreparedStatement sql) - (db-do-execute-prepared-statement db sql params (assoc opts :transaction? transaction?)) - (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] - (db-do-execute-prepared-statement db stmt params (assoc opts :transaction? transaction?))))) - (with-open [con (get-connection db opts)] - (db-do-prepared (add-connection db con) transaction? sql-params opts)))))) - -(defn db-query-with-resultset - "Executes a query, then evaluates func passing in the raw ResultSet as an - argument. The second argument is a vector containing either: - [sql & params] - a SQL query, followed by any parameters it needs - [stmt & params] - a PreparedStatement, followed by any parameters it needs - (the PreparedStatement already contains the SQL query) - The opts map is passed to prepare-statement. - Uses executeQuery. This may affect what SQL you can run via query." - ([db sql-params func] (db-query-with-resultset db sql-params func {})) - ([db sql-params func opts] - (let [opts (merge (when (map? db) db) opts) - [sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params)) - run-query-with-params (^{:once true} fn* [^PreparedStatement stmt] - ((:set-parameters opts dft-set-parameters) stmt params) - (with-open [rset (.executeQuery stmt)] - (func rset)))] - (when-not (sql-stmt? sql) - (let [^Class sql-class (class sql) - ^String msg (format "\"%s\" expected %s %s, found %s %s" - "sql-params" - "vector" - "[sql param*]" - (.getName sql-class) - (pr-str sql))] - (throw (IllegalArgumentException. msg)))) - (if (instance? PreparedStatement sql) - (let [^PreparedStatement stmt sql] - (run-query-with-params stmt)) - (if-let [con (db-find-connection db)] - (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] - (run-query-with-params stmt)) - (with-open [con (get-connection db opts)] - (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] - (run-query-with-params stmt)))))))) - -;; top-level API for actual SQL operations - -(defn query - "Given a database connection and a vector containing SQL and optional parameters, - perform a simple database query. The options specify how to construct the result - set (and are also passed to prepare-statement as needed): - :as-arrays? - return the results as a set of arrays, default false. - :identifiers - applied to each column name in the result set, default lower-case - :keywordize? - defaults to true, can be false to opt-out of converting - identifiers to keywords - :qualifier - optionally provides the namespace qualifier for identifiers - :result-set-fn - applied to the entire result set, default doall / vec - if :as-arrays? true, :result-set-fn will default to vec - if :as-arrays? false, :result-set-fn will default to doall - :row-fn - applied to each row as the result set is constructed, default identity - The second argument is a vector containing a SQL string or PreparedStatement, followed - by any parameters it needs. - See also prepare-statement for additional options." - ([db sql-params] (query db sql-params {})) - ([db sql-params opts] - (let [{:keys [as-arrays? explain? explain-fn result-set-fn row-fn] :as opts} - (merge {:explain-fn println :identifiers str/lower-case - :keywordize? true - :read-columns dft-read-columns :row-fn identity} - (when (map? db) db) - opts) - result-set-fn (or result-set-fn (if as-arrays? vec doall)) - sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] - (when (and explain? (string? (first sql-params-vector))) - (query db (into [(str (if (string? explain?) explain? "EXPLAIN") - " " - (first sql-params-vector))] - (rest sql-params-vector)) - (-> opts - (dissoc :explain? :result-set-fn :row-fn) - (assoc :result-set-fn explain-fn)))) - (db-query-with-resultset db sql-params-vector - (^{:once true} fn* [rset] - ((^{:once true} fn* [rs] - (result-set-fn (if as-arrays? - (cons (first rs) - (map row-fn (rest rs))) - (map row-fn rs)))) - (result-set-seq rset opts))) - opts)))) - -(defn reducible-result-set - "Given a java.sql.ResultSet return a reducible collection. - Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce - Note: :as-arrays? is not accepted here." - [^ResultSet rs {:keys [identifiers keywordize? qualifier read-columns] - :or {identifiers str/lower-case - keywordize? true - read-columns dft-read-columns}}] - (let [identifier-fn (cond (and qualifier (not keywordize?)) - (throw (IllegalArgumentException. - (str ":qualifier is not allowed unless " - ":keywordize? is true"))) - (and qualifier keywordize?) - (comp (partial keyword qualifier) identifiers) - keywordize? - (comp keyword identifiers) - :else - identifiers) - make-keys (fn [idxs ^ResultSetMetaData rsmeta] - (->> idxs - (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) - (make-cols-unique) - (mapv identifier-fn))) - init-reduce (fn [keys ^ResultSet rs rsmeta idxs f init] - (loop [init' init] - (if (.next rs) - (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] - (if (reduced? result) - @result - (recur result))) - init')))] - (reify clojure.lang.IReduce - (reduce [this f] - (let [rsmeta (.getMetaData rs) - idxs (range 1 (inc (.getColumnCount rsmeta))) - keys (make-keys idxs rsmeta)] - (if (.next rs) - ;; reduce init is first row of ResultSet - (init-reduce keys rs rsmeta idxs f - (zipmap keys (read-columns rs rsmeta idxs))) - ;; no rows so call 0-arity f to get result value - ;; per reduce docstring contract - (f)))) - (reduce [this f init] - (let [rsmeta (.getMetaData rs) - idxs (range 1 (inc (.getColumnCount rsmeta))) - keys (make-keys idxs rsmeta)] - (init-reduce keys rs rsmeta idxs f init)))))) - -(defn reducible-query - "Given a database connection, a vector containing SQL and optional parameters, - return a reducible collection. When reduced, it will start the database query - and reduce the result set, and then close the connection: - (transduce (map :cost) + (reducible-query db sql-params)) - Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce - The following options from query etc are not accepted here: - :as-arrays? :explain :explain-fn :result-set-fn :row-fn - See also prepare-statement for additional options." - ([db sql-params] (reducible-query db sql-params {})) - ([db sql-params opts] - (let [{:keys [reducing-fn] :as opts} - (merge {:identifiers str/lower-case :keywordize? true - :read-columns dft-read-columns} - (when (map? db) db) - opts) - sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] - (reify clojure.lang.IReduce - (reduce [this f] - (db-query-with-resultset - db sql-params-vector - (^{:once true} fn* [rset] - (reduce f (reducible-result-set rset opts))) - opts)) - (reduce [this f init] - (db-query-with-resultset - db sql-params-vector - (^{:once true} fn* [rset] - (reduce f init (reducible-result-set rset opts))) - opts)))))) - -(defn- direction - "Given an entities function, a column name, and a direction, - return the matching SQL column / order. - Throw an exception for an invalid direction." - [entities c d] - (str (as-sql-name entities c) " " - (if-let [dir (#{"ASC" "DESC"} (str/upper-case (name d)))] - dir - (throw (IllegalArgumentException. (str "expected :asc or :desc, found: " d)))))) - -(defn- order-by-sql - "Given a sequence of column specs and an entities function, return - a SQL fragment for the ORDER BY clause. A column spec may be a name - (either a string or keyword) or a map from column name to direction - (:asc or :desc)." - [order-by entities] - (str/join ", " (mapcat (fn [col] - (if (map? col) - (reduce-kv (fn [v c d] - (conj v (direction entities c d))) - [] - col) - [(direction entities col :asc)])) order-by))) - -(defn find-by-keys - "Given a database connection, a table name, a map of column name/value - pairs, and an optional options map, return any matching rows. - An :order-by option may be supplied to sort the rows by a sequence of - columns, e.g,. {:order-by [:name {:age :desc]}" - ([db table columns] (find-by-keys db table columns {})) - ([db table columns opts] - (let [{:keys [entities order-by] :as opts} - (merge {:entities identity} (when (map? db) db) opts) - ks (keys columns) - vs (vals columns)] - (query db (into [(str "SELECT * FROM " (table-str table entities) - " WHERE " (str/join " AND " - (kv-sql ks vs entities " IS NULL")) - (when (seq order-by) - (str " ORDER BY " - (order-by-sql order-by entities))))] - (remove nil? vs)) - opts)))) - -(defn get-by-id - "Given a database connection, a table name, a primary key value, an - optional primary key column name, and an optional options map, return - a single matching row, or nil. - The primary key column name defaults to :id." - ([db table pk-value] (get-by-id db table pk-value :id {})) - ([db table pk-value pk-name-or-opts] - (if (map? pk-name-or-opts) - (get-by-id db table pk-value :id pk-name-or-opts) - (get-by-id db table pk-value pk-name-or-opts {}))) - ([db table pk-value pk-name opts] - (let [opts (merge (when (map? db) db) opts) - r-s-fn (or (:result-set-fn opts) identity)] - (find-by-keys db table {pk-name pk-value} - (assoc opts :result-set-fn (comp first r-s-fn)))))) - -(defn execute! - "Given a database connection and a vector containing SQL (or PreparedStatement) - followed by optional parameters, perform a general (non-select) SQL operation. - The :transaction? option specifies whether to run the operation in a - transaction or not (default true). - If the :multi? option is false (the default), the SQL statement should be - followed by the parameters for that statement. - If the :multi? option is true, the SQL statement should be followed by one or - more vectors of parameters, one for each application of the SQL statement. - If there are no parameters specified, executeUpdate will be used, otherwise - executeBatch will be used. This may affect what SQL you can run via execute!" - ([db sql-params] (execute! db sql-params {})) - ([db sql-params opts] - (let [{:keys [transaction?] :as opts} - (merge {:transaction? true :multi? false} (when (map? db) db) opts) - execute-helper (^{:once true} fn* [db] - (db-do-prepared db transaction? sql-params opts))] - (if-let [con (db-find-connection db)] - (execute-helper db) - (with-open [con (get-connection db opts)] - (execute-helper (add-connection db con))))))) - -(defn- delete-sql - "Given a table name, a where class and its parameters and an optional entities spec, - return a vector of the SQL for that delete operation followed by its parameters. The - entities spec (default 'as-is') specifies how to transform column names." - [table [where & params] entities] - (into [(str "DELETE FROM " (table-str table entities) - (when where " WHERE ") where)] - params)) - -(defn delete! - "Given a database connection, a table name and a where clause of columns to match, - perform a delete. The options may specify how to transform column names in the - map (default 'as-is') and whether to run the delete in a transaction (default true). - Example: - (delete! db :person [\"zip = ?\" 94546]) - is equivalent to: - (execute! db [\"DELETE FROM person WHERE zip = ?\" 94546])" - ([db table where-clause] (delete! db table where-clause {})) - ([db table where-clause opts] - (let [{:keys [entities] :as opts} - (merge {:entities identity :transaction? true} (when (map? db) db) opts)] - (execute! db (delete-sql table where-clause entities) opts)))) - -(defn- multi-insert-helper - "Given a (connected) database connection and some SQL statements (for multiple - inserts), run a prepared statement on each and return any generated keys. - Note: we are eager so an unrealized lazy-seq cannot escape from the connection." - [db stmts opts] - (doall (map (fn [row] (db-do-prepared-return-keys db false row opts)) stmts))) - -(defn- insert-helper - "Given a (connected) database connection, a transaction flag and some SQL statements - (for one or more inserts), run a prepared statement or a sequence of them." - [db transaction? stmts opts] - (if transaction? - (with-db-transaction [t-db db] (multi-insert-helper t-db stmts opts)) - (multi-insert-helper db stmts opts))) - -(defn- col-str - "Transform a column spec to an entity name for SQL. The column spec may be a - string, a keyword or a map with a single pair - column name and alias." - [col entities] - (if (map? col) - (let [[k v] (first col)] - (str (as-sql-name entities k) " AS " (as-sql-name entities v))) - (as-sql-name entities col))) - -(defn- insert-multi-row-sql - "Given a table and a list of columns, followed by a list of column value sequences, - return a vector of the SQL needed for the insert followed by the list of column - value sequences. The entities function specifies how column names are transformed." - [table columns values entities] - (let [nc (count columns) - vcs (map count values)] - (if (not (and (or (zero? nc) (= nc (first vcs))) (apply = vcs))) - (throw (IllegalArgumentException. "insert! called with inconsistent number of columns / values")) - (into [(str "INSERT INTO " (table-str table entities) - (when (seq columns) - (str " ( " - (str/join ", " (map (fn [col] (col-str col entities)) columns)) - " )")) - " VALUES ( " - (str/join ", " (repeat (first vcs) "?")) - " )")] - values)))) - -(defn- insert-single-row-sql - "Given a table and a map representing a row, return a vector of the SQL needed for - the insert followed by the list of column values. The entities function specifies - how column names are transformed." - [table row entities] - (let [ks (keys row)] - (into [(str "INSERT INTO " (table-str table entities) " ( " - (str/join ", " (map (fn [col] (col-str col entities)) ks)) - " ) VALUES ( " - (str/join ", " (repeat (count ks) "?")) - " )")] - (vals row)))) - -(defn- insert-rows! - "Given a database connection, a table name, a sequence of rows, and an options - map, insert the rows into the database." - [db table rows opts] - (let [{:keys [entities transaction?] :as opts} - (merge {:entities identity :identifiers str/lower-case - :keywordize? true :transaction? true} - (when (map? db) db) - opts) - sql-params (map (fn [row] - (when-not (map? row) - (throw (IllegalArgumentException. "insert! / insert-multi! called with a non-map row"))) - (insert-single-row-sql table row entities)) rows)] - (if-let [con (db-find-connection db)] - (insert-helper db transaction? sql-params opts) - (with-open [con (get-connection db opts)] - (insert-helper (add-connection db con) transaction? sql-params opts))))) - -(defn- insert-cols! - "Given a database connection, a table name, a sequence of columns names, a - sequence of vectors of column values, one per row, and an options map, - insert the rows into the database." - [db table cols values opts] - (let [{:keys [entities transaction?] :as opts} - (merge {:entities identity :transaction? true} (when (map? db) db) opts) - sql-params (insert-multi-row-sql table cols values entities)] - (if-let [con (db-find-connection db)] - (db-do-prepared db transaction? sql-params (assoc opts :multi? true)) - (with-open [con (get-connection db opts)] - (db-do-prepared (add-connection db con) transaction? sql-params - (assoc opts :multi? true)))))) - -(defn insert! - "Given a database connection, a table name and either a map representing a rows, - or a list of column names followed by a list of column values also representing - a single row, perform an insert. - When inserting a row as a map, the result is the database-specific form of the - generated keys, if available (note: PostgreSQL returns the whole row). - When inserting a row as a list of column values, the result is the count of - rows affected (1), if available (from getUpdateCount after executeBatch). - The row map or column value vector may be followed by a map of options: - The :transaction? option specifies whether to run in a transaction or not. - The default is true (use a transaction). The :entities option specifies how - to convert the table name and column names to SQL entities." - ([db table row] (insert! db table row {})) - ([db table cols-or-row values-or-opts] - (if (map? values-or-opts) - (insert-rows! db table [cols-or-row] values-or-opts) - (insert-cols! db table cols-or-row [values-or-opts] {}))) - ([db table cols values opts] - (insert-cols! db table cols [values] opts))) - -(defn insert-multi! - "Given a database connection, a table name and either a sequence of maps (for - rows) or a sequence of column names, followed by a sequence of vectors (for - the values in each row), and possibly a map of options, insert that data into - the database. - - When inserting rows as a sequence of maps, the result is a sequence of the - generated keys, if available (note: PostgreSQL returns the whole rows). A - separate database operation is used for each row inserted. This may be slow - for if a large sequence of maps is provided. - - When inserting rows as a sequence of lists of column values, the result is - a sequence of the counts of rows affected (a sequence of 1's), if available. - Yes, that is singularly unhelpful. Thank you getUpdateCount and executeBatch! - A single database operation is used to insert all the rows at once. This may - be much faster than inserting a sequence of rows (which performs an insert for - each map in the sequence). - - The :transaction? option specifies whether to run in a transaction or not. - The default is true (use a transaction). The :entities option specifies how - to convert the table name and column names to SQL entities." - ([db table rows] (insert-rows! db table rows {})) - ([db table cols-or-rows values-or-opts] - (if (map? values-or-opts) - (insert-rows! db table cols-or-rows values-or-opts) - (insert-cols! db table cols-or-rows values-or-opts {}))) - ([db table cols values opts] - (insert-cols! db table cols values opts))) - -(defn- update-sql - "Given a table name, a map of columns to set, a optional map of columns to - match, and an entities, return a vector of the SQL for that update followed - by its parameters. Example: - (update :person {:zip 94540} [\"zip = ?\" 94546] identity) - returns: - [\"UPDATE person SET zip = ? WHERE zip = ?\" 94540 94546]" - [table set-map [where & params] entities] - (let [ks (keys set-map) - vs (vals set-map)] - (cons (str "UPDATE " (table-str table entities) - " SET " (str/join - "," - (kv-sql ks vs entities " = NULL")) - (when where " WHERE ") - where) - (concat (remove nil? vs) params)))) - -(defn update! - "Given a database connection, a table name, a map of column values to set and a - where clause of columns to match, perform an update. The options may specify - how column names (in the set / match maps) should be transformed (default - 'as-is') and whether to run the update in a transaction (default true). - Example: - (update! db :person {:zip 94540} [\"zip = ?\" 94546]) - is equivalent to: - (execute! db [\"UPDATE person SET zip = ? WHERE zip = ?\" 94540 94546])" - ([db table set-map where-clause] (update! db table set-map where-clause {})) - ([db table set-map where-clause opts] - (let [{:keys [entities] :as opts} - (merge {:entities identity :transaction? true} (when (map? db) db) opts)] - (execute! db (update-sql table set-map where-clause entities) opts)))) - -(defn create-table-ddl - "Given a table name and a vector of column specs, return the DDL string for - creating that table. Each column spec is, in turn, a vector of keywords or - strings that is converted to strings and concatenated with spaces to form - a single column description in DDL, e.g., - [:cost :int \"not null\"] - [:name \"varchar(32)\"] - The first element of a column spec is treated as a SQL entity (so if you - provide the :entities option, that will be used to transform it). The - remaining elements are left as-is when converting them to strings. - An options map may be provided that can contain: - :table-spec -- a string that is appended to the DDL -- and/or - :entities -- a function to specify how column names are transformed. - :conditional? -- either a boolean, indicating whether to add 'IF NOT EXISTS', - or a string, which is inserted literally before the table name, or a - function of two arguments (table name and the create statement), that can - manipulate the generated statement to better support other databases, e.g., - MS SQL Server which need to wrap create table in an existence query." - ([table specs] (create-table-ddl table specs {})) - ([table specs opts] - (let [table-spec (:table-spec opts) - conditional? (:conditional? opts) - entities (:entities opts identity) - table-name (as-sql-name entities table) - table-spec-str (or (and table-spec (str " " table-spec)) "") - spec-to-string (fn [spec] - (try - (str/join " " (cons (as-sql-name entities (first spec)) - (map name (rest spec)))) - (catch Exception _ - (throw (IllegalArgumentException. - "column spec is not a sequence of keywords / strings")))))] - (cond->> (format "CREATE TABLE%s %s (%s)%s" - (cond (or (nil? conditional?) - (instance? Boolean conditional?)) - (if conditional? " IF NOT EXISTS" "") - (fn? conditional?) - "" - :else - (str " " conditional?)) - table-name - (str/join ", " (map spec-to-string specs)) - table-spec-str) - (fn? conditional?) (conditional? table-name))))) - -(defn drop-table-ddl - "Given a table name, return the DDL string for dropping that table. - An options map may be provided that can contain: - :entities -- a function to specify how column names are transformed. - :conditional? -- either a boolean, indicating whether to add 'IF EXISTS', - or a string, which is inserted literally before the table name, or a - function of two arguments (table name and the create statement), that can - manipulate the generated statement to better support other databases, e.g., - MS SQL Server which need to wrap create table in an existence query." - ([table] (drop-table-ddl table {})) - ([table {:keys [entities conditional?] :or {entities identity}}] - (let [table-name (as-sql-name entities table)] - (cond->> (format "DROP TABLE%s %s" - (cond (or (nil? conditional?) - (instance? Boolean conditional?)) - (if conditional? " IF EXISTS" "") - (fn? conditional?) - "" - :else - (str " " conditional?)) - table-name) - (fn? conditional?) (conditional? table-name))))) +;; Copyright (c) 2008-2017 Sean Corfield, 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. +;; +;; jdbc.clj +;; +;; A Clojure interface to sql databases via jdbc +;; +;; scgilardi (gmail) +;; Created 2 April 2008 +;; +;; seancorfield (gmail) +;; Migrated from clojure.contrib.sql 17 April 2011 + +(ns + ^{:author "Stephen C. Gilardi, Sean Corfield", + :doc "A Clojure interface to SQL databases via JDBC + +clojure.java.jdbc provides a simple abstraction for CRUD (create, read, +update, delete) operations on a SQL database, along with basic transaction +support. Basic DDL operations are also supported (create table, drop table, +access to table metadata). + +Maps are used to represent records, making it easy to store and retrieve +data. Results can be processed using any standard sequence operations. + +For most operations, Java's PreparedStatement is used so your SQL and +parameters can be represented as simple vectors where the first element +is the SQL string, with ? for each parameter, and the remaining elements +are the parameter values to be substituted. In general, operations return +the number of rows affected, except for a single record insert where any +generated keys are returned (as a map). + +For more documentation, see: + +http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} + clojure.java.jdbc + (:require [clojure.set :as set] + [clojure.string :as str] + [clojure.walk :as walk]) + (:import (java.net URI) + (java.sql BatchUpdateException DriverManager + PreparedStatement ResultSet ResultSetMetaData + SQLException Statement Types) + (java.util Hashtable Map Properties) + (javax.sql DataSource))) + +(defn as-sql-name + "Given a naming strategy function and a keyword or string, return + a string per that naming strategy. + A name of the form x.y is treated as multiple names, x, y, etc, + and each are turned into strings via the naming strategy and then + joined back together so x.y might become `x`.`y` if the naming + strategy quotes identifiers with `." + [f x] + (let [n (name x) + i (.indexOf n (int \.))] + (if (= -1 i) + (f n) + (str/join "." (map f (.split n "\\.")))))) + +(defn quoted + "Given a (vector) pair of delimiters (characters or strings), return a naming + strategy function that will quote SQL entities with them. + Given a single delimiter, treat it as a (vector) pair of that delimiter. + ((quoted [\\[ \\]]) \"foo\") will return \"[foo]\" -- for MS SQL Server + ((quoted \\`') \"foo\") will return \"`foo`\" -- for MySQL + Intended to be used with :entities to provide a quoting (naming) strategy that + is appropriate for your database." + [q] + (cond (vector? q) + (fn [x] + (str (first q) x (last q))) + (keyword? q) + (case q + :ansi (quoted \") + :mysql (quoted \`) + :oracle (quoted \") + :sqlserver (quoted [\[ \]])) + :else + (quoted [q q]))) + +(defn- table-str + "Transform a table spec to an entity name for SQL. The table spec may be a + string, a keyword or a map with a single pair - table name and alias." + [table entities] + (let [entities (or entities identity)] + (if (map? table) + (let [[k v] (first table)] + (str (as-sql-name entities k) " " (as-sql-name entities v))) + (as-sql-name entities table)))) + +(defn- kv-sql + "Given a sequence of column name keys and a matching sequence of column + values, and an entities mapping function, return a sequence of SQL fragments + that can be joined for part of an UPDATE SET or a SELECT WHERE clause. + Note that we pass the appropriate operator for NULL since it is different + in each case." + [ks vs entities null-op] + (map (fn [k v] + (str (as-sql-name entities k) + (if (nil? v) null-op " = ?"))) + ks vs)) + +(defn- ^Properties as-properties + "Convert any seq of pairs to a java.utils.Properties instance. + Uses as-sql-name to convert both keys and values into strings." + [m] + (let [p (Properties.)] + (doseq [[k v] m] + (.setProperty p (as-sql-name identity k) + (if (instance? clojure.lang.Named v) + (as-sql-name identity v) + (str v)))) + p)) + +;; convenience for working with different forms of connections +(defprotocol Connectable + (add-connection [db connection]) + (get-level [db])) + +(defn- inc-level + "Increment the nesting level for a transacted database connection. + If we are at the top level, also add in a rollback state." + [db] + (let [nested-db (update-in db [:level] (fnil inc 0))] + (if (= 1 (:level nested-db)) + (assoc nested-db :rollback (atom false)) + nested-db))) + +(extend-protocol Connectable + String + (add-connection [s connection] {:connection connection :level 0 :connection-string s}) + (get-level [_] 0) + + clojure.lang.Associative + (add-connection [m connection] (assoc m :connection connection)) + (get-level [m] (or (:level m) 0)) + + nil + (add-connection [_ connection] {:connection connection :level 0 :legacy true}) + (get-level [_] 0)) + +(def ^:private classnames + "Map of subprotocols to classnames. dbtype specifies one of these keys. + The subprotocols map below provides aliases for dbtype." + {"derby" "org.apache.derby.jdbc.EmbeddedDriver" + "h2" "org.h2.Driver" + "hsqldb" "org.hsqldb.jdbcDriver" + "jtds:sqlserver" "net.sourceforge.jtds.jdbc.Driver" + "mysql" "com.mysql.jdbc.Driver" + "oracle:oci" "oracle.jdbc.OracleDriver" + "oracle:thin" "oracle.jdbc.OracleDriver" + "postgresql" "org.postgresql.Driver" + "pgsql" "com.impossibl.postgres.jdbc.PGDriver" + "redshift" "com.amazon.redshift.jdbc.Driver" + "sqlite" "org.sqlite.JDBC" + "sqlserver" "com.microsoft.sqlserver.jdbc.SQLServerDriver"}) + +(def ^:private subprotocols + "Map of schemes to subprotocols. Used to provide aliases for dbtype." + {"hsql" "hsqldb" + "jtds" "jtds:sqlserver" + "mssql" "sqlserver" + "oracle" "oracle:thin" + "postgres" "postgresql"}) + +(def ^:private host-prefixes + "Map of subprotocols to non-standard host-prefixes. + Anything not listed is assumed to use //." + {"oracle:oci" "@" + "oracle:thin" "@"}) + +(defn- parse-properties-uri [^URI uri] + (let [host (.getHost uri) + port (if (pos? (.getPort uri)) (.getPort uri)) + path (.getPath uri) + scheme (.getScheme uri) + subprotocol (subprotocols scheme scheme) + host-prefix (host-prefixes subprotocol "//") + ^String query (.getQuery uri) + query-parts (and query + (for [^String kvs (.split query "&")] + ((juxt first second) (.split kvs "="))))] + (merge + {:subname (if host + (if port + (str host-prefix host ":" port path) + (str host-prefix host path)) + (.getSchemeSpecificPart uri)) + :subprotocol subprotocol} + (if-let [user-info (.getUserInfo uri)] + {:user (first (str/split user-info #":")) + :password (second (str/split user-info #":"))}) + (walk/keywordize-keys (into {} query-parts))))) + +(defn- strip-jdbc [^String spec] + (if (.startsWith spec "jdbc:") + (.substring spec 5) + spec)) + +;; feature testing macro, based on suggestion from Chas Emerick: +(defmacro when-available + [sym & body] + (try + (when (resolve sym) + (list* 'do body)) + (catch ClassNotFoundException _#))) + +(defn- modify-connection + "Given a database connection and a map of options, update the connection + as specified by the options." + ^java.sql.Connection + [^java.sql.Connection connection opts] + (when (contains? opts :auto-commit?) + (.setAutoCommit connection (:auto-commit? opts))) + (when (contains? opts :read-only?) + (.setReadOnly connection (:read-only? opts))) + connection) + +(defn get-connection + "Creates a connection to a database. db-spec is usually a map containing connection + parameters but can also be a URI or a String. The various possibilities are described + below: + + DriverManager (preferred): + :dbtype (required) a String, the type of the database (the jdbc subprotocol) + :dbname (required) a String, the name of the database + :classname (optional) a String, the jdbc driver class name + :host (optional) a String, the host name/IP of the database + (defaults to 127.0.0.1) + :port (optional) a Long, the port of the database + (defaults to 3306 for mysql, 1433 for mssql/jtds, else nil) + (others) (optional) passed to the driver as properties + (may include :user and :password) + + Raw: + :connection-uri (required) a String + Passed directly to DriverManager/getConnection + (both :user and :password may be specified as well, rather + than passing them as part of the connection string) + + Other formats accepted: + + Existing Connection: + :connection (required) an existing open connection that can be used + but cannot be closed (only the parent connection can be closed) + + DriverManager (alternative / legacy style): + :subprotocol (required) a String, the jdbc subprotocol + :subname (required) a String, the jdbc subname + :classname (optional) a String, the jdbc driver class name + (others) (optional) passed to the driver as properties + (may include :user and :password) + + Factory: + :factory (required) a function of one argument, a map of params + (others) (optional) passed to the factory function in a map + + DataSource: + :datasource (required) a javax.sql.DataSource + :username (optional) a String - deprecated, use :user instead + :user (optional) a String - preferred + :password (optional) a String, required if :user is supplied + + JNDI: + :name (required) a String or javax.naming.Name + :environment (optional) a java.util.Map + + java.net.URI: + Parsed JDBC connection string (see java.lang.String format next) + + java.lang.String: + subprotocol://user:password@host:post/subname + An optional prefix of jdbc: is allowed." + (^java.sql.Connection [db-spec] (get-connection db-spec {})) + (^java.sql.Connection + [{:keys [connection + factory + connection-uri + classname subprotocol subname + dbtype dbname host port + datasource username password user + name environment] + :as db-spec} + opts] + (cond + (string? db-spec) + (get-connection (URI. (strip-jdbc db-spec)) opts) + + (instance? URI db-spec) + (get-connection (parse-properties-uri db-spec) opts) + + connection + connection ;; do not apply opts here + + (or (and datasource username password) ; legacy + (and datasource user password)) ; preferred + (-> (.getConnection ^DataSource datasource + ^String (or username user) + ^String password) + (modify-connection opts)) + + datasource + (-> (.getConnection ^DataSource datasource) + (modify-connection opts)) + + factory + (-> (factory (dissoc db-spec :factory)) + (modify-connection opts)) + + connection-uri + (-> (if (and user password) + (DriverManager/getConnection connection-uri user password) + (DriverManager/getConnection connection-uri)) + (modify-connection opts)) + + (and dbtype dbname) + (let [;; allow aliases for dbtype + subprotocol (subprotocols dbtype dbtype) + host (or host "127.0.0.1") + port (or port (condp = subprotocol + "jtds:sqlserver" 1433 + "mysql" 3306 + "oracle:oci" 1521 + "oracle:thin" 1521 + "postgresql" 5432 + "sqlserver" 1433 + nil)) + db-sep (if (= "sqlserver" subprotocol) ";DATABASENAME=" "/") + url (if (#{"derby" "h2" "hsqldb" "sqlite"} subprotocol) + (str "jdbc:" subprotocol ":" dbname) + (str "jdbc:" subprotocol ":" + (host-prefixes subprotocol "//") + host + (when port (str ":" port)) + db-sep dbname)) + etc (dissoc db-spec :dbtype :dbname)] + (if-let [class-name (or classname (classnames subprotocol))] + (do + ;; force DriverManager to be loaded + (DriverManager/getLoginTimeout) + (clojure.lang.RT/loadClassForName class-name)) + (throw (ex-info (str "Unknown dbtype: " dbtype) db-spec))) + (-> (DriverManager/getConnection url (as-properties etc)) + (modify-connection opts))) + + (and subprotocol subname) + (let [;; allow aliases for subprotocols + subprotocol (subprotocols subprotocol subprotocol) + url (format "jdbc:%s:%s" subprotocol subname) + etc (dissoc db-spec :classname :subprotocol :subname)] + (if-let [class-name (or classname (classnames subprotocol))] + (do + ;; force DriverManager to be loaded + (DriverManager/getLoginTimeout) + (clojure.lang.RT/loadClassForName class-name)) + (throw (ex-info (str "Unknown subprotocol: " subprotocol) db-spec))) + (-> (DriverManager/getConnection url (as-properties etc)) + (modify-connection opts))) + + name + (or (when-available javax.naming.InitialContext + (let [env (and environment (Hashtable. ^Map environment)) + context (javax.naming.InitialContext. env) + ^DataSource datasource (.lookup context ^String name)] + (-> (.getConnection datasource) + (modify-connection opts)))) + (throw (ex-info (str "javax.naming.InitialContext is not available for: " + name) + db-spec))) + + :else + (let [^String msg (format "db-spec %s is missing a required parameter" db-spec)] + (throw (IllegalArgumentException. msg)))))) + +(defn- make-name-unique + "Given a collection of column names and a new column name, + return the new column name made unique, if necessary, by + appending _N where N is some unique integer suffix." + [cols col-name n] + (let [suffixed-name (if (= n 1) col-name (str col-name "_" n))] + (if (apply distinct? suffixed-name cols) + suffixed-name + (recur cols col-name (inc n))))) + +(defn- make-cols-unique + "Given a collection of column names, rename duplicates so + that the result is a collection of unique column names." + [cols] + (if (or (empty? cols) (apply distinct? cols)) + cols + (reduce (fn [unique-cols col-name] + (conj unique-cols (make-name-unique unique-cols col-name 1))) [] cols))) + +(defprotocol ISQLValue + "Protocol for creating SQL values from Clojure values. Default + implementations (for Object and nil) just return the argument, + but it can be extended to provide custom behavior to support + exotic types supported by different databases." + (sql-value [val] "Convert a Clojure value into a SQL value.")) + +(extend-protocol ISQLValue + Object + (sql-value [v] v) + + nil + (sql-value [_] nil)) + +(defprotocol ISQLParameter + "Protocol for setting SQL parameters in statement objects, which + can convert from Clojure values. The default implementation just + delegates the conversion to ISQLValue's sql-value conversion and + uses .setObject on the parameter. 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 (sql-value v))) + + nil + (set-parameter [_ ^PreparedStatement s ^long i] + (.setObject s i (sql-value nil)))) + +(defn- dft-set-parameters + "Default implementation of parameter setting for the given statement." + [stmt params] + (dorun (map-indexed (fn [ix value] + (set-parameter value stmt (inc ix))) + params))) + +(defprotocol IResultSetReadColumn + "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." + (result-set-read-column [val rsmeta idx] + "Function for transforming values after reading them from the database")) + +(extend-protocol IResultSetReadColumn + Object + (result-set-read-column [x _2 _3] x) + + Boolean + (result-set-read-column [x _2 _3] (if (= true x) true false)) + + nil + (result-set-read-column [_1 _2 _3] nil)) + +(defn- dft-read-columns + "Default implementation of reading row values from result set, given the + result set metadata and the indices." + [^ResultSet rs rsmeta idxs] + (mapv (fn [^Integer i] (result-set-read-column (.getObject rs i) rsmeta i)) idxs)) + +(defn result-set-seq + "Creates and returns a lazy sequence of maps corresponding to the rows in the + java.sql.ResultSet rs. Loosely based on clojure.core/resultset-seq but it + respects the specified naming strategy. Duplicate column names are made unique + by appending _N before applying the naming strategy (where N is a unique integer), + unless the :as-arrays? option is :cols-as-is, in which case the column names + are untouched (the result set maintains column name/value order). + The :identifiers option specifies how SQL column names are converted to Clojure + keywords. The default is to convert them to lower case. + The :keywordize? option can be specified as false to opt-out of the conversion + to keywords. + The :qualifier option specifies the namespace qualifier for those identifiers + (and this may not be specified when :keywordize? is false)." + ([rs] (result-set-seq rs {})) + ([^ResultSet rs {:keys [as-arrays? identifiers keywordize? + qualifier read-columns] + :or {identifiers str/lower-case + keywordize? true + read-columns dft-read-columns}}] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + col-name-fn (if (= :cols-as-is as-arrays?) identity make-cols-unique) + identifier-fn (cond (and qualifier (not keywordize?)) + (throw (IllegalArgumentException. + (str ":qualifier is not allowed unless " + ":keywordize? is true"))) + (and qualifier keywordize?) + (comp (partial keyword qualifier) identifiers) + keywordize? + (comp keyword identifiers) + :else + identifiers) + keys (->> idxs + (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) + col-name-fn + (mapv identifier-fn)) + row-values (fn [] (read-columns rs rsmeta 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 + ;; want to switch to records so we're using regular maps instead. We no longer + ;; guarantee column order in rows but using into {} should preserve order for up + ;; to 16 columns (because it will use a PersistentArrayMap). If someone is relying + ;; on the order-preserving behavior of structmaps, we can reconsider... + records (fn thisfn [] + (when (.next rs) + (cons (zipmap keys (row-values)) (lazy-seq (thisfn))))) + rows (fn thisfn [] + (when (.next rs) + (cons (vec (row-values)) (lazy-seq (thisfn)))))] + (if as-arrays? + (cons (vec keys) (rows)) + (records))))) + +(defn- execute-batch + "Executes a batch of SQL commands and returns a sequence of update counts. + (-2) indicates a single operation operating on an unknown number of rows. + Specifically, Oracle returns that and we must call getUpdateCount() to get + the actual number of rows affected. In general, operations return an array + of update counts, so this may not be a general solution for Oracle..." + [^Statement stmt] + (let [result (.executeBatch stmt)] + (if (and (= 1 (count result)) (= -2 (first result))) + (list (.getUpdateCount stmt)) + (seq result)))) + +(def ^{:private true + :doc "Map friendly :concurrency values to ResultSet constants."} + result-set-concurrency + {:read-only ResultSet/CONCUR_READ_ONLY + :updatable ResultSet/CONCUR_UPDATABLE}) + +(def ^{:private true + :doc "Map friendly :cursors values to ResultSet constants."} + result-set-holdability + {:hold ResultSet/HOLD_CURSORS_OVER_COMMIT + :close ResultSet/CLOSE_CURSORS_AT_COMMIT}) + +(def ^{:private true + :doc "Map friendly :type values to ResultSet constants."} + result-set-type + {:forward-only ResultSet/TYPE_FORWARD_ONLY + :scroll-insensitive ResultSet/TYPE_SCROLL_INSENSITIVE + :scroll-sensitive ResultSet/TYPE_SCROLL_SENSITIVE}) + +(defn ^{:tag (class (into-array String []))} string-array + [return-keys] + (into-array String return-keys)) + +(defn prepare-statement + "Create a prepared statement from a connection, a SQL string and a map + of options: + :return-keys truthy | nil - default nil + for some drivers, this may be a vector of column names to identify + the generated keys to return, otherwise it should just be true + :result-type :forward-only | :scroll-insensitive | :scroll-sensitive + :concurrency :read-only | :updatable + :cursors :hold | :close + :fetch-size n + :max-rows n + :timeout n + Note that :result-type and :concurrency must be specified together as the + underlying Java API expects both (or neither)." + ([con sql] (prepare-statement con sql {})) + ([^java.sql.Connection con ^String sql + {:keys [return-keys result-type concurrency cursors + fetch-size max-rows timeout]}] + (let [^PreparedStatement + stmt (cond return-keys + (try + (when (or result-type concurrency cursors) + (throw (IllegalArgumentException. + (str ":concurrency, :cursors, and :result-type " + "may not be specified with :return-keys.")))) + (if (vector? return-keys) + (try + (.prepareStatement con sql (string-array return-keys)) + (catch Exception _ + ;; assume it is unsupported and try regular generated keys: + (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS))) + (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS)) + (catch Exception _ + ;; assume it is unsupported and try basic PreparedStatement: + (.prepareStatement con sql))) + + (and result-type concurrency) + (if cursors + (.prepareStatement con sql + (get result-set-type result-type result-type) + (get result-set-concurrency concurrency concurrency) + (get result-set-holdability cursors cursors)) + (.prepareStatement con sql + (get result-set-type result-type result-type) + (get result-set-concurrency concurrency concurrency))) + + (or result-type concurrency cursors) + (throw (IllegalArgumentException. + (str ":concurrency, :cursors, and :result-type " + "may not be specified independently."))) + :else + (.prepareStatement con sql))] + (when fetch-size (.setFetchSize stmt fetch-size)) + (when max-rows (.setMaxRows stmt max-rows)) + (when timeout (.setQueryTimeout stmt timeout)) + stmt))) + +(defn print-sql-exception + "Prints the contents of an SQLException to *out*" + [^SQLException exception] + (let [^Class exception-class (class exception)] + (println + (format (str "%s:" \newline + " Message: %s" \newline + " SQLState: %s" \newline + " Error Code: %d") + (.getSimpleName exception-class) + (.getMessage exception) + (.getSQLState exception) + (.getErrorCode exception))))) + +(defn print-sql-exception-chain + "Prints a chain of SQLExceptions to *out*" + [^SQLException exception] + (loop [e exception] + (when e + (print-sql-exception e) + (recur (.getNextException e))))) + +(def ^{:private true} special-counts + {Statement/EXECUTE_FAILED "EXECUTE_FAILED" + Statement/SUCCESS_NO_INFO "SUCCESS_NO_INFO"}) + +(defn print-update-counts + "Prints the update counts from a BatchUpdateException to *out*" + [^BatchUpdateException exception] + (println "Update counts:") + (dorun + (map-indexed + (fn [index count] + (println (format " Statement %d: %s" + index + (get special-counts count count)))) + (.getUpdateCounts exception)))) + +;; java.jdbc pieces rewritten to not use dynamic bindings + +(defn db-find-connection + "Returns the current database connection (or nil if there is none)" + ^java.sql.Connection [db] + (and (map? db) + (:connection db))) + +(defn db-connection + "Returns the current database connection (or throws if there is none)" + ^java.sql.Connection [db] + (or (db-find-connection db) + (throw (Exception. "no current database connection")))) + +(defn db-set-rollback-only! + "Marks the outermost transaction such that it will rollback rather than + commit when complete" + [db] + (reset! (:rollback db) true)) + +(defn db-unset-rollback-only! + "Marks the outermost transaction such that it will not rollback when complete" + [db] + (reset! (:rollback db) false)) + +(defn db-is-rollback-only + "Returns true if the outermost transaction will rollback rather than + commit when complete" + [db] + (deref (:rollback db))) + +(def ^:private + isolation-levels + "Transaction isolation levels." + {:none java.sql.Connection/TRANSACTION_NONE + :read-committed java.sql.Connection/TRANSACTION_READ_COMMITTED + :read-uncommitted java.sql.Connection/TRANSACTION_READ_UNCOMMITTED + :repeatable-read java.sql.Connection/TRANSACTION_REPEATABLE_READ + :serializable java.sql.Connection/TRANSACTION_SERIALIZABLE}) + +(def ^:private isolation-kws + "Map transaction isolation constants to our keywords." + (set/map-invert isolation-levels)) + +(defn get-isolation-level + "Given a db-spec (with an optional connection), return the current + transaction isolation level, if known. Return nil if there is no + active connection in the db-spec. Return :unknown if we do not + recognize the isolation level." + [db] + (when-let [con (db-find-connection db)] + (isolation-kws (.getTransactionIsolation con) :unknown))) + +(defn db-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. + The isolation option may be :none, :read-committed, :read-uncommitted, + :repeatable-read, or :serializable. Note that not all databases support + all of those isolation levels, and may either throw an exception or + substitute another isolation level. + The read-only? option puts the transaction in readonly mode (if supported)." + ([db func] (db-transaction* db func {})) + ([db func opts] + (let [{:keys [isolation read-only?] :as opts} + (merge (when (map? db) db) opts)] + (if (zero? (get-level db)) + (if-let [con (db-find-connection db)] + (let [nested-db (inc-level db) + auto-commit (.getAutoCommit con) + old-isolation (.getTransactionIsolation con) + old-readonly (.isReadOnly con)] + (io! + (when isolation + (.setTransactionIsolation con (isolation isolation-levels))) + (when read-only? + (.setReadOnly con true)) + (.setAutoCommit con false) + (try + (let [result (func nested-db)] + (if (db-is-rollback-only nested-db) + (.rollback con) + (.commit con)) + result) + (catch Throwable t + (try + (.rollback con) + (catch Throwable rb + ;; combine both exceptions + (throw (ex-info (str "Rollback failed handling \"" + (.getMessage t) + "\"") + {:rollback rb + :handling t})))) + (throw t)) + (finally + (db-unset-rollback-only! nested-db) + ;; the following can throw SQLExceptions but we do not + ;; want those to replace any exception currently being + ;; handled -- and if the connection got closed, we just + ;; want to ignore exceptions here anyway + (try + (.setAutoCommit con auto-commit) + (catch Exception _)) + (when isolation + (try + (.setTransactionIsolation con old-isolation) + (catch Exception _))) + (when read-only? + (try + (.setReadOnly con old-readonly) + (catch Exception _))))))) + (with-open [con (get-connection db opts)] + (db-transaction* (add-connection db con) func opts))) + (do + (when (and isolation + (let [con (db-find-connection db)] + (not= (isolation isolation-levels) + (.getTransactionIsolation con)))) + (let [msg "Nested transactions may not have different isolation levels"] + (throw (IllegalStateException. msg)))) + (func (inc-level db))))))) + +(defmacro with-db-transaction + "Evaluates body in the context of a transaction on the specified database connection. + The binding provides the database connection for the transaction and the name to which + that is bound for evaluation of the body. The binding may also specify the isolation + level for the transaction, via the :isolation option and/or set the transaction to + readonly via the :read-only? option. + (with-db-transaction [t-con db-spec {:isolation level :read-only? true}] + ... t-con ...) + See db-transaction* for more details." + [binding & body] + `(db-transaction* ~(second binding) + (^{:once true} fn* [~(first binding)] ~@body) + ~@(rest (rest binding)))) + +(defmacro with-db-connection + "Evaluates body in the context of an active connection to the database. + (with-db-connection [con-db db-spec opts] + ... con-db ...)" + [binding & body] + `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] + (with-open [con# (get-connection db-spec# opts#)] + (let [~(first binding) (add-connection db-spec# con#)] + ~@body)))) + +(defmacro with-db-metadata + "Evaluates body in the context of an active connection with metadata bound + to the specified name. See also metadata-result for dealing with the results + of operations that retrieve information from the metadata. + (with-db-metadata [md db-spec opts] + ... md ...)" + [binding & body] + `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] + (with-open [con# (get-connection db-spec# opts#)] + (let [~(first binding) (.getMetaData con#)] + ~@body)))) + +(defn metadata-result + "If the argument is a java.sql.ResultSet, turn it into a result-set-seq, + else return it as-is. This makes working with metadata easier. + Also accepts an option map containing :identifiers, :keywordize?, :qualifier, + :as-arrays?, :row-fn,and :result-set-fn to control how the ResultSet is + transformed and returned. See query for more details." + ([rs-or-value] (metadata-result rs-or-value {})) + ([rs-or-value opts] + (let [{:keys [as-arrays? result-set-fn row-fn] :as opts} + (merge {:identifiers str/lower-case + :keywordize? true + :read-columns dft-read-columns + :row-fn identity} opts) + result-set-fn (or result-set-fn (if as-arrays? vec doall))] + (if (instance? java.sql.ResultSet rs-or-value) + ((^{:once true} fn* [rs] + (result-set-fn (if as-arrays? + (cons (first rs) + (map row-fn (rest rs))) + (map row-fn rs)))) + (result-set-seq rs-or-value opts)) + rs-or-value)))) + +(defmacro metadata-query + "Given a Java expression that extracts metadata (in the context of with-db-metadata), + and a map of options like metadata-result, manage the connection for a single + metadata-based query. Example usage: + + (with-db-metadata [meta db-spec] + (metadata-query (.getTables meta nil nil nil (into-array String [\"TABLE\"])) + {:row-fn ... :result-set-fn ...}))" + [meta-query & opt-args] + `(with-open [rs# ~meta-query] + (metadata-result rs# ~@opt-args))) + +(defn db-do-commands + "Executes SQL commands on the specified database connection. Wraps the commands + in a transaction if transaction? is true. transaction? can be ommitted and it + defaults to true. Accepts a single SQL command (string) or a vector of them. + Uses executeBatch. This may affect what SQL you can run via db-do-commands." + ([db sql-commands] + (db-do-commands db true (if (string? sql-commands) [sql-commands] sql-commands))) + ([db transaction? sql-commands] + (if (string? sql-commands) + (db-do-commands db transaction? [sql-commands]) + (if-let [con (db-find-connection db)] + (with-open [^Statement stmt (.createStatement con)] + (doseq [^String cmd sql-commands] + (.addBatch stmt cmd)) + (if transaction? + (with-db-transaction [t-db (add-connection db (.getConnection stmt))] + (execute-batch stmt)) + (execute-batch stmt))) + (with-open [con (get-connection db)] + (db-do-commands (add-connection db con) transaction? sql-commands)))))) + +(defn- db-do-execute-prepared-return-keys + "Executes a PreparedStatement, optionally in a transaction, and (attempts to) + return any generated keys." + [db ^PreparedStatement stmt param-group opts] + (let [{:keys [transaction?] :as opts} (merge (when (map? db) db) opts) + exec-and-return-keys + (^{:once true} fn* [] + (let [counts (.executeUpdate stmt)] + (try + (let [rs (.getGeneratedKeys stmt) + result (first (result-set-seq rs opts))] + ;; sqlite (and maybe others?) requires + ;; record set to be closed + (.close rs) + result) + (catch Exception _ + ;; assume generated keys is unsupported and return counts instead: + counts))))] + ((:set-parameters opts dft-set-parameters) stmt param-group) + (if transaction? + (with-db-transaction [t-db (add-connection db (.getConnection stmt))] + (exec-and-return-keys)) + (exec-and-return-keys)))) + +(defn- sql-stmt? + "Given an expression, return true if it is either a string (SQL) or a + PreparedStatement." + [expr] + (or (string? expr) (instance? PreparedStatement expr))) + +(defn db-do-prepared-return-keys + "Executes an (optionally parameterized) SQL prepared statement on the + open database connection. The param-group is a seq of values for all of + the parameters. transaction? can be ommitted and will default to true. + Return the generated keys for the (single) update/insert. + A PreparedStatement may be passed in, instead of a SQL string, in which + case :return-keys MUST BE SET on that PreparedStatement!" + ([db sql-params] + (db-do-prepared-return-keys db true sql-params {})) + ([db transaction? sql-params] + (if (map? sql-params) + (db-do-prepared-return-keys db true transaction? sql-params) + (db-do-prepared-return-keys db transaction? sql-params {}))) + ([db transaction? sql-params opts] + (let [opts (merge (when (map? db) db) opts)] + (if-let [con (db-find-connection db)] + (let [[sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (if (instance? PreparedStatement sql) + (db-do-execute-prepared-return-keys db sql params (assoc opts :transaction? transaction?)) + (with-open [^PreparedStatement stmt (prepare-statement con sql (assoc opts :return-keys true))] + (db-do-execute-prepared-return-keys db stmt params (assoc opts :transaction? transaction?))))) + (with-open [con (get-connection db opts)] + (db-do-prepared-return-keys (add-connection db con) transaction? sql-params opts)))))) + +(defn- db-do-execute-prepared-statement + "Execute a PreparedStatement, optionally in a transaction." + [db ^PreparedStatement stmt param-groups opts] + (let [{:keys [transaction?] :as opts} (merge (when (map? db) db) opts)] + (if (empty? param-groups) + (if transaction? + (with-db-transaction [t-db (add-connection db (.getConnection stmt))] + (vector (.executeUpdate stmt))) + (vector (.executeUpdate stmt))) + (do + (doseq [param-group param-groups] + ((:set-parameters opts dft-set-parameters) stmt param-group) + (.addBatch stmt)) + (if transaction? + (with-db-transaction [t-db (add-connection db (.getConnection stmt))] + (execute-batch stmt)) + (execute-batch stmt)))))) + +(defn db-do-prepared + "Executes an (optionally parameterized) SQL prepared statement on the + open database connection. Each param-group is a seq of values for all of + the parameters. transaction? can be omitted and defaults to true. + The sql parameter can either be a SQL string or a PreparedStatement. + Return a seq of update counts (one count for each param-group)." + ([db sql-params] + (db-do-prepared db true sql-params {})) + ([db transaction? sql-params] + (if (map? sql-params) + (db-do-prepared db true transaction? sql-params) + (db-do-prepared db transaction? sql-params {}))) + ([db transaction? sql-params opts] + (let [opts (merge (when (map? db) db) opts)] + (if-let [con (db-find-connection db)] + (let [[sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params)) + params (if (or (:multi? opts) (empty? params)) params [params])] + (if (instance? PreparedStatement sql) + (db-do-execute-prepared-statement db sql params (assoc opts :transaction? transaction?)) + (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] + (db-do-execute-prepared-statement db stmt params (assoc opts :transaction? transaction?))))) + (with-open [con (get-connection db opts)] + (db-do-prepared (add-connection db con) transaction? sql-params opts)))))) + +(defn db-query-with-resultset + "Executes a query, then evaluates func passing in the raw ResultSet as an + argument. The second argument is a vector containing either: + [sql & params] - a SQL query, followed by any parameters it needs + [stmt & params] - a PreparedStatement, followed by any parameters it needs + (the PreparedStatement already contains the SQL query) + The opts map is passed to prepare-statement. + Uses executeQuery. This may affect what SQL you can run via query." + ([db sql-params func] (db-query-with-resultset db sql-params func {})) + ([db sql-params func opts] + (let [opts (merge (when (map? db) db) opts) + [sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params)) + run-query-with-params (^{:once true} fn* [^PreparedStatement stmt] + ((:set-parameters opts dft-set-parameters) stmt params) + (with-open [rset (.executeQuery stmt)] + (func rset)))] + (when-not (sql-stmt? sql) + (let [^Class sql-class (class sql) + ^String msg (format "\"%s\" expected %s %s, found %s %s" + "sql-params" + "vector" + "[sql param*]" + (.getName sql-class) + (pr-str sql))] + (throw (IllegalArgumentException. msg)))) + (if (instance? PreparedStatement sql) + (let [^PreparedStatement stmt sql] + (run-query-with-params stmt)) + (if-let [con (db-find-connection db)] + (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] + (run-query-with-params stmt)) + (with-open [con (get-connection db opts)] + (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] + (run-query-with-params stmt)))))))) + +;; top-level API for actual SQL operations + +(defn query + "Given a database connection and a vector containing SQL and optional parameters, + perform a simple database query. The options specify how to construct the result + set (and are also passed to prepare-statement as needed): + :as-arrays? - return the results as a set of arrays, default false. + :identifiers - applied to each column name in the result set, default lower-case + :keywordize? - defaults to true, can be false to opt-out of converting + identifiers to keywords + :qualifier - optionally provides the namespace qualifier for identifiers + :result-set-fn - applied to the entire result set, default doall / vec + if :as-arrays? true, :result-set-fn will default to vec + if :as-arrays? false, :result-set-fn will default to doall + :row-fn - applied to each row as the result set is constructed, default identity + The second argument is a vector containing a SQL string or PreparedStatement, followed + by any parameters it needs. + See also prepare-statement for additional options." + ([db sql-params] (query db sql-params {})) + ([db sql-params opts] + (let [{:keys [as-arrays? explain? explain-fn result-set-fn row-fn] :as opts} + (merge {:explain-fn println :identifiers str/lower-case + :keywordize? true + :read-columns dft-read-columns :row-fn identity} + (when (map? db) db) + opts) + result-set-fn (or result-set-fn (if as-arrays? vec doall)) + sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (when (and explain? (string? (first sql-params-vector))) + (query db (into [(str (if (string? explain?) explain? "EXPLAIN") + " " + (first sql-params-vector))] + (rest sql-params-vector)) + (-> opts + (dissoc :explain? :result-set-fn :row-fn) + (assoc :result-set-fn explain-fn)))) + (db-query-with-resultset db sql-params-vector + (^{:once true} fn* [rset] + ((^{:once true} fn* [rs] + (result-set-fn (if as-arrays? + (cons (first rs) + (map row-fn (rest rs))) + (map row-fn rs)))) + (result-set-seq rset opts))) + opts)))) + +;; performance notes -- goal is to lift as much logic as possible into a "once" +;; pass (so reducible-query preloads all the stuff that doesn't depend on the +;; query results, and then you can repeatedly reduce it, which runs the query +;; each time and runs the minimal result set reduction) +;; turn make-cols-unique into a transducer and optimize it +;; turn make-keys into a transducer pipeline and lift it +;; lift identifier-fn out as make-identifier-fn and refactor +;; lift init-reduce +;; refactor reducible-result-set to lift identifier-fn out +;; call new reducible-result-set version from reducible-query (after calling +;; make-identifier-fn etc) +;; create an optimized version of db-query-with-resultset without :as-arrays? +;; and with options handling lifted + +(defn reducible-result-set + "Given a java.sql.ResultSet return a reducible collection. + Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce + Note: :as-arrays? is not accepted here." + [^ResultSet rs {:keys [identifiers keywordize? qualifier read-columns] + :or {identifiers str/lower-case + keywordize? true + read-columns dft-read-columns}}] + (let [identifier-fn (cond (and qualifier (not keywordize?)) + (throw (IllegalArgumentException. + (str ":qualifier is not allowed unless " + ":keywordize? is true"))) + (and qualifier keywordize?) + (comp (partial keyword qualifier) identifiers) + keywordize? + (comp keyword identifiers) + :else + identifiers) + make-keys (fn [idxs ^ResultSetMetaData rsmeta] + (->> idxs + (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) + (make-cols-unique) + (mapv identifier-fn))) + init-reduce (fn [keys ^ResultSet rs rsmeta idxs f init] + (loop [init' init] + (if (.next rs) + (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] + (if (reduced? result) + @result + (recur result))) + init')))] + (reify clojure.lang.IReduce + (reduce [this f] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + keys (make-keys idxs rsmeta)] + (if (.next rs) + ;; reduce init is first row of ResultSet + (init-reduce keys rs rsmeta idxs f + (zipmap keys (read-columns rs rsmeta idxs))) + ;; no rows so call 0-arity f to get result value + ;; per reduce docstring contract + (f)))) + (reduce [this f init] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + keys (make-keys idxs rsmeta)] + (init-reduce keys rs rsmeta idxs f init)))))) + +(defn reducible-query + "Given a database connection, a vector containing SQL and optional parameters, + return a reducible collection. When reduced, it will start the database query + and reduce the result set, and then close the connection: + (transduce (map :cost) + (reducible-query db sql-params)) + Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce + The following options from query etc are not accepted here: + :as-arrays? :explain :explain-fn :result-set-fn :row-fn + See also prepare-statement for additional options." + ([db sql-params] (reducible-query db sql-params {})) + ([db sql-params opts] + (let [{:keys [reducing-fn] :as opts} + (merge {:identifiers str/lower-case :keywordize? true + :read-columns dft-read-columns} + (when (map? db) db) + opts) + sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (reify clojure.lang.IReduce + (reduce [this f] + (db-query-with-resultset + db sql-params-vector + (^{:once true} fn* [rset] + (reduce f (reducible-result-set rset opts))) + opts)) + (reduce [this f init] + (db-query-with-resultset + db sql-params-vector + (^{:once true} fn* [rset] + (reduce f init (reducible-result-set rset opts))) + opts)))))) + +(defn- direction + "Given an entities function, a column name, and a direction, + return the matching SQL column / order. + Throw an exception for an invalid direction." + [entities c d] + (str (as-sql-name entities c) " " + (if-let [dir (#{"ASC" "DESC"} (str/upper-case (name d)))] + dir + (throw (IllegalArgumentException. (str "expected :asc or :desc, found: " d)))))) + +(defn- order-by-sql + "Given a sequence of column specs and an entities function, return + a SQL fragment for the ORDER BY clause. A column spec may be a name + (either a string or keyword) or a map from column name to direction + (:asc or :desc)." + [order-by entities] + (str/join ", " (mapcat (fn [col] + (if (map? col) + (reduce-kv (fn [v c d] + (conj v (direction entities c d))) + [] + col) + [(direction entities col :asc)])) order-by))) + +(defn find-by-keys + "Given a database connection, a table name, a map of column name/value + pairs, and an optional options map, return any matching rows. + An :order-by option may be supplied to sort the rows by a sequence of + columns, e.g,. {:order-by [:name {:age :desc]}" + ([db table columns] (find-by-keys db table columns {})) + ([db table columns opts] + (let [{:keys [entities order-by] :as opts} + (merge {:entities identity} (when (map? db) db) opts) + ks (keys columns) + vs (vals columns)] + (query db (into [(str "SELECT * FROM " (table-str table entities) + " WHERE " (str/join " AND " + (kv-sql ks vs entities " IS NULL")) + (when (seq order-by) + (str " ORDER BY " + (order-by-sql order-by entities))))] + (remove nil? vs)) + opts)))) + +(defn get-by-id + "Given a database connection, a table name, a primary key value, an + optional primary key column name, and an optional options map, return + a single matching row, or nil. + The primary key column name defaults to :id." + ([db table pk-value] (get-by-id db table pk-value :id {})) + ([db table pk-value pk-name-or-opts] + (if (map? pk-name-or-opts) + (get-by-id db table pk-value :id pk-name-or-opts) + (get-by-id db table pk-value pk-name-or-opts {}))) + ([db table pk-value pk-name opts] + (let [opts (merge (when (map? db) db) opts) + r-s-fn (or (:result-set-fn opts) identity)] + (find-by-keys db table {pk-name pk-value} + (assoc opts :result-set-fn (comp first r-s-fn)))))) + +(defn execute! + "Given a database connection and a vector containing SQL (or PreparedStatement) + followed by optional parameters, perform a general (non-select) SQL operation. + The :transaction? option specifies whether to run the operation in a + transaction or not (default true). + If the :multi? option is false (the default), the SQL statement should be + followed by the parameters for that statement. + If the :multi? option is true, the SQL statement should be followed by one or + more vectors of parameters, one for each application of the SQL statement. + If there are no parameters specified, executeUpdate will be used, otherwise + executeBatch will be used. This may affect what SQL you can run via execute!" + ([db sql-params] (execute! db sql-params {})) + ([db sql-params opts] + (let [{:keys [transaction?] :as opts} + (merge {:transaction? true :multi? false} (when (map? db) db) opts) + execute-helper (^{:once true} fn* [db] + (db-do-prepared db transaction? sql-params opts))] + (if-let [con (db-find-connection db)] + (execute-helper db) + (with-open [con (get-connection db opts)] + (execute-helper (add-connection db con))))))) + +(defn- delete-sql + "Given a table name, a where class and its parameters and an optional entities spec, + return a vector of the SQL for that delete operation followed by its parameters. The + entities spec (default 'as-is') specifies how to transform column names." + [table [where & params] entities] + (into [(str "DELETE FROM " (table-str table entities) + (when where " WHERE ") where)] + params)) + +(defn delete! + "Given a database connection, a table name and a where clause of columns to match, + perform a delete. The options may specify how to transform column names in the + map (default 'as-is') and whether to run the delete in a transaction (default true). + Example: + (delete! db :person [\"zip = ?\" 94546]) + is equivalent to: + (execute! db [\"DELETE FROM person WHERE zip = ?\" 94546])" + ([db table where-clause] (delete! db table where-clause {})) + ([db table where-clause opts] + (let [{:keys [entities] :as opts} + (merge {:entities identity :transaction? true} (when (map? db) db) opts)] + (execute! db (delete-sql table where-clause entities) opts)))) + +(defn- multi-insert-helper + "Given a (connected) database connection and some SQL statements (for multiple + inserts), run a prepared statement on each and return any generated keys. + Note: we are eager so an unrealized lazy-seq cannot escape from the connection." + [db stmts opts] + (doall (map (fn [row] (db-do-prepared-return-keys db false row opts)) stmts))) + +(defn- insert-helper + "Given a (connected) database connection, a transaction flag and some SQL statements + (for one or more inserts), run a prepared statement or a sequence of them." + [db transaction? stmts opts] + (if transaction? + (with-db-transaction [t-db db] (multi-insert-helper t-db stmts opts)) + (multi-insert-helper db stmts opts))) + +(defn- col-str + "Transform a column spec to an entity name for SQL. The column spec may be a + string, a keyword or a map with a single pair - column name and alias." + [col entities] + (if (map? col) + (let [[k v] (first col)] + (str (as-sql-name entities k) " AS " (as-sql-name entities v))) + (as-sql-name entities col))) + +(defn- insert-multi-row-sql + "Given a table and a list of columns, followed by a list of column value sequences, + return a vector of the SQL needed for the insert followed by the list of column + value sequences. The entities function specifies how column names are transformed." + [table columns values entities] + (let [nc (count columns) + vcs (map count values)] + (if (not (and (or (zero? nc) (= nc (first vcs))) (apply = vcs))) + (throw (IllegalArgumentException. "insert! called with inconsistent number of columns / values")) + (into [(str "INSERT INTO " (table-str table entities) + (when (seq columns) + (str " ( " + (str/join ", " (map (fn [col] (col-str col entities)) columns)) + " )")) + " VALUES ( " + (str/join ", " (repeat (first vcs) "?")) + " )")] + values)))) + +(defn- insert-single-row-sql + "Given a table and a map representing a row, return a vector of the SQL needed for + the insert followed by the list of column values. The entities function specifies + how column names are transformed." + [table row entities] + (let [ks (keys row)] + (into [(str "INSERT INTO " (table-str table entities) " ( " + (str/join ", " (map (fn [col] (col-str col entities)) ks)) + " ) VALUES ( " + (str/join ", " (repeat (count ks) "?")) + " )")] + (vals row)))) + +(defn- insert-rows! + "Given a database connection, a table name, a sequence of rows, and an options + map, insert the rows into the database." + [db table rows opts] + (let [{:keys [entities transaction?] :as opts} + (merge {:entities identity :identifiers str/lower-case + :keywordize? true :transaction? true} + (when (map? db) db) + opts) + sql-params (map (fn [row] + (when-not (map? row) + (throw (IllegalArgumentException. "insert! / insert-multi! called with a non-map row"))) + (insert-single-row-sql table row entities)) rows)] + (if-let [con (db-find-connection db)] + (insert-helper db transaction? sql-params opts) + (with-open [con (get-connection db opts)] + (insert-helper (add-connection db con) transaction? sql-params opts))))) + +(defn- insert-cols! + "Given a database connection, a table name, a sequence of columns names, a + sequence of vectors of column values, one per row, and an options map, + insert the rows into the database." + [db table cols values opts] + (let [{:keys [entities transaction?] :as opts} + (merge {:entities identity :transaction? true} (when (map? db) db) opts) + sql-params (insert-multi-row-sql table cols values entities)] + (if-let [con (db-find-connection db)] + (db-do-prepared db transaction? sql-params (assoc opts :multi? true)) + (with-open [con (get-connection db opts)] + (db-do-prepared (add-connection db con) transaction? sql-params + (assoc opts :multi? true)))))) + +(defn insert! + "Given a database connection, a table name and either a map representing a rows, + or a list of column names followed by a list of column values also representing + a single row, perform an insert. + When inserting a row as a map, the result is the database-specific form of the + generated keys, if available (note: PostgreSQL returns the whole row). + When inserting a row as a list of column values, the result is the count of + rows affected (1), if available (from getUpdateCount after executeBatch). + The row map or column value vector may be followed by a map of options: + The :transaction? option specifies whether to run in a transaction or not. + The default is true (use a transaction). The :entities option specifies how + to convert the table name and column names to SQL entities." + ([db table row] (insert! db table row {})) + ([db table cols-or-row values-or-opts] + (if (map? values-or-opts) + (insert-rows! db table [cols-or-row] values-or-opts) + (insert-cols! db table cols-or-row [values-or-opts] {}))) + ([db table cols values opts] + (insert-cols! db table cols [values] opts))) + +(defn insert-multi! + "Given a database connection, a table name and either a sequence of maps (for + rows) or a sequence of column names, followed by a sequence of vectors (for + the values in each row), and possibly a map of options, insert that data into + the database. + + When inserting rows as a sequence of maps, the result is a sequence of the + generated keys, if available (note: PostgreSQL returns the whole rows). A + separate database operation is used for each row inserted. This may be slow + for if a large sequence of maps is provided. + + When inserting rows as a sequence of lists of column values, the result is + a sequence of the counts of rows affected (a sequence of 1's), if available. + Yes, that is singularly unhelpful. Thank you getUpdateCount and executeBatch! + A single database operation is used to insert all the rows at once. This may + be much faster than inserting a sequence of rows (which performs an insert for + each map in the sequence). + + The :transaction? option specifies whether to run in a transaction or not. + The default is true (use a transaction). The :entities option specifies how + to convert the table name and column names to SQL entities." + ([db table rows] (insert-rows! db table rows {})) + ([db table cols-or-rows values-or-opts] + (if (map? values-or-opts) + (insert-rows! db table cols-or-rows values-or-opts) + (insert-cols! db table cols-or-rows values-or-opts {}))) + ([db table cols values opts] + (insert-cols! db table cols values opts))) + +(defn- update-sql + "Given a table name, a map of columns to set, a optional map of columns to + match, and an entities, return a vector of the SQL for that update followed + by its parameters. Example: + (update :person {:zip 94540} [\"zip = ?\" 94546] identity) + returns: + [\"UPDATE person SET zip = ? WHERE zip = ?\" 94540 94546]" + [table set-map [where & params] entities] + (let [ks (keys set-map) + vs (vals set-map)] + (cons (str "UPDATE " (table-str table entities) + " SET " (str/join + "," + (kv-sql ks vs entities " = NULL")) + (when where " WHERE ") + where) + (concat (remove nil? vs) params)))) + +(defn update! + "Given a database connection, a table name, a map of column values to set and a + where clause of columns to match, perform an update. The options may specify + how column names (in the set / match maps) should be transformed (default + 'as-is') and whether to run the update in a transaction (default true). + Example: + (update! db :person {:zip 94540} [\"zip = ?\" 94546]) + is equivalent to: + (execute! db [\"UPDATE person SET zip = ? WHERE zip = ?\" 94540 94546])" + ([db table set-map where-clause] (update! db table set-map where-clause {})) + ([db table set-map where-clause opts] + (let [{:keys [entities] :as opts} + (merge {:entities identity :transaction? true} (when (map? db) db) opts)] + (execute! db (update-sql table set-map where-clause entities) opts)))) + +(defn create-table-ddl + "Given a table name and a vector of column specs, return the DDL string for + creating that table. Each column spec is, in turn, a vector of keywords or + strings that is converted to strings and concatenated with spaces to form + a single column description in DDL, e.g., + [:cost :int \"not null\"] + [:name \"varchar(32)\"] + The first element of a column spec is treated as a SQL entity (so if you + provide the :entities option, that will be used to transform it). The + remaining elements are left as-is when converting them to strings. + An options map may be provided that can contain: + :table-spec -- a string that is appended to the DDL -- and/or + :entities -- a function to specify how column names are transformed. + :conditional? -- either a boolean, indicating whether to add 'IF NOT EXISTS', + or a string, which is inserted literally before the table name, or a + function of two arguments (table name and the create statement), that can + manipulate the generated statement to better support other databases, e.g., + MS SQL Server which need to wrap create table in an existence query." + ([table specs] (create-table-ddl table specs {})) + ([table specs opts] + (let [table-spec (:table-spec opts) + conditional? (:conditional? opts) + entities (:entities opts identity) + table-name (as-sql-name entities table) + table-spec-str (or (and table-spec (str " " table-spec)) "") + spec-to-string (fn [spec] + (try + (str/join " " (cons (as-sql-name entities (first spec)) + (map name (rest spec)))) + (catch Exception _ + (throw (IllegalArgumentException. + "column spec is not a sequence of keywords / strings")))))] + (cond->> (format "CREATE TABLE%s %s (%s)%s" + (cond (or (nil? conditional?) + (instance? Boolean conditional?)) + (if conditional? " IF NOT EXISTS" "") + (fn? conditional?) + "" + :else + (str " " conditional?)) + table-name + (str/join ", " (map spec-to-string specs)) + table-spec-str) + (fn? conditional?) (conditional? table-name))))) + +(defn drop-table-ddl + "Given a table name, return the DDL string for dropping that table. + An options map may be provided that can contain: + :entities -- a function to specify how column names are transformed. + :conditional? -- either a boolean, indicating whether to add 'IF EXISTS', + or a string, which is inserted literally before the table name, or a + function of two arguments (table name and the create statement), that can + manipulate the generated statement to better support other databases, e.g., + MS SQL Server which need to wrap create table in an existence query." + ([table] (drop-table-ddl table {})) + ([table {:keys [entities conditional?] :or {entities identity}}] + (let [table-name (as-sql-name entities table)] + (cond->> (format "DROP TABLE%s %s" + (cond (or (nil? conditional?) + (instance? Boolean conditional?)) + (if conditional? " IF EXISTS" "") + (fn? conditional?) + "" + :else + (str " " conditional?)) + table-name) + (fn? conditional?) (conditional? table-name))))) From 816b0cf323b1c672f2231c0f88e831a5a9fe57fe Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 13 Oct 2017 15:49:39 -0400 Subject: [PATCH 055/175] Lift return-keys validation out of try/catch If you asked to return keys provided other disallowed options, you did not get an exception, instead it attempted a basic PreparedStatement without returning keys... --- src/main/clojure/clojure/java/jdbc.clj | 64 +++++++++++++------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index ce51d0d8..1886d04f 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -570,39 +570,41 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} {:keys [return-keys result-type concurrency cursors fetch-size max-rows timeout]}] (let [^PreparedStatement - stmt (cond return-keys - (try - (when (or result-type concurrency cursors) - (throw (IllegalArgumentException. - (str ":concurrency, :cursors, and :result-type " - "may not be specified with :return-keys.")))) - (if (vector? return-keys) - (try - (.prepareStatement con sql (string-array return-keys)) - (catch Exception _ - ;; assume it is unsupported and try regular generated keys: - (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS))) - (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS)) - (catch Exception _ - ;; assume it is unsupported and try basic PreparedStatement: - (.prepareStatement con sql))) - - (and result-type concurrency) - (if cursors - (.prepareStatement con sql - (get result-set-type result-type result-type) - (get result-set-concurrency concurrency concurrency) - (get result-set-holdability cursors cursors)) - (.prepareStatement con sql - (get result-set-type result-type result-type) - (get result-set-concurrency concurrency concurrency))) - - (or result-type concurrency cursors) + stmt (cond + return-keys + (do + (when (or result-type concurrency cursors) (throw (IllegalArgumentException. (str ":concurrency, :cursors, and :result-type " - "may not be specified independently."))) - :else - (.prepareStatement con sql))] + "may not be specified with :return-keys.")))) + (try + (if (vector? return-keys) + (try + (.prepareStatement con sql (string-array return-keys)) + (catch Exception _ + ;; assume it is unsupported and try regular generated keys: + (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS))) + (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS)) + (catch Exception _ + ;; assume it is unsupported and try basic PreparedStatement: + (.prepareStatement con sql)))) + + (and result-type concurrency) + (if cursors + (.prepareStatement con sql + (get result-set-type result-type result-type) + (get result-set-concurrency concurrency concurrency) + (get result-set-holdability cursors cursors)) + (.prepareStatement con sql + (get result-set-type result-type result-type) + (get result-set-concurrency concurrency concurrency))) + + (or result-type concurrency cursors) + (throw (IllegalArgumentException. + (str ":concurrency, :cursors, and :result-type " + "may not be specified independently."))) + :else + (.prepareStatement con sql))] (when fetch-size (.setFetchSize stmt fetch-size)) (when max-rows (.setMaxRows stmt max-rows)) (when timeout (.setQueryTimeout stmt timeout)) From 2c7d7fb16a5df81a44fee4112223dd8715bafcab Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 13 Oct 2017 16:12:32 -0400 Subject: [PATCH 056/175] Remove unused reducing-fn binding --- src/main/clojure/clojure/java/jdbc.clj | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 1886d04f..fb82a9c5 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1118,11 +1118,10 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} See also prepare-statement for additional options." ([db sql-params] (reducible-query db sql-params {})) ([db sql-params opts] - (let [{:keys [reducing-fn] :as opts} - (merge {:identifiers str/lower-case :keywordize? true - :read-columns dft-read-columns} - (when (map? db) db) - opts) + (let [opts (merge {:identifiers str/lower-case :keywordize? true} + :read-columns dft-read-columns + (when (map? db) db) + opts) sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] (reify clojure.lang.IReduce (reduce [this f] From b0d6bb0d9fc8983848cdd9764eacf469c88a6608 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 18 Oct 2017 10:04:43 -0700 Subject: [PATCH 057/175] Fix typo in options hash map --- src/main/clojure/clojure/java/jdbc.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index fb82a9c5..7fd2c482 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1118,8 +1118,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} See also prepare-statement for additional options." ([db sql-params] (reducible-query db sql-params {})) ([db sql-params opts] - (let [opts (merge {:identifiers str/lower-case :keywordize? true} - :read-columns dft-read-columns + (let [opts (merge {:identifiers str/lower-case :keywordize? true + :read-columns dft-read-columns} (when (map? db) db) opts) sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] From 3a4321012eb04e82fafcf28c2303dd924389a39d Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 19 Oct 2017 13:31:49 -0700 Subject: [PATCH 058/175] Fix CRLF --- src/main/clojure/clojure/java/jdbc.clj | 2962 ++++++++++++------------ 1 file changed, 1481 insertions(+), 1481 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 7fd2c482..f2d0517c 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1,1481 +1,1481 @@ -;; Copyright (c) 2008-2017 Sean Corfield, 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. -;; -;; jdbc.clj -;; -;; A Clojure interface to sql databases via jdbc -;; -;; scgilardi (gmail) -;; Created 2 April 2008 -;; -;; seancorfield (gmail) -;; Migrated from clojure.contrib.sql 17 April 2011 - -(ns - ^{:author "Stephen C. Gilardi, Sean Corfield", - :doc "A Clojure interface to SQL databases via JDBC - -clojure.java.jdbc provides a simple abstraction for CRUD (create, read, -update, delete) operations on a SQL database, along with basic transaction -support. Basic DDL operations are also supported (create table, drop table, -access to table metadata). - -Maps are used to represent records, making it easy to store and retrieve -data. Results can be processed using any standard sequence operations. - -For most operations, Java's PreparedStatement is used so your SQL and -parameters can be represented as simple vectors where the first element -is the SQL string, with ? for each parameter, and the remaining elements -are the parameter values to be substituted. In general, operations return -the number of rows affected, except for a single record insert where any -generated keys are returned (as a map). - -For more documentation, see: - -http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} - clojure.java.jdbc - (:require [clojure.set :as set] - [clojure.string :as str] - [clojure.walk :as walk]) - (:import (java.net URI) - (java.sql BatchUpdateException DriverManager - PreparedStatement ResultSet ResultSetMetaData - SQLException Statement Types) - (java.util Hashtable Map Properties) - (javax.sql DataSource))) - -(defn as-sql-name - "Given a naming strategy function and a keyword or string, return - a string per that naming strategy. - A name of the form x.y is treated as multiple names, x, y, etc, - and each are turned into strings via the naming strategy and then - joined back together so x.y might become `x`.`y` if the naming - strategy quotes identifiers with `." - [f x] - (let [n (name x) - i (.indexOf n (int \.))] - (if (= -1 i) - (f n) - (str/join "." (map f (.split n "\\.")))))) - -(defn quoted - "Given a (vector) pair of delimiters (characters or strings), return a naming - strategy function that will quote SQL entities with them. - Given a single delimiter, treat it as a (vector) pair of that delimiter. - ((quoted [\\[ \\]]) \"foo\") will return \"[foo]\" -- for MS SQL Server - ((quoted \\`') \"foo\") will return \"`foo`\" -- for MySQL - Intended to be used with :entities to provide a quoting (naming) strategy that - is appropriate for your database." - [q] - (cond (vector? q) - (fn [x] - (str (first q) x (last q))) - (keyword? q) - (case q - :ansi (quoted \") - :mysql (quoted \`) - :oracle (quoted \") - :sqlserver (quoted [\[ \]])) - :else - (quoted [q q]))) - -(defn- table-str - "Transform a table spec to an entity name for SQL. The table spec may be a - string, a keyword or a map with a single pair - table name and alias." - [table entities] - (let [entities (or entities identity)] - (if (map? table) - (let [[k v] (first table)] - (str (as-sql-name entities k) " " (as-sql-name entities v))) - (as-sql-name entities table)))) - -(defn- kv-sql - "Given a sequence of column name keys and a matching sequence of column - values, and an entities mapping function, return a sequence of SQL fragments - that can be joined for part of an UPDATE SET or a SELECT WHERE clause. - Note that we pass the appropriate operator for NULL since it is different - in each case." - [ks vs entities null-op] - (map (fn [k v] - (str (as-sql-name entities k) - (if (nil? v) null-op " = ?"))) - ks vs)) - -(defn- ^Properties as-properties - "Convert any seq of pairs to a java.utils.Properties instance. - Uses as-sql-name to convert both keys and values into strings." - [m] - (let [p (Properties.)] - (doseq [[k v] m] - (.setProperty p (as-sql-name identity k) - (if (instance? clojure.lang.Named v) - (as-sql-name identity v) - (str v)))) - p)) - -;; convenience for working with different forms of connections -(defprotocol Connectable - (add-connection [db connection]) - (get-level [db])) - -(defn- inc-level - "Increment the nesting level for a transacted database connection. - If we are at the top level, also add in a rollback state." - [db] - (let [nested-db (update-in db [:level] (fnil inc 0))] - (if (= 1 (:level nested-db)) - (assoc nested-db :rollback (atom false)) - nested-db))) - -(extend-protocol Connectable - String - (add-connection [s connection] {:connection connection :level 0 :connection-string s}) - (get-level [_] 0) - - clojure.lang.Associative - (add-connection [m connection] (assoc m :connection connection)) - (get-level [m] (or (:level m) 0)) - - nil - (add-connection [_ connection] {:connection connection :level 0 :legacy true}) - (get-level [_] 0)) - -(def ^:private classnames - "Map of subprotocols to classnames. dbtype specifies one of these keys. - The subprotocols map below provides aliases for dbtype." - {"derby" "org.apache.derby.jdbc.EmbeddedDriver" - "h2" "org.h2.Driver" - "hsqldb" "org.hsqldb.jdbcDriver" - "jtds:sqlserver" "net.sourceforge.jtds.jdbc.Driver" - "mysql" "com.mysql.jdbc.Driver" - "oracle:oci" "oracle.jdbc.OracleDriver" - "oracle:thin" "oracle.jdbc.OracleDriver" - "postgresql" "org.postgresql.Driver" - "pgsql" "com.impossibl.postgres.jdbc.PGDriver" - "redshift" "com.amazon.redshift.jdbc.Driver" - "sqlite" "org.sqlite.JDBC" - "sqlserver" "com.microsoft.sqlserver.jdbc.SQLServerDriver"}) - -(def ^:private subprotocols - "Map of schemes to subprotocols. Used to provide aliases for dbtype." - {"hsql" "hsqldb" - "jtds" "jtds:sqlserver" - "mssql" "sqlserver" - "oracle" "oracle:thin" - "postgres" "postgresql"}) - -(def ^:private host-prefixes - "Map of subprotocols to non-standard host-prefixes. - Anything not listed is assumed to use //." - {"oracle:oci" "@" - "oracle:thin" "@"}) - -(defn- parse-properties-uri [^URI uri] - (let [host (.getHost uri) - port (if (pos? (.getPort uri)) (.getPort uri)) - path (.getPath uri) - scheme (.getScheme uri) - subprotocol (subprotocols scheme scheme) - host-prefix (host-prefixes subprotocol "//") - ^String query (.getQuery uri) - query-parts (and query - (for [^String kvs (.split query "&")] - ((juxt first second) (.split kvs "="))))] - (merge - {:subname (if host - (if port - (str host-prefix host ":" port path) - (str host-prefix host path)) - (.getSchemeSpecificPart uri)) - :subprotocol subprotocol} - (if-let [user-info (.getUserInfo uri)] - {:user (first (str/split user-info #":")) - :password (second (str/split user-info #":"))}) - (walk/keywordize-keys (into {} query-parts))))) - -(defn- strip-jdbc [^String spec] - (if (.startsWith spec "jdbc:") - (.substring spec 5) - spec)) - -;; feature testing macro, based on suggestion from Chas Emerick: -(defmacro when-available - [sym & body] - (try - (when (resolve sym) - (list* 'do body)) - (catch ClassNotFoundException _#))) - -(defn- modify-connection - "Given a database connection and a map of options, update the connection - as specified by the options." - ^java.sql.Connection - [^java.sql.Connection connection opts] - (when (contains? opts :auto-commit?) - (.setAutoCommit connection (:auto-commit? opts))) - (when (contains? opts :read-only?) - (.setReadOnly connection (:read-only? opts))) - connection) - -(defn get-connection - "Creates a connection to a database. db-spec is usually a map containing connection - parameters but can also be a URI or a String. The various possibilities are described - below: - - DriverManager (preferred): - :dbtype (required) a String, the type of the database (the jdbc subprotocol) - :dbname (required) a String, the name of the database - :classname (optional) a String, the jdbc driver class name - :host (optional) a String, the host name/IP of the database - (defaults to 127.0.0.1) - :port (optional) a Long, the port of the database - (defaults to 3306 for mysql, 1433 for mssql/jtds, else nil) - (others) (optional) passed to the driver as properties - (may include :user and :password) - - Raw: - :connection-uri (required) a String - Passed directly to DriverManager/getConnection - (both :user and :password may be specified as well, rather - than passing them as part of the connection string) - - Other formats accepted: - - Existing Connection: - :connection (required) an existing open connection that can be used - but cannot be closed (only the parent connection can be closed) - - DriverManager (alternative / legacy style): - :subprotocol (required) a String, the jdbc subprotocol - :subname (required) a String, the jdbc subname - :classname (optional) a String, the jdbc driver class name - (others) (optional) passed to the driver as properties - (may include :user and :password) - - Factory: - :factory (required) a function of one argument, a map of params - (others) (optional) passed to the factory function in a map - - DataSource: - :datasource (required) a javax.sql.DataSource - :username (optional) a String - deprecated, use :user instead - :user (optional) a String - preferred - :password (optional) a String, required if :user is supplied - - JNDI: - :name (required) a String or javax.naming.Name - :environment (optional) a java.util.Map - - java.net.URI: - Parsed JDBC connection string (see java.lang.String format next) - - java.lang.String: - subprotocol://user:password@host:post/subname - An optional prefix of jdbc: is allowed." - (^java.sql.Connection [db-spec] (get-connection db-spec {})) - (^java.sql.Connection - [{:keys [connection - factory - connection-uri - classname subprotocol subname - dbtype dbname host port - datasource username password user - name environment] - :as db-spec} - opts] - (cond - (string? db-spec) - (get-connection (URI. (strip-jdbc db-spec)) opts) - - (instance? URI db-spec) - (get-connection (parse-properties-uri db-spec) opts) - - connection - connection ;; do not apply opts here - - (or (and datasource username password) ; legacy - (and datasource user password)) ; preferred - (-> (.getConnection ^DataSource datasource - ^String (or username user) - ^String password) - (modify-connection opts)) - - datasource - (-> (.getConnection ^DataSource datasource) - (modify-connection opts)) - - factory - (-> (factory (dissoc db-spec :factory)) - (modify-connection opts)) - - connection-uri - (-> (if (and user password) - (DriverManager/getConnection connection-uri user password) - (DriverManager/getConnection connection-uri)) - (modify-connection opts)) - - (and dbtype dbname) - (let [;; allow aliases for dbtype - subprotocol (subprotocols dbtype dbtype) - host (or host "127.0.0.1") - port (or port (condp = subprotocol - "jtds:sqlserver" 1433 - "mysql" 3306 - "oracle:oci" 1521 - "oracle:thin" 1521 - "postgresql" 5432 - "sqlserver" 1433 - nil)) - db-sep (if (= "sqlserver" subprotocol) ";DATABASENAME=" "/") - url (if (#{"derby" "h2" "hsqldb" "sqlite"} subprotocol) - (str "jdbc:" subprotocol ":" dbname) - (str "jdbc:" subprotocol ":" - (host-prefixes subprotocol "//") - host - (when port (str ":" port)) - db-sep dbname)) - etc (dissoc db-spec :dbtype :dbname)] - (if-let [class-name (or classname (classnames subprotocol))] - (do - ;; force DriverManager to be loaded - (DriverManager/getLoginTimeout) - (clojure.lang.RT/loadClassForName class-name)) - (throw (ex-info (str "Unknown dbtype: " dbtype) db-spec))) - (-> (DriverManager/getConnection url (as-properties etc)) - (modify-connection opts))) - - (and subprotocol subname) - (let [;; allow aliases for subprotocols - subprotocol (subprotocols subprotocol subprotocol) - url (format "jdbc:%s:%s" subprotocol subname) - etc (dissoc db-spec :classname :subprotocol :subname)] - (if-let [class-name (or classname (classnames subprotocol))] - (do - ;; force DriverManager to be loaded - (DriverManager/getLoginTimeout) - (clojure.lang.RT/loadClassForName class-name)) - (throw (ex-info (str "Unknown subprotocol: " subprotocol) db-spec))) - (-> (DriverManager/getConnection url (as-properties etc)) - (modify-connection opts))) - - name - (or (when-available javax.naming.InitialContext - (let [env (and environment (Hashtable. ^Map environment)) - context (javax.naming.InitialContext. env) - ^DataSource datasource (.lookup context ^String name)] - (-> (.getConnection datasource) - (modify-connection opts)))) - (throw (ex-info (str "javax.naming.InitialContext is not available for: " - name) - db-spec))) - - :else - (let [^String msg (format "db-spec %s is missing a required parameter" db-spec)] - (throw (IllegalArgumentException. msg)))))) - -(defn- make-name-unique - "Given a collection of column names and a new column name, - return the new column name made unique, if necessary, by - appending _N where N is some unique integer suffix." - [cols col-name n] - (let [suffixed-name (if (= n 1) col-name (str col-name "_" n))] - (if (apply distinct? suffixed-name cols) - suffixed-name - (recur cols col-name (inc n))))) - -(defn- make-cols-unique - "Given a collection of column names, rename duplicates so - that the result is a collection of unique column names." - [cols] - (if (or (empty? cols) (apply distinct? cols)) - cols - (reduce (fn [unique-cols col-name] - (conj unique-cols (make-name-unique unique-cols col-name 1))) [] cols))) - -(defprotocol ISQLValue - "Protocol for creating SQL values from Clojure values. Default - implementations (for Object and nil) just return the argument, - but it can be extended to provide custom behavior to support - exotic types supported by different databases." - (sql-value [val] "Convert a Clojure value into a SQL value.")) - -(extend-protocol ISQLValue - Object - (sql-value [v] v) - - nil - (sql-value [_] nil)) - -(defprotocol ISQLParameter - "Protocol for setting SQL parameters in statement objects, which - can convert from Clojure values. The default implementation just - delegates the conversion to ISQLValue's sql-value conversion and - uses .setObject on the parameter. 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 (sql-value v))) - - nil - (set-parameter [_ ^PreparedStatement s ^long i] - (.setObject s i (sql-value nil)))) - -(defn- dft-set-parameters - "Default implementation of parameter setting for the given statement." - [stmt params] - (dorun (map-indexed (fn [ix value] - (set-parameter value stmt (inc ix))) - params))) - -(defprotocol IResultSetReadColumn - "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." - (result-set-read-column [val rsmeta idx] - "Function for transforming values after reading them from the database")) - -(extend-protocol IResultSetReadColumn - Object - (result-set-read-column [x _2 _3] x) - - Boolean - (result-set-read-column [x _2 _3] (if (= true x) true false)) - - nil - (result-set-read-column [_1 _2 _3] nil)) - -(defn- dft-read-columns - "Default implementation of reading row values from result set, given the - result set metadata and the indices." - [^ResultSet rs rsmeta idxs] - (mapv (fn [^Integer i] (result-set-read-column (.getObject rs i) rsmeta i)) idxs)) - -(defn result-set-seq - "Creates and returns a lazy sequence of maps corresponding to the rows in the - java.sql.ResultSet rs. Loosely based on clojure.core/resultset-seq but it - respects the specified naming strategy. Duplicate column names are made unique - by appending _N before applying the naming strategy (where N is a unique integer), - unless the :as-arrays? option is :cols-as-is, in which case the column names - are untouched (the result set maintains column name/value order). - The :identifiers option specifies how SQL column names are converted to Clojure - keywords. The default is to convert them to lower case. - The :keywordize? option can be specified as false to opt-out of the conversion - to keywords. - The :qualifier option specifies the namespace qualifier for those identifiers - (and this may not be specified when :keywordize? is false)." - ([rs] (result-set-seq rs {})) - ([^ResultSet rs {:keys [as-arrays? identifiers keywordize? - qualifier read-columns] - :or {identifiers str/lower-case - keywordize? true - read-columns dft-read-columns}}] - (let [rsmeta (.getMetaData rs) - idxs (range 1 (inc (.getColumnCount rsmeta))) - col-name-fn (if (= :cols-as-is as-arrays?) identity make-cols-unique) - identifier-fn (cond (and qualifier (not keywordize?)) - (throw (IllegalArgumentException. - (str ":qualifier is not allowed unless " - ":keywordize? is true"))) - (and qualifier keywordize?) - (comp (partial keyword qualifier) identifiers) - keywordize? - (comp keyword identifiers) - :else - identifiers) - keys (->> idxs - (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) - col-name-fn - (mapv identifier-fn)) - row-values (fn [] (read-columns rs rsmeta 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 - ;; want to switch to records so we're using regular maps instead. We no longer - ;; guarantee column order in rows but using into {} should preserve order for up - ;; to 16 columns (because it will use a PersistentArrayMap). If someone is relying - ;; on the order-preserving behavior of structmaps, we can reconsider... - records (fn thisfn [] - (when (.next rs) - (cons (zipmap keys (row-values)) (lazy-seq (thisfn))))) - rows (fn thisfn [] - (when (.next rs) - (cons (vec (row-values)) (lazy-seq (thisfn)))))] - (if as-arrays? - (cons (vec keys) (rows)) - (records))))) - -(defn- execute-batch - "Executes a batch of SQL commands and returns a sequence of update counts. - (-2) indicates a single operation operating on an unknown number of rows. - Specifically, Oracle returns that and we must call getUpdateCount() to get - the actual number of rows affected. In general, operations return an array - of update counts, so this may not be a general solution for Oracle..." - [^Statement stmt] - (let [result (.executeBatch stmt)] - (if (and (= 1 (count result)) (= -2 (first result))) - (list (.getUpdateCount stmt)) - (seq result)))) - -(def ^{:private true - :doc "Map friendly :concurrency values to ResultSet constants."} - result-set-concurrency - {:read-only ResultSet/CONCUR_READ_ONLY - :updatable ResultSet/CONCUR_UPDATABLE}) - -(def ^{:private true - :doc "Map friendly :cursors values to ResultSet constants."} - result-set-holdability - {:hold ResultSet/HOLD_CURSORS_OVER_COMMIT - :close ResultSet/CLOSE_CURSORS_AT_COMMIT}) - -(def ^{:private true - :doc "Map friendly :type values to ResultSet constants."} - result-set-type - {:forward-only ResultSet/TYPE_FORWARD_ONLY - :scroll-insensitive ResultSet/TYPE_SCROLL_INSENSITIVE - :scroll-sensitive ResultSet/TYPE_SCROLL_SENSITIVE}) - -(defn ^{:tag (class (into-array String []))} string-array - [return-keys] - (into-array String return-keys)) - -(defn prepare-statement - "Create a prepared statement from a connection, a SQL string and a map - of options: - :return-keys truthy | nil - default nil - for some drivers, this may be a vector of column names to identify - the generated keys to return, otherwise it should just be true - :result-type :forward-only | :scroll-insensitive | :scroll-sensitive - :concurrency :read-only | :updatable - :cursors :hold | :close - :fetch-size n - :max-rows n - :timeout n - Note that :result-type and :concurrency must be specified together as the - underlying Java API expects both (or neither)." - ([con sql] (prepare-statement con sql {})) - ([^java.sql.Connection con ^String sql - {:keys [return-keys result-type concurrency cursors - fetch-size max-rows timeout]}] - (let [^PreparedStatement - stmt (cond - return-keys - (do - (when (or result-type concurrency cursors) - (throw (IllegalArgumentException. - (str ":concurrency, :cursors, and :result-type " - "may not be specified with :return-keys.")))) - (try - (if (vector? return-keys) - (try - (.prepareStatement con sql (string-array return-keys)) - (catch Exception _ - ;; assume it is unsupported and try regular generated keys: - (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS))) - (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS)) - (catch Exception _ - ;; assume it is unsupported and try basic PreparedStatement: - (.prepareStatement con sql)))) - - (and result-type concurrency) - (if cursors - (.prepareStatement con sql - (get result-set-type result-type result-type) - (get result-set-concurrency concurrency concurrency) - (get result-set-holdability cursors cursors)) - (.prepareStatement con sql - (get result-set-type result-type result-type) - (get result-set-concurrency concurrency concurrency))) - - (or result-type concurrency cursors) - (throw (IllegalArgumentException. - (str ":concurrency, :cursors, and :result-type " - "may not be specified independently."))) - :else - (.prepareStatement con sql))] - (when fetch-size (.setFetchSize stmt fetch-size)) - (when max-rows (.setMaxRows stmt max-rows)) - (when timeout (.setQueryTimeout stmt timeout)) - stmt))) - -(defn print-sql-exception - "Prints the contents of an SQLException to *out*" - [^SQLException exception] - (let [^Class exception-class (class exception)] - (println - (format (str "%s:" \newline - " Message: %s" \newline - " SQLState: %s" \newline - " Error Code: %d") - (.getSimpleName exception-class) - (.getMessage exception) - (.getSQLState exception) - (.getErrorCode exception))))) - -(defn print-sql-exception-chain - "Prints a chain of SQLExceptions to *out*" - [^SQLException exception] - (loop [e exception] - (when e - (print-sql-exception e) - (recur (.getNextException e))))) - -(def ^{:private true} special-counts - {Statement/EXECUTE_FAILED "EXECUTE_FAILED" - Statement/SUCCESS_NO_INFO "SUCCESS_NO_INFO"}) - -(defn print-update-counts - "Prints the update counts from a BatchUpdateException to *out*" - [^BatchUpdateException exception] - (println "Update counts:") - (dorun - (map-indexed - (fn [index count] - (println (format " Statement %d: %s" - index - (get special-counts count count)))) - (.getUpdateCounts exception)))) - -;; java.jdbc pieces rewritten to not use dynamic bindings - -(defn db-find-connection - "Returns the current database connection (or nil if there is none)" - ^java.sql.Connection [db] - (and (map? db) - (:connection db))) - -(defn db-connection - "Returns the current database connection (or throws if there is none)" - ^java.sql.Connection [db] - (or (db-find-connection db) - (throw (Exception. "no current database connection")))) - -(defn db-set-rollback-only! - "Marks the outermost transaction such that it will rollback rather than - commit when complete" - [db] - (reset! (:rollback db) true)) - -(defn db-unset-rollback-only! - "Marks the outermost transaction such that it will not rollback when complete" - [db] - (reset! (:rollback db) false)) - -(defn db-is-rollback-only - "Returns true if the outermost transaction will rollback rather than - commit when complete" - [db] - (deref (:rollback db))) - -(def ^:private - isolation-levels - "Transaction isolation levels." - {:none java.sql.Connection/TRANSACTION_NONE - :read-committed java.sql.Connection/TRANSACTION_READ_COMMITTED - :read-uncommitted java.sql.Connection/TRANSACTION_READ_UNCOMMITTED - :repeatable-read java.sql.Connection/TRANSACTION_REPEATABLE_READ - :serializable java.sql.Connection/TRANSACTION_SERIALIZABLE}) - -(def ^:private isolation-kws - "Map transaction isolation constants to our keywords." - (set/map-invert isolation-levels)) - -(defn get-isolation-level - "Given a db-spec (with an optional connection), return the current - transaction isolation level, if known. Return nil if there is no - active connection in the db-spec. Return :unknown if we do not - recognize the isolation level." - [db] - (when-let [con (db-find-connection db)] - (isolation-kws (.getTransactionIsolation con) :unknown))) - -(defn db-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. - The isolation option may be :none, :read-committed, :read-uncommitted, - :repeatable-read, or :serializable. Note that not all databases support - all of those isolation levels, and may either throw an exception or - substitute another isolation level. - The read-only? option puts the transaction in readonly mode (if supported)." - ([db func] (db-transaction* db func {})) - ([db func opts] - (let [{:keys [isolation read-only?] :as opts} - (merge (when (map? db) db) opts)] - (if (zero? (get-level db)) - (if-let [con (db-find-connection db)] - (let [nested-db (inc-level db) - auto-commit (.getAutoCommit con) - old-isolation (.getTransactionIsolation con) - old-readonly (.isReadOnly con)] - (io! - (when isolation - (.setTransactionIsolation con (isolation isolation-levels))) - (when read-only? - (.setReadOnly con true)) - (.setAutoCommit con false) - (try - (let [result (func nested-db)] - (if (db-is-rollback-only nested-db) - (.rollback con) - (.commit con)) - result) - (catch Throwable t - (try - (.rollback con) - (catch Throwable rb - ;; combine both exceptions - (throw (ex-info (str "Rollback failed handling \"" - (.getMessage t) - "\"") - {:rollback rb - :handling t})))) - (throw t)) - (finally - (db-unset-rollback-only! nested-db) - ;; the following can throw SQLExceptions but we do not - ;; want those to replace any exception currently being - ;; handled -- and if the connection got closed, we just - ;; want to ignore exceptions here anyway - (try - (.setAutoCommit con auto-commit) - (catch Exception _)) - (when isolation - (try - (.setTransactionIsolation con old-isolation) - (catch Exception _))) - (when read-only? - (try - (.setReadOnly con old-readonly) - (catch Exception _))))))) - (with-open [con (get-connection db opts)] - (db-transaction* (add-connection db con) func opts))) - (do - (when (and isolation - (let [con (db-find-connection db)] - (not= (isolation isolation-levels) - (.getTransactionIsolation con)))) - (let [msg "Nested transactions may not have different isolation levels"] - (throw (IllegalStateException. msg)))) - (func (inc-level db))))))) - -(defmacro with-db-transaction - "Evaluates body in the context of a transaction on the specified database connection. - The binding provides the database connection for the transaction and the name to which - that is bound for evaluation of the body. The binding may also specify the isolation - level for the transaction, via the :isolation option and/or set the transaction to - readonly via the :read-only? option. - (with-db-transaction [t-con db-spec {:isolation level :read-only? true}] - ... t-con ...) - See db-transaction* for more details." - [binding & body] - `(db-transaction* ~(second binding) - (^{:once true} fn* [~(first binding)] ~@body) - ~@(rest (rest binding)))) - -(defmacro with-db-connection - "Evaluates body in the context of an active connection to the database. - (with-db-connection [con-db db-spec opts] - ... con-db ...)" - [binding & body] - `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] - (with-open [con# (get-connection db-spec# opts#)] - (let [~(first binding) (add-connection db-spec# con#)] - ~@body)))) - -(defmacro with-db-metadata - "Evaluates body in the context of an active connection with metadata bound - to the specified name. See also metadata-result for dealing with the results - of operations that retrieve information from the metadata. - (with-db-metadata [md db-spec opts] - ... md ...)" - [binding & body] - `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] - (with-open [con# (get-connection db-spec# opts#)] - (let [~(first binding) (.getMetaData con#)] - ~@body)))) - -(defn metadata-result - "If the argument is a java.sql.ResultSet, turn it into a result-set-seq, - else return it as-is. This makes working with metadata easier. - Also accepts an option map containing :identifiers, :keywordize?, :qualifier, - :as-arrays?, :row-fn,and :result-set-fn to control how the ResultSet is - transformed and returned. See query for more details." - ([rs-or-value] (metadata-result rs-or-value {})) - ([rs-or-value opts] - (let [{:keys [as-arrays? result-set-fn row-fn] :as opts} - (merge {:identifiers str/lower-case - :keywordize? true - :read-columns dft-read-columns - :row-fn identity} opts) - result-set-fn (or result-set-fn (if as-arrays? vec doall))] - (if (instance? java.sql.ResultSet rs-or-value) - ((^{:once true} fn* [rs] - (result-set-fn (if as-arrays? - (cons (first rs) - (map row-fn (rest rs))) - (map row-fn rs)))) - (result-set-seq rs-or-value opts)) - rs-or-value)))) - -(defmacro metadata-query - "Given a Java expression that extracts metadata (in the context of with-db-metadata), - and a map of options like metadata-result, manage the connection for a single - metadata-based query. Example usage: - - (with-db-metadata [meta db-spec] - (metadata-query (.getTables meta nil nil nil (into-array String [\"TABLE\"])) - {:row-fn ... :result-set-fn ...}))" - [meta-query & opt-args] - `(with-open [rs# ~meta-query] - (metadata-result rs# ~@opt-args))) - -(defn db-do-commands - "Executes SQL commands on the specified database connection. Wraps the commands - in a transaction if transaction? is true. transaction? can be ommitted and it - defaults to true. Accepts a single SQL command (string) or a vector of them. - Uses executeBatch. This may affect what SQL you can run via db-do-commands." - ([db sql-commands] - (db-do-commands db true (if (string? sql-commands) [sql-commands] sql-commands))) - ([db transaction? sql-commands] - (if (string? sql-commands) - (db-do-commands db transaction? [sql-commands]) - (if-let [con (db-find-connection db)] - (with-open [^Statement stmt (.createStatement con)] - (doseq [^String cmd sql-commands] - (.addBatch stmt cmd)) - (if transaction? - (with-db-transaction [t-db (add-connection db (.getConnection stmt))] - (execute-batch stmt)) - (execute-batch stmt))) - (with-open [con (get-connection db)] - (db-do-commands (add-connection db con) transaction? sql-commands)))))) - -(defn- db-do-execute-prepared-return-keys - "Executes a PreparedStatement, optionally in a transaction, and (attempts to) - return any generated keys." - [db ^PreparedStatement stmt param-group opts] - (let [{:keys [transaction?] :as opts} (merge (when (map? db) db) opts) - exec-and-return-keys - (^{:once true} fn* [] - (let [counts (.executeUpdate stmt)] - (try - (let [rs (.getGeneratedKeys stmt) - result (first (result-set-seq rs opts))] - ;; sqlite (and maybe others?) requires - ;; record set to be closed - (.close rs) - result) - (catch Exception _ - ;; assume generated keys is unsupported and return counts instead: - counts))))] - ((:set-parameters opts dft-set-parameters) stmt param-group) - (if transaction? - (with-db-transaction [t-db (add-connection db (.getConnection stmt))] - (exec-and-return-keys)) - (exec-and-return-keys)))) - -(defn- sql-stmt? - "Given an expression, return true if it is either a string (SQL) or a - PreparedStatement." - [expr] - (or (string? expr) (instance? PreparedStatement expr))) - -(defn db-do-prepared-return-keys - "Executes an (optionally parameterized) SQL prepared statement on the - open database connection. The param-group is a seq of values for all of - the parameters. transaction? can be ommitted and will default to true. - Return the generated keys for the (single) update/insert. - A PreparedStatement may be passed in, instead of a SQL string, in which - case :return-keys MUST BE SET on that PreparedStatement!" - ([db sql-params] - (db-do-prepared-return-keys db true sql-params {})) - ([db transaction? sql-params] - (if (map? sql-params) - (db-do-prepared-return-keys db true transaction? sql-params) - (db-do-prepared-return-keys db transaction? sql-params {}))) - ([db transaction? sql-params opts] - (let [opts (merge (when (map? db) db) opts)] - (if-let [con (db-find-connection db)] - (let [[sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] - (if (instance? PreparedStatement sql) - (db-do-execute-prepared-return-keys db sql params (assoc opts :transaction? transaction?)) - (with-open [^PreparedStatement stmt (prepare-statement con sql (assoc opts :return-keys true))] - (db-do-execute-prepared-return-keys db stmt params (assoc opts :transaction? transaction?))))) - (with-open [con (get-connection db opts)] - (db-do-prepared-return-keys (add-connection db con) transaction? sql-params opts)))))) - -(defn- db-do-execute-prepared-statement - "Execute a PreparedStatement, optionally in a transaction." - [db ^PreparedStatement stmt param-groups opts] - (let [{:keys [transaction?] :as opts} (merge (when (map? db) db) opts)] - (if (empty? param-groups) - (if transaction? - (with-db-transaction [t-db (add-connection db (.getConnection stmt))] - (vector (.executeUpdate stmt))) - (vector (.executeUpdate stmt))) - (do - (doseq [param-group param-groups] - ((:set-parameters opts dft-set-parameters) stmt param-group) - (.addBatch stmt)) - (if transaction? - (with-db-transaction [t-db (add-connection db (.getConnection stmt))] - (execute-batch stmt)) - (execute-batch stmt)))))) - -(defn db-do-prepared - "Executes an (optionally parameterized) SQL prepared statement on the - open database connection. Each param-group is a seq of values for all of - the parameters. transaction? can be omitted and defaults to true. - The sql parameter can either be a SQL string or a PreparedStatement. - Return a seq of update counts (one count for each param-group)." - ([db sql-params] - (db-do-prepared db true sql-params {})) - ([db transaction? sql-params] - (if (map? sql-params) - (db-do-prepared db true transaction? sql-params) - (db-do-prepared db transaction? sql-params {}))) - ([db transaction? sql-params opts] - (let [opts (merge (when (map? db) db) opts)] - (if-let [con (db-find-connection db)] - (let [[sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params)) - params (if (or (:multi? opts) (empty? params)) params [params])] - (if (instance? PreparedStatement sql) - (db-do-execute-prepared-statement db sql params (assoc opts :transaction? transaction?)) - (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] - (db-do-execute-prepared-statement db stmt params (assoc opts :transaction? transaction?))))) - (with-open [con (get-connection db opts)] - (db-do-prepared (add-connection db con) transaction? sql-params opts)))))) - -(defn db-query-with-resultset - "Executes a query, then evaluates func passing in the raw ResultSet as an - argument. The second argument is a vector containing either: - [sql & params] - a SQL query, followed by any parameters it needs - [stmt & params] - a PreparedStatement, followed by any parameters it needs - (the PreparedStatement already contains the SQL query) - The opts map is passed to prepare-statement. - Uses executeQuery. This may affect what SQL you can run via query." - ([db sql-params func] (db-query-with-resultset db sql-params func {})) - ([db sql-params func opts] - (let [opts (merge (when (map? db) db) opts) - [sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params)) - run-query-with-params (^{:once true} fn* [^PreparedStatement stmt] - ((:set-parameters opts dft-set-parameters) stmt params) - (with-open [rset (.executeQuery stmt)] - (func rset)))] - (when-not (sql-stmt? sql) - (let [^Class sql-class (class sql) - ^String msg (format "\"%s\" expected %s %s, found %s %s" - "sql-params" - "vector" - "[sql param*]" - (.getName sql-class) - (pr-str sql))] - (throw (IllegalArgumentException. msg)))) - (if (instance? PreparedStatement sql) - (let [^PreparedStatement stmt sql] - (run-query-with-params stmt)) - (if-let [con (db-find-connection db)] - (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] - (run-query-with-params stmt)) - (with-open [con (get-connection db opts)] - (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] - (run-query-with-params stmt)))))))) - -;; top-level API for actual SQL operations - -(defn query - "Given a database connection and a vector containing SQL and optional parameters, - perform a simple database query. The options specify how to construct the result - set (and are also passed to prepare-statement as needed): - :as-arrays? - return the results as a set of arrays, default false. - :identifiers - applied to each column name in the result set, default lower-case - :keywordize? - defaults to true, can be false to opt-out of converting - identifiers to keywords - :qualifier - optionally provides the namespace qualifier for identifiers - :result-set-fn - applied to the entire result set, default doall / vec - if :as-arrays? true, :result-set-fn will default to vec - if :as-arrays? false, :result-set-fn will default to doall - :row-fn - applied to each row as the result set is constructed, default identity - The second argument is a vector containing a SQL string or PreparedStatement, followed - by any parameters it needs. - See also prepare-statement for additional options." - ([db sql-params] (query db sql-params {})) - ([db sql-params opts] - (let [{:keys [as-arrays? explain? explain-fn result-set-fn row-fn] :as opts} - (merge {:explain-fn println :identifiers str/lower-case - :keywordize? true - :read-columns dft-read-columns :row-fn identity} - (when (map? db) db) - opts) - result-set-fn (or result-set-fn (if as-arrays? vec doall)) - sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] - (when (and explain? (string? (first sql-params-vector))) - (query db (into [(str (if (string? explain?) explain? "EXPLAIN") - " " - (first sql-params-vector))] - (rest sql-params-vector)) - (-> opts - (dissoc :explain? :result-set-fn :row-fn) - (assoc :result-set-fn explain-fn)))) - (db-query-with-resultset db sql-params-vector - (^{:once true} fn* [rset] - ((^{:once true} fn* [rs] - (result-set-fn (if as-arrays? - (cons (first rs) - (map row-fn (rest rs))) - (map row-fn rs)))) - (result-set-seq rset opts))) - opts)))) - -;; performance notes -- goal is to lift as much logic as possible into a "once" -;; pass (so reducible-query preloads all the stuff that doesn't depend on the -;; query results, and then you can repeatedly reduce it, which runs the query -;; each time and runs the minimal result set reduction) -;; turn make-cols-unique into a transducer and optimize it -;; turn make-keys into a transducer pipeline and lift it -;; lift identifier-fn out as make-identifier-fn and refactor -;; lift init-reduce -;; refactor reducible-result-set to lift identifier-fn out -;; call new reducible-result-set version from reducible-query (after calling -;; make-identifier-fn etc) -;; create an optimized version of db-query-with-resultset without :as-arrays? -;; and with options handling lifted - -(defn reducible-result-set - "Given a java.sql.ResultSet return a reducible collection. - Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce - Note: :as-arrays? is not accepted here." - [^ResultSet rs {:keys [identifiers keywordize? qualifier read-columns] - :or {identifiers str/lower-case - keywordize? true - read-columns dft-read-columns}}] - (let [identifier-fn (cond (and qualifier (not keywordize?)) - (throw (IllegalArgumentException. - (str ":qualifier is not allowed unless " - ":keywordize? is true"))) - (and qualifier keywordize?) - (comp (partial keyword qualifier) identifiers) - keywordize? - (comp keyword identifiers) - :else - identifiers) - make-keys (fn [idxs ^ResultSetMetaData rsmeta] - (->> idxs - (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) - (make-cols-unique) - (mapv identifier-fn))) - init-reduce (fn [keys ^ResultSet rs rsmeta idxs f init] - (loop [init' init] - (if (.next rs) - (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] - (if (reduced? result) - @result - (recur result))) - init')))] - (reify clojure.lang.IReduce - (reduce [this f] - (let [rsmeta (.getMetaData rs) - idxs (range 1 (inc (.getColumnCount rsmeta))) - keys (make-keys idxs rsmeta)] - (if (.next rs) - ;; reduce init is first row of ResultSet - (init-reduce keys rs rsmeta idxs f - (zipmap keys (read-columns rs rsmeta idxs))) - ;; no rows so call 0-arity f to get result value - ;; per reduce docstring contract - (f)))) - (reduce [this f init] - (let [rsmeta (.getMetaData rs) - idxs (range 1 (inc (.getColumnCount rsmeta))) - keys (make-keys idxs rsmeta)] - (init-reduce keys rs rsmeta idxs f init)))))) - -(defn reducible-query - "Given a database connection, a vector containing SQL and optional parameters, - return a reducible collection. When reduced, it will start the database query - and reduce the result set, and then close the connection: - (transduce (map :cost) + (reducible-query db sql-params)) - Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce - The following options from query etc are not accepted here: - :as-arrays? :explain :explain-fn :result-set-fn :row-fn - See also prepare-statement for additional options." - ([db sql-params] (reducible-query db sql-params {})) - ([db sql-params opts] - (let [opts (merge {:identifiers str/lower-case :keywordize? true - :read-columns dft-read-columns} - (when (map? db) db) - opts) - sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] - (reify clojure.lang.IReduce - (reduce [this f] - (db-query-with-resultset - db sql-params-vector - (^{:once true} fn* [rset] - (reduce f (reducible-result-set rset opts))) - opts)) - (reduce [this f init] - (db-query-with-resultset - db sql-params-vector - (^{:once true} fn* [rset] - (reduce f init (reducible-result-set rset opts))) - opts)))))) - -(defn- direction - "Given an entities function, a column name, and a direction, - return the matching SQL column / order. - Throw an exception for an invalid direction." - [entities c d] - (str (as-sql-name entities c) " " - (if-let [dir (#{"ASC" "DESC"} (str/upper-case (name d)))] - dir - (throw (IllegalArgumentException. (str "expected :asc or :desc, found: " d)))))) - -(defn- order-by-sql - "Given a sequence of column specs and an entities function, return - a SQL fragment for the ORDER BY clause. A column spec may be a name - (either a string or keyword) or a map from column name to direction - (:asc or :desc)." - [order-by entities] - (str/join ", " (mapcat (fn [col] - (if (map? col) - (reduce-kv (fn [v c d] - (conj v (direction entities c d))) - [] - col) - [(direction entities col :asc)])) order-by))) - -(defn find-by-keys - "Given a database connection, a table name, a map of column name/value - pairs, and an optional options map, return any matching rows. - An :order-by option may be supplied to sort the rows by a sequence of - columns, e.g,. {:order-by [:name {:age :desc]}" - ([db table columns] (find-by-keys db table columns {})) - ([db table columns opts] - (let [{:keys [entities order-by] :as opts} - (merge {:entities identity} (when (map? db) db) opts) - ks (keys columns) - vs (vals columns)] - (query db (into [(str "SELECT * FROM " (table-str table entities) - " WHERE " (str/join " AND " - (kv-sql ks vs entities " IS NULL")) - (when (seq order-by) - (str " ORDER BY " - (order-by-sql order-by entities))))] - (remove nil? vs)) - opts)))) - -(defn get-by-id - "Given a database connection, a table name, a primary key value, an - optional primary key column name, and an optional options map, return - a single matching row, or nil. - The primary key column name defaults to :id." - ([db table pk-value] (get-by-id db table pk-value :id {})) - ([db table pk-value pk-name-or-opts] - (if (map? pk-name-or-opts) - (get-by-id db table pk-value :id pk-name-or-opts) - (get-by-id db table pk-value pk-name-or-opts {}))) - ([db table pk-value pk-name opts] - (let [opts (merge (when (map? db) db) opts) - r-s-fn (or (:result-set-fn opts) identity)] - (find-by-keys db table {pk-name pk-value} - (assoc opts :result-set-fn (comp first r-s-fn)))))) - -(defn execute! - "Given a database connection and a vector containing SQL (or PreparedStatement) - followed by optional parameters, perform a general (non-select) SQL operation. - The :transaction? option specifies whether to run the operation in a - transaction or not (default true). - If the :multi? option is false (the default), the SQL statement should be - followed by the parameters for that statement. - If the :multi? option is true, the SQL statement should be followed by one or - more vectors of parameters, one for each application of the SQL statement. - If there are no parameters specified, executeUpdate will be used, otherwise - executeBatch will be used. This may affect what SQL you can run via execute!" - ([db sql-params] (execute! db sql-params {})) - ([db sql-params opts] - (let [{:keys [transaction?] :as opts} - (merge {:transaction? true :multi? false} (when (map? db) db) opts) - execute-helper (^{:once true} fn* [db] - (db-do-prepared db transaction? sql-params opts))] - (if-let [con (db-find-connection db)] - (execute-helper db) - (with-open [con (get-connection db opts)] - (execute-helper (add-connection db con))))))) - -(defn- delete-sql - "Given a table name, a where class and its parameters and an optional entities spec, - return a vector of the SQL for that delete operation followed by its parameters. The - entities spec (default 'as-is') specifies how to transform column names." - [table [where & params] entities] - (into [(str "DELETE FROM " (table-str table entities) - (when where " WHERE ") where)] - params)) - -(defn delete! - "Given a database connection, a table name and a where clause of columns to match, - perform a delete. The options may specify how to transform column names in the - map (default 'as-is') and whether to run the delete in a transaction (default true). - Example: - (delete! db :person [\"zip = ?\" 94546]) - is equivalent to: - (execute! db [\"DELETE FROM person WHERE zip = ?\" 94546])" - ([db table where-clause] (delete! db table where-clause {})) - ([db table where-clause opts] - (let [{:keys [entities] :as opts} - (merge {:entities identity :transaction? true} (when (map? db) db) opts)] - (execute! db (delete-sql table where-clause entities) opts)))) - -(defn- multi-insert-helper - "Given a (connected) database connection and some SQL statements (for multiple - inserts), run a prepared statement on each and return any generated keys. - Note: we are eager so an unrealized lazy-seq cannot escape from the connection." - [db stmts opts] - (doall (map (fn [row] (db-do-prepared-return-keys db false row opts)) stmts))) - -(defn- insert-helper - "Given a (connected) database connection, a transaction flag and some SQL statements - (for one or more inserts), run a prepared statement or a sequence of them." - [db transaction? stmts opts] - (if transaction? - (with-db-transaction [t-db db] (multi-insert-helper t-db stmts opts)) - (multi-insert-helper db stmts opts))) - -(defn- col-str - "Transform a column spec to an entity name for SQL. The column spec may be a - string, a keyword or a map with a single pair - column name and alias." - [col entities] - (if (map? col) - (let [[k v] (first col)] - (str (as-sql-name entities k) " AS " (as-sql-name entities v))) - (as-sql-name entities col))) - -(defn- insert-multi-row-sql - "Given a table and a list of columns, followed by a list of column value sequences, - return a vector of the SQL needed for the insert followed by the list of column - value sequences. The entities function specifies how column names are transformed." - [table columns values entities] - (let [nc (count columns) - vcs (map count values)] - (if (not (and (or (zero? nc) (= nc (first vcs))) (apply = vcs))) - (throw (IllegalArgumentException. "insert! called with inconsistent number of columns / values")) - (into [(str "INSERT INTO " (table-str table entities) - (when (seq columns) - (str " ( " - (str/join ", " (map (fn [col] (col-str col entities)) columns)) - " )")) - " VALUES ( " - (str/join ", " (repeat (first vcs) "?")) - " )")] - values)))) - -(defn- insert-single-row-sql - "Given a table and a map representing a row, return a vector of the SQL needed for - the insert followed by the list of column values. The entities function specifies - how column names are transformed." - [table row entities] - (let [ks (keys row)] - (into [(str "INSERT INTO " (table-str table entities) " ( " - (str/join ", " (map (fn [col] (col-str col entities)) ks)) - " ) VALUES ( " - (str/join ", " (repeat (count ks) "?")) - " )")] - (vals row)))) - -(defn- insert-rows! - "Given a database connection, a table name, a sequence of rows, and an options - map, insert the rows into the database." - [db table rows opts] - (let [{:keys [entities transaction?] :as opts} - (merge {:entities identity :identifiers str/lower-case - :keywordize? true :transaction? true} - (when (map? db) db) - opts) - sql-params (map (fn [row] - (when-not (map? row) - (throw (IllegalArgumentException. "insert! / insert-multi! called with a non-map row"))) - (insert-single-row-sql table row entities)) rows)] - (if-let [con (db-find-connection db)] - (insert-helper db transaction? sql-params opts) - (with-open [con (get-connection db opts)] - (insert-helper (add-connection db con) transaction? sql-params opts))))) - -(defn- insert-cols! - "Given a database connection, a table name, a sequence of columns names, a - sequence of vectors of column values, one per row, and an options map, - insert the rows into the database." - [db table cols values opts] - (let [{:keys [entities transaction?] :as opts} - (merge {:entities identity :transaction? true} (when (map? db) db) opts) - sql-params (insert-multi-row-sql table cols values entities)] - (if-let [con (db-find-connection db)] - (db-do-prepared db transaction? sql-params (assoc opts :multi? true)) - (with-open [con (get-connection db opts)] - (db-do-prepared (add-connection db con) transaction? sql-params - (assoc opts :multi? true)))))) - -(defn insert! - "Given a database connection, a table name and either a map representing a rows, - or a list of column names followed by a list of column values also representing - a single row, perform an insert. - When inserting a row as a map, the result is the database-specific form of the - generated keys, if available (note: PostgreSQL returns the whole row). - When inserting a row as a list of column values, the result is the count of - rows affected (1), if available (from getUpdateCount after executeBatch). - The row map or column value vector may be followed by a map of options: - The :transaction? option specifies whether to run in a transaction or not. - The default is true (use a transaction). The :entities option specifies how - to convert the table name and column names to SQL entities." - ([db table row] (insert! db table row {})) - ([db table cols-or-row values-or-opts] - (if (map? values-or-opts) - (insert-rows! db table [cols-or-row] values-or-opts) - (insert-cols! db table cols-or-row [values-or-opts] {}))) - ([db table cols values opts] - (insert-cols! db table cols [values] opts))) - -(defn insert-multi! - "Given a database connection, a table name and either a sequence of maps (for - rows) or a sequence of column names, followed by a sequence of vectors (for - the values in each row), and possibly a map of options, insert that data into - the database. - - When inserting rows as a sequence of maps, the result is a sequence of the - generated keys, if available (note: PostgreSQL returns the whole rows). A - separate database operation is used for each row inserted. This may be slow - for if a large sequence of maps is provided. - - When inserting rows as a sequence of lists of column values, the result is - a sequence of the counts of rows affected (a sequence of 1's), if available. - Yes, that is singularly unhelpful. Thank you getUpdateCount and executeBatch! - A single database operation is used to insert all the rows at once. This may - be much faster than inserting a sequence of rows (which performs an insert for - each map in the sequence). - - The :transaction? option specifies whether to run in a transaction or not. - The default is true (use a transaction). The :entities option specifies how - to convert the table name and column names to SQL entities." - ([db table rows] (insert-rows! db table rows {})) - ([db table cols-or-rows values-or-opts] - (if (map? values-or-opts) - (insert-rows! db table cols-or-rows values-or-opts) - (insert-cols! db table cols-or-rows values-or-opts {}))) - ([db table cols values opts] - (insert-cols! db table cols values opts))) - -(defn- update-sql - "Given a table name, a map of columns to set, a optional map of columns to - match, and an entities, return a vector of the SQL for that update followed - by its parameters. Example: - (update :person {:zip 94540} [\"zip = ?\" 94546] identity) - returns: - [\"UPDATE person SET zip = ? WHERE zip = ?\" 94540 94546]" - [table set-map [where & params] entities] - (let [ks (keys set-map) - vs (vals set-map)] - (cons (str "UPDATE " (table-str table entities) - " SET " (str/join - "," - (kv-sql ks vs entities " = NULL")) - (when where " WHERE ") - where) - (concat (remove nil? vs) params)))) - -(defn update! - "Given a database connection, a table name, a map of column values to set and a - where clause of columns to match, perform an update. The options may specify - how column names (in the set / match maps) should be transformed (default - 'as-is') and whether to run the update in a transaction (default true). - Example: - (update! db :person {:zip 94540} [\"zip = ?\" 94546]) - is equivalent to: - (execute! db [\"UPDATE person SET zip = ? WHERE zip = ?\" 94540 94546])" - ([db table set-map where-clause] (update! db table set-map where-clause {})) - ([db table set-map where-clause opts] - (let [{:keys [entities] :as opts} - (merge {:entities identity :transaction? true} (when (map? db) db) opts)] - (execute! db (update-sql table set-map where-clause entities) opts)))) - -(defn create-table-ddl - "Given a table name and a vector of column specs, return the DDL string for - creating that table. Each column spec is, in turn, a vector of keywords or - strings that is converted to strings and concatenated with spaces to form - a single column description in DDL, e.g., - [:cost :int \"not null\"] - [:name \"varchar(32)\"] - The first element of a column spec is treated as a SQL entity (so if you - provide the :entities option, that will be used to transform it). The - remaining elements are left as-is when converting them to strings. - An options map may be provided that can contain: - :table-spec -- a string that is appended to the DDL -- and/or - :entities -- a function to specify how column names are transformed. - :conditional? -- either a boolean, indicating whether to add 'IF NOT EXISTS', - or a string, which is inserted literally before the table name, or a - function of two arguments (table name and the create statement), that can - manipulate the generated statement to better support other databases, e.g., - MS SQL Server which need to wrap create table in an existence query." - ([table specs] (create-table-ddl table specs {})) - ([table specs opts] - (let [table-spec (:table-spec opts) - conditional? (:conditional? opts) - entities (:entities opts identity) - table-name (as-sql-name entities table) - table-spec-str (or (and table-spec (str " " table-spec)) "") - spec-to-string (fn [spec] - (try - (str/join " " (cons (as-sql-name entities (first spec)) - (map name (rest spec)))) - (catch Exception _ - (throw (IllegalArgumentException. - "column spec is not a sequence of keywords / strings")))))] - (cond->> (format "CREATE TABLE%s %s (%s)%s" - (cond (or (nil? conditional?) - (instance? Boolean conditional?)) - (if conditional? " IF NOT EXISTS" "") - (fn? conditional?) - "" - :else - (str " " conditional?)) - table-name - (str/join ", " (map spec-to-string specs)) - table-spec-str) - (fn? conditional?) (conditional? table-name))))) - -(defn drop-table-ddl - "Given a table name, return the DDL string for dropping that table. - An options map may be provided that can contain: - :entities -- a function to specify how column names are transformed. - :conditional? -- either a boolean, indicating whether to add 'IF EXISTS', - or a string, which is inserted literally before the table name, or a - function of two arguments (table name and the create statement), that can - manipulate the generated statement to better support other databases, e.g., - MS SQL Server which need to wrap create table in an existence query." - ([table] (drop-table-ddl table {})) - ([table {:keys [entities conditional?] :or {entities identity}}] - (let [table-name (as-sql-name entities table)] - (cond->> (format "DROP TABLE%s %s" - (cond (or (nil? conditional?) - (instance? Boolean conditional?)) - (if conditional? " IF EXISTS" "") - (fn? conditional?) - "" - :else - (str " " conditional?)) - table-name) - (fn? conditional?) (conditional? table-name))))) +;; Copyright (c) 2008-2017 Sean Corfield, 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. +;; +;; jdbc.clj +;; +;; A Clojure interface to sql databases via jdbc +;; +;; scgilardi (gmail) +;; Created 2 April 2008 +;; +;; seancorfield (gmail) +;; Migrated from clojure.contrib.sql 17 April 2011 + +(ns + ^{:author "Stephen C. Gilardi, Sean Corfield", + :doc "A Clojure interface to SQL databases via JDBC + +clojure.java.jdbc provides a simple abstraction for CRUD (create, read, +update, delete) operations on a SQL database, along with basic transaction +support. Basic DDL operations are also supported (create table, drop table, +access to table metadata). + +Maps are used to represent records, making it easy to store and retrieve +data. Results can be processed using any standard sequence operations. + +For most operations, Java's PreparedStatement is used so your SQL and +parameters can be represented as simple vectors where the first element +is the SQL string, with ? for each parameter, and the remaining elements +are the parameter values to be substituted. In general, operations return +the number of rows affected, except for a single record insert where any +generated keys are returned (as a map). + +For more documentation, see: + +http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} + clojure.java.jdbc + (:require [clojure.set :as set] + [clojure.string :as str] + [clojure.walk :as walk]) + (:import (java.net URI) + (java.sql BatchUpdateException DriverManager + PreparedStatement ResultSet ResultSetMetaData + SQLException Statement Types) + (java.util Hashtable Map Properties) + (javax.sql DataSource))) + +(defn as-sql-name + "Given a naming strategy function and a keyword or string, return + a string per that naming strategy. + A name of the form x.y is treated as multiple names, x, y, etc, + and each are turned into strings via the naming strategy and then + joined back together so x.y might become `x`.`y` if the naming + strategy quotes identifiers with `." + [f x] + (let [n (name x) + i (.indexOf n (int \.))] + (if (= -1 i) + (f n) + (str/join "." (map f (.split n "\\.")))))) + +(defn quoted + "Given a (vector) pair of delimiters (characters or strings), return a naming + strategy function that will quote SQL entities with them. + Given a single delimiter, treat it as a (vector) pair of that delimiter. + ((quoted [\\[ \\]]) \"foo\") will return \"[foo]\" -- for MS SQL Server + ((quoted \\`') \"foo\") will return \"`foo`\" -- for MySQL + Intended to be used with :entities to provide a quoting (naming) strategy that + is appropriate for your database." + [q] + (cond (vector? q) + (fn [x] + (str (first q) x (last q))) + (keyword? q) + (case q + :ansi (quoted \") + :mysql (quoted \`) + :oracle (quoted \") + :sqlserver (quoted [\[ \]])) + :else + (quoted [q q]))) + +(defn- table-str + "Transform a table spec to an entity name for SQL. The table spec may be a + string, a keyword or a map with a single pair - table name and alias." + [table entities] + (let [entities (or entities identity)] + (if (map? table) + (let [[k v] (first table)] + (str (as-sql-name entities k) " " (as-sql-name entities v))) + (as-sql-name entities table)))) + +(defn- kv-sql + "Given a sequence of column name keys and a matching sequence of column + values, and an entities mapping function, return a sequence of SQL fragments + that can be joined for part of an UPDATE SET or a SELECT WHERE clause. + Note that we pass the appropriate operator for NULL since it is different + in each case." + [ks vs entities null-op] + (map (fn [k v] + (str (as-sql-name entities k) + (if (nil? v) null-op " = ?"))) + ks vs)) + +(defn- ^Properties as-properties + "Convert any seq of pairs to a java.utils.Properties instance. + Uses as-sql-name to convert both keys and values into strings." + [m] + (let [p (Properties.)] + (doseq [[k v] m] + (.setProperty p (as-sql-name identity k) + (if (instance? clojure.lang.Named v) + (as-sql-name identity v) + (str v)))) + p)) + +;; convenience for working with different forms of connections +(defprotocol Connectable + (add-connection [db connection]) + (get-level [db])) + +(defn- inc-level + "Increment the nesting level for a transacted database connection. + If we are at the top level, also add in a rollback state." + [db] + (let [nested-db (update-in db [:level] (fnil inc 0))] + (if (= 1 (:level nested-db)) + (assoc nested-db :rollback (atom false)) + nested-db))) + +(extend-protocol Connectable + String + (add-connection [s connection] {:connection connection :level 0 :connection-string s}) + (get-level [_] 0) + + clojure.lang.Associative + (add-connection [m connection] (assoc m :connection connection)) + (get-level [m] (or (:level m) 0)) + + nil + (add-connection [_ connection] {:connection connection :level 0 :legacy true}) + (get-level [_] 0)) + +(def ^:private classnames + "Map of subprotocols to classnames. dbtype specifies one of these keys. + The subprotocols map below provides aliases for dbtype." + {"derby" "org.apache.derby.jdbc.EmbeddedDriver" + "h2" "org.h2.Driver" + "hsqldb" "org.hsqldb.jdbcDriver" + "jtds:sqlserver" "net.sourceforge.jtds.jdbc.Driver" + "mysql" "com.mysql.jdbc.Driver" + "oracle:oci" "oracle.jdbc.OracleDriver" + "oracle:thin" "oracle.jdbc.OracleDriver" + "postgresql" "org.postgresql.Driver" + "pgsql" "com.impossibl.postgres.jdbc.PGDriver" + "redshift" "com.amazon.redshift.jdbc.Driver" + "sqlite" "org.sqlite.JDBC" + "sqlserver" "com.microsoft.sqlserver.jdbc.SQLServerDriver"}) + +(def ^:private subprotocols + "Map of schemes to subprotocols. Used to provide aliases for dbtype." + {"hsql" "hsqldb" + "jtds" "jtds:sqlserver" + "mssql" "sqlserver" + "oracle" "oracle:thin" + "postgres" "postgresql"}) + +(def ^:private host-prefixes + "Map of subprotocols to non-standard host-prefixes. + Anything not listed is assumed to use //." + {"oracle:oci" "@" + "oracle:thin" "@"}) + +(defn- parse-properties-uri [^URI uri] + (let [host (.getHost uri) + port (if (pos? (.getPort uri)) (.getPort uri)) + path (.getPath uri) + scheme (.getScheme uri) + subprotocol (subprotocols scheme scheme) + host-prefix (host-prefixes subprotocol "//") + ^String query (.getQuery uri) + query-parts (and query + (for [^String kvs (.split query "&")] + ((juxt first second) (.split kvs "="))))] + (merge + {:subname (if host + (if port + (str host-prefix host ":" port path) + (str host-prefix host path)) + (.getSchemeSpecificPart uri)) + :subprotocol subprotocol} + (if-let [user-info (.getUserInfo uri)] + {:user (first (str/split user-info #":")) + :password (second (str/split user-info #":"))}) + (walk/keywordize-keys (into {} query-parts))))) + +(defn- strip-jdbc [^String spec] + (if (.startsWith spec "jdbc:") + (.substring spec 5) + spec)) + +;; feature testing macro, based on suggestion from Chas Emerick: +(defmacro when-available + [sym & body] + (try + (when (resolve sym) + (list* 'do body)) + (catch ClassNotFoundException _#))) + +(defn- modify-connection + "Given a database connection and a map of options, update the connection + as specified by the options." + ^java.sql.Connection + [^java.sql.Connection connection opts] + (when (contains? opts :auto-commit?) + (.setAutoCommit connection (:auto-commit? opts))) + (when (contains? opts :read-only?) + (.setReadOnly connection (:read-only? opts))) + connection) + +(defn get-connection + "Creates a connection to a database. db-spec is usually a map containing connection + parameters but can also be a URI or a String. The various possibilities are described + below: + + DriverManager (preferred): + :dbtype (required) a String, the type of the database (the jdbc subprotocol) + :dbname (required) a String, the name of the database + :classname (optional) a String, the jdbc driver class name + :host (optional) a String, the host name/IP of the database + (defaults to 127.0.0.1) + :port (optional) a Long, the port of the database + (defaults to 3306 for mysql, 1433 for mssql/jtds, else nil) + (others) (optional) passed to the driver as properties + (may include :user and :password) + + Raw: + :connection-uri (required) a String + Passed directly to DriverManager/getConnection + (both :user and :password may be specified as well, rather + than passing them as part of the connection string) + + Other formats accepted: + + Existing Connection: + :connection (required) an existing open connection that can be used + but cannot be closed (only the parent connection can be closed) + + DriverManager (alternative / legacy style): + :subprotocol (required) a String, the jdbc subprotocol + :subname (required) a String, the jdbc subname + :classname (optional) a String, the jdbc driver class name + (others) (optional) passed to the driver as properties + (may include :user and :password) + + Factory: + :factory (required) a function of one argument, a map of params + (others) (optional) passed to the factory function in a map + + DataSource: + :datasource (required) a javax.sql.DataSource + :username (optional) a String - deprecated, use :user instead + :user (optional) a String - preferred + :password (optional) a String, required if :user is supplied + + JNDI: + :name (required) a String or javax.naming.Name + :environment (optional) a java.util.Map + + java.net.URI: + Parsed JDBC connection string (see java.lang.String format next) + + java.lang.String: + subprotocol://user:password@host:post/subname + An optional prefix of jdbc: is allowed." + (^java.sql.Connection [db-spec] (get-connection db-spec {})) + (^java.sql.Connection + [{:keys [connection + factory + connection-uri + classname subprotocol subname + dbtype dbname host port + datasource username password user + name environment] + :as db-spec} + opts] + (cond + (string? db-spec) + (get-connection (URI. (strip-jdbc db-spec)) opts) + + (instance? URI db-spec) + (get-connection (parse-properties-uri db-spec) opts) + + connection + connection ;; do not apply opts here + + (or (and datasource username password) ; legacy + (and datasource user password)) ; preferred + (-> (.getConnection ^DataSource datasource + ^String (or username user) + ^String password) + (modify-connection opts)) + + datasource + (-> (.getConnection ^DataSource datasource) + (modify-connection opts)) + + factory + (-> (factory (dissoc db-spec :factory)) + (modify-connection opts)) + + connection-uri + (-> (if (and user password) + (DriverManager/getConnection connection-uri user password) + (DriverManager/getConnection connection-uri)) + (modify-connection opts)) + + (and dbtype dbname) + (let [;; allow aliases for dbtype + subprotocol (subprotocols dbtype dbtype) + host (or host "127.0.0.1") + port (or port (condp = subprotocol + "jtds:sqlserver" 1433 + "mysql" 3306 + "oracle:oci" 1521 + "oracle:thin" 1521 + "postgresql" 5432 + "sqlserver" 1433 + nil)) + db-sep (if (= "sqlserver" subprotocol) ";DATABASENAME=" "/") + url (if (#{"derby" "h2" "hsqldb" "sqlite"} subprotocol) + (str "jdbc:" subprotocol ":" dbname) + (str "jdbc:" subprotocol ":" + (host-prefixes subprotocol "//") + host + (when port (str ":" port)) + db-sep dbname)) + etc (dissoc db-spec :dbtype :dbname)] + (if-let [class-name (or classname (classnames subprotocol))] + (do + ;; force DriverManager to be loaded + (DriverManager/getLoginTimeout) + (clojure.lang.RT/loadClassForName class-name)) + (throw (ex-info (str "Unknown dbtype: " dbtype) db-spec))) + (-> (DriverManager/getConnection url (as-properties etc)) + (modify-connection opts))) + + (and subprotocol subname) + (let [;; allow aliases for subprotocols + subprotocol (subprotocols subprotocol subprotocol) + url (format "jdbc:%s:%s" subprotocol subname) + etc (dissoc db-spec :classname :subprotocol :subname)] + (if-let [class-name (or classname (classnames subprotocol))] + (do + ;; force DriverManager to be loaded + (DriverManager/getLoginTimeout) + (clojure.lang.RT/loadClassForName class-name)) + (throw (ex-info (str "Unknown subprotocol: " subprotocol) db-spec))) + (-> (DriverManager/getConnection url (as-properties etc)) + (modify-connection opts))) + + name + (or (when-available javax.naming.InitialContext + (let [env (and environment (Hashtable. ^Map environment)) + context (javax.naming.InitialContext. env) + ^DataSource datasource (.lookup context ^String name)] + (-> (.getConnection datasource) + (modify-connection opts)))) + (throw (ex-info (str "javax.naming.InitialContext is not available for: " + name) + db-spec))) + + :else + (let [^String msg (format "db-spec %s is missing a required parameter" db-spec)] + (throw (IllegalArgumentException. msg)))))) + +(defn- make-name-unique + "Given a collection of column names and a new column name, + return the new column name made unique, if necessary, by + appending _N where N is some unique integer suffix." + [cols col-name n] + (let [suffixed-name (if (= n 1) col-name (str col-name "_" n))] + (if (apply distinct? suffixed-name cols) + suffixed-name + (recur cols col-name (inc n))))) + +(defn- make-cols-unique + "Given a collection of column names, rename duplicates so + that the result is a collection of unique column names." + [cols] + (if (or (empty? cols) (apply distinct? cols)) + cols + (reduce (fn [unique-cols col-name] + (conj unique-cols (make-name-unique unique-cols col-name 1))) [] cols))) + +(defprotocol ISQLValue + "Protocol for creating SQL values from Clojure values. Default + implementations (for Object and nil) just return the argument, + but it can be extended to provide custom behavior to support + exotic types supported by different databases." + (sql-value [val] "Convert a Clojure value into a SQL value.")) + +(extend-protocol ISQLValue + Object + (sql-value [v] v) + + nil + (sql-value [_] nil)) + +(defprotocol ISQLParameter + "Protocol for setting SQL parameters in statement objects, which + can convert from Clojure values. The default implementation just + delegates the conversion to ISQLValue's sql-value conversion and + uses .setObject on the parameter. 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 (sql-value v))) + + nil + (set-parameter [_ ^PreparedStatement s ^long i] + (.setObject s i (sql-value nil)))) + +(defn- dft-set-parameters + "Default implementation of parameter setting for the given statement." + [stmt params] + (dorun (map-indexed (fn [ix value] + (set-parameter value stmt (inc ix))) + params))) + +(defprotocol IResultSetReadColumn + "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." + (result-set-read-column [val rsmeta idx] + "Function for transforming values after reading them from the database")) + +(extend-protocol IResultSetReadColumn + Object + (result-set-read-column [x _2 _3] x) + + Boolean + (result-set-read-column [x _2 _3] (if (= true x) true false)) + + nil + (result-set-read-column [_1 _2 _3] nil)) + +(defn- dft-read-columns + "Default implementation of reading row values from result set, given the + result set metadata and the indices." + [^ResultSet rs rsmeta idxs] + (mapv (fn [^Integer i] (result-set-read-column (.getObject rs i) rsmeta i)) idxs)) + +(defn result-set-seq + "Creates and returns a lazy sequence of maps corresponding to the rows in the + java.sql.ResultSet rs. Loosely based on clojure.core/resultset-seq but it + respects the specified naming strategy. Duplicate column names are made unique + by appending _N before applying the naming strategy (where N is a unique integer), + unless the :as-arrays? option is :cols-as-is, in which case the column names + are untouched (the result set maintains column name/value order). + The :identifiers option specifies how SQL column names are converted to Clojure + keywords. The default is to convert them to lower case. + The :keywordize? option can be specified as false to opt-out of the conversion + to keywords. + The :qualifier option specifies the namespace qualifier for those identifiers + (and this may not be specified when :keywordize? is false)." + ([rs] (result-set-seq rs {})) + ([^ResultSet rs {:keys [as-arrays? identifiers keywordize? + qualifier read-columns] + :or {identifiers str/lower-case + keywordize? true + read-columns dft-read-columns}}] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + col-name-fn (if (= :cols-as-is as-arrays?) identity make-cols-unique) + identifier-fn (cond (and qualifier (not keywordize?)) + (throw (IllegalArgumentException. + (str ":qualifier is not allowed unless " + ":keywordize? is true"))) + (and qualifier keywordize?) + (comp (partial keyword qualifier) identifiers) + keywordize? + (comp keyword identifiers) + :else + identifiers) + keys (->> idxs + (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) + col-name-fn + (mapv identifier-fn)) + row-values (fn [] (read-columns rs rsmeta 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 + ;; want to switch to records so we're using regular maps instead. We no longer + ;; guarantee column order in rows but using into {} should preserve order for up + ;; to 16 columns (because it will use a PersistentArrayMap). If someone is relying + ;; on the order-preserving behavior of structmaps, we can reconsider... + records (fn thisfn [] + (when (.next rs) + (cons (zipmap keys (row-values)) (lazy-seq (thisfn))))) + rows (fn thisfn [] + (when (.next rs) + (cons (vec (row-values)) (lazy-seq (thisfn)))))] + (if as-arrays? + (cons (vec keys) (rows)) + (records))))) + +(defn- execute-batch + "Executes a batch of SQL commands and returns a sequence of update counts. + (-2) indicates a single operation operating on an unknown number of rows. + Specifically, Oracle returns that and we must call getUpdateCount() to get + the actual number of rows affected. In general, operations return an array + of update counts, so this may not be a general solution for Oracle..." + [^Statement stmt] + (let [result (.executeBatch stmt)] + (if (and (= 1 (count result)) (= -2 (first result))) + (list (.getUpdateCount stmt)) + (seq result)))) + +(def ^{:private true + :doc "Map friendly :concurrency values to ResultSet constants."} + result-set-concurrency + {:read-only ResultSet/CONCUR_READ_ONLY + :updatable ResultSet/CONCUR_UPDATABLE}) + +(def ^{:private true + :doc "Map friendly :cursors values to ResultSet constants."} + result-set-holdability + {:hold ResultSet/HOLD_CURSORS_OVER_COMMIT + :close ResultSet/CLOSE_CURSORS_AT_COMMIT}) + +(def ^{:private true + :doc "Map friendly :type values to ResultSet constants."} + result-set-type + {:forward-only ResultSet/TYPE_FORWARD_ONLY + :scroll-insensitive ResultSet/TYPE_SCROLL_INSENSITIVE + :scroll-sensitive ResultSet/TYPE_SCROLL_SENSITIVE}) + +(defn ^{:tag (class (into-array String []))} string-array + [return-keys] + (into-array String return-keys)) + +(defn prepare-statement + "Create a prepared statement from a connection, a SQL string and a map + of options: + :return-keys truthy | nil - default nil + for some drivers, this may be a vector of column names to identify + the generated keys to return, otherwise it should just be true + :result-type :forward-only | :scroll-insensitive | :scroll-sensitive + :concurrency :read-only | :updatable + :cursors :hold | :close + :fetch-size n + :max-rows n + :timeout n + Note that :result-type and :concurrency must be specified together as the + underlying Java API expects both (or neither)." + ([con sql] (prepare-statement con sql {})) + ([^java.sql.Connection con ^String sql + {:keys [return-keys result-type concurrency cursors + fetch-size max-rows timeout]}] + (let [^PreparedStatement + stmt (cond + return-keys + (do + (when (or result-type concurrency cursors) + (throw (IllegalArgumentException. + (str ":concurrency, :cursors, and :result-type " + "may not be specified with :return-keys.")))) + (try + (if (vector? return-keys) + (try + (.prepareStatement con sql (string-array return-keys)) + (catch Exception _ + ;; assume it is unsupported and try regular generated keys: + (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS))) + (.prepareStatement con sql java.sql.Statement/RETURN_GENERATED_KEYS)) + (catch Exception _ + ;; assume it is unsupported and try basic PreparedStatement: + (.prepareStatement con sql)))) + + (and result-type concurrency) + (if cursors + (.prepareStatement con sql + (get result-set-type result-type result-type) + (get result-set-concurrency concurrency concurrency) + (get result-set-holdability cursors cursors)) + (.prepareStatement con sql + (get result-set-type result-type result-type) + (get result-set-concurrency concurrency concurrency))) + + (or result-type concurrency cursors) + (throw (IllegalArgumentException. + (str ":concurrency, :cursors, and :result-type " + "may not be specified independently."))) + :else + (.prepareStatement con sql))] + (when fetch-size (.setFetchSize stmt fetch-size)) + (when max-rows (.setMaxRows stmt max-rows)) + (when timeout (.setQueryTimeout stmt timeout)) + stmt))) + +(defn print-sql-exception + "Prints the contents of an SQLException to *out*" + [^SQLException exception] + (let [^Class exception-class (class exception)] + (println + (format (str "%s:" \newline + " Message: %s" \newline + " SQLState: %s" \newline + " Error Code: %d") + (.getSimpleName exception-class) + (.getMessage exception) + (.getSQLState exception) + (.getErrorCode exception))))) + +(defn print-sql-exception-chain + "Prints a chain of SQLExceptions to *out*" + [^SQLException exception] + (loop [e exception] + (when e + (print-sql-exception e) + (recur (.getNextException e))))) + +(def ^{:private true} special-counts + {Statement/EXECUTE_FAILED "EXECUTE_FAILED" + Statement/SUCCESS_NO_INFO "SUCCESS_NO_INFO"}) + +(defn print-update-counts + "Prints the update counts from a BatchUpdateException to *out*" + [^BatchUpdateException exception] + (println "Update counts:") + (dorun + (map-indexed + (fn [index count] + (println (format " Statement %d: %s" + index + (get special-counts count count)))) + (.getUpdateCounts exception)))) + +;; java.jdbc pieces rewritten to not use dynamic bindings + +(defn db-find-connection + "Returns the current database connection (or nil if there is none)" + ^java.sql.Connection [db] + (and (map? db) + (:connection db))) + +(defn db-connection + "Returns the current database connection (or throws if there is none)" + ^java.sql.Connection [db] + (or (db-find-connection db) + (throw (Exception. "no current database connection")))) + +(defn db-set-rollback-only! + "Marks the outermost transaction such that it will rollback rather than + commit when complete" + [db] + (reset! (:rollback db) true)) + +(defn db-unset-rollback-only! + "Marks the outermost transaction such that it will not rollback when complete" + [db] + (reset! (:rollback db) false)) + +(defn db-is-rollback-only + "Returns true if the outermost transaction will rollback rather than + commit when complete" + [db] + (deref (:rollback db))) + +(def ^:private + isolation-levels + "Transaction isolation levels." + {:none java.sql.Connection/TRANSACTION_NONE + :read-committed java.sql.Connection/TRANSACTION_READ_COMMITTED + :read-uncommitted java.sql.Connection/TRANSACTION_READ_UNCOMMITTED + :repeatable-read java.sql.Connection/TRANSACTION_REPEATABLE_READ + :serializable java.sql.Connection/TRANSACTION_SERIALIZABLE}) + +(def ^:private isolation-kws + "Map transaction isolation constants to our keywords." + (set/map-invert isolation-levels)) + +(defn get-isolation-level + "Given a db-spec (with an optional connection), return the current + transaction isolation level, if known. Return nil if there is no + active connection in the db-spec. Return :unknown if we do not + recognize the isolation level." + [db] + (when-let [con (db-find-connection db)] + (isolation-kws (.getTransactionIsolation con) :unknown))) + +(defn db-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. + The isolation option may be :none, :read-committed, :read-uncommitted, + :repeatable-read, or :serializable. Note that not all databases support + all of those isolation levels, and may either throw an exception or + substitute another isolation level. + The read-only? option puts the transaction in readonly mode (if supported)." + ([db func] (db-transaction* db func {})) + ([db func opts] + (let [{:keys [isolation read-only?] :as opts} + (merge (when (map? db) db) opts)] + (if (zero? (get-level db)) + (if-let [con (db-find-connection db)] + (let [nested-db (inc-level db) + auto-commit (.getAutoCommit con) + old-isolation (.getTransactionIsolation con) + old-readonly (.isReadOnly con)] + (io! + (when isolation + (.setTransactionIsolation con (isolation isolation-levels))) + (when read-only? + (.setReadOnly con true)) + (.setAutoCommit con false) + (try + (let [result (func nested-db)] + (if (db-is-rollback-only nested-db) + (.rollback con) + (.commit con)) + result) + (catch Throwable t + (try + (.rollback con) + (catch Throwable rb + ;; combine both exceptions + (throw (ex-info (str "Rollback failed handling \"" + (.getMessage t) + "\"") + {:rollback rb + :handling t})))) + (throw t)) + (finally + (db-unset-rollback-only! nested-db) + ;; the following can throw SQLExceptions but we do not + ;; want those to replace any exception currently being + ;; handled -- and if the connection got closed, we just + ;; want to ignore exceptions here anyway + (try + (.setAutoCommit con auto-commit) + (catch Exception _)) + (when isolation + (try + (.setTransactionIsolation con old-isolation) + (catch Exception _))) + (when read-only? + (try + (.setReadOnly con old-readonly) + (catch Exception _))))))) + (with-open [con (get-connection db opts)] + (db-transaction* (add-connection db con) func opts))) + (do + (when (and isolation + (let [con (db-find-connection db)] + (not= (isolation isolation-levels) + (.getTransactionIsolation con)))) + (let [msg "Nested transactions may not have different isolation levels"] + (throw (IllegalStateException. msg)))) + (func (inc-level db))))))) + +(defmacro with-db-transaction + "Evaluates body in the context of a transaction on the specified database connection. + The binding provides the database connection for the transaction and the name to which + that is bound for evaluation of the body. The binding may also specify the isolation + level for the transaction, via the :isolation option and/or set the transaction to + readonly via the :read-only? option. + (with-db-transaction [t-con db-spec {:isolation level :read-only? true}] + ... t-con ...) + See db-transaction* for more details." + [binding & body] + `(db-transaction* ~(second binding) + (^{:once true} fn* [~(first binding)] ~@body) + ~@(rest (rest binding)))) + +(defmacro with-db-connection + "Evaluates body in the context of an active connection to the database. + (with-db-connection [con-db db-spec opts] + ... con-db ...)" + [binding & body] + `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] + (with-open [con# (get-connection db-spec# opts#)] + (let [~(first binding) (add-connection db-spec# con#)] + ~@body)))) + +(defmacro with-db-metadata + "Evaluates body in the context of an active connection with metadata bound + to the specified name. See also metadata-result for dealing with the results + of operations that retrieve information from the metadata. + (with-db-metadata [md db-spec opts] + ... md ...)" + [binding & body] + `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] + (with-open [con# (get-connection db-spec# opts#)] + (let [~(first binding) (.getMetaData con#)] + ~@body)))) + +(defn metadata-result + "If the argument is a java.sql.ResultSet, turn it into a result-set-seq, + else return it as-is. This makes working with metadata easier. + Also accepts an option map containing :identifiers, :keywordize?, :qualifier, + :as-arrays?, :row-fn,and :result-set-fn to control how the ResultSet is + transformed and returned. See query for more details." + ([rs-or-value] (metadata-result rs-or-value {})) + ([rs-or-value opts] + (let [{:keys [as-arrays? result-set-fn row-fn] :as opts} + (merge {:identifiers str/lower-case + :keywordize? true + :read-columns dft-read-columns + :row-fn identity} opts) + result-set-fn (or result-set-fn (if as-arrays? vec doall))] + (if (instance? java.sql.ResultSet rs-or-value) + ((^{:once true} fn* [rs] + (result-set-fn (if as-arrays? + (cons (first rs) + (map row-fn (rest rs))) + (map row-fn rs)))) + (result-set-seq rs-or-value opts)) + rs-or-value)))) + +(defmacro metadata-query + "Given a Java expression that extracts metadata (in the context of with-db-metadata), + and a map of options like metadata-result, manage the connection for a single + metadata-based query. Example usage: + + (with-db-metadata [meta db-spec] + (metadata-query (.getTables meta nil nil nil (into-array String [\"TABLE\"])) + {:row-fn ... :result-set-fn ...}))" + [meta-query & opt-args] + `(with-open [rs# ~meta-query] + (metadata-result rs# ~@opt-args))) + +(defn db-do-commands + "Executes SQL commands on the specified database connection. Wraps the commands + in a transaction if transaction? is true. transaction? can be ommitted and it + defaults to true. Accepts a single SQL command (string) or a vector of them. + Uses executeBatch. This may affect what SQL you can run via db-do-commands." + ([db sql-commands] + (db-do-commands db true (if (string? sql-commands) [sql-commands] sql-commands))) + ([db transaction? sql-commands] + (if (string? sql-commands) + (db-do-commands db transaction? [sql-commands]) + (if-let [con (db-find-connection db)] + (with-open [^Statement stmt (.createStatement con)] + (doseq [^String cmd sql-commands] + (.addBatch stmt cmd)) + (if transaction? + (with-db-transaction [t-db (add-connection db (.getConnection stmt))] + (execute-batch stmt)) + (execute-batch stmt))) + (with-open [con (get-connection db)] + (db-do-commands (add-connection db con) transaction? sql-commands)))))) + +(defn- db-do-execute-prepared-return-keys + "Executes a PreparedStatement, optionally in a transaction, and (attempts to) + return any generated keys." + [db ^PreparedStatement stmt param-group opts] + (let [{:keys [transaction?] :as opts} (merge (when (map? db) db) opts) + exec-and-return-keys + (^{:once true} fn* [] + (let [counts (.executeUpdate stmt)] + (try + (let [rs (.getGeneratedKeys stmt) + result (first (result-set-seq rs opts))] + ;; sqlite (and maybe others?) requires + ;; record set to be closed + (.close rs) + result) + (catch Exception _ + ;; assume generated keys is unsupported and return counts instead: + counts))))] + ((:set-parameters opts dft-set-parameters) stmt param-group) + (if transaction? + (with-db-transaction [t-db (add-connection db (.getConnection stmt))] + (exec-and-return-keys)) + (exec-and-return-keys)))) + +(defn- sql-stmt? + "Given an expression, return true if it is either a string (SQL) or a + PreparedStatement." + [expr] + (or (string? expr) (instance? PreparedStatement expr))) + +(defn db-do-prepared-return-keys + "Executes an (optionally parameterized) SQL prepared statement on the + open database connection. The param-group is a seq of values for all of + the parameters. transaction? can be ommitted and will default to true. + Return the generated keys for the (single) update/insert. + A PreparedStatement may be passed in, instead of a SQL string, in which + case :return-keys MUST BE SET on that PreparedStatement!" + ([db sql-params] + (db-do-prepared-return-keys db true sql-params {})) + ([db transaction? sql-params] + (if (map? sql-params) + (db-do-prepared-return-keys db true transaction? sql-params) + (db-do-prepared-return-keys db transaction? sql-params {}))) + ([db transaction? sql-params opts] + (let [opts (merge (when (map? db) db) opts)] + (if-let [con (db-find-connection db)] + (let [[sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (if (instance? PreparedStatement sql) + (db-do-execute-prepared-return-keys db sql params (assoc opts :transaction? transaction?)) + (with-open [^PreparedStatement stmt (prepare-statement con sql (assoc opts :return-keys true))] + (db-do-execute-prepared-return-keys db stmt params (assoc opts :transaction? transaction?))))) + (with-open [con (get-connection db opts)] + (db-do-prepared-return-keys (add-connection db con) transaction? sql-params opts)))))) + +(defn- db-do-execute-prepared-statement + "Execute a PreparedStatement, optionally in a transaction." + [db ^PreparedStatement stmt param-groups opts] + (let [{:keys [transaction?] :as opts} (merge (when (map? db) db) opts)] + (if (empty? param-groups) + (if transaction? + (with-db-transaction [t-db (add-connection db (.getConnection stmt))] + (vector (.executeUpdate stmt))) + (vector (.executeUpdate stmt))) + (do + (doseq [param-group param-groups] + ((:set-parameters opts dft-set-parameters) stmt param-group) + (.addBatch stmt)) + (if transaction? + (with-db-transaction [t-db (add-connection db (.getConnection stmt))] + (execute-batch stmt)) + (execute-batch stmt)))))) + +(defn db-do-prepared + "Executes an (optionally parameterized) SQL prepared statement on the + open database connection. Each param-group is a seq of values for all of + the parameters. transaction? can be omitted and defaults to true. + The sql parameter can either be a SQL string or a PreparedStatement. + Return a seq of update counts (one count for each param-group)." + ([db sql-params] + (db-do-prepared db true sql-params {})) + ([db transaction? sql-params] + (if (map? sql-params) + (db-do-prepared db true transaction? sql-params) + (db-do-prepared db transaction? sql-params {}))) + ([db transaction? sql-params opts] + (let [opts (merge (when (map? db) db) opts)] + (if-let [con (db-find-connection db)] + (let [[sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params)) + params (if (or (:multi? opts) (empty? params)) params [params])] + (if (instance? PreparedStatement sql) + (db-do-execute-prepared-statement db sql params (assoc opts :transaction? transaction?)) + (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] + (db-do-execute-prepared-statement db stmt params (assoc opts :transaction? transaction?))))) + (with-open [con (get-connection db opts)] + (db-do-prepared (add-connection db con) transaction? sql-params opts)))))) + +(defn db-query-with-resultset + "Executes a query, then evaluates func passing in the raw ResultSet as an + argument. The second argument is a vector containing either: + [sql & params] - a SQL query, followed by any parameters it needs + [stmt & params] - a PreparedStatement, followed by any parameters it needs + (the PreparedStatement already contains the SQL query) + The opts map is passed to prepare-statement. + Uses executeQuery. This may affect what SQL you can run via query." + ([db sql-params func] (db-query-with-resultset db sql-params func {})) + ([db sql-params func opts] + (let [opts (merge (when (map? db) db) opts) + [sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params)) + run-query-with-params (^{:once true} fn* [^PreparedStatement stmt] + ((:set-parameters opts dft-set-parameters) stmt params) + (with-open [rset (.executeQuery stmt)] + (func rset)))] + (when-not (sql-stmt? sql) + (let [^Class sql-class (class sql) + ^String msg (format "\"%s\" expected %s %s, found %s %s" + "sql-params" + "vector" + "[sql param*]" + (.getName sql-class) + (pr-str sql))] + (throw (IllegalArgumentException. msg)))) + (if (instance? PreparedStatement sql) + (let [^PreparedStatement stmt sql] + (run-query-with-params stmt)) + (if-let [con (db-find-connection db)] + (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] + (run-query-with-params stmt)) + (with-open [con (get-connection db opts)] + (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] + (run-query-with-params stmt)))))))) + +;; top-level API for actual SQL operations + +(defn query + "Given a database connection and a vector containing SQL and optional parameters, + perform a simple database query. The options specify how to construct the result + set (and are also passed to prepare-statement as needed): + :as-arrays? - return the results as a set of arrays, default false. + :identifiers - applied to each column name in the result set, default lower-case + :keywordize? - defaults to true, can be false to opt-out of converting + identifiers to keywords + :qualifier - optionally provides the namespace qualifier for identifiers + :result-set-fn - applied to the entire result set, default doall / vec + if :as-arrays? true, :result-set-fn will default to vec + if :as-arrays? false, :result-set-fn will default to doall + :row-fn - applied to each row as the result set is constructed, default identity + The second argument is a vector containing a SQL string or PreparedStatement, followed + by any parameters it needs. + See also prepare-statement for additional options." + ([db sql-params] (query db sql-params {})) + ([db sql-params opts] + (let [{:keys [as-arrays? explain? explain-fn result-set-fn row-fn] :as opts} + (merge {:explain-fn println :identifiers str/lower-case + :keywordize? true + :read-columns dft-read-columns :row-fn identity} + (when (map? db) db) + opts) + result-set-fn (or result-set-fn (if as-arrays? vec doall)) + sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (when (and explain? (string? (first sql-params-vector))) + (query db (into [(str (if (string? explain?) explain? "EXPLAIN") + " " + (first sql-params-vector))] + (rest sql-params-vector)) + (-> opts + (dissoc :explain? :result-set-fn :row-fn) + (assoc :result-set-fn explain-fn)))) + (db-query-with-resultset db sql-params-vector + (^{:once true} fn* [rset] + ((^{:once true} fn* [rs] + (result-set-fn (if as-arrays? + (cons (first rs) + (map row-fn (rest rs))) + (map row-fn rs)))) + (result-set-seq rset opts))) + opts)))) + +;; performance notes -- goal is to lift as much logic as possible into a "once" +;; pass (so reducible-query preloads all the stuff that doesn't depend on the +;; query results, and then you can repeatedly reduce it, which runs the query +;; each time and runs the minimal result set reduction) +;; turn make-cols-unique into a transducer and optimize it +;; turn make-keys into a transducer pipeline and lift it +;; lift identifier-fn out as make-identifier-fn and refactor +;; lift init-reduce +;; refactor reducible-result-set to lift identifier-fn out +;; call new reducible-result-set version from reducible-query (after calling +;; make-identifier-fn etc) +;; create an optimized version of db-query-with-resultset without :as-arrays? +;; and with options handling lifted + +(defn reducible-result-set + "Given a java.sql.ResultSet return a reducible collection. + Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce + Note: :as-arrays? is not accepted here." + [^ResultSet rs {:keys [identifiers keywordize? qualifier read-columns] + :or {identifiers str/lower-case + keywordize? true + read-columns dft-read-columns}}] + (let [identifier-fn (cond (and qualifier (not keywordize?)) + (throw (IllegalArgumentException. + (str ":qualifier is not allowed unless " + ":keywordize? is true"))) + (and qualifier keywordize?) + (comp (partial keyword qualifier) identifiers) + keywordize? + (comp keyword identifiers) + :else + identifiers) + make-keys (fn [idxs ^ResultSetMetaData rsmeta] + (->> idxs + (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) + (make-cols-unique) + (mapv identifier-fn))) + init-reduce (fn [keys ^ResultSet rs rsmeta idxs f init] + (loop [init' init] + (if (.next rs) + (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] + (if (reduced? result) + @result + (recur result))) + init')))] + (reify clojure.lang.IReduce + (reduce [this f] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + keys (make-keys idxs rsmeta)] + (if (.next rs) + ;; reduce init is first row of ResultSet + (init-reduce keys rs rsmeta idxs f + (zipmap keys (read-columns rs rsmeta idxs))) + ;; no rows so call 0-arity f to get result value + ;; per reduce docstring contract + (f)))) + (reduce [this f init] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + keys (make-keys idxs rsmeta)] + (init-reduce keys rs rsmeta idxs f init)))))) + +(defn reducible-query + "Given a database connection, a vector containing SQL and optional parameters, + return a reducible collection. When reduced, it will start the database query + and reduce the result set, and then close the connection: + (transduce (map :cost) + (reducible-query db sql-params)) + Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce + The following options from query etc are not accepted here: + :as-arrays? :explain :explain-fn :result-set-fn :row-fn + See also prepare-statement for additional options." + ([db sql-params] (reducible-query db sql-params {})) + ([db sql-params opts] + (let [opts (merge {:identifiers str/lower-case :keywordize? true + :read-columns dft-read-columns} + (when (map? db) db) + opts) + sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (reify clojure.lang.IReduce + (reduce [this f] + (db-query-with-resultset + db sql-params-vector + (^{:once true} fn* [rset] + (reduce f (reducible-result-set rset opts))) + opts)) + (reduce [this f init] + (db-query-with-resultset + db sql-params-vector + (^{:once true} fn* [rset] + (reduce f init (reducible-result-set rset opts))) + opts)))))) + +(defn- direction + "Given an entities function, a column name, and a direction, + return the matching SQL column / order. + Throw an exception for an invalid direction." + [entities c d] + (str (as-sql-name entities c) " " + (if-let [dir (#{"ASC" "DESC"} (str/upper-case (name d)))] + dir + (throw (IllegalArgumentException. (str "expected :asc or :desc, found: " d)))))) + +(defn- order-by-sql + "Given a sequence of column specs and an entities function, return + a SQL fragment for the ORDER BY clause. A column spec may be a name + (either a string or keyword) or a map from column name to direction + (:asc or :desc)." + [order-by entities] + (str/join ", " (mapcat (fn [col] + (if (map? col) + (reduce-kv (fn [v c d] + (conj v (direction entities c d))) + [] + col) + [(direction entities col :asc)])) order-by))) + +(defn find-by-keys + "Given a database connection, a table name, a map of column name/value + pairs, and an optional options map, return any matching rows. + An :order-by option may be supplied to sort the rows by a sequence of + columns, e.g,. {:order-by [:name {:age :desc]}" + ([db table columns] (find-by-keys db table columns {})) + ([db table columns opts] + (let [{:keys [entities order-by] :as opts} + (merge {:entities identity} (when (map? db) db) opts) + ks (keys columns) + vs (vals columns)] + (query db (into [(str "SELECT * FROM " (table-str table entities) + " WHERE " (str/join " AND " + (kv-sql ks vs entities " IS NULL")) + (when (seq order-by) + (str " ORDER BY " + (order-by-sql order-by entities))))] + (remove nil? vs)) + opts)))) + +(defn get-by-id + "Given a database connection, a table name, a primary key value, an + optional primary key column name, and an optional options map, return + a single matching row, or nil. + The primary key column name defaults to :id." + ([db table pk-value] (get-by-id db table pk-value :id {})) + ([db table pk-value pk-name-or-opts] + (if (map? pk-name-or-opts) + (get-by-id db table pk-value :id pk-name-or-opts) + (get-by-id db table pk-value pk-name-or-opts {}))) + ([db table pk-value pk-name opts] + (let [opts (merge (when (map? db) db) opts) + r-s-fn (or (:result-set-fn opts) identity)] + (find-by-keys db table {pk-name pk-value} + (assoc opts :result-set-fn (comp first r-s-fn)))))) + +(defn execute! + "Given a database connection and a vector containing SQL (or PreparedStatement) + followed by optional parameters, perform a general (non-select) SQL operation. + The :transaction? option specifies whether to run the operation in a + transaction or not (default true). + If the :multi? option is false (the default), the SQL statement should be + followed by the parameters for that statement. + If the :multi? option is true, the SQL statement should be followed by one or + more vectors of parameters, one for each application of the SQL statement. + If there are no parameters specified, executeUpdate will be used, otherwise + executeBatch will be used. This may affect what SQL you can run via execute!" + ([db sql-params] (execute! db sql-params {})) + ([db sql-params opts] + (let [{:keys [transaction?] :as opts} + (merge {:transaction? true :multi? false} (when (map? db) db) opts) + execute-helper (^{:once true} fn* [db] + (db-do-prepared db transaction? sql-params opts))] + (if-let [con (db-find-connection db)] + (execute-helper db) + (with-open [con (get-connection db opts)] + (execute-helper (add-connection db con))))))) + +(defn- delete-sql + "Given a table name, a where class and its parameters and an optional entities spec, + return a vector of the SQL for that delete operation followed by its parameters. The + entities spec (default 'as-is') specifies how to transform column names." + [table [where & params] entities] + (into [(str "DELETE FROM " (table-str table entities) + (when where " WHERE ") where)] + params)) + +(defn delete! + "Given a database connection, a table name and a where clause of columns to match, + perform a delete. The options may specify how to transform column names in the + map (default 'as-is') and whether to run the delete in a transaction (default true). + Example: + (delete! db :person [\"zip = ?\" 94546]) + is equivalent to: + (execute! db [\"DELETE FROM person WHERE zip = ?\" 94546])" + ([db table where-clause] (delete! db table where-clause {})) + ([db table where-clause opts] + (let [{:keys [entities] :as opts} + (merge {:entities identity :transaction? true} (when (map? db) db) opts)] + (execute! db (delete-sql table where-clause entities) opts)))) + +(defn- multi-insert-helper + "Given a (connected) database connection and some SQL statements (for multiple + inserts), run a prepared statement on each and return any generated keys. + Note: we are eager so an unrealized lazy-seq cannot escape from the connection." + [db stmts opts] + (doall (map (fn [row] (db-do-prepared-return-keys db false row opts)) stmts))) + +(defn- insert-helper + "Given a (connected) database connection, a transaction flag and some SQL statements + (for one or more inserts), run a prepared statement or a sequence of them." + [db transaction? stmts opts] + (if transaction? + (with-db-transaction [t-db db] (multi-insert-helper t-db stmts opts)) + (multi-insert-helper db stmts opts))) + +(defn- col-str + "Transform a column spec to an entity name for SQL. The column spec may be a + string, a keyword or a map with a single pair - column name and alias." + [col entities] + (if (map? col) + (let [[k v] (first col)] + (str (as-sql-name entities k) " AS " (as-sql-name entities v))) + (as-sql-name entities col))) + +(defn- insert-multi-row-sql + "Given a table and a list of columns, followed by a list of column value sequences, + return a vector of the SQL needed for the insert followed by the list of column + value sequences. The entities function specifies how column names are transformed." + [table columns values entities] + (let [nc (count columns) + vcs (map count values)] + (if (not (and (or (zero? nc) (= nc (first vcs))) (apply = vcs))) + (throw (IllegalArgumentException. "insert! called with inconsistent number of columns / values")) + (into [(str "INSERT INTO " (table-str table entities) + (when (seq columns) + (str " ( " + (str/join ", " (map (fn [col] (col-str col entities)) columns)) + " )")) + " VALUES ( " + (str/join ", " (repeat (first vcs) "?")) + " )")] + values)))) + +(defn- insert-single-row-sql + "Given a table and a map representing a row, return a vector of the SQL needed for + the insert followed by the list of column values. The entities function specifies + how column names are transformed." + [table row entities] + (let [ks (keys row)] + (into [(str "INSERT INTO " (table-str table entities) " ( " + (str/join ", " (map (fn [col] (col-str col entities)) ks)) + " ) VALUES ( " + (str/join ", " (repeat (count ks) "?")) + " )")] + (vals row)))) + +(defn- insert-rows! + "Given a database connection, a table name, a sequence of rows, and an options + map, insert the rows into the database." + [db table rows opts] + (let [{:keys [entities transaction?] :as opts} + (merge {:entities identity :identifiers str/lower-case + :keywordize? true :transaction? true} + (when (map? db) db) + opts) + sql-params (map (fn [row] + (when-not (map? row) + (throw (IllegalArgumentException. "insert! / insert-multi! called with a non-map row"))) + (insert-single-row-sql table row entities)) rows)] + (if-let [con (db-find-connection db)] + (insert-helper db transaction? sql-params opts) + (with-open [con (get-connection db opts)] + (insert-helper (add-connection db con) transaction? sql-params opts))))) + +(defn- insert-cols! + "Given a database connection, a table name, a sequence of columns names, a + sequence of vectors of column values, one per row, and an options map, + insert the rows into the database." + [db table cols values opts] + (let [{:keys [entities transaction?] :as opts} + (merge {:entities identity :transaction? true} (when (map? db) db) opts) + sql-params (insert-multi-row-sql table cols values entities)] + (if-let [con (db-find-connection db)] + (db-do-prepared db transaction? sql-params (assoc opts :multi? true)) + (with-open [con (get-connection db opts)] + (db-do-prepared (add-connection db con) transaction? sql-params + (assoc opts :multi? true)))))) + +(defn insert! + "Given a database connection, a table name and either a map representing a rows, + or a list of column names followed by a list of column values also representing + a single row, perform an insert. + When inserting a row as a map, the result is the database-specific form of the + generated keys, if available (note: PostgreSQL returns the whole row). + When inserting a row as a list of column values, the result is the count of + rows affected (1), if available (from getUpdateCount after executeBatch). + The row map or column value vector may be followed by a map of options: + The :transaction? option specifies whether to run in a transaction or not. + The default is true (use a transaction). The :entities option specifies how + to convert the table name and column names to SQL entities." + ([db table row] (insert! db table row {})) + ([db table cols-or-row values-or-opts] + (if (map? values-or-opts) + (insert-rows! db table [cols-or-row] values-or-opts) + (insert-cols! db table cols-or-row [values-or-opts] {}))) + ([db table cols values opts] + (insert-cols! db table cols [values] opts))) + +(defn insert-multi! + "Given a database connection, a table name and either a sequence of maps (for + rows) or a sequence of column names, followed by a sequence of vectors (for + the values in each row), and possibly a map of options, insert that data into + the database. + + When inserting rows as a sequence of maps, the result is a sequence of the + generated keys, if available (note: PostgreSQL returns the whole rows). A + separate database operation is used for each row inserted. This may be slow + for if a large sequence of maps is provided. + + When inserting rows as a sequence of lists of column values, the result is + a sequence of the counts of rows affected (a sequence of 1's), if available. + Yes, that is singularly unhelpful. Thank you getUpdateCount and executeBatch! + A single database operation is used to insert all the rows at once. This may + be much faster than inserting a sequence of rows (which performs an insert for + each map in the sequence). + + The :transaction? option specifies whether to run in a transaction or not. + The default is true (use a transaction). The :entities option specifies how + to convert the table name and column names to SQL entities." + ([db table rows] (insert-rows! db table rows {})) + ([db table cols-or-rows values-or-opts] + (if (map? values-or-opts) + (insert-rows! db table cols-or-rows values-or-opts) + (insert-cols! db table cols-or-rows values-or-opts {}))) + ([db table cols values opts] + (insert-cols! db table cols values opts))) + +(defn- update-sql + "Given a table name, a map of columns to set, a optional map of columns to + match, and an entities, return a vector of the SQL for that update followed + by its parameters. Example: + (update :person {:zip 94540} [\"zip = ?\" 94546] identity) + returns: + [\"UPDATE person SET zip = ? WHERE zip = ?\" 94540 94546]" + [table set-map [where & params] entities] + (let [ks (keys set-map) + vs (vals set-map)] + (cons (str "UPDATE " (table-str table entities) + " SET " (str/join + "," + (kv-sql ks vs entities " = NULL")) + (when where " WHERE ") + where) + (concat (remove nil? vs) params)))) + +(defn update! + "Given a database connection, a table name, a map of column values to set and a + where clause of columns to match, perform an update. The options may specify + how column names (in the set / match maps) should be transformed (default + 'as-is') and whether to run the update in a transaction (default true). + Example: + (update! db :person {:zip 94540} [\"zip = ?\" 94546]) + is equivalent to: + (execute! db [\"UPDATE person SET zip = ? WHERE zip = ?\" 94540 94546])" + ([db table set-map where-clause] (update! db table set-map where-clause {})) + ([db table set-map where-clause opts] + (let [{:keys [entities] :as opts} + (merge {:entities identity :transaction? true} (when (map? db) db) opts)] + (execute! db (update-sql table set-map where-clause entities) opts)))) + +(defn create-table-ddl + "Given a table name and a vector of column specs, return the DDL string for + creating that table. Each column spec is, in turn, a vector of keywords or + strings that is converted to strings and concatenated with spaces to form + a single column description in DDL, e.g., + [:cost :int \"not null\"] + [:name \"varchar(32)\"] + The first element of a column spec is treated as a SQL entity (so if you + provide the :entities option, that will be used to transform it). The + remaining elements are left as-is when converting them to strings. + An options map may be provided that can contain: + :table-spec -- a string that is appended to the DDL -- and/or + :entities -- a function to specify how column names are transformed. + :conditional? -- either a boolean, indicating whether to add 'IF NOT EXISTS', + or a string, which is inserted literally before the table name, or a + function of two arguments (table name and the create statement), that can + manipulate the generated statement to better support other databases, e.g., + MS SQL Server which need to wrap create table in an existence query." + ([table specs] (create-table-ddl table specs {})) + ([table specs opts] + (let [table-spec (:table-spec opts) + conditional? (:conditional? opts) + entities (:entities opts identity) + table-name (as-sql-name entities table) + table-spec-str (or (and table-spec (str " " table-spec)) "") + spec-to-string (fn [spec] + (try + (str/join " " (cons (as-sql-name entities (first spec)) + (map name (rest spec)))) + (catch Exception _ + (throw (IllegalArgumentException. + "column spec is not a sequence of keywords / strings")))))] + (cond->> (format "CREATE TABLE%s %s (%s)%s" + (cond (or (nil? conditional?) + (instance? Boolean conditional?)) + (if conditional? " IF NOT EXISTS" "") + (fn? conditional?) + "" + :else + (str " " conditional?)) + table-name + (str/join ", " (map spec-to-string specs)) + table-spec-str) + (fn? conditional?) (conditional? table-name))))) + +(defn drop-table-ddl + "Given a table name, return the DDL string for dropping that table. + An options map may be provided that can contain: + :entities -- a function to specify how column names are transformed. + :conditional? -- either a boolean, indicating whether to add 'IF EXISTS', + or a string, which is inserted literally before the table name, or a + function of two arguments (table name and the create statement), that can + manipulate the generated statement to better support other databases, e.g., + MS SQL Server which need to wrap create table in an existence query." + ([table] (drop-table-ddl table {})) + ([table {:keys [entities conditional?] :or {entities identity}}] + (let [table-name (as-sql-name entities table)] + (cond->> (format "DROP TABLE%s %s" + (cond (or (nil? conditional?) + (instance? Boolean conditional?)) + (if conditional? " IF EXISTS" "") + (fn? conditional?) + "" + :else + (str " " conditional?)) + table-name) + (fn? conditional?) (conditional? table-name))))) From 0a040750a2cac392ec9455e18f9edea7a0ee83f1 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 19 Oct 2017 16:22:42 -0700 Subject: [PATCH 059/175] Various refactorings to improve query/reducible-query performance --- CHANGES.md | 1 + project.clj | 4 +- src/main/clojure/clojure/java/jdbc.clj | 311 +++++++++++------- .../clojure/clojure/java/test_utilities.clj | 23 +- 4 files changed, 208 insertions(+), 131 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 03a1ea99..5039260a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ Changes coming in 0.7.4 * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160). * Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases. +* Performance improvements to `query` and `reducible-query`. Changes in 0.7.3 diff --git a/project.clj b/project.clj index a7d7550a..9f857ccf 100644 --- a/project.clj +++ b/project.clj @@ -27,8 +27,8 @@ [org.postgresql/postgresql "9.4.1212.jre7"] [com.impossibl.pgjdbc-ng/pgjdbc-ng "0.7.1"] [org.xerial/sqlite-jdbc "3.16.1"] - ;; if you have the MS driver in your local repo - [sqljdbc4 "4.0"]] + ;; Assumes Java 8; there's a .jre7 version as well: + [com.microsoft.sqlserver/mssql-jdbc "6.2.2.jre8"]] :profiles {:1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]} diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index f2d0517c..10114ffd 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -379,24 +379,22 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (let [^String msg (format "db-spec %s is missing a required parameter" db-spec)] (throw (IllegalArgumentException. msg)))))) -(defn- make-name-unique - "Given a collection of column names and a new column name, - return the new column name made unique, if necessary, by - appending _N where N is some unique integer suffix." - [cols col-name n] - (let [suffixed-name (if (= n 1) col-name (str col-name "_" n))] - (if (apply distinct? suffixed-name cols) - suffixed-name - (recur cols col-name (inc n))))) - (defn- make-cols-unique - "Given a collection of column names, rename duplicates so - that the result is a collection of unique column names." - [cols] - (if (or (empty? cols) (apply distinct? cols)) - cols - (reduce (fn [unique-cols col-name] - (conj unique-cols (make-name-unique unique-cols col-name 1))) [] cols))) + "A transducer that, given a collection of strings, returns a collection of + strings that have been made unique by appending _n to duplicates." + [xf] + (let [seen (volatile! {})] + (fn + ([] (xf)) + ([result] (xf result)) + ([result input] + (if-let [suffix (get @seen input)] + (do + (vswap! seen assoc input (inc suffix)) + (xf result (str input "_" suffix))) + (do + (vswap! seen assoc input 2) + (xf result input))))))) (defprotocol ISQLValue "Protocol for creating SQL values from Clojure values. Default @@ -462,6 +460,22 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} [^ResultSet rs rsmeta idxs] (mapv (fn [^Integer i] (result-set-read-column (.getObject rs i) rsmeta i)) idxs)) +(defn- make-identifier-fn + "Given the user's identifiers function, an optional namespace qualifier, and + a flag indicating whether to produce keywords or not, return a compound + function that will perform the appropriate entity to identifier conversion." + [identifiers qualifier keywordize?] + (cond (and qualifier (not keywordize?)) + (throw (IllegalArgumentException. + (str ":qualifier is not allowed unless " + ":keywordize? is true"))) + (and qualifier keywordize?) + (comp (partial keyword qualifier) identifiers) + keywordize? + (comp keyword identifiers) + :else + identifiers)) + (defn result-set-seq "Creates and returns a lazy sequence of maps corresponding to the rows in the java.sql.ResultSet rs. Loosely based on clojure.core/resultset-seq but it @@ -484,20 +498,12 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (let [rsmeta (.getMetaData rs) idxs (range 1 (inc (.getColumnCount rsmeta))) col-name-fn (if (= :cols-as-is as-arrays?) identity make-cols-unique) - identifier-fn (cond (and qualifier (not keywordize?)) - (throw (IllegalArgumentException. - (str ":qualifier is not allowed unless " - ":keywordize? is true"))) - (and qualifier keywordize?) - (comp (partial keyword qualifier) identifiers) - keywordize? - (comp keyword identifiers) - :else - identifiers) - keys (->> idxs - (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) - col-name-fn - (mapv identifier-fn)) + keys (into [] (comp (map (fn [^Integer i] (.getColumnLabel rsmeta i))) + col-name-fn + (map (make-identifier-fn identifiers + qualifier + keywordize?))) + idxs) row-values (fn [] (read-columns rs rsmeta 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 @@ -963,6 +969,41 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (with-open [con (get-connection db opts)] (db-do-prepared (add-connection db con) transaction? sql-params opts)))))) +(defn- execute-query-with-params + "Given a prepared statement, a set of parameters, a parameter setting + function, and a function to process the result set, execute the query and + apply the processing function." + [^PreparedStatement stmt params set-parameters func] + (set-parameters stmt params) + (with-open [rset (.executeQuery stmt)] + (func rset))) + +(defn- db-query-with-resultset* + "Given a db-spec, a SQL statement (or a prepared statement), a set of + parameters, a result set processing function and options, execute the query." + [db sql params func opts] + (if (instance? PreparedStatement sql) + (let [^PreparedStatement stmt sql] + (execute-query-with-params + stmt + params + (:set-parameters opts dft-set-parameters) + func)) + (if-let [con (db-find-connection db)] + (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] + (execute-query-with-params + stmt + params + (:set-parameters opts dft-set-parameters) + func)) + (with-open [con (get-connection db opts)] + (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] + (execute-query-with-params + stmt + params + (:set-parameters opts dft-set-parameters) + func)))))) + (defn db-query-with-resultset "Executes a query, then evaluates func passing in the raw ResultSet as an argument. The second argument is a vector containing either: @@ -974,11 +1015,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} ([db sql-params func] (db-query-with-resultset db sql-params func {})) ([db sql-params func opts] (let [opts (merge (when (map? db) db) opts) - [sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params)) - run-query-with-params (^{:once true} fn* [^PreparedStatement stmt] - ((:set-parameters opts dft-set-parameters) stmt params) - (with-open [rset (.executeQuery stmt)] - (func rset)))] + [sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] (when-not (sql-stmt? sql) (let [^Class sql-class (class sql) ^String msg (format "\"%s\" expected %s %s, found %s %s" @@ -988,15 +1025,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (.getName sql-class) (pr-str sql))] (throw (IllegalArgumentException. msg)))) - (if (instance? PreparedStatement sql) - (let [^PreparedStatement stmt sql] - (run-query-with-params stmt)) - (if-let [con (db-find-connection db)] - (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] - (run-query-with-params stmt)) - (with-open [con (get-connection db opts)] - (with-open [^PreparedStatement stmt (prepare-statement con sql opts)] - (run-query-with-params stmt)))))))) + (db-query-with-resultset* db sql params func opts)))) ;; top-level API for actual SQL operations @@ -1025,39 +1054,92 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (when (map? db) db) opts) result-set-fn (or result-set-fn (if as-arrays? vec doall)) - sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] - (when (and explain? (string? (first sql-params-vector))) + [sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (when-not (sql-stmt? sql) + (let [^Class sql-class (class sql) + ^String msg (format "\"%s\" expected %s %s, found %s %s" + "sql-params" + "vector" + "[sql param*]" + (.getName sql-class) + (pr-str sql))] + (throw (IllegalArgumentException. msg)))) + (when (and explain? (string? sql)) (query db (into [(str (if (string? explain?) explain? "EXPLAIN") " " - (first sql-params-vector))] - (rest sql-params-vector)) + sql)] + params) (-> opts (dissoc :explain? :result-set-fn :row-fn) (assoc :result-set-fn explain-fn)))) - (db-query-with-resultset db sql-params-vector - (^{:once true} fn* [rset] - ((^{:once true} fn* [rs] - (result-set-fn (if as-arrays? - (cons (first rs) - (map row-fn (rest rs))) - (map row-fn rs)))) - (result-set-seq rset opts))) + (db-query-with-resultset* db sql params + (if as-arrays? + (^{:once true} fn* [rset] + ((^{:once true} fn* [rs] + (result-set-fn (cons (first rs) + (map row-fn (rest rs))))) + (result-set-seq rset opts))) + (^{:once true} fn* [rset] + (result-set-fn (map row-fn + (result-set-seq rset opts))))) opts)))) ;; performance notes -- goal is to lift as much logic as possible into a "once" ;; pass (so reducible-query preloads all the stuff that doesn't depend on the ;; query results, and then you can repeatedly reduce it, which runs the query ;; each time and runs the minimal result set reduction) -;; turn make-cols-unique into a transducer and optimize it -;; turn make-keys into a transducer pipeline and lift it -;; lift identifier-fn out as make-identifier-fn and refactor -;; lift init-reduce -;; refactor reducible-result-set to lift identifier-fn out -;; call new reducible-result-set version from reducible-query (after calling -;; make-identifier-fn etc) +;; done: turn make-cols-unique into a transducer and optimize it +;; done: turn make-keys into a transducer pipeline and lift it +;; done: lift identifier-fn out as make-identifier-fn and refactor +;; done: lift init-reduce +;; done: refactor reducible-result-set to lift identifier-fn out +;; done: call new reducible-result-set version from reducible-query (after calling +;; make-identifier-fn etc) ;; create an optimized version of db-query-with-resultset without :as-arrays? ;; and with options handling lifted +(defn- get-rs-columns + "Given a set of indices, a result set's metadata, and a function to convert + SQL entity names to Clojure column names, + return the unique vector of column names." + [idxs ^ResultSetMetaData rsmeta identifier-fn] + (into [] (comp (map (fn [^Integer i] (.getColumnLabel rsmeta i))) + make-cols-unique + (map identifier-fn)) + idxs)) + +(defn- init-reduce-rs + "Given a sequence of columns, a result set, its metadata, a sequence of + indices, a mapping function to apply, an initial value, and a function that + can read column data from the result set, reduce the result set and + return the result of that reduction." + [cols ^ResultSet rs rsmeta idxs f init read-columns] + (loop [init' init] + (if (.next rs) + (let [result (f init' (zipmap cols (read-columns rs rsmeta idxs)))] + (if (reduced? result) + @result + (recur result))) + init'))) + +(defn- reducible-result-set* + "Given a java.sql.ResultSet, indices, metadata, column names and a reader, + return a reducible collection. + Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce." + [^ResultSet rs idxs ^ResultSetMetaData rsmeta cols read-columns] + (reify clojure.lang.IReduce + (reduce [this f] + (if (.next rs) + ;; reduce init is first row of ResultSet + (init-reduce-rs cols rs rsmeta idxs f + (zipmap cols (read-columns rs rsmeta idxs)) + read-columns) + ;; no rows so call 0-arity f to get result value + ;; per reduce docstring contract + (f))) + (reduce [this f init] + (init-reduce-rs cols rs rsmeta idxs f init read-columns)))) + (defn reducible-result-set "Given a java.sql.ResultSet return a reducible collection. Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce @@ -1066,46 +1148,32 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} :or {identifiers str/lower-case keywordize? true read-columns dft-read-columns}}] - (let [identifier-fn (cond (and qualifier (not keywordize?)) - (throw (IllegalArgumentException. - (str ":qualifier is not allowed unless " - ":keywordize? is true"))) - (and qualifier keywordize?) - (comp (partial keyword qualifier) identifiers) - keywordize? - (comp keyword identifiers) - :else - identifiers) - make-keys (fn [idxs ^ResultSetMetaData rsmeta] - (->> idxs - (mapv (fn [^Integer i] (.getColumnLabel rsmeta i))) - (make-cols-unique) - (mapv identifier-fn))) - init-reduce (fn [keys ^ResultSet rs rsmeta idxs f init] - (loop [init' init] - (if (.next rs) - (let [result (f init' (zipmap keys (read-columns rs rsmeta idxs)))] - (if (reduced? result) - @result - (recur result))) - init')))] - (reify clojure.lang.IReduce - (reduce [this f] - (let [rsmeta (.getMetaData rs) - idxs (range 1 (inc (.getColumnCount rsmeta))) - keys (make-keys idxs rsmeta)] - (if (.next rs) - ;; reduce init is first row of ResultSet - (init-reduce keys rs rsmeta idxs f - (zipmap keys (read-columns rs rsmeta idxs))) - ;; no rows so call 0-arity f to get result value - ;; per reduce docstring contract - (f)))) - (reduce [this f init] - (let [rsmeta (.getMetaData rs) - idxs (range 1 (inc (.getColumnCount rsmeta))) - keys (make-keys idxs rsmeta)] - (init-reduce keys rs rsmeta idxs f init)))))) + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + cols (get-rs-columns idxs rsmeta + (make-identifier-fn identifiers + qualifier + keywordize?))] + (reducible-result-set* rs idxs rsmeta cols read-columns))) + +(defn- query-reducer + "Given options, return a function of f that accepts a result set and reduces + it using f." + [identifiers keywordize? qualifier read-columns] + (let [identifier-fn (make-identifier-fn identifiers qualifier keywordize?)] + (fn + ([f] + (^{:once true} fn* [^ResultSet rs] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + cols (get-rs-columns idxs rsmeta identifier-fn)] + (reduce f (reducible-result-set* rs idxs rsmeta cols read-columns))))) + ([f init] + (^{:once true} fn* [^ResultSet rs] + (let [rsmeta (.getMetaData rs) + idxs (range 1 (inc (.getColumnCount rsmeta))) + cols (get-rs-columns idxs rsmeta identifier-fn)] + (reduce f init (reducible-result-set* rs idxs rsmeta cols read-columns)))))))) (defn reducible-query "Given a database connection, a vector containing SQL and optional parameters, @@ -1118,23 +1186,32 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} See also prepare-statement for additional options." ([db sql-params] (reducible-query db sql-params {})) ([db sql-params opts] - (let [opts (merge {:identifiers str/lower-case :keywordize? true - :read-columns dft-read-columns} - (when (map? db) db) - opts) - sql-params-vector (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] + (let [{:keys [identifiers keywordize? qualifier read-columns] :as opts} + (merge {:identifiers str/lower-case :keywordize? true + :read-columns dft-read-columns} + (when (map? db) db) + opts) + [sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params)) + reducing-fn (query-reducer identifiers keywordize? qualifier read-columns)] + (when-not (sql-stmt? sql) + (let [^Class sql-class (class sql) + ^String msg (format "\"%s\" expected %s %s, found %s %s" + "sql-params" + "vector" + "[sql param*]" + (.getName sql-class) + (pr-str sql))] + (throw (IllegalArgumentException. msg)))) (reify clojure.lang.IReduce (reduce [this f] - (db-query-with-resultset - db sql-params-vector - (^{:once true} fn* [rset] - (reduce f (reducible-result-set rset opts))) + (db-query-with-resultset* + db sql params + (reducing-fn f) opts)) (reduce [this f init] - (db-query-with-resultset - db sql-params-vector - (^{:once true} fn* [rset] - (reduce f init (reducible-result-set rset opts))) + (db-query-with-resultset* + db sql params + (reducing-fn f init) opts)))))) (defn- direction diff --git a/src/test/clojure/clojure/java/test_utilities.clj b/src/test/clojure/clojure/java/test_utilities.clj index faf2015c..02d7ba27 100644 --- a/src/test/clojure/clojure/java/test_utilities.clj +++ b/src/test/clojure/clojure/java/test_utilities.clj @@ -1,4 +1,4 @@ -;; Copyright (c) 2008-2016 Sean Corfield, Stephen C. Gilardi. +;; Copyright (c) 2008-2017 Sean Corfield, 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 @@ -41,19 +41,18 @@ (is (re-find (pattern "Message: Test Message.*Message: Base Message") except-str)) (is (re-find (pattern "SQLState: Test State.*SQLState: Base State") except-str))))) -(deftest test-make-name-unique - (let [make-name-unique @#'sql/make-name-unique] - (is (= "a" (make-name-unique '() "a" 1))) - (is (= "a_2" (make-name-unique '("a") "a" 1))) - (is (= "a_3" (make-name-unique '("a" "b" "a_2") "a" 1))))) - (deftest test-make-cols-unique (let [make-cols-unique @#'sql/make-cols-unique] - (is (= '() (make-cols-unique '()))) - (is (= '("a") (make-cols-unique '("a")))) - (is (= '("a" "a_2") (make-cols-unique '("a" "a")))) - (is (= '("a" "b" "a_2" "a_3") (make-cols-unique '("a" "b" "a" "a")))) - (is (= '("a" "b" "a_2" "b_2" "a_3" "b_3") (make-cols-unique '("a" "b" "a" "b" "a" "b")))))) + (is (= [] + (into [] make-cols-unique []))) + (is (= ["a"] + (into [] make-cols-unique ["a"]))) + (is (= ["a" "a_2"] + (into [] make-cols-unique ["a" "a"]))) + (is (= ["a" "b" "a_2" "a_3"] + (into [] make-cols-unique ["a" "b" "a" "a"]))) + (is (= ["a" "b" "a_2" "b_2" "a_3" "b_3"] + (into [] make-cols-unique ["a" "b" "a" "b" "a" "b"]))))) ;; DDL tests From e4af62abc4f8c39eaf76a414d615634f5ca9d67b Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 19 Oct 2017 17:14:02 -0700 Subject: [PATCH 060/175] Improve performance of dft-set-parameters --- src/main/clojure/clojure/java/jdbc.clj | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 10114ffd..9ec08b4b 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -432,9 +432,10 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defn- dft-set-parameters "Default implementation of parameter setting for the given statement." [stmt params] - (dorun (map-indexed (fn [ix value] - (set-parameter value stmt (inc ix))) - params))) + (loop [ix 1 values params] + (when (seq values) + (set-parameter (first values) stmt ix) + (recur (inc ix) (rest values))))) (defprotocol IResultSetReadColumn "Protocol for reading objects from the java.sql.ResultSet. Default From 5f1a5eeb54d8e8f06c9e36ba53f2036eb128c94d Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 19 Oct 2017 18:25:14 -0700 Subject: [PATCH 061/175] Experimental support for :raw? result set in reducible-query --- src/main/clojure/clojure/java/jdbc.clj | 57 ++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 9ec08b4b..f7e43f41 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1158,8 +1158,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (reducible-result-set* rs idxs rsmeta cols read-columns))) (defn- query-reducer - "Given options, return a function of f that accepts a result set and reduces - it using f." + "Given options, return a function of f (or f and init) that accepts a + result set and reduces it using f." [identifiers keywordize? qualifier read-columns] (let [identifier-fn (make-identifier-fn identifiers qualifier keywordize?)] (fn @@ -1176,6 +1176,55 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} cols (get-rs-columns idxs rsmeta identifier-fn)] (reduce f init (reducible-result-set* rs idxs rsmeta cols read-columns)))))))) +(defn- mapify-result-set + "Given a result set, return an object that wraps the current row as a hash + map. Note that a result set is mutable and the current row will change behind + this wrapper so operations need to be eager (and fairly limited). + Supports ILookup (keywords are treated as strings). + Supports Associative for lookup only (again, keywords are treated as strings). + Later we may realize a new hash map when assoc (and other, future, operations + are performed on the result set row)." + [^ResultSet rs] + (reify + clojure.lang.ILookup + (valAt [this k] + (try + (.getObject rs (name k)) + (catch SQLException _))) + (valAt [this k not-found] + (try + (.getObject rs (name k)) + (catch SQLException _ + not-found))) + clojure.lang.Associative + (containsKey [this k] + (try + (.getObject rs (name k)) + true + (catch SQLException _ + false))) + (entryAt [this k] + (try + (clojure.lang.MapEntry/create k (.getObject rs (name k))) + (catch SQLException _))) + (assoc [this _ _] + (throw (ex-info "assoc not supported on raw result set" {}))))) + +(defn- raw-query-reducer + "Given a function f and an initial value, return a function that accepts a + result set and reduces it using no translation. The result set is extended + to support ILookup only." + [f init] + (^{:once true} fn* [^ResultSet rs] + (let [rs-map (mapify-result-set rs)] + (loop [init' init] + (if (.next rs) + (let [result (f init' rs-map)] + (if (reduced? result) + @result + (recur result))) + init'))))) + (defn reducible-query "Given a database connection, a vector containing SQL and optional parameters, return a reducible collection. When reduced, it will start the database query @@ -1193,7 +1242,9 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (when (map? db) db) opts) [sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params)) - reducing-fn (query-reducer identifiers keywordize? qualifier read-columns)] + reducing-fn (if (:raw? opts) + raw-query-reducer + (query-reducer identifiers keywordize? qualifier read-columns))] (when-not (sql-stmt? sql) (let [^Class sql-class (class sql) ^String msg (format "\"%s\" expected %s %s, found %s %s" From 1dd511aea3675d40e0e42a64d2997309bf5d1958 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 19 Oct 2017 18:43:13 -0700 Subject: [PATCH 062/175] Clean up comments / docstrings --- CHANGES.md | 1 + src/main/clojure/clojure/java/jdbc.clj | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5039260a..2f054b0e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ Changes coming in 0.7.4 * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160). * Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases. * Performance improvements to `query` and `reducible-query`. +* Experimental `:raw?` result set handling in `reducible-query`. Changes in 0.7.3 diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index f7e43f41..5a3f8255 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1213,7 +1213,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defn- raw-query-reducer "Given a function f and an initial value, return a function that accepts a result set and reduces it using no translation. The result set is extended - to support ILookup only." + to support ILookup and the readonly parts of Associative only." [f init] (^{:once true} fn* [^ResultSet rs] (let [rs-map (mapify-result-set rs)] @@ -1230,10 +1230,18 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} return a reducible collection. When reduced, it will start the database query and reduce the result set, and then close the connection: (transduce (map :cost) + (reducible-query db sql-params)) - Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce + The following options from query etc are not accepted here: :as-arrays? :explain :explain-fn :result-set-fn :row-fn - See also prepare-statement for additional options." + See prepare-statement for additional options that may be passed through. + + If :raw? true is specified, the rows of the result set are not converted to + hash maps, and it as if the following options were specified: + :identifiers identity :keywordize? false :qualifier nil + In addition, the rows of the result set may only be read as if they were hash + maps (get, keyword lookup, select-keys) but the sequence representation is + not available (so, no keys, no vals, and no seq calls). This is much faster + than converting each row to a hash map but it is also more restrictive." ([db sql-params] (reducible-query db sql-params {})) ([db sql-params opts] (let [{:keys [identifiers keywordize? qualifier read-columns] :as opts} From 01b6fd13fd98917e7db73c187cfdcde599df2b1e Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 19 Oct 2017 19:17:15 -0700 Subject: [PATCH 063/175] Clojure 1.7 compatibility: clojure.lang.MapEntry/create did not exist then --- src/main/clojure/clojure/java/jdbc.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 5a3f8255..e8140685 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1205,7 +1205,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} false))) (entryAt [this k] (try - (clojure.lang.MapEntry/create k (.getObject rs (name k))) + (clojure.lang.MapEntry. k (.getObject rs (name k))) (catch SQLException _))) (assoc [this _ _] (throw (ex-info "assoc not supported on raw result set" {}))))) From 4a62e2819401925f61ecce58ae47dd75579f4607 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 19 Oct 2017 19:17:35 -0700 Subject: [PATCH 064/175] Ensure a few tests on :raw? result sets --- src/test/clojure/clojure/java/test_jdbc.clj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index eb35b68f..c3d3fd8b 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -445,7 +445,7 @@ ;; reduce with init (and ensure we can pass :fetch-size & connection opts through) (is (= 466 (reduce (fn [n r] (+ n (:cost r))) 100 (sql/reducible-query db "SELECT * FROM fruit" - (cond-> {:fetch-size 100} + (cond-> {:fetch-size 100 :raw? true} (not (sqlite? db)) (assoc :read-only? true) (not (derby? db)) @@ -494,7 +494,8 @@ (into [] (map :cost) (sql/reducible-query db (str "SELECT * FROM fruit" - " ORDER BY cost"))))) + " ORDER BY cost") + {:raw? true})))) ;; transduce without init (calls (+) to get init value) (is (= 366 (transduce (map :cost) + (sql/reducible-query db "SELECT * FROM fruit")))) From 99a3d6611f973ff47597f4c7df3c36237ab4bde3 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 20 Oct 2017 09:28:45 -0700 Subject: [PATCH 065/175] Add Tommi Reiman's benchmarks, clean up comments --- CHANGES.md | 2 +- project.clj | 11 +- src/main/clojure/clojure/java/jdbc.clj | 14 -- src/perf/clojure/clojure/java/perf_jdbc.clj | 226 ++++++++++++++++++++ 4 files changed, 235 insertions(+), 18 deletions(-) create mode 100644 src/perf/clojure/clojure/java/perf_jdbc.clj diff --git a/CHANGES.md b/CHANGES.md index 2f054b0e..12170b57 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes coming in 0.7.4 * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160). * Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases. -* Performance improvements to `query` and `reducible-query`. +* Performance improvements, primarily in `query` and `reducible-query`. * Experimental `:raw?` result set handling in `reducible-query`. Changes in 0.7.3 diff --git a/project.clj b/project.clj index 9f857ccf..1952153f 100644 --- a/project.clj +++ b/project.clj @@ -33,10 +33,15 @@ :profiles {:1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]} :1.9 {:dependencies [[org.clojure/clojure "1.9.0-master-SNAPSHOT"]]} - :dev {:dependencies [[org.clojure/test.check "0.9.0"]]}} - + :dev {:dependencies [[org.clojure/test.check "0.9.0"] + [criterium "0.4.4"]]} + :perf {:test-paths ["src/perf/clojure"] + :jvm-opts ^:replace ["-server" + "-Xmx4096m" + "-Dclojure.compiler.direct-linking=true"]}} :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/" "ws-archiva" "https://d259tvauhnips9.cloudfront.net/archiva/repository/internal/"} ;; include dev profile with 1.9 to pull in test.check - :aliases {"test-all" ["with-profile" "test,1.7:test,1.8:dev,test,1.9" "test"]} + :aliases {"test-all" ["with-profile" "test,1.7:test,1.8:dev,test,1.9" "test"] + "perf" ["with-profile" "default,dev,perf"]} :min-lein-version "2.0.0") diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index e8140685..770f35f7 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1085,20 +1085,6 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (result-set-seq rset opts))))) opts)))) -;; performance notes -- goal is to lift as much logic as possible into a "once" -;; pass (so reducible-query preloads all the stuff that doesn't depend on the -;; query results, and then you can repeatedly reduce it, which runs the query -;; each time and runs the minimal result set reduction) -;; done: turn make-cols-unique into a transducer and optimize it -;; done: turn make-keys into a transducer pipeline and lift it -;; done: lift identifier-fn out as make-identifier-fn and refactor -;; done: lift init-reduce -;; done: refactor reducible-result-set to lift identifier-fn out -;; done: call new reducible-result-set version from reducible-query (after calling -;; make-identifier-fn etc) -;; create an optimized version of db-query-with-resultset without :as-arrays? -;; and with options handling lifted - (defn- get-rs-columns "Given a set of indices, a result set's metadata, and a function to convert SQL entity names to Clojure column names, diff --git a/src/perf/clojure/clojure/java/perf_jdbc.clj b/src/perf/clojure/clojure/java/perf_jdbc.clj new file mode 100644 index 00000000..3554ed12 --- /dev/null +++ b/src/perf/clojure/clojure/java/perf_jdbc.clj @@ -0,0 +1,226 @@ +;; Copyright (c) 2017 Tommmi Reiman, Sean Corfield. 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. + +(ns clojure.java.perf-jdbc + "Performance tests for parts of clojure.java.jdbc. + + Start a REPL with `lein perf repl` + Require this namespace and then run `(calibrate)` + followed by `(test-dummy)` and `(test-h2)` + + These test compare the raw performance (against an in-memory H2 database) + for hand-crafted Java JDBC calls and various `query` and `reducible-query` + calls." + (:require [criterium.core :as cc] + [clojure.java.jdbc :as sql]) + (:import (java.sql Connection PreparedStatement ResultSet Statement ResultSetMetaData))) + +(defn calibrate [] + ;; 840ms + (cc/quick-bench (reduce + (take 10e6 (range))))) + +(def db + "Note: loading this namespace creates a connection to the H2 database!" + {:connection (sql/get-connection "jdbc:h2:mem:test_mem")}) + +(defn create-table! [db] + (sql/db-do-commands + db (sql/create-table-ddl + :fruit + [[:id :int "DEFAULT 0"] + [:name "VARCHAR(32)" "PRIMARY KEY"] + [:appearance "VARCHAR(32)"] + [:cost :int] + [:grade :real]] + {:table-spec ""}))) + +(defn- drop-table! [db] + (doseq [table [:fruit :fruit2 :veggies :veggies2]] + (try + (sql/db-do-commands db (sql/drop-table-ddl table)) + (catch java.sql.SQLException _)))) + +(defn add-stuff! [db] + (sql/insert-multi! db + :fruit + nil + [[1 "Apple" "red" 59 87] + [2 "Banana" "yellow" 29 92.2] + [3 "Peach" "fuzzy" 139 90.0] + [4 "Orange" "juicy" 89 88.6]])) + +(def dummy-con + (reify + Connection + (createStatement [_] + (reify + Statement + (addBatch [_ _]))) + (prepareStatement [_ _] + (reify + PreparedStatement + (setObject [_ _ _]) + (setString [_ _ _]) + (close [_]) + (executeQuery [_] + (reify + ResultSet + (getMetaData [_] + (reify + ResultSetMetaData + (getColumnCount [_] 1) + (getColumnLabel [_ _] "name"))) + (next [_] true) + (close [_]) + (^Object getObject [_ ^int s] + "Apple") + (^Object getObject [_ ^String s] + "Apple") + (^String getString [_ ^String s] + "Apple"))))))) + +(defn select [db] + (sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "red"] + {:row-fn :name :result-set-fn first})) + +(defn select-p [db ps] + (sql/query db [ps "red"] + {:row-fn :name :result-set-fn first})) + +(defn select* [^Connection con] + (let [ps (doto (.prepareStatement con "SELECT * FROM fruit WHERE appearance = ?") + (.setObject 1 "red")) + rs (.executeQuery ps) + _ (.next rs) + value (.getObject rs "name")] + (.close ps) + value)) + +(defn test-dummy [] + (do + (let [db {:connection dummy-con}] + (assert (= "Apple" (select db))) + ;(time (dotimes [_ 100000] (select db))) + + ; 3.029268 ms (3030 ns) + (cc/quick-bench (dotimes [_ 1000] (select db)))) + + (let [con dummy-con] + (assert (= "Apple" (select* con))) + ;(time (dotimes [_ 100000] (select* con))) + + ; 716.661522 ns (0.7ns) -> 4300x faster + (cc/quick-bench (dotimes [_ 1000] (select* con)))))) + +(defn test-h2 [] + (do + (drop-table! db) + (create-table! db) + (add-stuff! db) + + (println "Basic select...") + (let [db db] + (assert (= "Apple" (select db))) + (cc/quick-bench (select db))) + + (println "Select with prepared statement...") + (let [con (:connection db)] + (with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")] + (assert (= "Apple" (select-p db ps))) + (cc/quick-bench (select db)))) + + (println "Reducible query...") + (let [db db + rq (sql/reducible-query db ["SELECT * FROM fruit WHERE appearance = ?" "red"])] + (assert (= "Apple" (reduce (fn [_ row] (reduced (:name row))) + nil rq))) + (cc/quick-bench (reduce (fn [_ row] (reduced (:name row))) + nil rq))) + + (println "Reducible query with prepared statement...") + (let [con (:connection db)] + (with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")] + (let [rq (sql/reducible-query db [ps "red"])] + (assert (= "Apple" (reduce (fn [_ row] (reduced (:name row))) + nil rq))) + (cc/quick-bench (reduce (fn [_ row] (reduced (:name row))) + nil rq))))) + + (println "Reducible query with prepared statement and simple identifiers...") + (let [con (:connection db)] + (with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")] + (let [rq (sql/reducible-query db [ps "red"] + {:keywordize? false :identifiers identity})] + (assert (= "Apple" (reduce (fn [_ row] (reduced (get row "NAME"))) + nil rq))) + (cc/quick-bench (reduce (fn [_ row] (reduced (get row "NAME"))) + nil rq))))) + + (println "Reducible query with prepared statement and raw result set...") + (let [con (:connection db)] + (with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")] + (let [rq (sql/reducible-query db [ps "red"] {:raw? true})] + (assert (= "Apple" (reduce (fn [_ row] (reduced (:name row))) + nil rq))) + (cc/quick-bench (reduce (fn [_ row] (reduced (:name row))) + nil rq))))) + + (println "Reducible query with raw result set...") + (let [db db + rq (sql/reducible-query + db + ["SELECT * FROM fruit WHERE appearance = ?" "red"] + {:raw? true})] + (assert (= "Apple" (reduce (fn [_ row] (reduced (:name row))) + nil rq))) + (cc/quick-bench (reduce (fn [_ row] (reduced (:name row))) + nil rq))) + + (println "Repeated reducible query with raw result set...") + (let [db db] + (assert (= "Apple" (reduce (fn [_ row] (reduced (:name row))) + nil (sql/reducible-query + db + ["SELECT * FROM fruit WHERE appearance = ?" "red"] + {:raw? true})))) + (cc/quick-bench (reduce (fn [_ row] (reduced (:name row))) + nil (sql/reducible-query + db + ["SELECT * FROM fruit WHERE appearance = ?" "red"] + {:raw? true})))) + + (println "Raw Java...") + (let [con (:connection db)] + (assert (= "Apple" (select* con))) + (cc/quick-bench (select* con))))) + +(comment + (calibrate) + (test-dummy) + (test-h2) + ;; The following are just some things I was double-checking while adding + ;; to Tommi's original tests -- Sean. + ;; Shows the row is a reify instance, but you can select by key: + (reduce (fn [_ row] (println row) (reduced (:name row))) + nil (sql/reducible-query + db + ["SELECT * FROM fruit WHERE appearance = ?" "red"] + {:raw? true})) + ;; Shows you can construct a map result using select-keys: + (reduce (fn [_ row] (reduced (select-keys row [:cost]))) + nil (sql/reducible-query + db + ["SELECT * FROM fruit WHERE appearance = ?" "red"] + {:raw? true})) + ;; Shows you can reconstruct an entire result set (with very little overhead): + (transduce (map #(select-keys % [:id :name :cost :appearance :grade])) + conj [] (sql/reducible-query + db + "SELECT * FROM fruit" + {:raw? true}))) From 183288ba692fbd62ba736c7b1280397dc718dc70 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 20 Oct 2017 10:22:24 -0700 Subject: [PATCH 066/175] Clean up readme to match SQL Server in project.clj --- README.md | 8 +------- project.clj | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3bcdb532..4a36de70 100644 --- a/README.md +++ b/README.md @@ -50,19 +50,13 @@ You will also need to add dependencies for the JDBC driver you intend to use. He * [H2](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.h2database%22%20AND%20a%3A%22h2%22) * [HSQLDB](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22hsqldb%22%20AND%20a%3A%22hsqldb%22) * [Microsoft SQL Server jTDS](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22net.sourceforge.jtds%22%20AND%20a%3A%22jtds%22) +* [Microsoft SQL Server -- Official MS Version](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.microsoft.sqlserver%22%20AND%20a%3A%22mssql-jdbc%22) * [MySQL](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22mysql%22%20AND%20a%3A%22mysql-connector-java%22) * [PostgreSQL](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.postgresql%22%20AND%20a%3A%22postgresql%22) * [SQLite](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.xerial%22%20AND%20a%3A%22sqlite-jdbc%22) Note: different versions of various database drivers have different Java/JVM version requirements. In particular, recent versions of Apache Derby require at least Java 8 and recent versions of H2 require at least Java 7. Clojure's Continuous Integration system uses older versions so tests can be run on Java 6 (see `pom.xml`); local testing is done with more recent versions on Java 8. -`clojure.java.jdbc` is also tested against Microsoft's own JDBC4 Driver 4.0 but that -has to be [downloaded manually](https://www.microsoft.com/en-us/download/details.aspx?id=11774) and placed in a Maven repository accessible to your system. For testing, it was installed locally as: -```clojure -;; Microsoft SQL Server JDBC4 Driver 4.0 -[sqljdbc4/sqljdbc4 "4.0"] -``` - Example Usage ======================================== ```clojure diff --git a/project.clj b/project.clj index 1952153f..0310dc68 100644 --- a/project.clj +++ b/project.clj @@ -10,7 +10,7 @@ :url "http://www.eclipse.org/legal/epl-v10.html"} :source-paths ["src/main/clojure"] :test-paths ["src/test/clojure"] - :dependencies [[org.clojure/clojure "1.9.0-beta1"] + :dependencies [[org.clojure/clojure "1.9.0-beta2"] ;; These are just the versions most recently test against ;; for your own projects, use whatever version is most ;; appropriate for you. Again, note that this project.clj From 1e856a7a8fd1cc50950012cf7562e3c0368bdc3d Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 7 Dec 2017 12:42:32 -0800 Subject: [PATCH 067/175] JDBC-164 fix typos in docstrings (Stuart Hinson) --- src/main/clojure/clojure/java/jdbc.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 770f35f7..1f03a424 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -855,7 +855,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defn db-do-commands "Executes SQL commands on the specified database connection. Wraps the commands - in a transaction if transaction? is true. transaction? can be ommitted and it + in a transaction if transaction? is true. transaction? can be omitted and it defaults to true. Accepts a single SQL command (string) or a vector of them. Uses executeBatch. This may affect what SQL you can run via db-do-commands." ([db sql-commands] @@ -907,7 +907,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defn db-do-prepared-return-keys "Executes an (optionally parameterized) SQL prepared statement on the open database connection. The param-group is a seq of values for all of - the parameters. transaction? can be ommitted and will default to true. + the parameters. transaction? can be omitted and will default to true. Return the generated keys for the (single) update/insert. A PreparedStatement may be passed in, instead of a SQL string, in which case :return-keys MUST BE SET on that PreparedStatement!" From 878a5e0593a21d4adb442f3f0b107a3ea2208f13 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 14 Dec 2017 15:11:21 -0800 Subject: [PATCH 068/175] Address reports of NPE while trying to make a connection read only This may be a problem introduced by allowing get-connection to respect :read-only? and that being passed through db-transaction* (which can call get-connection). Added nil checks, and casts to Boolean, to make modify-connection more robust. --- CHANGES.md | 3 ++- README.md | 13 ++++++++++--- project.clj | 2 +- src/main/clojure/clojure/java/jdbc.clj | 11 ++++++----- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 12170b57..6c4ae644 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,10 @@ -Changes coming in 0.7.4 +Changes in 0.7.4 * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160). * Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases. * Performance improvements, primarily in `query` and `reducible-query`. * Experimental `:raw?` result set handling in `reducible-query`. +* `modify-connection` is more robust in the face of `null` connections and bad option values. Changes in 0.7.3 diff --git a/README.md b/README.md index 4a36de70..41d04ce2 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Support Releases and Dependency Information ======================================== -Latest stable release: 0.7.3 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.4 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -32,14 +32,14 @@ Latest stable release: 0.7.3 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.3"] +[org.clojure/java.jdbc "0.7.4"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.3 + 0.7.4 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -136,6 +136,13 @@ Developer Information Change Log ==================== +* Release 0.7.4 on 2017-12-14 + * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160). + * Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases. + * Performance improvements, primarily in `query` and `reducible-query`. + * Experimental `:raw?` result set handling in `reducible-query`. + * `modify-connection` is more robust in the face of `null` connections and bad option values. + * Release 0.7.3 on 2017-10-05 * Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://dev.clojure.org/jira/browse/JDBC-159). * If an exception occurs during a transaction, and then rollback fails with another exception, both exceptions will now be combined into an `ex-info`. Previously the rollback exception obscured the transaction exception [JDBC-158](https://dev.clojure.org/jira/browse/JDBC-158). diff --git a/project.clj b/project.clj index 0310dc68..4471e6b0 100644 --- a/project.clj +++ b/project.clj @@ -2,7 +2,7 @@ ;; develop and test java.jdbc locally. The pom.xml file is the ;; "system of record" as far as the project version is concerned. -(defproject org.clojure/java.jdbc "0.7.4-SNAPSHOT" +(defproject org.clojure/java.jdbc "0.7.5-SNAPSHOT" :description "A low-level Clojure wrapper for JDBC-based access to databases." :parent [org.clojure/pom.contrib "0.1.2"] :url "https://github.com/clojure/java.jdbc" diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 1f03a424..520da4b6 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -217,10 +217,10 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} as specified by the options." ^java.sql.Connection [^java.sql.Connection connection opts] - (when (contains? opts :auto-commit?) - (.setAutoCommit connection (:auto-commit? opts))) - (when (contains? opts :read-only?) - (.setReadOnly connection (:read-only? opts))) + (when (and connection (contains? opts :auto-commit?)) + (.setAutoCommit connection (boolean (:auto-commit? opts)))) + (when (and connection (contains? opts :read-only?)) + (.setReadOnly connection (boolean (:read-only? opts)))) connection) (defn get-connection @@ -771,7 +771,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (try (.setReadOnly con old-readonly) (catch Exception _))))))) - (with-open [con (get-connection db opts)] + ;; avoid confusion of read-only? TX and read-only? connection: + (with-open [con (get-connection db (dissoc opts :read-only?))] (db-transaction* (add-connection db con) func opts))) (do (when (and isolation From 613874c761b597075b684f4215d6c0739ee6e1aa Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 14 Dec 2017 17:13:41 -0600 Subject: [PATCH 069/175] [maven-release-plugin] prepare release java.jdbc-0.7.4 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 16727262..0a49d84a 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.4-SNAPSHOT + 0.7.4 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.4 From b2cc14a82e5e36e394169ba3404c5f247f8e1ba6 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 14 Dec 2017 17:13:41 -0600 Subject: [PATCH 070/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0a49d84a..3425d8fc 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.4 + 0.7.5-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.4 + HEAD From 5bdd1c03e049305bdb68925ec04e5c2eed2ec148 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 29 Dec 2017 11:56:27 -0800 Subject: [PATCH 071/175] JDBC-163 add :return-keys support to execute! Also adds :multi? support to db-do-prepared-return-keys. Note: not all database drivers allow executeBatch to return keys! --- CHANGES.md | 4 ++ README.md | 9 ++- project.clj | 15 ++--- src/main/clojure/clojure/java/jdbc.clj | 38 ++++++++++--- src/main/clojure/clojure/java/jdbc/spec.clj | 6 +- src/test/clojure/clojure/java/test_jdbc.clj | 63 +++++++++++++++++++-- 6 files changed, 110 insertions(+), 25 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6c4ae644..420b9022 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes in 0.7.5 + +* Add support for `:return-keys` in `execute!` and `:multi?` in `db-do-prepared-return-keys` [JDBC-163](https://dev.clojure.org/jira/browse/JDBC-163). + Changes in 0.7.4 * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160). diff --git a/README.md b/README.md index 41d04ce2..21fd181b 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Support Releases and Dependency Information ======================================== -Latest stable release: 0.7.4 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.5 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -32,14 +32,14 @@ Latest stable release: 0.7.4 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.4"] +[org.clojure/java.jdbc "0.7.5"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.4 + 0.7.5 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -136,6 +136,9 @@ Developer Information Change Log ==================== +* Release 0.7.5 on 2017-12-29 + * Add support for `:return-keys` in `execute!` and `:multi?` in `db-do-prepared-return-keys` [JDBC-163](https://dev.clojure.org/jira/browse/JDBC-163). + * Release 0.7.4 on 2017-12-14 * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160). * Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases. diff --git a/project.clj b/project.clj index 4471e6b0..c25fcaa8 100644 --- a/project.clj +++ b/project.clj @@ -2,7 +2,7 @@ ;; develop and test java.jdbc locally. The pom.xml file is the ;; "system of record" as far as the project version is concerned. -(defproject org.clojure/java.jdbc "0.7.5-SNAPSHOT" +(defproject org.clojure/java.jdbc "0.7.6-SNAPSHOT" :description "A low-level Clojure wrapper for JDBC-based access to databases." :parent [org.clojure/pom.contrib "0.1.2"] :url "https://github.com/clojure/java.jdbc" @@ -10,7 +10,7 @@ :url "http://www.eclipse.org/legal/epl-v10.html"} :source-paths ["src/main/clojure"] :test-paths ["src/test/clojure"] - :dependencies [[org.clojure/clojure "1.9.0-beta2"] + :dependencies [[org.clojure/clojure "1.9.0"] ;; These are just the versions most recently test against ;; for your own projects, use whatever version is most ;; appropriate for you. Again, note that this project.clj @@ -32,16 +32,17 @@ :profiles {:1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]} - :1.9 {:dependencies [[org.clojure/clojure "1.9.0-master-SNAPSHOT"]]} + :master {:dependencies [[org.clojure/clojure "1.10.0-master-SNAPSHOT"]]} :dev {:dependencies [[org.clojure/test.check "0.9.0"] [criterium "0.4.4"]]} :perf {:test-paths ["src/perf/clojure"] :jvm-opts ^:replace ["-server" "-Xmx4096m" "-Dclojure.compiler.direct-linking=true"]}} - :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/" - "ws-archiva" "https://d259tvauhnips9.cloudfront.net/archiva/repository/internal/"} - ;; include dev profile with 1.9 to pull in test.check - :aliases {"test-all" ["with-profile" "test,1.7:test,1.8:dev,test,1.9" "test"] + :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"} + ;; include dev profile with 1.9+ to pull in test.check + :aliases {"test-all" ["with-profile" + "dev,test,master:dev,test:test,1.8:test,1.7" + "test"] "perf" ["with-profile" "default,dev,perf"]} :min-lein-version "2.0.0") diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 520da4b6..cdb4c3a8 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -877,15 +877,24 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defn- db-do-execute-prepared-return-keys "Executes a PreparedStatement, optionally in a transaction, and (attempts to) - return any generated keys." + return any generated keys. + + Supports :multi? which causes a full result set sequence of keys to be + returned, and assumes the param-group is a sequence of parameter lists, + rather than a single sequence of parameters." [db ^PreparedStatement stmt param-group opts] - (let [{:keys [transaction?] :as opts} (merge (when (map? db) db) opts) + (let [{:keys [transaction? multi?] :as opts} (merge (when (map? db) db) opts) exec-and-return-keys (^{:once true} fn* [] - (let [counts (.executeUpdate stmt)] + (let [counts (if multi? + (.executeBatch stmt) + (.executeUpdate stmt))] (try (let [rs (.getGeneratedKeys stmt) - result (first (result-set-seq rs opts))] + rss (result-set-seq rs opts) + result (if multi? + (doall rss) + (first rss))] ;; sqlite (and maybe others?) requires ;; record set to be closed (.close rs) @@ -893,7 +902,11 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (catch Exception _ ;; assume generated keys is unsupported and return counts instead: counts))))] - ((:set-parameters opts dft-set-parameters) stmt param-group) + (if multi? + (doseq [params param-group] + ((:set-parameters opts dft-set-parameters) stmt params) + (.addBatch stmt)) + ((:set-parameters opts dft-set-parameters) stmt param-group)) (if transaction? (with-db-transaction [t-db (add-connection db (.getConnection stmt))] (exec-and-return-keys)) @@ -1324,20 +1337,31 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defn execute! "Given a database connection and a vector containing SQL (or PreparedStatement) followed by optional parameters, perform a general (non-select) SQL operation. + The :transaction? option specifies whether to run the operation in a transaction or not (default true). + If the :multi? option is false (the default), the SQL statement should be followed by the parameters for that statement. + If the :multi? option is true, the SQL statement should be followed by one or more vectors of parameters, one for each application of the SQL statement. + + If :return-keys is provided, db-do-prepared-return-keys will be called + instead of db-do-prepared, and the result will be a sequence of maps + containing the generated keys. + If there are no parameters specified, executeUpdate will be used, otherwise executeBatch will be used. This may affect what SQL you can run via execute!" ([db sql-params] (execute! db sql-params {})) ([db sql-params opts] - (let [{:keys [transaction?] :as opts} + (let [{:keys [transaction? return-keys] :as opts} (merge {:transaction? true :multi? false} (when (map? db) db) opts) + db-do-helper (if return-keys + db-do-prepared-return-keys + db-do-prepared) execute-helper (^{:once true} fn* [db] - (db-do-prepared db transaction? sql-params opts))] + (db-do-helper db transaction? sql-params opts))] (if-let [con (db-find-connection db)] (execute-helper db) (with-open [con (get-connection db opts)] diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index 2b512d97..9a4dfb03 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -161,7 +161,8 @@ (s/def ::exec-sql-options (s/keys :req-un [] :opt-un [::entities ::transaction?])) -(s/def ::execute-options (s/keys :req-un [] :opt-un [::transaction? ::multi?])) +(s/def ::execute-options (s/keys :req-un [] :opt-un [::transaction? ::multi? + ::return-keys])) (s/def ::find-by-keys-options (s/keys :req-un [] :opt-un [::entities ::order-by @@ -358,7 +359,8 @@ :args (s/cat :db ::db-spec :sql-params ::sql-params :opts (s/? ::execute-options)) - :ret ::execute-result) + :ret (s/or :rows ::execute-result + :keys (s/coll-of map?))) (s/fdef sql/delete! :args (s/cat :db ::db-spec diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index c3d3fd8b..1130be3b 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -1,9 +1,9 @@ -;; 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 +;; Copyright (c) Stephen C. Gilardi, Sean Corfield. 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, +;; terms of this license. You must not remove this notice, or any other, ;; from this software. ;; ;; test_jdbc.clj @@ -382,7 +382,7 @@ (deftest test-do-prepared1e (doseq [db (test-specs)] ;; Derby/SQL Server does not have auto-generated id column which we're testing here - (when-not (#{"derby" "jtds:sqlserver"} (or (:subprotocol db) (:dbtype db))) + (when-not (#{"derby" "jtds" "jtds:sqlserver"} (or (:subprotocol db) (:dbtype db))) (create-test-table :fruit db) (with-open [con (sql/get-connection db)] (let [stmt (sql/prepare-statement con "INSERT INTO fruit ( name, appearance, cost, grade ) VALUES ( 'test', 'test', 1, 1.0 )" @@ -893,6 +893,57 @@ (create-test-table :fruit db) (is (= [] (sql/query db "SELECT * FROM fruit"))))) +(deftest insert-one-via-execute + (doseq [db (test-specs)] + (create-test-table :fruit db) + (let [new-key ((select-key db) + (sql/execute! db [(str "INSERT INTO fruit ( name )" + " VALUES ( ? )") + "Apple"] + {:return-keys true}))] + (is (= (returned-key db 1) new-key))))) + +(deftest insert-two-via-execute + (doseq [db (test-specs)] + (create-test-table :fruit db) + (let [execute-multi-insert + (fn [db] + (sql/execute! db [(str "INSERT INTO fruit ( name )" + " VALUES ( ? )") + ["Apple"] + ["Orange"]] + {:return-keys true + :multi? true})) + new-keys (map (select-key db) + (if (#{"jtds" "jtds:sqlserver"} + (or (:subprotocol db) (:dbtype db))) + (do + (is (thrown? java.sql.BatchUpdateException + (execute-multi-insert db))) + []) + (execute-multi-insert db)))] + (case (or (:subprotocol db) (:dbtype db)) + ;; SQLite only returns the last key inserted in a batch + "sqlite" (is (= [(returned-key db 2)] new-keys)) + ;; Derby returns a single row count + "derby" (is (= [(returned-key db 1)] new-keys)) + ;; H2 returns nothing useful + "h2" (is (= [] new-keys)) + ;; HSQL returns nothing useful + "hsql" (is (= [] new-keys)) + ;; MS SQL returns row counts + "mssql" (is (= [1 1] new-keys)) + ;; jTDS disallows batch updates returning keys (handled above) + ("jtds" "jtds:sqlserver") + (is (= [] new-keys)) + ;; otherwise expect two rows with the correct keys + (do + (when-not (= [(returned-key db 1) (returned-key db 2)] new-keys) + (println "FAIL FOR" db)) + (is (= [(returned-key db 1) + (returned-key db 2)] + new-keys))))))) + (deftest insert-one-row (doseq [db (test-specs)] (create-test-table :fruit db) From 1183d5738001b8962d83f9883c865d4d4b8578eb Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 29 Dec 2017 14:00:18 -0600 Subject: [PATCH 072/175] [maven-release-plugin] prepare release java.jdbc-0.7.5 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3425d8fc..ab1f77e3 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.5-SNAPSHOT + 0.7.5 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.5 From d0407501d6702e8c08b9452edd706011e4a40454 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Fri, 29 Dec 2017 14:00:18 -0600 Subject: [PATCH 073/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ab1f77e3..b047a1cf 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.5 + 0.7.6-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.5 + HEAD From 5b2b9a3985d54b70bdd789891819f0fde37fbeb6 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 12 Jan 2018 12:42:56 -0800 Subject: [PATCH 074/175] Expand/improve find-by-keys docstring --- src/main/clojure/clojure/java/jdbc.clj | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index cdb4c3a8..e79b72c0 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1301,8 +1301,21 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defn find-by-keys "Given a database connection, a table name, a map of column name/value pairs, and an optional options map, return any matching rows. - An :order-by option may be supplied to sort the rows by a sequence of - columns, e.g,. {:order-by [:name {:age :desc]}" + + An :order-by option may be supplied to sort the rows, e.g., + + {:order-by [{:name :asc} {:age :desc} {:income :asc}]} + ;; equivalent to: + {:order-by [:name {:age :desc} :income]} + + The :order-by value is a sequence of column names (to sort in ascending + order) and/or maps from column names to directions (:asc or :desc). The + directions may be strings or keywords and are not case-sensitive. They + are mapped to ASC or DESC in the generated SQL. + + Note: if a ordering map has more than one key, the order of the columns + in the generated SQL ORDER BY clause is unspecified (so such maps should + only contain one key/value pair)." ([db table columns] (find-by-keys db table columns {})) ([db table columns opts] (let [{:keys [entities order-by] :as opts} From e8040925beddab6048873625cfe791341082290d Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 22 Feb 2018 16:17:42 -0800 Subject: [PATCH 075/175] Replace Leiningen with clj script and deps.edn --- .gitignore | 1 + README.md | 2 + deps.edn | 36 +++++++++++++ project.clj | 48 ------------------ run-tests.sh | 56 +++++++++++++++++++++ src/perf/clojure/clojure/java/perf_jdbc.clj | 17 +++++-- 6 files changed, 109 insertions(+), 51 deletions(-) create mode 100644 deps.edn delete mode 100644 project.clj create mode 100755 run-tests.sh diff --git a/.gitignore b/.gitignore index 4c0fdbdb..c1878206 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.jar .classpath +.cpcache .project .settings /.lein-failures diff --git a/README.md b/README.md index 21fd181b..77014295 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,8 @@ Developer Information $ TEST_DBS=mysql,postgres mvn test +* Also see the `run-tests.sh` shell script which uses the `clj` CLI and `deps.edn` for multi-version testing! + Change Log ==================== diff --git a/deps.edn b/deps.edn new file mode 100644 index 00000000..5cc4b181 --- /dev/null +++ b/deps.edn @@ -0,0 +1,36 @@ +;; You can run clojure.java.jdbc tests with: clj -A:test -A:runner +;; You can also specify an alias to select which version of Clojure to test +;; against: :1.7 :1.8 :1.9 :master + +{:paths ["src/main/clojure"] + :aliases {:test + {:extra-paths ["src/test/clojure"] + :extra-deps {org.clojure/test.check {:mvn/version "0.9.0"} + ;; Note: 1.12.1.1 is used in pom.xml for Java 6/7 compat + org.apache.derby/derby {:mvn/version "10.13.1.1"} + org.hsqldb/hsqldb {:mvn/version "2.3.4"} + ;; Note: 1.4.191 is used in pom.xml for Java 6 compat + com.h2database/h2 {:mvn/version "1.4.193"} + net.sourceforge.jtds/jtds {:mvn/version "1.3.1"} + ;; Note: Tests fail with 6.0.2+ driver + mysql/mysql-connector-java {:mvn/version "5.1.41"} + org.postgresql/postgresql {:mvn/version "9.4.1212.jre7"} + com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.7.1"} + org.xerial/sqlite-jdbc {:mvn/version "3.16.1"} + ;; Note: Assumes Java 8; there's a .jre7 version as well + com.microsoft.sqlserver/mssql-jdbc {:mvn/version "6.2.2.jre8"}}} + :1.7 {:override-deps {org.clojure/clojure {:mvn/version "1.7.0"}}} + :1.8 {:override-deps {org.clojure/clojure {:mvn/version "1.8.0"}}} + :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} + :master {:override-deps {org.clojure/clojure {:mvn/version "1.10.0-master-SNAPSHOT"}}} + :perf {:extra-paths ["src/perf/clojure"] + :extra-deps {criterium {:mvn/version "0.4.4"}} + :jvm-opts ["-server" + "-Xmx4096m" + "-Dclojure.compiler.direct-linking=true"]} + :runner + {:extra-deps {com.cognitect/test-runner + {:git/url "https://github.com/cognitect-labs/test-runner" + :sha "637c14437ba31c92a9168a65b1a787ad8713919c"}} + :main-opts ["-m" "cognitect.test-runner" + "-d" "src/test/clojure"]}}} diff --git a/project.clj b/project.clj deleted file mode 100644 index c25fcaa8..00000000 --- a/project.clj +++ /dev/null @@ -1,48 +0,0 @@ -;; NOTE: This project.clj file exists purely to make it easier to -;; develop and test java.jdbc locally. The pom.xml file is the -;; "system of record" as far as the project version is concerned. - -(defproject org.clojure/java.jdbc "0.7.6-SNAPSHOT" - :description "A low-level Clojure wrapper for JDBC-based access to databases." - :parent [org.clojure/pom.contrib "0.1.2"] - :url "https://github.com/clojure/java.jdbc" - :license {:name "Eclipse Public License" - :url "http://www.eclipse.org/legal/epl-v10.html"} - :source-paths ["src/main/clojure"] - :test-paths ["src/test/clojure"] - :dependencies [[org.clojure/clojure "1.9.0"] - ;; These are just the versions most recently test against - ;; for your own projects, use whatever version is most - ;; appropriate for you. Again, note that this project.clj - ;; file exists for convenience -- the pom.xml file is the - ;; "system of record" as far as dependencies go! - ;; Note: 1.12.1.1 is used in pom.xml for Java 6/7 compat - [org.apache.derby/derby "10.13.1.1"] - [org.hsqldb/hsqldb "2.3.4"] - ;; Note: 1.4.191 is used in pom.xml for Java 6 compat - [com.h2database/h2 "1.4.193"] - [net.sourceforge.jtds/jtds "1.3.1"] - ;; Tests fail with 6.0.2 driver: - [mysql/mysql-connector-java "5.1.41"] - [org.postgresql/postgresql "9.4.1212.jre7"] - [com.impossibl.pgjdbc-ng/pgjdbc-ng "0.7.1"] - [org.xerial/sqlite-jdbc "3.16.1"] - ;; Assumes Java 8; there's a .jre7 version as well: - [com.microsoft.sqlserver/mssql-jdbc "6.2.2.jre8"]] - - :profiles {:1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} - :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]} - :master {:dependencies [[org.clojure/clojure "1.10.0-master-SNAPSHOT"]]} - :dev {:dependencies [[org.clojure/test.check "0.9.0"] - [criterium "0.4.4"]]} - :perf {:test-paths ["src/perf/clojure"] - :jvm-opts ^:replace ["-server" - "-Xmx4096m" - "-Dclojure.compiler.direct-linking=true"]}} - :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"} - ;; include dev profile with 1.9+ to pull in test.check - :aliases {"test-all" ["with-profile" - "dev,test,master:dev,test:test,1.8:test,1.7" - "test"] - "perf" ["with-profile" "default,dev,perf"]} - :min-lein-version "2.0.0") diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 00000000..526c30e6 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# +# Run this shell script with a list of databases you want to test against: +# +# ./run-tests.sh mysql postgres +# +# The default list of derby h2 hsqldb sqlite are always tested against. +# +# Additionally you can specify: mssql jtds postgres pgsql mysql +# +# Alternatives to mysql (a db-spec) are mysql-str and mysql-jdbc-str which use +# connection strings instead. You may only specify one of these! +# +# Note: for mysql, postgres, and pgsql, the tests assume a database schema +# called clojure_test that is accesible by a user clojure_test with the +# password # clojure_test (currently hardcoded in the tests, sorry!). +# This will eventually change... +# +# For postgres or pgsql, you can set the following environment variables +# to override the defaults of 127.0.0.1 and 5432: +# +# TEST_POSTGRES_HOST TEST_POSTGRES_PORT +# +# Currently you may only specify one of postgres or pgsql! +# +# For mssql, you can set the following environment variables to override the +# defaults of 127.0.0.1\\SQLEXPRESS, 1433, clojure_test, sa, (empty string): +# +# TEST_MSSQL_HOST TEST_MSSQL_PORT TEST_MSSQL_NAME TEST_MSSQL_USER TEST_MSSQL_PASS +# +# For jtds, you can set the following environment variables (defaults per above): +# +# TEST_JTDS_HOST TEST_JTDS_PORT TEST_JTDS_NAME TEST_JTDS_USER TEST_JTDS_PASS +# +# Note: if you specify both mssql and jtds, make sure they're pointing at +# different database names or the tests will fail! +# +# Default set of databases to test: +dbs=sqlite,derby,hsqldb,h2 +# Can also specify: mssql,jtds,postgres,pgsql +# +while test "x$1" != "x" +do + dbs=${dbs},$1 + shift +done + +# Start with clean databases each time to avoid slowdown +rm -rf clojure_test_* + +versions="1.7 1.8 1.9 master" +for v in $versions +do + echo "\nRunning tests for Clojure $v" + TEST_DBS=$dbs time clj -A:test -A:$v -A:runner +done diff --git a/src/perf/clojure/clojure/java/perf_jdbc.clj b/src/perf/clojure/clojure/java/perf_jdbc.clj index 3554ed12..854254fb 100644 --- a/src/perf/clojure/clojure/java/perf_jdbc.clj +++ b/src/perf/clojure/clojure/java/perf_jdbc.clj @@ -10,9 +10,20 @@ (ns clojure.java.perf-jdbc "Performance tests for parts of clojure.java.jdbc. - Start a REPL with `lein perf repl` - Require this namespace and then run `(calibrate)` - followed by `(test-dummy)` and `(test-h2)` + Here's how to run these tests: + + $ clj -A:test -A:perf + Clojure 1.9.0 + user=> (p/calibrate) + ... + nil + user=> (p/test-dummy) + ... + nil + user=> (p/test-h2) + ... + nil + user=> These test compare the raw performance (against an in-memory H2 database) for hand-crafted Java JDBC calls and various `query` and `reducible-query` From cd1d9f05d79d4deb2324fd0eb13d93ebe4872be2 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 22 Feb 2018 16:48:32 -0800 Subject: [PATCH 076/175] Clean up how the tests are run Uses spaces instead of commas so the command line list of DB types can just be passed straight through. Print Clojure version under test from inside JDBC test to confirm exact version being run. --- README.md | 2 +- run-tests.sh | 15 +++++---------- src/test/clojure/clojure/java/test_jdbc.clj | 3 ++- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 77014295..1cc46500 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ Developer Information * Then run the tests with the TEST_DBS environment variable: - $ TEST_DBS=mysql,postgres mvn test + $ TEST_DBS="mysql postgres" mvn test * Also see the `run-tests.sh` shell script which uses the `clj` CLI and `deps.edn` for multi-version testing! diff --git a/run-tests.sh b/run-tests.sh index 526c30e6..dd41b4ea 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -32,18 +32,14 @@ # # TEST_JTDS_HOST TEST_JTDS_PORT TEST_JTDS_NAME TEST_JTDS_USER TEST_JTDS_PASS # +# For jtds you can just specify the IP address or hostname, you do not need +# the \\SQLEXPRESS part. +# # Note: if you specify both mssql and jtds, make sure they're pointing at # different database names or the tests will fail! # # Default set of databases to test: -dbs=sqlite,derby,hsqldb,h2 -# Can also specify: mssql,jtds,postgres,pgsql -# -while test "x$1" != "x" -do - dbs=${dbs},$1 - shift -done +dbs="derby h2 hsqldb sqlite" # Start with clean databases each time to avoid slowdown rm -rf clojure_test_* @@ -51,6 +47,5 @@ rm -rf clojure_test_* versions="1.7 1.8 1.9 master" for v in $versions do - echo "\nRunning tests for Clojure $v" - TEST_DBS=$dbs time clj -A:test -A:$v -A:runner + TEST_DBS="$dbs $*" time clj -A:test -A:$v -A:runner done diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index 1130be3b..6f818456 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -25,6 +25,7 @@ [clojure.java.jdbc :as sql] [clojure.string :as str])) +(println "\nTesting with Clojure" (clojure-version)) (def with-spec? (try (require 'clojure.java.jdbc.spec) (require 'clojure.spec.test.alpha) @@ -42,7 +43,7 @@ ;; Apache Derby and HSQLDB can run without an external setup. (def test-databases (if-let [dbs (System/getenv "TEST_DBS")] - (map keyword (.split dbs ",")) + (map keyword (.split dbs " ")) ;; enable more by default once the build server is equipped? [:derby :hsqldb :h2 :sqlite])) From 8d32a56e7691d64cbcdcbcf83f325b19e3f90968 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 22 Feb 2018 16:51:09 -0800 Subject: [PATCH 077/175] Noted switch from Leiningen to CLI/deps in Change Log --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 420b9022..ee97153f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes coming in 0.7.6 + +* Switched local test infrastructure over to CLI and `deps.edn` (from Leiningen) as an example of multi-version testing without a "build tool". + Changes in 0.7.5 * Add support for `:return-keys` in `execute!` and `:multi?` in `db-do-prepared-return-keys` [JDBC-163](https://dev.clojure.org/jira/browse/JDBC-163). From 7ee07ca5e80de665b022683920ada28b8bbe252b Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 22 Feb 2018 17:55:45 -0800 Subject: [PATCH 078/175] Add warn-on-reflection to ensure no reflection creeps in --- src/main/clojure/clojure/java/jdbc.clj | 2 ++ src/main/clojure/clojure/java/jdbc/spec.clj | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index e79b72c0..28011d94 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -50,6 +50,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (java.util Hashtable Map Properties) (javax.sql DataSource))) +(set! *warn-on-reflection* true) + (defn as-sql-name "Given a naming strategy function and a keyword or string, return a string per that naming strategy. diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index 9a4dfb03..be4b104d 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -17,6 +17,8 @@ (:require [clojure.spec.alpha :as s] [clojure.java.jdbc :as sql])) +(set! *warn-on-reflection* true) + ;; basic java.sql types -- cannot be generated! (s/def ::connection #(instance? java.sql.Connection %)) From 3d636725ba42fbfecc4a6be561baf37158638efc Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 22 Feb 2018 17:56:12 -0800 Subject: [PATCH 079/175] Add missing spec for db-spec being a URI object --- src/main/clojure/clojure/java/jdbc/spec.clj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index be4b104d..9c7e9fee 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -26,6 +26,7 @@ (s/def ::prepared-statement #(instance? java.sql.PreparedStatement %)) (s/def ::result-set #(instance? java.sql.ResultSet %)) (s/def ::result-set-metadata #(instance? java.sql.ResultSetMetaData %)) +(s/def ::uri #(instance? java.net.URI %)) ;; database specification (connection description) @@ -73,6 +74,7 @@ (s/def ::db-spec-jndi (s/keys :req-un [::name] :opt-un [::environment])) (s/def ::db-spec-string string?) +(s/def ::db-spec-uri ::uri) (s/def ::db-spec (s/or :connection ::db-spec-connection :friendly ::db-spec-friendly :raw ::db-spec-raw @@ -80,7 +82,8 @@ :factory ::db-spec-factory :datasource ::db-spec-data-source :jndi ::db-spec-jndi - :uri ::db-spec-string)) + :uri-str ::db-spec-string + :uri-obj ::db-spec-uri)) ;; naming From c61346dadf7cf123f64cd048c5e4f1a45926b1e9 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 22 Feb 2018 17:56:41 -0800 Subject: [PATCH 080/175] Correct :connection-string to :connection-uri in add-connection --- src/main/clojure/clojure/java/jdbc.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 28011d94..5eef88ca 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -137,7 +137,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (extend-protocol Connectable String - (add-connection [s connection] {:connection connection :level 0 :connection-string s}) + (add-connection [s connection] {:connection connection :level 0 :connection-uri s}) (get-level [_] 0) clojure.lang.Associative From 4a996d2e9d37a1e79a8f0e4991e9071acdb4b7d4 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 22 Feb 2018 17:58:56 -0800 Subject: [PATCH 081/175] Update tests so they work again with a db-spec string --- src/test/clojure/clojure/java/test_jdbc.clj | 80 +++++++++++++-------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index 6f818456..c98c51fb 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -146,41 +146,63 @@ ;; necessary for it to do its job, and populate it as needed... (defn- derby? [db] - (if (string? db) - (re-find #"derby:" db) - (= "derby" (or (:subprotocol db) (:dbtype db))))) + (cond (string? db) + (re-find #"derby:" db) + (:connection-uri db) + (re-find #"derby:" (:connection-uri db)) + :else + (= "derby" (or (:subprotocol db) (:dbtype db))))) (defn- hsqldb? [db] - (if (string? db) - (re-find #"hsqldb:" db) - (#{"hsql" "hsqldb"} (or (:subprotocol db) (:dbtype db))))) + (cond (string? db) + (re-find #"hsqldb:" db) + (:connection-uri db) + (re-find #"hsqldb:" (:connection-uri db)) + :else + (#{"hsql" "hsqldb"} (or (:subprotocol db) (:dbtype db))))) (defn- mssql? [db] - (if (string? db) - (re-find #"sqlserver" db) - (#{"jtds" "jtds:sqlserver" "mssql" "sqlserver"} - (or (:subprotocol db) (:dbtype db))))) + (cond (string? db) + (re-find #"sqlserver" db) + (:connection-uri db) + (re-find #"sqlserver" (:connection-uri db)) + :else + (#{"jtds" "jtds:sqlserver" "mssql" "sqlserver"} + (or (:subprotocol db) (:dbtype db))))) (defn- mysql? [db] - (if (string? db) - (re-find #"mysql:" db) - (= "mysql" (or (:subprotocol db) (:dbtype db))))) + (cond (string? db) + (re-find #"mysql:" db) + (:connection-uri db) + (re-find #"mysql:" (:connection-uri db)) + :else + (= "mysql" (or (:subprotocol db) (:dbtype db))))) (defn- postgres? [db] - (if (string? db) - (or (re-find #"postgres" db) (re-find #"pgsql")) - (or (re-find #"postgres" (or (:subprotocol db) (:dbtype db))) - (re-find #"pgsql" (or (:subprotocol db) (:dbtype db)))))) + (cond (string? db) + (or (re-find #"postgres" db) (re-find #"pgsql" db)) + (:connection-uri db) + (or (re-find #"postgres" (:connection-uri db)) + (re-find #"pgsql" (:connection-uri db))) + :else + (or (re-find #"postgres" (or (:subprotocol db) (:dbtype db))) + (re-find #"pgsql" (or (:subprotocol db) (:dbtype db)))))) (defn- pgsql? [db] - (if (string? db) - (re-find #"pgsql") - (re-find #"pgsql" (or (:subprotocol db) (:dbtype db))))) + (cond (string? db) + (re-find #"pgsql" db) + (:connection-uri db) + (re-find #"pgsql" (:connection-uri db)) + :else + (re-find #"pgsql" (or (:subprotocol db) (:dbtype db))))) (defn- sqlite? [db] - (if (string? db) - (re-find #"sqlite:" db) - (= "sqlite" (or (:subprotocol db) (:dbtype db))))) + (cond (string? db) + (re-find #"sqlite:" db) + (:connection-uri db) + (re-find #"sqlite:" (:connection-uri db)) + :else + (= "sqlite" (or (:subprotocol db) (:dbtype db))))) (defmulti create-test-table "Create a standard test table. Uses db-do-commands. @@ -980,8 +1002,9 @@ (sql/query db ["SELECT * FROM fruit"]))) (is (= [{:ID (generated-key db 1) :NAME "Apple" :APPEARANCE nil :GRADE nil :COST nil}] (sql/query db ["SELECT * FROM fruit"] {:identifiers str/upper-case}))) - (is (= [{:ID (generated-key db 1) :NAME "Apple" :APPEARANCE nil :GRADE nil :COST nil}] - (sql/query (assoc db :identifiers str/upper-case) ["SELECT * FROM fruit"]))) + (when (map? db) + (is (= [{:ID (generated-key db 1) :NAME "Apple" :APPEARANCE nil :GRADE nil :COST nil}] + (sql/query (assoc db :identifiers str/upper-case) ["SELECT * FROM fruit"])))) (is (= [{:fruit/id (generated-key db 1) :fruit/name "Apple" :fruit/appearance nil :fruit/grade nil :fruit/cost nil}] (sql/query db ["SELECT * FROM fruit"] {:qualifier "fruit"}))) @@ -991,9 +1014,10 @@ (is (= [{:name "Apple"}] (sql/query db ["SELECT name FROM fruit"] {:identifiers (comp keyword str/lower-case)}))) - (is (= [{:fruit/id (generated-key db 1) :fruit/name "Apple" :fruit/appearance nil - :fruit/grade nil :fruit/cost nil}] - (sql/query (assoc db :qualifier "fruit") ["SELECT * FROM fruit"]))) + (when (map? db) + (is (= [{:fruit/id (generated-key db 1) :fruit/name "Apple" :fruit/appearance nil + :fruit/grade nil :fruit/cost nil}] + (sql/query (assoc db :qualifier "fruit") ["SELECT * FROM fruit"])))) (is (= [{:id (generated-key db 1) :name "Apple" :appearance nil :grade nil :cost nil}] (with-open [con (sql/get-connection db)] (sql/query db [(sql/prepare-statement con "SELECT * FROM fruit")])))) From e70af1ef373fe1ba8335fe18cbe63d21125df263 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 22 Feb 2018 18:00:18 -0800 Subject: [PATCH 082/175] Update to reflect today's changes --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index ee97153f..005982d1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ Changes coming in 0.7.6 +* Add missing spec for `db-spec` being a `java.net.URI` object. +* Fix `add-connection` handling of string `db-spec` (becomes `:connection-uri`, not `:connection-string`). +* Update tests so they work properly with string `db-spec` test databases. +* Ensure no reflection warnings are present. * Switched local test infrastructure over to CLI and `deps.edn` (from Leiningen) as an example of multi-version testing without a "build tool". Changes in 0.7.5 From 64a79366fa464be75bdf4bdda133441b9d1efb26 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 22 Feb 2018 18:22:00 -0800 Subject: [PATCH 083/175] More test clean up for string db-spec handling --- src/test/clojure/clojure/java/test_jdbc.clj | 77 ++++++--------------- 1 file changed, 23 insertions(+), 54 deletions(-) diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/test_jdbc.clj index c98c51fb..f388003b 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/test_jdbc.clj @@ -145,64 +145,34 @@ ;; We start with all tables dropped and each test has to create the tables ;; necessary for it to do its job, and populate it as needed... +(defn- string->type [db] + (last (re-find #"^(jdbc:)?([^:]+):" db))) + +(defn- db-type [db] + (or (:subprotocol db) (:dbtype db) + (and (string? db) (string->type db)) + (and (:connection-uri db) (string->type (:connection-uri db))))) + (defn- derby? [db] - (cond (string? db) - (re-find #"derby:" db) - (:connection-uri db) - (re-find #"derby:" (:connection-uri db)) - :else - (= "derby" (or (:subprotocol db) (:dbtype db))))) + (= "derby" (db-type db))) (defn- hsqldb? [db] - (cond (string? db) - (re-find #"hsqldb:" db) - (:connection-uri db) - (re-find #"hsqldb:" (:connection-uri db)) - :else - (#{"hsql" "hsqldb"} (or (:subprotocol db) (:dbtype db))))) + (#{"hsql" "hsqldb"} (db-type db))) (defn- mssql? [db] - (cond (string? db) - (re-find #"sqlserver" db) - (:connection-uri db) - (re-find #"sqlserver" (:connection-uri db)) - :else - (#{"jtds" "jtds:sqlserver" "mssql" "sqlserver"} - (or (:subprotocol db) (:dbtype db))))) + (#{"jtds" "jtds:sqlserver" "mssql" "sqlserver"} (db-type db))) (defn- mysql? [db] - (cond (string? db) - (re-find #"mysql:" db) - (:connection-uri db) - (re-find #"mysql:" (:connection-uri db)) - :else - (= "mysql" (or (:subprotocol db) (:dbtype db))))) + (= "mysql" (db-type db))) (defn- postgres? [db] - (cond (string? db) - (or (re-find #"postgres" db) (re-find #"pgsql" db)) - (:connection-uri db) - (or (re-find #"postgres" (:connection-uri db)) - (re-find #"pgsql" (:connection-uri db))) - :else - (or (re-find #"postgres" (or (:subprotocol db) (:dbtype db))) - (re-find #"pgsql" (or (:subprotocol db) (:dbtype db)))))) + (#{"postgres" "pgsql"} (db-type db))) (defn- pgsql? [db] - (cond (string? db) - (re-find #"pgsql" db) - (:connection-uri db) - (re-find #"pgsql" (:connection-uri db)) - :else - (re-find #"pgsql" (or (:subprotocol db) (:dbtype db))))) + (= "pgsql" (db-type db))) (defn- sqlite? [db] - (cond (string? db) - (re-find #"sqlite:" db) - (:connection-uri db) - (re-find #"sqlite:" (:connection-uri db)) - :else - (= "sqlite" (or (:subprotocol db) (:dbtype db))))) + (= "sqlite" (db-type db))) (defmulti create-test-table "Create a standard test table. Uses db-do-commands. @@ -267,7 +237,7 @@ "mysql://clojure_test:clojure_test@localhost:3306/clojure_test"))))) (defn- returned-key [db k] - (case (or (:subprotocol db) (:dbtype db)) + (case (db-type db) "derby" {(keyword "1") nil} ("hsql" "hsqldb") nil "h2" nil @@ -281,12 +251,12 @@ k)) (defn- select-key [db] - (case (or (:subprotocol db) (:dbtype db)) + (case (db-type db) ("postgres" "postgresql" "pgsql") :id identity)) (defn- generated-key [db k] - (case (or (:subprotocol db) (:dbtype db)) + (case (db-type db) "derby" 0 ("hsql" "hsqldb") 0 "h2" 0 @@ -296,7 +266,7 @@ k)) (defn- float-or-double [db v] - (case (or (:subprotocol db) (:dbtype db)) + (case (db-type db) "derby" (Float. v) "h2" (Float. v) ("jtds" "jtds:sqlserver") (Float. v) @@ -405,7 +375,7 @@ (deftest test-do-prepared1e (doseq [db (test-specs)] ;; Derby/SQL Server does not have auto-generated id column which we're testing here - (when-not (#{"derby" "jtds" "jtds:sqlserver"} (or (:subprotocol db) (:dbtype db))) + (when-not (#{"derby" "jtds" "jtds:sqlserver"} (db-type db)) (create-test-table :fruit db) (with-open [con (sql/get-connection db)] (let [stmt (sql/prepare-statement con "INSERT INTO fruit ( name, appearance, cost, grade ) VALUES ( 'test', 'test', 1, 1.0 )" @@ -578,7 +548,7 @@ (deftest execute-with-prepared-statement-return-keys (doseq [db (test-specs)] ;; Derby/SQL Server does not have auto-generated id column which we're testing here - (when-not (#{"derby" "jtds" "jtds:sqlserver"} (or (:subprotocol db) (:dbtype db))) + (when-not (#{"derby" "jtds" "jtds:sqlserver"} (db-type db)) (create-test-table :fruit db) (sql/with-db-connection [conn db] (let [connection (:connection conn) @@ -938,14 +908,13 @@ {:return-keys true :multi? true})) new-keys (map (select-key db) - (if (#{"jtds" "jtds:sqlserver"} - (or (:subprotocol db) (:dbtype db))) + (if (#{"jtds" "jtds:sqlserver"} (db-type db)) (do (is (thrown? java.sql.BatchUpdateException (execute-multi-insert db))) []) (execute-multi-insert db)))] - (case (or (:subprotocol db) (:dbtype db)) + (case (db-type db) ;; SQLite only returns the last key inserted in a batch "sqlite" (is (= [(returned-key db 2)] new-keys)) ;; Derby returns a single row count From 05d06a3784a66390073c30a5c075f057d5f6d797 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 22 Feb 2018 18:43:12 -0800 Subject: [PATCH 084/175] Clean up clj alias usage/instructions --- deps.edn | 2 +- run-tests.sh | 2 +- src/perf/clojure/clojure/java/perf_jdbc.clj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deps.edn b/deps.edn index 5cc4b181..d8929853 100644 --- a/deps.edn +++ b/deps.edn @@ -1,4 +1,4 @@ -;; You can run clojure.java.jdbc tests with: clj -A:test -A:runner +;; You can run clojure.java.jdbc tests with: clj -A:test:runner ;; You can also specify an alias to select which version of Clojure to test ;; against: :1.7 :1.8 :1.9 :master diff --git a/run-tests.sh b/run-tests.sh index dd41b4ea..715fc84d 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -47,5 +47,5 @@ rm -rf clojure_test_* versions="1.7 1.8 1.9 master" for v in $versions do - TEST_DBS="$dbs $*" time clj -A:test -A:$v -A:runner + TEST_DBS="$dbs $*" time clj -A:test:runner:$v done diff --git a/src/perf/clojure/clojure/java/perf_jdbc.clj b/src/perf/clojure/clojure/java/perf_jdbc.clj index 854254fb..f104db4b 100644 --- a/src/perf/clojure/clojure/java/perf_jdbc.clj +++ b/src/perf/clojure/clojure/java/perf_jdbc.clj @@ -12,7 +12,7 @@ Here's how to run these tests: - $ clj -A:test -A:perf + $ clj -A:test:perf Clojure 1.9.0 user=> (p/calibrate) ... From 932f6c7137dbc227c4a6aa4f77155cd1f6429114 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 3 Mar 2018 14:58:26 -0800 Subject: [PATCH 085/175] Update to latest test-runner Move and rename tests to reflect more standard -test namespace approach. --- .gitignore | 1 + deps.edn | 2 +- .../java/{test_utilities.clj => jdbc/utilities_test.clj} | 2 +- .../clojure/clojure/java/{test_jdbc.clj => jdbc_test.clj} | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) rename src/test/clojure/clojure/java/{test_utilities.clj => jdbc/utilities_test.clj} (99%) rename src/test/clojure/clojure/java/{test_jdbc.clj => jdbc_test.clj} (99%) diff --git a/.gitignore b/.gitignore index c1878206..b3db9d32 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /.lein-failures /.lein-repl-history /.nrepl-port +/build.boot /clojure_test_* bin classes diff --git a/deps.edn b/deps.edn index d8929853..e7bb6946 100644 --- a/deps.edn +++ b/deps.edn @@ -31,6 +31,6 @@ :runner {:extra-deps {com.cognitect/test-runner {:git/url "https://github.com/cognitect-labs/test-runner" - :sha "637c14437ba31c92a9168a65b1a787ad8713919c"}} + :sha "5f2b5c2efb444df76fb5252102b33f542ebf7f58"}} :main-opts ["-m" "cognitect.test-runner" "-d" "src/test/clojure"]}}} diff --git a/src/test/clojure/clojure/java/test_utilities.clj b/src/test/clojure/clojure/java/jdbc/utilities_test.clj similarity index 99% rename from src/test/clojure/clojure/java/test_utilities.clj rename to src/test/clojure/clojure/java/jdbc/utilities_test.clj index 02d7ba27..886b7cfa 100644 --- a/src/test/clojure/clojure/java/test_utilities.clj +++ b/src/test/clojure/clojure/java/jdbc/utilities_test.clj @@ -18,7 +18,7 @@ ;; seancorfield (gmail) ;; Migrated from clojure.contrib.test-sql 17 April 2011 -(ns clojure.java.test-utilities +(ns clojure.java.jdbc.utilities-test (:use clojure.test) (:require [clojure.java.jdbc :as sql])) diff --git a/src/test/clojure/clojure/java/test_jdbc.clj b/src/test/clojure/clojure/java/jdbc_test.clj similarity index 99% rename from src/test/clojure/clojure/java/test_jdbc.clj rename to src/test/clojure/clojure/java/jdbc_test.clj index f388003b..74ffdfa6 100644 --- a/src/test/clojure/clojure/java/test_jdbc.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -20,7 +20,7 @@ ;; seancorfield (gmail) ;; Migrated from clojure.contrib.test-sql 17 April 2011 -(ns clojure.java.test-jdbc +(ns clojure.java.jdbc-test (:require [clojure.test :refer :all] [clojure.java.jdbc :as sql] [clojure.string :as str])) @@ -125,7 +125,7 @@ "Return a sequence of db-spec maps that should be used for tests" [] (for [db test-databases] - @(ns-resolve 'clojure.java.test-jdbc (symbol (str (name db) "-db"))))) + @(ns-resolve 'clojure.java.jdbc-test (symbol (str (name db) "-db"))))) (defn- clean-up "Attempt to drop any test tables before we start a test." From ff778ad8b5eab7554995895a110342bd370544a2 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 3 Mar 2018 21:30:46 -0800 Subject: [PATCH 086/175] Bump to latest test-runner --- deps.edn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps.edn b/deps.edn index e7bb6946..4f47ceda 100644 --- a/deps.edn +++ b/deps.edn @@ -31,6 +31,6 @@ :runner {:extra-deps {com.cognitect/test-runner {:git/url "https://github.com/cognitect-labs/test-runner" - :sha "5f2b5c2efb444df76fb5252102b33f542ebf7f58"}} + :sha "76568540e7f40268ad2b646110f237a60295fa3c"}} :main-opts ["-m" "cognitect.test-runner" "-d" "src/test/clojure"]}}} From c74de0b65f94ba88e33710fd8f51921f8948412a Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 4 Mar 2018 18:19:48 -0800 Subject: [PATCH 087/175] JDBC-165 fix specs for with-db-* functions --- CHANGES.md | 1 + src/main/clojure/clojure/java/jdbc/spec.clj | 9 +++++---- src/test/clojure/clojure/java/jdbc_test.clj | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 005982d1..8adcd7f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ Changes coming in 0.7.6 * Add missing spec for `db-spec` being a `java.net.URI` object. * Fix `add-connection` handling of string `db-spec` (becomes `:connection-uri`, not `:connection-string`). +* Fix specs for `with-db-*` functions, to support options in the binding form [JDBC-165](https://dev.clojure.org/jira/browse/JDBC-165). * Update tests so they work properly with string `db-spec` test databases. * Ensure no reflection warnings are present. * Switched local test infrastructure over to CLI and `deps.edn` (from Leiningen) as an example of multi-version testing without a "build tool". diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index 9c7e9fee..dd77989c 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -276,15 +276,16 @@ :args (s/cat :binding ::transaction-binding :body (s/* any?))) -(s/def ::simple-binding (s/spec (s/cat :con-db simple-symbol? - :db-spec any?))) +(s/def ::connection-binding (s/spec (s/cat :con-db simple-symbol? + :db-spec any? + :opts (s/? any?)))) (s/fdef sql/with-db-connection - :args (s/cat :binding ::simple-binding + :args (s/cat :binding ::connection-binding :body (s/* any?))) (s/fdef sql/with-db-metadata - :args (s/cat :binding ::simple-binding + :args (s/cat :binding ::connection-binding :body (s/* any?))) (s/fdef sql/metadata-result diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index 74ffdfa6..68fc4647 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -420,7 +420,7 @@ [4 "Orange" "juicy" 139 88.6]])] (is (= '(1 1 1 1) r))) (is (= 4 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) - (is (= 4 (sql/with-db-connection [con db] + (is (= 4 (sql/with-db-connection [con db {}] (sql/query con (sql/prepare-statement (sql/db-connection con) "SELECT * FROM fruit") {:result-set-fn count})))) (when-not (pgsql? db) ;; maxRows does not appear to be supported on Impossibl pgsql? @@ -840,7 +840,7 @@ (deftest test-metadata-managed (doseq [db (test-specs)] (create-test-table :fruit db) - (sql/with-db-metadata [metadata db] + (sql/with-db-metadata [metadata db {}] (let [table-info (sql/metadata-query (.getTables metadata nil nil nil (into-array ["TABLE" "VIEW"])))] From 37587dc32a29ea39ace9d944a250445619f528a3 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Tue, 6 Mar 2018 15:14:37 -0800 Subject: [PATCH 088/175] Appease Eastwood! --- src/main/clojure/clojure/java/jdbc.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 5eef88ca..ff45fb3b 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -804,7 +804,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (with-db-connection [con-db db-spec opts] ... con-db ...)" [binding & body] - `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] + `(let [db-spec# ~(second binding) opts# ~(or (second (rest binding)) {})] (with-open [con# (get-connection db-spec# opts#)] (let [~(first binding) (add-connection db-spec# con#)] ~@body)))) @@ -816,7 +816,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (with-db-metadata [md db-spec opts] ... md ...)" [binding & body] - `(let [db-spec# ~(second binding) opts# (or ~(second (rest binding)) {})] + `(let [db-spec# ~(second binding) opts# ~(or (second (rest binding)) {})] (with-open [con# (get-connection db-spec# opts#)] (let [~(first binding) (.getMetaData con#)] ~@body)))) From 412580cebfd699aa5b1814a29ff384aee955a8ed Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 23 Apr 2018 14:37:18 -0700 Subject: [PATCH 089/175] Add built-in :dbtype h2:mem support --- CHANGES.md | 1 + src/main/clojure/clojure/java/jdbc.clj | 18 +++++++++++------- src/main/clojure/clojure/java/jdbc/spec.clj | 2 +- src/test/clojure/clojure/java/jdbc_test.clj | 13 +++++++------ 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8adcd7f8..997c7ec7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ Changes coming in 0.7.6 +* Add built-in support for H2 in-memory database (`:dbtype "h2:mem"`). * Add missing spec for `db-spec` being a `java.net.URI` object. * Fix `add-connection` handling of string `db-spec` (becomes `:connection-uri`, not `:connection-string`). * Fix specs for `with-db-*` functions, to support options in the binding form [JDBC-165](https://dev.clojure.org/jira/browse/JDBC-165). diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index ff45fb3b..957e76c6 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -153,6 +153,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} The subprotocols map below provides aliases for dbtype." {"derby" "org.apache.derby.jdbc.EmbeddedDriver" "h2" "org.h2.Driver" + "h2:mem" "org.h2.Driver" "hsqldb" "org.hsqldb.jdbcDriver" "jtds:sqlserver" "net.sourceforge.jtds.jdbc.Driver" "mysql" "com.mysql.jdbc.Driver" @@ -335,13 +336,16 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} "sqlserver" 1433 nil)) db-sep (if (= "sqlserver" subprotocol) ";DATABASENAME=" "/") - url (if (#{"derby" "h2" "hsqldb" "sqlite"} subprotocol) - (str "jdbc:" subprotocol ":" dbname) - (str "jdbc:" subprotocol ":" - (host-prefixes subprotocol "//") - host - (when port (str ":" port)) - db-sep dbname)) + url (cond (= "h2:mem" dbtype) + (str "jdbc:" subprotocol ":" dbname ";DB_CLOSE_DELAY=-1") + (#{"derby" "h2" "hsqldb" "sqlite"} subprotocol) + (str "jdbc:" subprotocol ":" dbname) + :else + (str "jdbc:" subprotocol ":" + (host-prefixes subprotocol "//") + host + (when port (str ":" port)) + db-sep dbname)) etc (dissoc db-spec :dbtype :dbname)] (if-let [class-name (or classname (classnames subprotocol))] (do diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index dd77989c..2a93a059 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -30,7 +30,7 @@ ;; database specification (connection description) -(s/def ::subprotocol-base #{"derby" "h2" "hsqldb" "jtds:sqlserver" "mysql" +(s/def ::subprotocol-base #{"derby" "h2" "h2:mem" "hsqldb" "jtds:sqlserver" "mysql" "oracle:oci" "oracle:thin" "pgsql" "postgresql" "redshift" "sqlite" "sqlserver"}) (s/def ::subprotocol-alias #{"hsql" "jtds" "mssql" "oracle" "postgres"}) diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index 68fc4647..f6e2874d 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -77,8 +77,9 @@ (def hsqldb-db {:dbtype "hsql" :dbname "clojure_test_hsqldb"}) -(def h2-db {:dbtype "h2" - :dbname "./clojure_test_h2"}) +;; test with new (0.7.6) in-memory H2 database +(def h2-db {:dbtype "h2:mem" + :dbname "clojure_test_h2"}) (def sqlite-db {:dbtype "sqlite" :dbname "clojure_test_sqlite"}) @@ -240,7 +241,7 @@ (case (db-type db) "derby" {(keyword "1") nil} ("hsql" "hsqldb") nil - "h2" nil + ("h2" "h2:mem") nil "mysql" {:generated_key k} nil (if (mysql? db) ; string-based tests {:generated_key k} @@ -259,7 +260,7 @@ (case (db-type db) "derby" 0 ("hsql" "hsqldb") 0 - "h2" 0 + ("h2" "h2:mem") 0 ("jtds" "jtds:sqlserver") 0 ("mssql" "sqlserver") 0 "sqlite" 0 @@ -268,7 +269,7 @@ (defn- float-or-double [db v] (case (db-type db) "derby" (Float. v) - "h2" (Float. v) + ("h2" "h2:mem") (Float. v) ("jtds" "jtds:sqlserver") (Float. v) ("mssql" "sqlserver") (Float. v) ("postgres" "postgresql" "pgsql") (Float. v) @@ -920,7 +921,7 @@ ;; Derby returns a single row count "derby" (is (= [(returned-key db 1)] new-keys)) ;; H2 returns nothing useful - "h2" (is (= [] new-keys)) + ("h2" "h2:mem") (is (= [] new-keys)) ;; HSQL returns nothing useful "hsql" (is (= [] new-keys)) ;; MS SQL returns row counts From 51b7260696c35b53ad9802199f7fe62dc97330be Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 23 Apr 2018 21:29:42 -0700 Subject: [PATCH 090/175] JDBC-166 execute! :return-keys col names support --- CHANGES.md | 1 + src/main/clojure/clojure/java/jdbc.clj | 7 +++++-- src/test/clojure/clojure/java/jdbc_test.clj | 22 ++++++++++++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 997c7ec7..c1ffdb5d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ Changes coming in 0.7.6 +* `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://dev.clojure.org/jira/browse/JDBC-166). * Add built-in support for H2 in-memory database (`:dbtype "h2:mem"`). * Add missing spec for `db-spec` being a `java.net.URI` object. * Fix `add-connection` handling of string `db-spec` (becomes `:connection-uri`, not `:connection-string`). diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 957e76c6..c08af2eb 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -938,12 +938,15 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (db-do-prepared-return-keys db true transaction? sql-params) (db-do-prepared-return-keys db transaction? sql-params {}))) ([db transaction? sql-params opts] - (let [opts (merge (when (map? db) db) opts)] + (let [opts (merge (when (map? db) db) opts) + return-keys (or (:return-keys opts) true)] (if-let [con (db-find-connection db)] (let [[sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] (if (instance? PreparedStatement sql) (db-do-execute-prepared-return-keys db sql params (assoc opts :transaction? transaction?)) - (with-open [^PreparedStatement stmt (prepare-statement con sql (assoc opts :return-keys true))] + (with-open [^PreparedStatement stmt (prepare-statement + con sql + (assoc opts :return-keys return-keys))] (db-do-execute-prepared-return-keys db stmt params (assoc opts :transaction? transaction?))))) (with-open [con (get-connection db opts)] (db-do-prepared-return-keys (add-connection db con) transaction? sql-params opts)))))) diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index f6e2874d..dc7a2095 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -546,13 +546,16 @@ (is (= "Pear" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 99] {:row-fn :name :result-set-fn first}))))) -(deftest execute-with-prepared-statement-return-keys +(deftest execute-with-prepared-statement-with-return-keys (doseq [db (test-specs)] ;; Derby/SQL Server does not have auto-generated id column which we're testing here (when-not (#{"derby" "jtds" "jtds:sqlserver"} (db-type db)) (create-test-table :fruit db) (sql/with-db-connection [conn db] (let [connection (:connection conn) + ;; although we ask for keys to come back, execute! cannot see into + ;; the PreparedStatement so it doesn't know to call things in a + ;; different way, so we get affected row counts instead! prepared-statement (sql/prepare-statement connection (str "INSERT INTO fruit ( name, appearance, cost ) " "VALUES ( ?, ?, ? )") {:return-keys ["id"]})] @@ -563,6 +566,23 @@ (is (= "Pear" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 99] {:row-fn :name :result-set-fn first})))))) +(deftest execute-with-return-keys-option + (doseq [db (test-specs)] + ;; Derby/SQL Server does not have auto-generated id column which we're testing here + (when-not (#{"derby" "jtds" "jtds:sqlserver"} (db-type db)) + (create-test-table :fruit db) + (sql/with-db-connection [conn db] + (let [connection (:connection conn) + sql-stmt (str "INSERT INTO fruit ( name, appearance, cost ) " + "VALUES ( ?, ?, ? )")] + (is (= (returned-key db 1) (sql/execute! db [sql-stmt "Apple" "Green" 75] + {:return-keys ["id"]}))) + (is (= (returned-key db 2) (sql/execute! db [sql-stmt "Pear" "Yellow" 99] + {:return-keys ["id"]}))))) + (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) + (is (= "Pear" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 99] + {:row-fn :name :result-set-fn first})))))) + (deftest test-update-values (doseq [db (test-specs)] (create-test-table :fruit db) From 4de9aba0003358f663f1da77570006b30d51855f Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Tue, 24 Apr 2018 20:49:17 -0700 Subject: [PATCH 091/175] Prep for 0.7.6 release --- CHANGES.md | 2 +- README.md | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c1ffdb5d..384393e1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes coming in 0.7.6 +Changes in 0.7.6 * `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://dev.clojure.org/jira/browse/JDBC-166). * Add built-in support for H2 in-memory database (`:dbtype "h2:mem"`). diff --git a/README.md b/README.md index 1cc46500..56643a7c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Support Releases and Dependency Information ======================================== -Latest stable release: 0.7.5 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.6 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -32,14 +32,14 @@ Latest stable release: 0.7.5 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.5"] +[org.clojure/java.jdbc "0.7.6"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.5 + 0.7.6 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -138,6 +138,16 @@ Developer Information Change Log ==================== +* Release 0.7.6 on 2018-04-24 + * `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://dev.clojure.org/jira/browse/JDBC-166). + * Add built-in support for H2 in-memory database (`:dbtype "h2:mem"`). + * Add missing spec for `db-spec` being a `java.net.URI` object. + * Fix `add-connection` handling of string `db-spec` (becomes `:connection-uri`, not `:connection-string`). + * Fix specs for `with-db-*` functions, to support options in the binding form [JDBC-165](https://dev.clojure.org/jira/browse/JDBC-165). + * Update tests so they work properly with string `db-spec` test databases. + * Ensure no reflection warnings are present. + * Switched local test infrastructure over to CLI and `deps.edn` (from Leiningen) as an example of multi-version testing without a "build tool". + * Release 0.7.5 on 2017-12-29 * Add support for `:return-keys` in `execute!` and `:multi?` in `db-do-prepared-return-keys` [JDBC-163](https://dev.clojure.org/jira/browse/JDBC-163). From a12b7202b61bfbb5f35c51e977c942252bd607ea Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 24 Apr 2018 22:51:55 -0500 Subject: [PATCH 092/175] [maven-release-plugin] prepare release java.jdbc-0.7.6 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b047a1cf..547cb6a3 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.6-SNAPSHOT + 0.7.6 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.6 From 1320e630ba41ac6ef33abc178af9e7d1aeaeddb9 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 24 Apr 2018 22:51:55 -0500 Subject: [PATCH 093/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 547cb6a3..d8b4d4e5 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.6 + 0.7.7-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.6 + HEAD From 70224905f79c3523fb4803e1b9854efa47ac7bfb Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 9 May 2018 10:48:18 -0700 Subject: [PATCH 094/175] Clarify use of get-connection --- src/main/clojure/clojure/java/jdbc.clj | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index c08af2eb..e44b08bf 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1,4 +1,4 @@ -;; Copyright (c) 2008-2017 Sean Corfield, Stephen C. Gilardi. All rights reserved. +;; Copyright (c) 2008-2018 Sean Corfield, 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 @@ -228,8 +228,22 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (defn get-connection "Creates a connection to a database. db-spec is usually a map containing connection - parameters but can also be a URI or a String. The various possibilities are described - below: + parameters but can also be a URI or a String. + + The only time you should call this function is when you need a Connection for + prepare-statement -- no other public functions in clojure.java.jdbc accept a + raw Connection object: they all expect a db-spec (either a raw db-spec or one + obtained via with-db-connection or with-db-transaction). + + The correct usage of get-connection for prepare-statement is: + + (with-open [conn (jdbc/get-connection db-spec)] + ... (jdbc/prepare-statement conn sql-statement options) ...) + + Any connection obtained via calling get-connection directly must be closed + explicitly (via with-open or a direct call to .close on the Connection object). + + The various possibilities are described below: DriverManager (preferred): :dbtype (required) a String, the type of the database (the jdbc subprotocol) @@ -1006,6 +1020,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} "Given a db-spec, a SQL statement (or a prepared statement), a set of parameters, a result set processing function and options, execute the query." [db sql params func opts] + #_(println "\nquery" sql params) (if (instance? PreparedStatement sql) (let [^PreparedStatement stmt sql] (execute-query-with-params From 2c3175affe073fc51ffbf5a34c7fa9a8029066e8 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 9 May 2018 11:22:29 -0700 Subject: [PATCH 095/175] Add custom exception for misuse of Connection where db-spec is expected --- src/main/clojure/clojure/java/jdbc.clj | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index e44b08bf..5a33ba08 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -395,6 +395,16 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} name) db-spec))) + ;; passing a raw Connection object to a function expecting a db-spec is + ;; usually a confusion over how/when to use get-connection and deserves + ;; a custom error message: + (instance? java.sql.Connection db-spec) + (let [^String msg (str "db-spec is a raw Connection object!\n" + "Did you call get-connection in the wrong context?\n" + "You should only call that to pass a Connection into prepare-statement.\n" + "(and don't forget to close it via with-open or .close)")] + (throw (IllegalArgumentException. msg))) + :else (let [^String msg (format "db-spec %s is missing a required parameter" db-spec)] (throw (IllegalArgumentException. msg)))))) From b560db958d3aef1fb404c5f05377a2915b5c3327 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 9 May 2018 11:26:21 -0700 Subject: [PATCH 096/175] Note the enhanced exception message for misuse of get-connection --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 384393e1..fac5edbe 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes coming in 0.7.7 + +* `get-connection` provides much better feedback if you accidentally call a function that expects a `db-spec` but pass a `java.sql.Connection` object instead (which is only required for `prepare-statement`). + Changes in 0.7.6 * `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://dev.clojure.org/jira/browse/JDBC-166). From 29cde7211d6e2edd451b6034ac0909e914b48dbc Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 25 May 2018 09:35:02 -0700 Subject: [PATCH 097/175] Fix execute! :return-keys test for Postgres Because I always forget that Postgres returns rows, not just values. --- src/test/clojure/clojure/java/jdbc_test.clj | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index dc7a2095..58ec6566 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -574,11 +574,14 @@ (sql/with-db-connection [conn db] (let [connection (:connection conn) sql-stmt (str "INSERT INTO fruit ( name, appearance, cost ) " - "VALUES ( ?, ?, ? )")] - (is (= (returned-key db 1) (sql/execute! db [sql-stmt "Apple" "Green" 75] - {:return-keys ["id"]}))) - (is (= (returned-key db 2) (sql/execute! db [sql-stmt "Pear" "Yellow" 99] - {:return-keys ["id"]}))))) + "VALUES ( ?, ?, ? )") + selector (select-key db)] + (is (= (returned-key db 1) + (selector (sql/execute! db [sql-stmt "Apple" "Green" 75] + {:return-keys ["id"]})))) + (is (= (returned-key db 2) + (selector (sql/execute! db [sql-stmt "Pear" "Yellow" 99] + {:return-keys ["id"]})))))) (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) (is (= "Pear" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 99] {:row-fn :name :result-set-fn first})))))) From f71bf4ec9c71d4c7931282c0a18639f1a4389487 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 25 May 2018 09:38:02 -0700 Subject: [PATCH 098/175] Illustrate qualified-keyword usage on query/insert! --- src/test/clojure/clojure/java/jdbc_test.clj | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index 58ec6566..ed634ba3 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -1059,13 +1059,20 @@ (is (= rows inserted))))) (deftest insert-two-by-map-and-query-as-arrays + ;; this test also serves to illustrate qualified keyword usage (doseq [db (test-specs)] - (create-test-table :fruit db) - (let [new-keys (map (select-key db) (sql/insert-multi! db :fruit [{:name "Apple"} {:name "Pear"}])) + ;; qualifier on table name ignored by default + (create-test-table :table/fruit db) + (let [new-keys (map (select-key db) + ;; insert ignores namespace qualifier by default + (sql/insert-multi! db :table/fruit + [{:fruit/name "Apple"} + {:fruit/name "Pear"}])) rows (sql/query db ["SELECT * FROM fruit ORDER BY name"] - {:as-arrays? :cols-as-is})] + {:as-arrays? :cols-as-is + :qualifier "fruit"})] (is (= [(returned-key db 1) (returned-key db 2)] new-keys)) - (is (= [[:id :name :appearance :cost :grade] + (is (= [[:fruit/id :fruit/name :fruit/appearance :fruit/cost :fruit/grade] [(generated-key db 1) "Apple" nil nil nil] [(generated-key db 2) "Pear" nil nil nil]] rows))))) From 0f3350be73d513fa6fd35ab82944dc9e796e2fe7 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 21 Jun 2018 20:21:29 -0700 Subject: [PATCH 099/175] JDBC-169 refactoring to standardize result set handling Currently :as-arrays? true :multi? false :return-keys true may return strange results -- need to add tests to confirm edge case behavior here. --- src/main/clojure/clojure/java/jdbc.clj | 82 +++++++++++++------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 5a33ba08..abe687bb 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -849,28 +849,31 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (let [~(first binding) (.getMetaData con#)] ~@body)))) +(defn- process-result-set + "Given a Java ResultSet and options, produce a processed result-set-seq, + honoring as-arrays?, result-set-fn, and row-fn from opts." + [rset opts] + (let [{:keys [as-arrays? result-set-fn row-fn]} + (merge {:row-fn identity} opts) + result-set-fn (or result-set-fn (if as-arrays? vec doall))] + (if as-arrays? + ((^:once fn* [rs] + (result-set-fn (cons (first rs) + (map row-fn (rest rs))))) + (result-set-seq rset opts)) + (result-set-fn (map row-fn (result-set-seq rset opts)))))) + (defn metadata-result "If the argument is a java.sql.ResultSet, turn it into a result-set-seq, else return it as-is. This makes working with metadata easier. Also accepts an option map containing :identifiers, :keywordize?, :qualifier, - :as-arrays?, :row-fn,and :result-set-fn to control how the ResultSet is + :as-arrays?, :row-fn, and :result-set-fn to control how the ResultSet is transformed and returned. See query for more details." ([rs-or-value] (metadata-result rs-or-value {})) ([rs-or-value opts] - (let [{:keys [as-arrays? result-set-fn row-fn] :as opts} - (merge {:identifiers str/lower-case - :keywordize? true - :read-columns dft-read-columns - :row-fn identity} opts) - result-set-fn (or result-set-fn (if as-arrays? vec doall))] - (if (instance? java.sql.ResultSet rs-or-value) - ((^{:once true} fn* [rs] - (result-set-fn (if as-arrays? - (cons (first rs) - (map row-fn (rest rs))) - (map row-fn rs)))) - (result-set-seq rs-or-value opts)) - rs-or-value)))) + (if (instance? java.sql.ResultSet rs-or-value) + (process-result-set rs-or-value opts) + rs-or-value))) (defmacro metadata-query "Given a Java expression that extracts metadata (in the context of with-db-metadata), @@ -911,9 +914,12 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} Supports :multi? which causes a full result set sequence of keys to be returned, and assumes the param-group is a sequence of parameter lists, - rather than a single sequence of parameters." + rather than a single sequence of parameters. + + Also supports :row-fn and, if :multi? is truthy, :result-set-fn" [db ^PreparedStatement stmt param-group opts] - (let [{:keys [transaction? multi?] :as opts} (merge (when (map? db) db) opts) + (let [{:keys [as-arrays? multi? row-fn transaction?] :as opts} + (merge {:row-fn identity} (when (map? db) db) opts) exec-and-return-keys (^{:once true} fn* [] (let [counts (if multi? @@ -921,10 +927,16 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (.executeUpdate stmt))] (try (let [rs (.getGeneratedKeys stmt) - rss (result-set-seq rs opts) - result (if multi? - (doall rss) - (first rss))] + result (cond multi? + (process-result-set rs opts) + as-arrays? + ((^:once fn* [rs] + (list (first rs) + (row-fn (second rs)))) + ;; not quite: as-arrays? will yield entire rs? + (result-set-seq rs opts)) + :else + (row-fn (first (result-set-seq rs opts))))] ;; sqlite (and maybe others?) requires ;; record set to be closed (.close rs) @@ -1096,13 +1108,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} See also prepare-statement for additional options." ([db sql-params] (query db sql-params {})) ([db sql-params opts] - (let [{:keys [as-arrays? explain? explain-fn result-set-fn row-fn] :as opts} - (merge {:explain-fn println :identifiers str/lower-case - :keywordize? true - :read-columns dft-read-columns :row-fn identity} - (when (map? db) db) - opts) - result-set-fn (or result-set-fn (if as-arrays? vec doall)) + (let [{:keys [explain? explain-fn] :as opts} + (merge {:explain-fn println} (when (map? db) db) opts) [sql & params] (if (sql-stmt? sql-params) (vector sql-params) (vec sql-params))] (when-not (sql-stmt? sql) (let [^Class sql-class (class sql) @@ -1122,15 +1129,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (dissoc :explain? :result-set-fn :row-fn) (assoc :result-set-fn explain-fn)))) (db-query-with-resultset* db sql params - (if as-arrays? - (^{:once true} fn* [rset] - ((^{:once true} fn* [rs] - (result-set-fn (cons (first rs) - (map row-fn (rest rs))))) - (result-set-seq rset opts))) - (^{:once true} fn* [rset] - (result-set-fn (map row-fn - (result-set-seq rset opts))))) + (^:once fn* [rset] + (process-result-set rset opts)) opts)))) (defn- get-rs-columns @@ -1409,7 +1409,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} db-do-prepared) execute-helper (^{:once true} fn* [db] (db-do-helper db transaction? sql-params opts))] - (if-let [con (db-find-connection db)] + (if (db-find-connection db) (execute-helper db) (with-open [con (get-connection db opts)] (execute-helper (add-connection db con))))))) @@ -1442,7 +1442,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} inserts), run a prepared statement on each and return any generated keys. Note: we are eager so an unrealized lazy-seq cannot escape from the connection." [db stmts opts] - (doall (map (fn [row] (db-do-prepared-return-keys db false row opts)) stmts))) + (doall (map (fn [stmt] (db-do-prepared-return-keys db false stmt opts)) stmts))) (defn- insert-helper "Given a (connected) database connection, a transaction flag and some SQL statements @@ -1506,7 +1506,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (when-not (map? row) (throw (IllegalArgumentException. "insert! / insert-multi! called with a non-map row"))) (insert-single-row-sql table row entities)) rows)] - (if-let [con (db-find-connection db)] + (if (db-find-connection db) (insert-helper db transaction? sql-params opts) (with-open [con (get-connection db opts)] (insert-helper (add-connection db con) transaction? sql-params opts))))) @@ -1519,7 +1519,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (let [{:keys [entities transaction?] :as opts} (merge {:entities identity :transaction? true} (when (map? db) db) opts) sql-params (insert-multi-row-sql table cols values entities)] - (if-let [con (db-find-connection db)] + (if (db-find-connection db) (db-do-prepared db transaction? sql-params (assoc opts :multi? true)) (with-open [con (get-connection db opts)] (db-do-prepared (add-connection db con) transaction? sql-params From b8c7b5b69cb0a835b1721722298f962b188d8787 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 23 Jun 2018 16:15:44 -0700 Subject: [PATCH 100/175] run-tests.sh should "fail fast" If tests fail on an early version of Clojure, later versions should not be tested --- run-tests.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index 715fc84d..c628aa48 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -47,5 +47,9 @@ rm -rf clojure_test_* versions="1.7 1.8 1.9 master" for v in $versions do - TEST_DBS="$dbs $*" time clj -A:test:runner:$v + TEST_DBS="$dbs $*" clj -A:test:runner:$v + if test $? -ne 0 + then + exit $? + fi done From 4dd036d368964da9561634cb69ce730269e3de78 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 23 Jun 2018 16:18:06 -0700 Subject: [PATCH 101/175] JDBC-169 apply result-set-fn more consistently Apply it to row counts when a DB cannot return generated keys. Apply it to multiple generated keys returned (by db-do-prepared-return-keys in an insert-multi!). Apply it to homogeneous as-arrays? generated keys. --- CHANGES.md | 1 + src/main/clojure/clojure/java/jdbc.clj | 24 +++++++- src/test/clojure/clojure/java/jdbc_test.clj | 65 +++++++++++++++++++-- 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fac5edbe..e326cdeb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ Changes coming in 0.7.7 +* Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://dev.clojure.org/jira/browse/JDBC-169). * `get-connection` provides much better feedback if you accidentally call a function that expects a `db-spec` but pass a `java.sql.Connection` object instead (which is only required for `prepare-statement`). Changes in 0.7.6 diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index abe687bb..fbcf21ef 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -933,7 +933,6 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} ((^:once fn* [rs] (list (first rs) (row-fn (second rs)))) - ;; not quite: as-arrays? will yield entire rs? (result-set-seq rs opts)) :else (row-fn (first (result-set-seq rs opts))))] @@ -943,7 +942,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} result) (catch Exception _ ;; assume generated keys is unsupported and return counts instead: - counts))))] + (let [result-set-fn (or (:result-set-fn opts) doall)] + (result-set-fn (map row-fn counts)))))))] (if multi? (doseq [params param-group] ((:set-parameters opts dft-set-parameters) stmt params) @@ -1442,7 +1442,25 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} inserts), run a prepared statement on each and return any generated keys. Note: we are eager so an unrealized lazy-seq cannot escape from the connection." [db stmts opts] - (doall (map (fn [stmt] (db-do-prepared-return-keys db false stmt opts)) stmts))) + (let [{:keys [as-arrays? result-set-fn]} (merge (when (map? db) db) opts) + per-statement (fn [stmt] + (db-do-prepared-return-keys db false stmt opts))] + (if as-arrays? + (let [rs (map per-statement stmts)] + (cond (apply = (map first rs)) + ;; all the columns are the same, rearrange to cols + rows format + ((or result-set-fn vec) + (cons (ffirst rs) + (map second rs))) + result-set-fn + (throw (ex-info (str "Cannot apply result-set-fn to" + " non-homogeneous generated keys array") rs)) + :else + ;; non-non-homogeneous generated keys array - return as-is + rs)) + (if result-set-fn + (result-set-fn (map per-statement stmts)) + (seq (mapv per-statement stmts)))))) (defn- insert-helper "Given a (connected) database connection, a transaction flag and some SQL statements diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index ed634ba3..18ca8f32 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -572,16 +572,16 @@ (when-not (#{"derby" "jtds" "jtds:sqlserver"} (db-type db)) (create-test-table :fruit db) (sql/with-db-connection [conn db] - (let [connection (:connection conn) - sql-stmt (str "INSERT INTO fruit ( name, appearance, cost ) " + (let [sql-stmt (str "INSERT INTO fruit ( name, appearance, cost ) " "VALUES ( ?, ?, ? )") selector (select-key db)] (is (= (returned-key db 1) (selector (sql/execute! db [sql-stmt "Apple" "Green" 75] {:return-keys ["id"]})))) (is (= (returned-key db 2) - (selector (sql/execute! db [sql-stmt "Pear" "Yellow" 99] - {:return-keys ["id"]})))))) + (sql/execute! db [sql-stmt "Pear" "Yellow" 99] + {:return-keys ["id"] + :row-fn selector}))))) (is (= 2 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count}))) (is (= "Pear" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 99] {:row-fn :name :result-set-fn first})))))) @@ -960,6 +960,44 @@ (returned-key db 2)] new-keys))))))) +(deftest insert-two-via-execute-result-set-fn + (doseq [db (test-specs)] + (create-test-table :fruit db) + (let [execute-multi-insert + (fn [db] + (sql/execute! db [(str "INSERT INTO fruit ( name )" + " VALUES ( ? )") + ["Apple"] + ["Orange"]] + {:return-keys true + :multi? true + :result-set-fn count})) + n (if (#{"jtds" "jtds:sqlserver"} (db-type db)) + (do + (is (thrown? java.sql.BatchUpdateException + (execute-multi-insert db))) + 0) + (execute-multi-insert db))] + (case (db-type db) + ;; SQLite only returns the last key inserted in a batch + "sqlite" (is (= 1 n)) + ;; Derby returns a single row count + "derby" (is (= 1 n)) + ;; H2 returns nothing useful + ("h2" "h2:mem") (is (= 0 n)) + ;; HSQL returns nothing useful + "hsql" (is (= 0 n)) + ;; MS SQL returns row counts (we still apply result-set-fn) + "mssql" (is (= 2 n)) + ;; jTDS disallows batch updates returning keys (handled above) + ("jtds" "jtds:sqlserver") + (is (= 0 n)) + ;; otherwise expect two rows with the correct keys + (do + (when-not (= 2 n) + (println "FAIL FOR" db)) + (is (= 2 n))))))) + (deftest insert-one-row (doseq [db (test-specs)] (create-test-table :fruit db) @@ -972,6 +1010,12 @@ (let [new-keys (map (select-key db) (sql/insert! db :fruit {:name "Apple"} {}))] (is (= [(returned-key db 1)] new-keys))))) +(deftest insert-one-row-opts-row-fn + (doseq [db (test-specs)] + (create-test-table :fruit db) + (let [new-keys (sql/insert! db :fruit {:name "Apple"} {:row-fn (select-key db)})] + (is (= [(returned-key db 1)] new-keys))))) + (deftest insert-one-col-val (doseq [db (test-specs)] (create-test-table :fruit db) @@ -1034,6 +1078,19 @@ (is (= [{:id (generated-key db 1) :name "Apple" :appearance nil :grade nil :cost nil} {:id (generated-key db 2) :name "Pear" :appearance nil :grade nil :cost nil}] rows))))) +(deftest insert-two-by-map-row-fn + (doseq [db (test-specs)] + (create-test-table :fruit db) + (let [new-keys (sql/insert-multi! db :fruit [{:name "Apple"} {:name "Pear"}] + {:row-fn (select-key db)})] + (is (= [(returned-key db 1) (returned-key db 2)] new-keys))))) + +(deftest insert-two-by-map-result-set-fn + (doseq [db (test-specs)] + (create-test-table :fruit db) + (is (= 2 (sql/insert-multi! db :fruit [{:name "Apple"} {:name "Pear"}] + {:result-set-fn count}))))) + (deftest insert-identifiers-respected-1 (doseq [db (filter postgres? (test-specs))] (create-test-table :fruit db) From ecbd64c92185b1b399053580ad61a70bda6f25ac Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 23 Jun 2018 16:22:18 -0700 Subject: [PATCH 102/175] JDBC-169 improve execute! docstring --- src/main/clojure/clojure/java/jdbc.clj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index fbcf21ef..ca805140 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1396,7 +1396,9 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} If :return-keys is provided, db-do-prepared-return-keys will be called instead of db-do-prepared, and the result will be a sequence of maps - containing the generated keys. + containing the generated keys. If present, :row-fn will be applied. If :multi? + then :result-set-fn will also be applied if present. :as-arrays? may also be + specified (which will affect what :result-set-fn is passed). If there are no parameters specified, executeUpdate will be used, otherwise executeBatch will be used. This may affect what SQL you can run via execute!" From cb8f51bf0bf3a9c77e68fd410650e3d3b7ddf30f Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 23 Jun 2018 16:30:31 -0700 Subject: [PATCH 103/175] Prep for 0.7.7 --- CHANGES.md | 2 +- README.md | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e326cdeb..0590d2f4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes coming in 0.7.7 +Changes in 0.7.7 * Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://dev.clojure.org/jira/browse/JDBC-169). * `get-connection` provides much better feedback if you accidentally call a function that expects a `db-spec` but pass a `java.sql.Connection` object instead (which is only required for `prepare-statement`). diff --git a/README.md b/README.md index 56643a7c..80e8f0f5 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Support Releases and Dependency Information ======================================== -Latest stable release: 0.7.6 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.7 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -32,14 +32,14 @@ Latest stable release: 0.7.6 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.6"] +[org.clojure/java.jdbc "0.7.7"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.6 + 0.7.7 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -138,6 +138,10 @@ Developer Information Change Log ==================== +* Release 0.7.7 on 2018-06-23 + * Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://dev.clojure.org/jira/browse/JDBC-169). + * `get-connection` provides much better feedback if you accidentally call a function that expects a `db-spec` but pass a `java.sql.Connection` object instead (which is only required for `prepare-statement`). + * Release 0.7.6 on 2018-04-24 * `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://dev.clojure.org/jira/browse/JDBC-166). * Add built-in support for H2 in-memory database (`:dbtype "h2:mem"`). From ad6929799fcedc675f1c515077fd79413e7db4e0 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Sat, 23 Jun 2018 18:35:54 -0500 Subject: [PATCH 104/175] [maven-release-plugin] prepare release java.jdbc-0.7.7 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d8b4d4e5..82c847c8 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.7-SNAPSHOT + 0.7.7 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.7 From 3f2e197373cd1a8dcd45274671022c2a8b1e96a6 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Sat, 23 Jun 2018 18:35:54 -0500 Subject: [PATCH 105/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 82c847c8..72e15556 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.7 + 0.7.8-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.7 + HEAD From d8a35b9531b76a772467ca24b2d866b7bc29b6c6 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 28 Jun 2018 10:26:32 -0700 Subject: [PATCH 106/175] Update tested versions of JDBC drivers (and tests) Java 8 compatibility (mostly). --- deps.edn | 12 +++++------- pom.xml | 10 +++++----- src/test/clojure/clojure/java/jdbc_test.clj | 21 +++++++++++++-------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/deps.edn b/deps.edn index 4f47ceda..1dfbff85 100644 --- a/deps.edn +++ b/deps.edn @@ -6,17 +6,15 @@ :aliases {:test {:extra-paths ["src/test/clojure"] :extra-deps {org.clojure/test.check {:mvn/version "0.9.0"} - ;; Note: 1.12.1.1 is used in pom.xml for Java 6/7 compat - org.apache.derby/derby {:mvn/version "10.13.1.1"} - org.hsqldb/hsqldb {:mvn/version "2.3.4"} - ;; Note: 1.4.191 is used in pom.xml for Java 6 compat - com.h2database/h2 {:mvn/version "1.4.193"} + org.apache.derby/derby {:mvn/version "10.14.2.0"} + org.hsqldb/hsqldb {:mvn/version "2.4.1"} + com.h2database/h2 {:mvn/version "1.4.197"} net.sourceforge.jtds/jtds {:mvn/version "1.3.1"} ;; Note: Tests fail with 6.0.2+ driver mysql/mysql-connector-java {:mvn/version "5.1.41"} - org.postgresql/postgresql {:mvn/version "9.4.1212.jre7"} + org.postgresql/postgresql {:mvn/version "42.2.2.jre7"} com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.7.1"} - org.xerial/sqlite-jdbc {:mvn/version "3.16.1"} + org.xerial/sqlite-jdbc {:mvn/version "3.23.1"} ;; Note: Assumes Java 8; there's a .jre7 version as well com.microsoft.sqlserver/mssql-jdbc {:mvn/version "6.2.2.jre8"}}} :1.7 {:override-deps {org.clojure/clojure {:mvn/version "1.7.0"}}} diff --git a/pom.xml b/pom.xml index 72e15556..0bba63c8 100644 --- a/pom.xml +++ b/pom.xml @@ -72,31 +72,31 @@ org.apache.derby derby - 10.12.1.1 + 10.14.2.0 test org.hsqldb hsqldb - 2.3.4 + 2.4.1 test com.h2database h2 - 1.4.191 + 1.4.197 test org.postgresql postgresql - 9.4.1212.jre7 + 42.2.2.jre7 test org.xerial sqlite-jdbc - 3.16.1 + 3.23.1 test diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index 18ca8f32..9d3668ee 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -241,7 +241,7 @@ (case (db-type db) "derby" {(keyword "1") nil} ("hsql" "hsqldb") nil - ("h2" "h2:mem") nil + ("h2" "h2:mem") {:id 0} "mysql" {:generated_key k} nil (if (mysql? db) ; string-based tests {:generated_key k} @@ -381,7 +381,9 @@ (with-open [con (sql/get-connection db)] (let [stmt (sql/prepare-statement con "INSERT INTO fruit ( name, appearance, cost, grade ) VALUES ( 'test', 'test', 1, 1.0 )" {:return-keys ["id"]})] - (is (= (returned-key db 1) ((select-key db) (sql/db-do-prepared-return-keys db stmt)))))) + ;; HSQLDB returns the named key if you ask + (is (= (if (hsqldb? db) {:id 0} (returned-key db 1)) + ((select-key db) (sql/db-do-prepared-return-keys db stmt)))))) (is (= 1 (sql/query db ["SELECT * FROM fruit"] {:result-set-fn count})))))) (deftest test-do-prepared2 @@ -575,10 +577,12 @@ (let [sql-stmt (str "INSERT INTO fruit ( name, appearance, cost ) " "VALUES ( ?, ?, ? )") selector (select-key db)] - (is (= (returned-key db 1) + ;; HSQLDB returns the named key if you ask + (is (= (if (hsqldb? db) {:id 0} (returned-key db 1)) (selector (sql/execute! db [sql-stmt "Apple" "Green" 75] {:return-keys ["id"]})))) - (is (= (returned-key db 2) + ;; HSQLDB returns the named key if you ask + (is (= (if (hsqldb? db) {:id 0} (returned-key db 2)) (sql/execute! db [sql-stmt "Pear" "Yellow" 99] {:return-keys ["id"] :row-fn selector}))))) @@ -943,8 +947,9 @@ "sqlite" (is (= [(returned-key db 2)] new-keys)) ;; Derby returns a single row count "derby" (is (= [(returned-key db 1)] new-keys)) - ;; H2 returns nothing useful - ("h2" "h2:mem") (is (= [] new-keys)) + ;; H2 returns dummy keys + ("h2" "h2:mem") + (is (= [(returned-key db 1) (returned-key db 2)] new-keys)) ;; HSQL returns nothing useful "hsql" (is (= [] new-keys)) ;; MS SQL returns row counts @@ -983,8 +988,8 @@ "sqlite" (is (= 1 n)) ;; Derby returns a single row count "derby" (is (= 1 n)) - ;; H2 returns nothing useful - ("h2" "h2:mem") (is (= 0 n)) + ;; H2 returns (zero) keys now + ("h2" "h2:mem") (is (= 2 n)) ;; HSQL returns nothing useful "hsql" (is (= 0 n)) ;; MS SQL returns row counts (we still apply result-set-fn) From 1482f8388b1e680c5fbda8698fa84ce12fcd98af Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Tue, 3 Jul 2018 11:15:05 -0700 Subject: [PATCH 107/175] Remove misleading portion of namespace docstring --- src/main/clojure/clojure/java/jdbc.clj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index ca805140..5973b24f 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -32,9 +32,7 @@ data. Results can be processed using any standard sequence operations. For most operations, Java's PreparedStatement is used so your SQL and parameters can be represented as simple vectors where the first element is the SQL string, with ? for each parameter, and the remaining elements -are the parameter values to be substituted. In general, operations return -the number of rows affected, except for a single record insert where any -generated keys are returned (as a map). +are the parameter values to be substituted. For more documentation, see: From ee1ebac6ffbe09810659541c6e68d3b1b190cd55 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 10 Aug 2018 20:29:59 -0700 Subject: [PATCH 108/175] JDBC-171 allow with-db-connection/with-db-metadata to be nested --- CHANGES.md | 4 +++ README.md | 4 +-- src/main/clojure/clojure/java/jdbc.clj | 16 +++++++---- src/test/clojure/clojure/java/jdbc_test.clj | 32 ++++++++++++++++++++- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0590d2f4..98edbc05 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes coming in 0.7.8 + +* Allow `with-db-connection` and `with-db-metadata` to nest [JDBC-171](https://dev.clojure.org/jira/browse/JDBC-171). + Changes in 0.7.7 * Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://dev.clojure.org/jira/browse/JDBC-169). diff --git a/README.md b/README.md index 80e8f0f5..c3124ae3 100644 --- a/README.md +++ b/README.md @@ -139,8 +139,8 @@ Change Log ==================== * Release 0.7.7 on 2018-06-23 - * Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://dev.clojure.org/jira/browse/JDBC-169). - * `get-connection` provides much better feedback if you accidentally call a function that expects a `db-spec` but pass a `java.sql.Connection` object instead (which is only required for `prepare-statement`). + * Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://dev.clojure.org/jira/browse/JDBC-169). + * `get-connection` provides much better feedback if you accidentally call a function that expects a `db-spec` but pass a `java.sql.Connection` object instead (which is only required for `prepare-statement`). * Release 0.7.6 on 2018-04-24 * `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://dev.clojure.org/jira/browse/JDBC-166). diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 5973b24f..08b85f2f 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -831,9 +831,12 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} ... con-db ...)" [binding & body] `(let [db-spec# ~(second binding) opts# ~(or (second (rest binding)) {})] - (with-open [con# (get-connection db-spec# opts#)] - (let [~(first binding) (add-connection db-spec# con#)] - ~@body)))) + (if (db-find-connection db-spec#) + (let [~(first binding) db-spec#] + ~@body) + (with-open [con# (get-connection db-spec# opts#)] + (let [~(first binding) (add-connection db-spec# con#)] + ~@body))))) (defmacro with-db-metadata "Evaluates body in the context of an active connection with metadata bound @@ -843,9 +846,12 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} ... md ...)" [binding & body] `(let [db-spec# ~(second binding) opts# ~(or (second (rest binding)) {})] - (with-open [con# (get-connection db-spec# opts#)] + (if-let [con# (db-find-connection db-spec#)] (let [~(first binding) (.getMetaData con#)] - ~@body)))) + ~@body) + (with-open [con# (get-connection db-spec# opts#)] + (let [~(first binding) (.getMetaData con#)] + ~@body))))) (defn- process-result-set "Given a Java ResultSet and options, produce a processed result-set-seq, diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index 9d3668ee..03321782 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -590,6 +590,16 @@ (is (= "Pear" (sql/query db ["SELECT * FROM fruit WHERE cost = ?" 99] {:row-fn :name :result-set-fn first})))))) +(deftest test-nested-with-connection + (doseq [db (test-specs)] + (create-test-table :fruit db) + (sql/with-db-connection [conn1 db] + (sql/query conn1 "select * from fruit") + (sql/with-db-connection [conn2 conn1] + (sql/query conn2 "select * from fruit")) + ;; JDBC-171 bug: this blows up because with-db-connection won't nest + (is (= [] (sql/query conn1 "select * from fruit")))))) + (deftest test-update-values (doseq [db (test-specs)] (create-test-table :fruit db) @@ -876,7 +886,27 @@ (is (= "fruit" (-> table-info first :table_name - clojure.string/lower-case))))))) + clojure.string/lower-case))))) + (sql/with-db-connection [conn db] + (sql/with-db-metadata [metadata conn {}] + (let [table-info (sql/metadata-query (.getTables metadata + nil nil nil + (into-array ["TABLE" "VIEW"])))] + (is (not= [] table-info)) + (is (= "fruit" (-> table-info + first + :table_name + clojure.string/lower-case))))) + ;; JDBC-171 this used to blow up because the connnection is closed + (sql/with-db-metadata [metadata conn {}] + (let [table-info (sql/metadata-query (.getTables metadata + nil nil nil + (into-array ["TABLE" "VIEW"])))] + (is (not= [] table-info)) + (is (= "fruit" (-> table-info + first + :table_name + clojure.string/lower-case)))))))) (deftest test-metadata-managed-computed (doseq [db (test-specs)] From fb5e44a399974311710519537fcb3e0d88c75d04 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 12 Aug 2018 16:40:56 -0700 Subject: [PATCH 109/175] JDBC-172 support updated MySQL driver --- src/main/clojure/clojure/java/jdbc.clj | 56 +++++++++++++++++--------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 5973b24f..462189b4 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -148,13 +148,19 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (def ^:private classnames "Map of subprotocols to classnames. dbtype specifies one of these keys. - The subprotocols map below provides aliases for dbtype." + + The subprotocols map below provides aliases for dbtype. + + Most databases have just a single class name for their driver but we + support a sequence of class names to try in order to allow for drivers + that change their names over time (e.g., MySQL)." {"derby" "org.apache.derby.jdbc.EmbeddedDriver" "h2" "org.h2.Driver" "h2:mem" "org.h2.Driver" "hsqldb" "org.hsqldb.jdbcDriver" "jtds:sqlserver" "net.sourceforge.jtds.jdbc.Driver" - "mysql" "com.mysql.jdbc.Driver" + "mysql" ["com.mysql.cj.jdbc.Driver" + "com.mysql.jdbc.Driver"] "oracle:oci" "oracle.jdbc.OracleDriver" "oracle:thin" "oracle.jdbc.OracleDriver" "postgresql" "org.postgresql.Driver" @@ -224,6 +230,30 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (.setReadOnly connection (boolean (:read-only? opts)))) connection) +(defn- get-driver-connection + "Common logic for loading the DriverManager and the designed JDBC driver + class and obtaining the appropriate Connection object." + [classname subprotocol db-spec url etc opts error-msg] + (if-let [class-name (or classname (classnames subprotocol))] + (do + ;; force DriverManager to be loaded + (DriverManager/getLoginTimeout) + (if (string? class-name) + (clojure.lang.RT/loadClassForName class-name) + (loop [[clazz & more] class-name] + (when-let [load-failure + (try + (clojure.lang.RT/loadClassForName clazz) + nil + (catch Exception e + e))] + (if (seq more) + (recur more) + (throw load-failure)))))) + (throw (ex-info error-msg db-spec))) + (-> (DriverManager/getConnection url (as-properties etc)) + (modify-connection opts))) + (defn get-connection "Creates a connection to a database. db-spec is usually a map containing connection parameters but can also be a URI or a String. @@ -359,28 +389,18 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (when port (str ":" port)) db-sep dbname)) etc (dissoc db-spec :dbtype :dbname)] - (if-let [class-name (or classname (classnames subprotocol))] - (do - ;; force DriverManager to be loaded - (DriverManager/getLoginTimeout) - (clojure.lang.RT/loadClassForName class-name)) - (throw (ex-info (str "Unknown dbtype: " dbtype) db-spec))) - (-> (DriverManager/getConnection url (as-properties etc)) - (modify-connection opts))) + (get-driver-connection classname subprotocol db-spec + url etc opts + (str "Unknown dbtype: " dbtype))) (and subprotocol subname) (let [;; allow aliases for subprotocols subprotocol (subprotocols subprotocol subprotocol) url (format "jdbc:%s:%s" subprotocol subname) etc (dissoc db-spec :classname :subprotocol :subname)] - (if-let [class-name (or classname (classnames subprotocol))] - (do - ;; force DriverManager to be loaded - (DriverManager/getLoginTimeout) - (clojure.lang.RT/loadClassForName class-name)) - (throw (ex-info (str "Unknown subprotocol: " subprotocol) db-spec))) - (-> (DriverManager/getConnection url (as-properties etc)) - (modify-connection opts))) + (get-driver-connection classname subprotocol db-spec + url etc opts + (str "Unknown subprotocol: " subprotocol))) name (or (when-available javax.naming.InitialContext From f9d480a1ad9f2ffe4218e2d460bfd90a60d18192 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 12 Aug 2018 16:47:53 -0700 Subject: [PATCH 110/175] JDBC-172 update changes --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 98edbc05..749da619 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ Changes coming in 0.7.8 +* Support multiple JDBC driver class names (MySQL introduced a new driver class name with its 6.x connector) [JDBC-172](https://dev.clojure.org/jira/browse/JDBC-172). * Allow `with-db-connection` and `with-db-metadata` to nest [JDBC-171](https://dev.clojure.org/jira/browse/JDBC-171). Changes in 0.7.7 From d19865ee1811d17db8aac1820ebc8996c55b54c1 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 13 Aug 2018 10:26:13 -0700 Subject: [PATCH 111/175] Prep for 0.7.8 release --- CHANGES.md | 2 +- README.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 749da619..f455fa84 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes coming in 0.7.8 +Changes in 0.7.8 * Support multiple JDBC driver class names (MySQL introduced a new driver class name with its 6.x connector) [JDBC-172](https://dev.clojure.org/jira/browse/JDBC-172). * Allow `with-db-connection` and `with-db-metadata` to nest [JDBC-171](https://dev.clojure.org/jira/browse/JDBC-171). diff --git a/README.md b/README.md index c3124ae3..8ac755c8 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,10 @@ Developer Information Change Log ==================== +* Release 0.7.8 on 2018-08-13 + * Support multiple JDBC driver class names (MySQL introduced a new driver class name with its 6.x connector) [JDBC-172](https://dev.clojure.org/jira/browse/JDBC-172). + * Allow `with-db-connection` and `with-db-metadata` to nest [JDBC-171](https://dev.clojure.org/jira/browse/JDBC-171). + * Release 0.7.7 on 2018-06-23 * Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://dev.clojure.org/jira/browse/JDBC-169). * `get-connection` provides much better feedback if you accidentally call a function that expects a `db-spec` but pass a `java.sql.Connection` object instead (which is only required for `prepare-statement`). From f79a5ae4d22a20441fb3b2f0cf52341d4b727e8a Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 13 Aug 2018 10:28:46 -0700 Subject: [PATCH 112/175] Update README for 0.7.8 release --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8ac755c8..23c13ceb 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Support Releases and Dependency Information ======================================== -Latest stable release: 0.7.7 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.8 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -32,14 +32,14 @@ Latest stable release: 0.7.7 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.7"] +[org.clojure/java.jdbc "0.7.8"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.7 + 0.7.8 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ From 49a033aef3254e46d23fe916e1aff04300143ebd Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 13 Aug 2018 12:29:57 -0500 Subject: [PATCH 113/175] [maven-release-plugin] prepare release java.jdbc-0.7.8 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0bba63c8..917e69e0 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.8-SNAPSHOT + 0.7.8 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.8 From f5ce75b8fb0370a2a4763524e9de5a0b9174835d Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 13 Aug 2018 12:29:57 -0500 Subject: [PATCH 114/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 917e69e0..52846e16 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.8 + 0.7.9-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.8 + HEAD From efe67919d532fda028c7f051f7f57991afba4bf2 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 28 Oct 2018 13:01:42 -0700 Subject: [PATCH 115/175] JDBC-173 oracle:sid support Cleans up port and dbname separator lookup. --- src/main/clojure/clojure/java/jdbc.clj | 38 ++++++++++++++++---------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index a83ee1b8..a2a0b378 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -171,11 +171,12 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (def ^:private subprotocols "Map of schemes to subprotocols. Used to provide aliases for dbtype." - {"hsql" "hsqldb" - "jtds" "jtds:sqlserver" - "mssql" "sqlserver" - "oracle" "oracle:thin" - "postgres" "postgresql"}) + {"hsql" "hsqldb" + "jtds" "jtds:sqlserver" + "mssql" "sqlserver" + "oracle" "oracle:thin" + "oracle:sid" "oracle:thin" + "postgres" "postgresql"}) (def ^:private host-prefixes "Map of subprotocols to non-standard host-prefixes. @@ -183,6 +184,22 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} {"oracle:oci" "@" "oracle:thin" "@"}) +(def ^:private ports + "Map of subprotocols to ports." + {"jtds:sqlserver" 1433 + "mysql" 3306 + "oracle:oci" 1521 + "oracle:sid" 1521 + "oracle:thin" 1521 + "postgresql" 5432 + "sqlserver" 1433}) + +(def ^:private dbname-separators + "Map of schemes to separators. The default is / but a couple are different." + {"mssql" ";DATABASENAME=" + "sqlserver" ";DATABASENAME=" + "oracle:sid" ":"}) + (defn- parse-properties-uri [^URI uri] (let [host (.getHost uri) port (if (pos? (.getPort uri)) (.getPort uri)) @@ -369,15 +386,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (let [;; allow aliases for dbtype subprotocol (subprotocols dbtype dbtype) host (or host "127.0.0.1") - port (or port (condp = subprotocol - "jtds:sqlserver" 1433 - "mysql" 3306 - "oracle:oci" 1521 - "oracle:thin" 1521 - "postgresql" 5432 - "sqlserver" 1433 - nil)) - db-sep (if (= "sqlserver" subprotocol) ";DATABASENAME=" "/") + port (or port (ports subprotocol)) + db-sep (dbname-separators dbtype "/") url (cond (= "h2:mem" dbtype) (str "jdbc:" subprotocol ":" dbname ";DB_CLOSE_DELAY=-1") (#{"derby" "h2" "hsqldb" "sqlite"} subprotocol) From 2fc16eb17f6af621dca12e60a094d4acb1d59b64 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 28 Oct 2018 13:05:47 -0700 Subject: [PATCH 116/175] Document upcoming JDBC-173 fix --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f455fa84..8bdcdb71 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes coming in 0.7.9 + +* Support Oracle SID style URLs (`dbtype` can be `oracle:sid` which maps to `oracle:thin` and uses `:` as the separator before the `dbname` value) [JDBC-173](https://dev.clojure.org/jira/browse/JDBC-173). + Changes in 0.7.8 * Support multiple JDBC driver class names (MySQL introduced a new driver class name with its 6.x connector) [JDBC-172](https://dev.clojure.org/jira/browse/JDBC-172). From e5d6183395e23c9b7b78fa20ac6d0ddba6074807 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 29 Oct 2018 17:57:17 -0700 Subject: [PATCH 117/175] Add Pull Request Template --- .github/PULL_REQUEST_TEMPLATE | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE new file mode 100644 index 00000000..0a5c486a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE @@ -0,0 +1,14 @@ +Hi! Thanks for your interest in contributing to this project. + +Clojure contrib projects do not use GitHub issues or pull requests, and +require a signed Contributor Agreement. If you would like to contribute, +please read more about the CA and sign that first (this can be done online). + +Then go to this project's issue tracker in JIRA to create tickets, update +tickets, or submit patches. For help in creating tickets and patches, +please see: + +- Signing the CA: https://dev.clojure.org/display/community/Contributing+FAQ +- Creating Tickets: https://dev.clojure.org/display/community/Creating+Tickets +- Developing Patches: https://dev.clojure.org/display/community/Developing+Patches +- Contributing FAQ: https://dev.clojure.org/display/community/Contributing+FAQ From ccf9be463e0c4073646423e239a32009da48b24b Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 30 Nov 2018 11:18:16 -0800 Subject: [PATCH 118/175] Add clojure.java.jdbc.datafy --- src/main/clojure/clojure/java/jdbc/datafy.clj | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/main/clojure/clojure/java/jdbc/datafy.clj diff --git a/src/main/clojure/clojure/java/jdbc/datafy.clj b/src/main/clojure/clojure/java/jdbc/datafy.clj new file mode 100644 index 00000000..a68ca747 --- /dev/null +++ b/src/main/clojure/clojure/java/jdbc/datafy.clj @@ -0,0 +1,143 @@ +;; Copyright (c) 2008-2018 Sean Corfield, 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. +;; +;; datafy.clj +;; +;; Exploring datafy and nav for JDBC databases + +(ns + ^{:author "Sean Corfield", + :doc "Variants of 'query' functions from clojure.java.jdbc that support + the new clojure.datafy functionality in Clojure 1.10."} + clojure.java.jdbc.datafy + (:require [clojure.core.protocols :as p] + [clojure.datafy :as d] + [clojure.java.jdbc :as jdbc])) + +(declare datafy-result-set) +(declare datafy-row) + +(defn- default-schema + "The default schema lookup rule for column names. + + If a column name ends with _id or id, it is assumed to be a foreign key + into the table identified by the first part of the column name." + [col] + (let [[_ table] (re-find #"^(.*)_?id$" (name col))] + (when table + [(keyword table) :id]))) + +(defn- schema-opt + "Returns the schema 'option'. + + As with other clojure.java.jdbc options, it can be provided in the db-spec + hash map or in the options for a particular function call. + + A schema can a simple map from column names to pairs of table name and + the key column to be used in that table. A schema can also be a function + that is called with column names and should return nil if the column is + not to be treated as a foreign key, or a pair of table name and the key + column within that table." + [db-spec opts] + (:schema (merge {:schema default-schema} + (when (map? db-spec) db-spec) + opts))) + +(defn- navize-row [db-spec opts row] + "Given a db-spec, a map of options, and a row -- a hash map -- return the + row with metadata that provides navigation via foreign keys." + (let [schema (schema-opt db-spec opts)] + (with-meta row + {`p/nav (fn [coll k v] + (let [[table fk cardinality] (schema k)] + (if fk + (if (= :many cardinality) + (datafy-result-set db-spec opts + (jdbc/find-by-keys db-spec table {fk v})) + (datafy-row db-spec opts + (jdbc/get-by-id db-spec table v fk))) + v)))}))) + +(defn- datafy-row [db-spec opts row] + "Given a db-spec, a map of options, and a row -- a hash map -- return the + row with metadata that provides datafication (which in turn provides). + navigation)." + (with-meta row {`p/datafy (partial navize-row db-spec opts)})) + +(defn- datafy-result-set [db-spec opts rs] + "Given a db-spec, a map of options, and a result set -- a sequence of hash + maps that represent rows -- return a sequence of datafiable rows." + (mapv (partial datafy-row db-spec opts) rs)) + +(defn get-by-id + "Given a database connection, a table name, a primary key value, an + optional primary key column name, and an optional options map, return + a single matching row, or nil. + The primary key column name defaults to :id." + ([db table pk-value] (get-by-id db table pk-value :id {})) + ([db table pk-value pk-name-or-opts] + (if (map? pk-name-or-opts) + (get-by-id db table pk-value :id pk-name-or-opts) + (get-by-id db table pk-value pk-name-or-opts {}))) + ([db table pk-value pk-name opts] + (datafy-row db opts + (jdbc/get-by-id db table pk-value pk-name opts)))) + +(defn find-by-keys + "Given a database connection, a table name, a map of column name/value + pairs, and an optional options map, return any matching rows. + + An :order-by option may be supplied to sort the rows, e.g., + + {:order-by [{:name :asc} {:age :desc} {:income :asc}]} + ;; equivalent to: + {:order-by [:name {:age :desc} :income]} + + The :order-by value is a sequence of column names (to sort in ascending + order) and/or maps from column names to directions (:asc or :desc). The + directions may be strings or keywords and are not case-sensitive. They + are mapped to ASC or DESC in the generated SQL. + + Note: if a ordering map has more than one key, the order of the columns + in the generated SQL ORDER BY clause is unspecified (so such maps should + only contain one key/value pair)." + ([db table columns] (find-by-keys db table columns {})) + ([db table columns opts] + (datafy-result-set db opts + (jdbc/find-by-keys db table columns opts)))) + +(defn query + "Given a database connection and a vector containing SQL and optional parameters, + perform a simple database query. The options specify how to construct the result + set (and are also passed to prepare-statement as needed): + :as-arrays? - return the results as a set of arrays, default false. + :identifiers - applied to each column name in the result set, default lower-case + :keywordize? - defaults to true, can be false to opt-out of converting + identifiers to keywords + :qualifier - optionally provides the namespace qualifier for identifiers + :result-set-fn - applied to the entire result set, default doall / vec + if :as-arrays? true, :result-set-fn will default to vec + if :as-arrays? false, :result-set-fn will default to doall + :row-fn - applied to each row as the result set is constructed, default identity + The second argument is a vector containing a SQL string or PreparedStatement, followed + by any parameters it needs. + See also prepare-statement for additional options." + ([db sql-params] (query db sql-params {})) + ([db sql-params opts] + (datafy-result-set db opts + (jdbc/query db sql-params opts)))) + +(comment + (def db-spec {:dbtype "derby" :dbname "datafy" :create true}) + (jdbc/db-do-commands db-spec (jdbc/create-table-ddl :fruit [[:id :int] [:name "varchar(256)"]])) + (jdbc/db-do-commands db-spec (jdbc/create-table-ddl :fruit2 [[:fruitid :int] [:name "varchar(256)"]])) + (jdbc/insert! db-spec :fruit {:id 1 :name "First fruit"}) + (jdbc/insert! db-spec :fruit2 {:fruitid 1 :name "First fruit"}) + (jdbc/insert! db-spec :fruit {:id 2 :name "Second fruit"}) + (jdbc/insert! db-spec :fruit2 {:fruitid 2 :name "More fruit"})) From 742b52674ed66427d9f5d9b8c2591ddd219db476 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 30 Nov 2018 11:22:52 -0800 Subject: [PATCH 119/175] Make nav operation more robust --- src/main/clojure/clojure/java/jdbc/datafy.clj | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc/datafy.clj b/src/main/clojure/clojure/java/jdbc/datafy.clj index a68ca747..fd4af004 100644 --- a/src/main/clojure/clojure/java/jdbc/datafy.clj +++ b/src/main/clojure/clojure/java/jdbc/datafy.clj @@ -57,11 +57,16 @@ {`p/nav (fn [coll k v] (let [[table fk cardinality] (schema k)] (if fk - (if (= :many cardinality) - (datafy-result-set db-spec opts - (jdbc/find-by-keys db-spec table {fk v})) - (datafy-row db-spec opts - (jdbc/get-by-id db-spec table v fk))) + (try + (if (= :many cardinality) + (datafy-result-set db-spec opts + (jdbc/find-by-keys db-spec table {fk v})) + (datafy-row db-spec opts + (jdbc/get-by-id db-spec table v fk))) + (catch Exception _ + ;; assume an exception means we just cannot + ;; navigate anywhere, so return just the value + v)) v)))}))) (defn- datafy-row [db-spec opts row] From e066db9bc51e92dd3c798db863347d1a095ab19c Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 30 Nov 2018 12:17:33 -0800 Subject: [PATCH 120/175] Expand ns docstring to provide guidance/caveats on the schema 'lookup' machinery --- src/main/clojure/clojure/java/jdbc/datafy.clj | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/main/clojure/clojure/java/jdbc/datafy.clj b/src/main/clojure/clojure/java/jdbc/datafy.clj index fd4af004..9d4481a9 100644 --- a/src/main/clojure/clojure/java/jdbc/datafy.clj +++ b/src/main/clojure/clojure/java/jdbc/datafy.clj @@ -14,10 +14,32 @@ (ns ^{:author "Sean Corfield", :doc "Variants of 'query' functions from clojure.java.jdbc that support - the new clojure.datafy functionality in Clojure 1.10."} + the new clojure.datafy functionality in Clojure 1.10. + + The whole schema/column lookup piece is very likely to change! + + Currently, the :schema option for a 'query' function is a mapping + from column name to a tuple of table name, key column, and optionally + the cardinality (:one -- the default -- or :many). The cardinality + determines whether navigation should produce a single row (hash map) + or a result set. + + One of the problems is that the general case -- query -- doesn't + have any concept of an associated table name (and may of course + join across multiple tables), so there's no good way to take the + table name into account when mapping a column to another table. + + For find-by-keys and get-by-id, you do have the starting table + name so you could map [table1 column1] to [table2 column2] and have + table-specific mappings. + + The obvious, logical thing would be to use SQL metadata to figure + out actual foreign key constraints but not everyone uses them, for + a variety of reasons. For folks who do use them, they can build + their schema structure from the database, and pass the relevant + part of it to the functions below (via :schema in options)."} clojure.java.jdbc.datafy (:require [clojure.core.protocols :as p] - [clojure.datafy :as d] [clojure.java.jdbc :as jdbc])) (declare datafy-result-set) @@ -137,12 +159,3 @@ ([db sql-params opts] (datafy-result-set db opts (jdbc/query db sql-params opts)))) - -(comment - (def db-spec {:dbtype "derby" :dbname "datafy" :create true}) - (jdbc/db-do-commands db-spec (jdbc/create-table-ddl :fruit [[:id :int] [:name "varchar(256)"]])) - (jdbc/db-do-commands db-spec (jdbc/create-table-ddl :fruit2 [[:fruitid :int] [:name "varchar(256)"]])) - (jdbc/insert! db-spec :fruit {:id 1 :name "First fruit"}) - (jdbc/insert! db-spec :fruit2 {:fruitid 1 :name "First fruit"}) - (jdbc/insert! db-spec :fruit {:id 2 :name "Second fruit"}) - (jdbc/insert! db-spec :fruit2 {:fruitid 2 :name "More fruit"})) From 3e0e693d620c98aa80eda417a69d405e65cf808e Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 2 Dec 2018 17:59:31 -0800 Subject: [PATCH 121/175] Update copyright --- src/main/clojure/clojure/java/jdbc/datafy.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/clojure/clojure/java/jdbc/datafy.clj b/src/main/clojure/clojure/java/jdbc/datafy.clj index 9d4481a9..720520e3 100644 --- a/src/main/clojure/clojure/java/jdbc/datafy.clj +++ b/src/main/clojure/clojure/java/jdbc/datafy.clj @@ -1,4 +1,4 @@ -;; Copyright (c) 2008-2018 Sean Corfield, Stephen C. Gilardi. All rights reserved. +;; Copyright (c) 2018 Sean Corfield. 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 From 79ad004b42e9234d2bbf54df6a254c22b1c68c72 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 10 Dec 2018 14:14:58 -0800 Subject: [PATCH 122/175] Fix JDBC-175 Make regex non-greedy --- src/main/clojure/clojure/java/jdbc/datafy.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/clojure/clojure/java/jdbc/datafy.clj b/src/main/clojure/clojure/java/jdbc/datafy.clj index 720520e3..10d61adc 100644 --- a/src/main/clojure/clojure/java/jdbc/datafy.clj +++ b/src/main/clojure/clojure/java/jdbc/datafy.clj @@ -51,7 +51,7 @@ If a column name ends with _id or id, it is assumed to be a foreign key into the table identified by the first part of the column name." [col] - (let [[_ table] (re-find #"^(.*)_?id$" (name col))] + (let [[_ table] (re-find #"^(.*?)_?id$" (name col))] (when table [(keyword table) :id]))) From cbe253f6644589c413bd94d540d27398d3f64dd1 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 10 Dec 2018 14:15:30 -0800 Subject: [PATCH 123/175] Remove old debug code --- src/main/clojure/clojure/java/jdbc.clj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index a2a0b378..efcf4fba 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1076,7 +1076,6 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} "Given a db-spec, a SQL statement (or a prepared statement), a set of parameters, a result set processing function and options, execute the query." [db sql params func opts] - #_(println "\nquery" sql params) (if (instance? PreparedStatement sql) (let [^PreparedStatement stmt sql] (execute-query-with-params From 4a4bab03cf665246223d09e763c6f04d027a7d63 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 7 Jan 2019 13:33:58 -0800 Subject: [PATCH 124/175] Fix JDBC-176 --- CHANGES.md | 1 + deps.edn | 3 ++- run-tests.sh | 2 +- src/main/clojure/clojure/java/jdbc.clj | 2 +- src/test/clojure/clojure/java/jdbc_test.clj | 3 ++- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8bdcdb71..0bb5c9ea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ Changes coming in 0.7.9 +* Fix behavior of multi-inserts when database does not support generated keys [JDBC-176](https://dev.clojure.org/jira/browse/JDBC-176). * Support Oracle SID style URLs (`dbtype` can be `oracle:sid` which maps to `oracle:thin` and uses `:` as the separator before the `dbname` value) [JDBC-173](https://dev.clojure.org/jira/browse/JDBC-173). Changes in 0.7.8 diff --git a/deps.edn b/deps.edn index 1dfbff85..b09bc738 100644 --- a/deps.edn +++ b/deps.edn @@ -20,7 +20,8 @@ :1.7 {:override-deps {org.clojure/clojure {:mvn/version "1.7.0"}}} :1.8 {:override-deps {org.clojure/clojure {:mvn/version "1.8.0"}}} :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} - :master {:override-deps {org.clojure/clojure {:mvn/version "1.10.0-master-SNAPSHOT"}}} + :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.0"}}} + :master {:override-deps {org.clojure/clojure {:mvn/version "1.11.0-master-SNAPSHOT"}}} :perf {:extra-paths ["src/perf/clojure"] :extra-deps {criterium {:mvn/version "0.4.4"}} :jvm-opts ["-server" diff --git a/run-tests.sh b/run-tests.sh index c628aa48..f4325f09 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -44,7 +44,7 @@ dbs="derby h2 hsqldb sqlite" # Start with clean databases each time to avoid slowdown rm -rf clojure_test_* -versions="1.7 1.8 1.9 master" +versions="1.7 1.8 1.9 1.10 master" for v in $versions do TEST_DBS="$dbs $*" clj -A:test:runner:$v diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index efcf4fba..95ffc27c 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -958,7 +958,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (^{:once true} fn* [] (let [counts (if multi? (.executeBatch stmt) - (.executeUpdate stmt))] + (vector (.executeUpdate stmt)))] (try (let [rs (.getGeneratedKeys stmt) result (cond multi? diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index 03321782..3f3607ba 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -68,7 +68,8 @@ (def mysql-db {:dbtype "mysql" :dbname "clojure_test" :user "clojure_test" - :password "clojure_test"}) + :password "clojure_test" + :useSSL false}) (def derby-db {:dbtype "derby" :dbname "clojure_test_derby" From 022a1fdcac870f4b39f27603d30d82e815f84a3b Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 21 Feb 2019 10:09:04 -0800 Subject: [PATCH 125/175] Minor tweak to perf tests Add another baseline test with a `PreparedStatement`. --- src/perf/clojure/clojure/java/perf_jdbc.clj | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/perf/clojure/clojure/java/perf_jdbc.clj b/src/perf/clojure/clojure/java/perf_jdbc.clj index f104db4b..7e2cdffd 100644 --- a/src/perf/clojure/clojure/java/perf_jdbc.clj +++ b/src/perf/clojure/clojure/java/perf_jdbc.clj @@ -14,6 +14,8 @@ $ clj -A:test:perf Clojure 1.9.0 + user=> (require '[clojure.java.perf-jdbc :as p]) + nil user=> (p/calibrate) ... nil @@ -140,11 +142,16 @@ (assert (= "Apple" (select db))) (cc/quick-bench (select db))) + (println "Basic select first rs...") + (let [db db] + (cc/quick-bench (sql/query db ["SELECT * FROM fruit WHERE appearance = ?" "red"] + {:result-set-fn first :qualifier "fruit"}))) + (println "Select with prepared statement...") (let [con (:connection db)] (with-open [ps (sql/prepare-statement con "SELECT * FROM fruit WHERE appearance = ?")] (assert (= "Apple" (select-p db ps))) - (cc/quick-bench (select db)))) + (cc/quick-bench (select-p db ps)))) (println "Reducible query...") (let [db db From 8deaebc87b1c6ce20e1481a835d11158cbb2e3f6 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 21 Feb 2019 10:23:03 -0800 Subject: [PATCH 126/175] JDBC-174 and JDBC-175 added to CHANGES.md Documents rewrite batched statement options in `insert-multi!` and new `clojure.java.jdbc.datafy` namespace (experimental). --- CHANGES.md | 4 +++- src/main/clojure/clojure/java/jdbc.clj | 15 +++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0bb5c9ea..e6d39412 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ -Changes coming in 0.7.9 +Changes in 0.7.9 * Fix behavior of multi-inserts when database does not support generated keys [JDBC-176](https://dev.clojure.org/jira/browse/JDBC-176). +* Added _highly experimental_ support for `datafy`/`nav` (in `clojure.java.jdbc.datafy` namespace). This includes a convention-based approach to foreign keys with some assistance from a `:schema` option. This is subject to change and is provided mostly for informational purposes, as an example of the new functionality in Clojure 1.10. This includes a fix for the conventions from [JDBC-175](https://dev.clojure.org/jira/browse/JDBC-175). +* Add note about rewriting batched operations to `insert-multi!` for some drivers [JDBC-174](https://dev.clojure.org/jira/browse/JDBC-174). * Support Oracle SID style URLs (`dbtype` can be `oracle:sid` which maps to `oracle:thin` and uses `:` as the separator before the `dbname` value) [JDBC-173](https://dev.clojure.org/jira/browse/JDBC-173). Changes in 0.7.8 diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 95ffc27c..6b75b8de 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1,4 +1,4 @@ -;; Copyright (c) 2008-2018 Sean Corfield, Stephen C. Gilardi. All rights reserved. +;; Copyright (c) 2008-2019 Sean Corfield, 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 @@ -1612,9 +1612,16 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} When inserting rows as a sequence of lists of column values, the result is a sequence of the counts of rows affected (a sequence of 1's), if available. Yes, that is singularly unhelpful. Thank you getUpdateCount and executeBatch! - A single database operation is used to insert all the rows at once. This may - be much faster than inserting a sequence of rows (which performs an insert for - each map in the sequence). + A single database operation should be used to insert all the rows at once. + This may be much faster than inserting a sequence of rows (which performs an + insert for each map in the sequence). + + Note: some database drivers need to be told to rewrite the SQL for this to + be performed as a single, batched operation. In particular, PostgreSQL + requires :reWriteBatchedInserts true and My SQL requires + :rewriteBatchedStatement true (both non-standard JDBC options, of course!). + These options should be passed into the driver when the connection is + created (however that is done in your program). The :transaction? option specifies whether to run in a transaction or not. The default is true (use a transaction). The :entities option specifies how From b2b7c70a39887ba00ef71c11ece5905192df5b87 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 21 Feb 2019 10:30:26 -0800 Subject: [PATCH 127/175] Prep for 0.7.9 release --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 23c13ceb..0124a8ac 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Support Releases and Dependency Information ======================================== -Latest stable release: 0.7.8 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.9 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -32,14 +32,14 @@ Latest stable release: 0.7.8 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.8"] +[org.clojure/java.jdbc "0.7.9"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.8 + 0.7.9 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -138,6 +138,12 @@ Developer Information Change Log ==================== +* Release 0.7.9 on 2019-02-21 + * Fix behavior of multi-inserts when database does not support generated keys [JDBC-176](https://dev.clojure.org/jira/browse/JDBC-176). + * Added _highly experimental_ support for `datafy`/`nav` (in `clojure.java.jdbc.datafy` namespace). This includes a convention-based approach to foreign keys with some assistance from a `:schema` option. This is subject to change and is provided mostly for informational purposes, as an example of the new functionality in Clojure 1.10. This includes a fix for the conventions from [JDBC-175](https://dev.clojure.org/jira/browse/JDBC-175). + * Add note about rewriting batched operations to `insert-multi!` for some drivers [JDBC-174](https://dev.clojure.org/jira/browse/JDBC-174). + * Support Oracle SID style URLs (`dbtype` can be `oracle:sid` which maps to `oracle:thin` and uses `:` as the separator before the `dbname` value) [JDBC-173](https://dev.clojure.org/jira/browse/JDBC-173). + * Release 0.7.8 on 2018-08-13 * Support multiple JDBC driver class names (MySQL introduced a new driver class name with its 6.x connector) [JDBC-172](https://dev.clojure.org/jira/browse/JDBC-172). * Allow `with-db-connection` and `with-db-metadata` to nest [JDBC-171](https://dev.clojure.org/jira/browse/JDBC-171). From ae39e94c71ddbe40c42f740a81c181a83b380b6a Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 21 Feb 2019 12:33:50 -0600 Subject: [PATCH 128/175] [maven-release-plugin] prepare release java.jdbc-0.7.9 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 52846e16..608d1fbc 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.9-SNAPSHOT + 0.7.9 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.9 From 8495acde705949a3f85409973b3c7eedc65628d7 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Thu, 21 Feb 2019 12:33:50 -0600 Subject: [PATCH 129/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 608d1fbc..2e00343f 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.9 + 0.7.10-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.9 + HEAD From 2374ea2dd85b2895dd98bc1d5a04aa0699e41d8d Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 1 Mar 2019 14:12:38 -0800 Subject: [PATCH 130/175] JDBC-177 allow numbers in column specs in create-table-ddl --- .joker | 3 ++ CHANGES.md | 4 ++ src/main/clojure/clojure/java/jdbc.clj | 3 +- src/main/clojure/clojure/java/jdbc/spec.clj | 6 ++- src/test/clojure/clojure/java/jdbc_test.clj | 60 +++++++++++++-------- 5 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 .joker diff --git a/.joker b/.joker new file mode 100644 index 00000000..0fd26273 --- /dev/null +++ b/.joker @@ -0,0 +1,3 @@ +{:known-macros [clojure.java.jdbc/with-db-connection + clojure.java.jdbc/with-db-metadata + clojure.java.jdbc/with-db-transaction]} diff --git a/CHANGES.md b/CHANGES.md index e6d39412..fc546d6c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes coming in 0.7.10 + +* Relax restriction on `create-table-ddl` column specs to allow numbers (as well as keywords and strings) [JDBC-177](https://dev.clojure.org/jira/browse/JDBC-177). + Changes in 0.7.9 * Fix behavior of multi-inserts when database does not support generated keys [JDBC-176](https://dev.clojure.org/jira/browse/JDBC-176). diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 6b75b8de..903e814d 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -1692,10 +1692,11 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} entities (:entities opts identity) table-name (as-sql-name entities table) table-spec-str (or (and table-spec (str " " table-spec)) "") + stringify (fn [x] (if (keyword? x) (name x) (str x))) spec-to-string (fn [spec] (try (str/join " " (cons (as-sql-name entities (first spec)) - (map name (rest spec)))) + (map stringify (rest spec)))) (catch Exception _ (throw (IllegalArgumentException. "column spec is not a sequence of keywords / strings")))))] diff --git a/src/main/clojure/clojure/java/jdbc/spec.clj b/src/main/clojure/clojure/java/jdbc/spec.clj index 2a93a059..eca21eaa 100644 --- a/src/main/clojure/clojure/java/jdbc/spec.clj +++ b/src/main/clojure/clojure/java/jdbc/spec.clj @@ -1,4 +1,4 @@ -;; Copyright (c) 2016-2017 Sean Corfield. All rights reserved. +;; Copyright (c) 2016-2019 Sean Corfield. 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 @@ -407,7 +407,9 @@ :opts (s/? ::exec-sql-options)) :ret ::execute-result) -(s/def ::column-spec (s/cat :col ::identifier :spec (s/* (s/or :kw keyword? :str string?)))) +(s/def ::column-spec (s/cat :col ::identifier :spec (s/* (s/or :kw keyword? + :str string? + :num number?)))) (s/fdef sql/create-table-ddl :args (s/cat :table ::identifier diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index 3f3607ba..f01de14c 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -21,7 +21,7 @@ ;; Migrated from clojure.contrib.test-sql 17 April 2011 (ns clojure.java.jdbc-test - (:require [clojure.test :refer :all] + (:require [clojure.test :refer [deftest is use-fixtures]] [clojure.java.jdbc :as sql] [clojure.string :as str])) @@ -887,27 +887,27 @@ (is (= "fruit" (-> table-info first :table_name - clojure.string/lower-case))))) - (sql/with-db-connection [conn db] - (sql/with-db-metadata [metadata conn {}] - (let [table-info (sql/metadata-query (.getTables metadata - nil nil nil - (into-array ["TABLE" "VIEW"])))] - (is (not= [] table-info)) - (is (= "fruit" (-> table-info - first - :table_name - clojure.string/lower-case))))) - ;; JDBC-171 this used to blow up because the connnection is closed - (sql/with-db-metadata [metadata conn {}] - (let [table-info (sql/metadata-query (.getTables metadata - nil nil nil - (into-array ["TABLE" "VIEW"])))] - (is (not= [] table-info)) - (is (= "fruit" (-> table-info - first - :table_name - clojure.string/lower-case)))))))) + clojure.string/lower-case)))) + (sql/with-db-connection [conn db] + (sql/with-db-metadata [metadata conn {}] + (let [table-info (sql/metadata-query (.getTables metadata + nil nil nil + (into-array ["TABLE" "VIEW"])))] + (is (not= [] table-info)) + (is (= "fruit" (-> table-info + first + :table_name + clojure.string/lower-case))))) + ;; JDBC-171 this used to blow up because the connnection is closed + (sql/with-db-metadata [metadata conn {}] + (let [table-info (sql/metadata-query (.getTables metadata + nil nil nil + (into-array ["TABLE" "VIEW"])))] + (is (not= [] table-info)) + (is (= "fruit" (-> table-info + first + :table_name + clojure.string/lower-case))))))))) (deftest test-metadata-managed-computed (doseq [db (test-specs)] @@ -1254,6 +1254,22 @@ {:id (generated-key db 3) :name "Orange" :appearance "round" :cost nil :grade nil} {:id (generated-key db 2) :name "Pear" :appearance "yellow" :cost nil :grade nil}] rows))))) +(deftest test-create-table-ddl + (is (re-find #"`foo` int default 0" + (sql/create-table-ddl :table + [[:foo :int :default 0]] + {:entities (sql/quoted :mysql)})))) + +(comment + db (sql/create-table-ddl + table + [[:id :int "PRIMARY KEY AUTO_INCREMENT"] + [:name "VARCHAR(32)"] + [:appearance "VARCHAR(32)"] + [:cost :int] + [:grade :real]] + {:table-spec "ENGINE=InnoDB"})) + (deftest test-resultset-read-column (extend-protocol sql/IResultSetReadColumn String From 93ff83e1aeb23a3b4a9a635c3f60f235a2eedc74 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 28 Apr 2019 15:01:18 -0700 Subject: [PATCH 131/175] Fix Clojure wiki links Point to clojure.org pages for CONTRIBUTING and PULL_REQUEST_TEMPLATE --- .github/PULL_REQUEST_TEMPLATE | 10 +++++----- .gitignore | 1 + CONTRIBUTING.md | 8 +++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 0a5c486a..686625a4 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -1,6 +1,6 @@ Hi! Thanks for your interest in contributing to this project. -Clojure contrib projects do not use GitHub issues or pull requests, and +Clojure contrib projects do not use GitHub issues or pull requests, and require a signed Contributor Agreement. If you would like to contribute, please read more about the CA and sign that first (this can be done online). @@ -8,7 +8,7 @@ Then go to this project's issue tracker in JIRA to create tickets, update tickets, or submit patches. For help in creating tickets and patches, please see: -- Signing the CA: https://dev.clojure.org/display/community/Contributing+FAQ -- Creating Tickets: https://dev.clojure.org/display/community/Creating+Tickets -- Developing Patches: https://dev.clojure.org/display/community/Developing+Patches -- Contributing FAQ: https://dev.clojure.org/display/community/Contributing+FAQ +- Signing the CA: https://clojure.org/community/contributing +- Creating Tickets: https://clojure.org/community/creating_tickets +- Developing Patches: https://clojure.org/community/developing_patches +- Contributing FAQ: https://clojure.org/community/contributing diff --git a/.gitignore b/.gitignore index b3db9d32..51cb74c5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .classpath .cpcache .project +.rebl .settings /.lein-failures /.lein-repl-history diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 512917d6..820a2754 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,12 +3,10 @@ This is a [Clojure contrib] project. Under the Clojure contrib [guidelines], this project cannot accept pull requests. All patches must be submitted via [JIRA]. -See [Contributing] and the [FAQ] on the Clojure development [wiki] for +See [Contributing] on the Clojure website for more information on how to contribute. -[Clojure contrib]: http://dev.clojure.org/display/doc/Clojure+Contrib +[Clojure contrib]: https://clojure.org/community/contrib_libs [Contributing]: https://clojure.org/community/contributing -[FAQ]: http://dev.clojure.org/display/community/Contributing+FAQ [JIRA]: http://dev.clojure.org/jira/browse/JDBC -[guidelines]: http://dev.clojure.org/display/community/Guidelines+for+Clojure+Contrib+committers -[wiki]: http://dev.clojure.org/ +[guidelines]: https://clojure.org/community/contrib_howto From 0801bc07ce2f01984f6a6bc03504fef73ac02197 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 28 Apr 2019 15:37:19 -0700 Subject: [PATCH 132/175] Fix typo in as-properties docstring --- src/main/clojure/clojure/java/jdbc.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 903e814d..b0a6e936 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -108,7 +108,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} ks vs)) (defn- ^Properties as-properties - "Convert any seq of pairs to a java.utils.Properties instance. + "Convert any seq of pairs to a java.util.Properties instance. Uses as-sql-name to convert both keys and values into strings." [m] (let [p (Properties.)] From 4138b0322715aff7ca5b8009e847d74cd3913da8 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 5 May 2019 00:16:41 -0700 Subject: [PATCH 133/175] Fix JDBC-178 by cleaning up properties hash map --- CHANGES.md | 1 + src/main/clojure/clojure/java/jdbc.clj | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fc546d6c..3e5a38e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ Changes coming in 0.7.10 +* Clean up `db-spec` options that are passed to the JDBC connection manager as properties [JDBC-178](https://dev.clojure.org/jira/browse/JDBC-178). * Relax restriction on `create-table-ddl` column specs to allow numbers (as well as keywords and strings) [JDBC-177](https://dev.clojure.org/jira/browse/JDBC-177). Changes in 0.7.9 diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index b0a6e936..7193c18e 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -398,7 +398,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} host (when port (str ":" port)) db-sep dbname)) - etc (dissoc db-spec :dbtype :dbname)] + etc (dissoc db-spec :dbtype :dbname :host :port :classname)] (get-driver-connection classname subprotocol db-spec url etc opts (str "Unknown dbtype: " dbtype))) From 28e19994fea8eeeaad1abe12ee6b2c42f7c87051 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 18 May 2019 12:17:39 -0700 Subject: [PATCH 134/175] JIRA migration --- CHANGES.md | 240 +++++++++++++++++++++++------------------------ CONTRIBUTING.md | 2 +- README.md | 244 ++++++++++++++++++++++++------------------------ 3 files changed, 243 insertions(+), 243 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3e5a38e0..0cc5737a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,43 +1,43 @@ Changes coming in 0.7.10 -* Clean up `db-spec` options that are passed to the JDBC connection manager as properties [JDBC-178](https://dev.clojure.org/jira/browse/JDBC-178). -* Relax restriction on `create-table-ddl` column specs to allow numbers (as well as keywords and strings) [JDBC-177](https://dev.clojure.org/jira/browse/JDBC-177). +* Clean up `db-spec` options that are passed to the JDBC connection manager as properties [JDBC-178](https://clojure.atlassian.net/browse/JDBC-178). +* Relax restriction on `create-table-ddl` column specs to allow numbers (as well as keywords and strings) [JDBC-177](https://clojure.atlassian.net/browse/JDBC-177). Changes in 0.7.9 -* Fix behavior of multi-inserts when database does not support generated keys [JDBC-176](https://dev.clojure.org/jira/browse/JDBC-176). -* Added _highly experimental_ support for `datafy`/`nav` (in `clojure.java.jdbc.datafy` namespace). This includes a convention-based approach to foreign keys with some assistance from a `:schema` option. This is subject to change and is provided mostly for informational purposes, as an example of the new functionality in Clojure 1.10. This includes a fix for the conventions from [JDBC-175](https://dev.clojure.org/jira/browse/JDBC-175). -* Add note about rewriting batched operations to `insert-multi!` for some drivers [JDBC-174](https://dev.clojure.org/jira/browse/JDBC-174). -* Support Oracle SID style URLs (`dbtype` can be `oracle:sid` which maps to `oracle:thin` and uses `:` as the separator before the `dbname` value) [JDBC-173](https://dev.clojure.org/jira/browse/JDBC-173). +* Fix behavior of multi-inserts when database does not support generated keys [JDBC-176](https://clojure.atlassian.net/browse/JDBC-176). +* Added _highly experimental_ support for `datafy`/`nav` (in `clojure.java.jdbc.datafy` namespace). This includes a convention-based approach to foreign keys with some assistance from a `:schema` option. This is subject to change and is provided mostly for informational purposes, as an example of the new functionality in Clojure 1.10. This includes a fix for the conventions from [JDBC-175](https://clojure.atlassian.net/browse/JDBC-175). +* Add note about rewriting batched operations to `insert-multi!` for some drivers [JDBC-174](https://clojure.atlassian.net/browse/JDBC-174). +* Support Oracle SID style URLs (`dbtype` can be `oracle:sid` which maps to `oracle:thin` and uses `:` as the separator before the `dbname` value) [JDBC-173](https://clojure.atlassian.net/browse/JDBC-173). Changes in 0.7.8 -* Support multiple JDBC driver class names (MySQL introduced a new driver class name with its 6.x connector) [JDBC-172](https://dev.clojure.org/jira/browse/JDBC-172). -* Allow `with-db-connection` and `with-db-metadata` to nest [JDBC-171](https://dev.clojure.org/jira/browse/JDBC-171). +* Support multiple JDBC driver class names (MySQL introduced a new driver class name with its 6.x connector) [JDBC-172](https://clojure.atlassian.net/browse/JDBC-172). +* Allow `with-db-connection` and `with-db-metadata` to nest [JDBC-171](https://clojure.atlassian.net/browse/JDBC-171). Changes in 0.7.7 -* Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://dev.clojure.org/jira/browse/JDBC-169). +* Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://clojure.atlassian.net/browse/JDBC-169). * `get-connection` provides much better feedback if you accidentally call a function that expects a `db-spec` but pass a `java.sql.Connection` object instead (which is only required for `prepare-statement`). Changes in 0.7.6 -* `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://dev.clojure.org/jira/browse/JDBC-166). +* `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://clojure.atlassian.net/browse/JDBC-166). * Add built-in support for H2 in-memory database (`:dbtype "h2:mem"`). * Add missing spec for `db-spec` being a `java.net.URI` object. * Fix `add-connection` handling of string `db-spec` (becomes `:connection-uri`, not `:connection-string`). -* Fix specs for `with-db-*` functions, to support options in the binding form [JDBC-165](https://dev.clojure.org/jira/browse/JDBC-165). +* Fix specs for `with-db-*` functions, to support options in the binding form [JDBC-165](https://clojure.atlassian.net/browse/JDBC-165). * Update tests so they work properly with string `db-spec` test databases. * Ensure no reflection warnings are present. * Switched local test infrastructure over to CLI and `deps.edn` (from Leiningen) as an example of multi-version testing without a "build tool". Changes in 0.7.5 -* Add support for `:return-keys` in `execute!` and `:multi?` in `db-do-prepared-return-keys` [JDBC-163](https://dev.clojure.org/jira/browse/JDBC-163). +* Add support for `:return-keys` in `execute!` and `:multi?` in `db-do-prepared-return-keys` [JDBC-163](https://clojure.atlassian.net/browse/JDBC-163). Changes in 0.7.4 -* Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160). +* Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://clojure.atlassian.net/browse/JDBC-160). * Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases. * Performance improvements, primarily in `query` and `reducible-query`. * Experimental `:raw?` result set handling in `reducible-query`. @@ -45,18 +45,18 @@ Changes in 0.7.4 Changes in 0.7.3 -* Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://dev.clojure.org/jira/browse/JDBC-159). -* If an exception occurs during a transaction, and then rollback fails with another exception, both exceptions will now be combined into an `ex-info`. Previously the rollback exception obscured the transaction exception [JDBC-158](https://dev.clojure.org/jira/browse/JDBC-158). +* Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://clojure.atlassian.net/browse/JDBC-159). +* If an exception occurs during a transaction, and then rollback fails with another exception, both exceptions will now be combined into an `ex-info`. Previously the rollback exception obscured the transaction exception [JDBC-158](https://clojure.atlassian.net/browse/JDBC-158). Changes in 0.7.2 -* `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://dev.clojure.org/jira/browse/JDBC-156). +* `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://clojure.atlassian.net/browse/JDBC-156). * Allow for `:user` and `:password` to be passed with `:connection-uri`, so credentials can be omitted from the connection string. * Clarified docstring for `get-connection` to show where `:user` and `:password` can be passed. Changes in 0.7.1 -* Connection strings with empty values were not parsed correctly [JDBC-155](https://dev.clojure.org/jira/browse/JDBC-155). +* Connection strings with empty values were not parsed correctly [JDBC-155](https://clojure.atlassian.net/browse/JDBC-155). Changes in 0.7.0 @@ -65,7 +65,7 @@ Changes in 0.7.0 Changes in 0.7.0-beta5 -* `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://dev.clojure.org/jira/browse/JDBC-153). +* `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://clojure.atlassian.net/browse/JDBC-153). * Additional validation of options is performed in `prepared-statement` to avoid silently ignoring invalid combinations of `:concurrency`, `:cursors`, `:result-type`, and `:return-keys`. Changes in 0.7.0-beta4 @@ -75,7 +75,7 @@ Changes in 0.7.0-beta4 Changes in 0.7.0-beta3 -* Reflection warnings removed in `reducible-result-set` [JDBC-152](https://dev.clojure.org/jira/browse/JDBC-152). +* Reflection warnings removed in `reducible-result-set` [JDBC-152](https://clojure.atlassian.net/browse/JDBC-152). Changes in 0.7.0-beta2 @@ -88,34 +88,34 @@ Changes in 0.7.0-beta1 * Support for Clojure 1.4.0 has been dropped -- breaking change. * Optional spec support now uses `clojure.spec.alpha`. -* `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99). +* `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://clojure.atlassian.net/browse/JDBC-99). Changes in 0.7.0-alpha3 -* `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](http://dev.clojure.org/jira/browse/JDBC-151). +* `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](http://clojure.atlassian.net/browse/JDBC-151). * `redshift` has been added as a `dbtype` with `com.amazon.redshift.jdbc.Driver` as the driver name. Changes in 0.7.0-alpha2 -* `pgsql` and the Impossibl PostgresSQL 'NG' driver are now supported (note that `:max-rows` does not work with this driver!); also, providing unknown `dbtype` or `subprotocol` in a `db-spec` should now throw a better exception [JDBC-150](http://dev.clojure.org/jira/browse/JDBC-150). -* `quoted` now accepts keywords for database / dialect (`:ansi` (including PostgresSQL), `:mysql`, `:oracle`, `:sqlserver` -- these match the keywords used in HoneySQL which is the recommended third party SQL DSL for java.jdbc) [JDBC-149](http://dev.clojure.org/jira/browse/JDBC-149). -* Reorder `get-connection` clauses to make it easier to combine keys in a `db-spec` [JDBC-148](http://dev.clojure.org/jira/browse/JDBC-148). -* Force load `DriverManager` before `classForName` call on drivers to avoid potential race condition on initialization [JDBC-145](http://dev.clojure.org/jira/browse/JDBC-145). +* `pgsql` and the Impossibl PostgresSQL 'NG' driver are now supported (note that `:max-rows` does not work with this driver!); also, providing unknown `dbtype` or `subprotocol` in a `db-spec` should now throw a better exception [JDBC-150](http://clojure.atlassian.net/browse/JDBC-150). +* `quoted` now accepts keywords for database / dialect (`:ansi` (including PostgresSQL), `:mysql`, `:oracle`, `:sqlserver` -- these match the keywords used in HoneySQL which is the recommended third party SQL DSL for java.jdbc) [JDBC-149](http://clojure.atlassian.net/browse/JDBC-149). +* Reorder `get-connection` clauses to make it easier to combine keys in a `db-spec` [JDBC-148](http://clojure.atlassian.net/browse/JDBC-148). +* Force load `DriverManager` before `classForName` call on drivers to avoid potential race condition on initialization [JDBC-145](http://clojure.atlassian.net/browse/JDBC-145). Changes in 0.7.0-alpha1 -- potentially breaking changes * The signatures of `as-sql-name` and `quoted` have changed slightly: the former no longer has the curried (single argument) version, and the latter no longer has the two argument version. This change came out of a discussion on Slack which indicated curried functions are non-idiomatic. If you relied on the curried version of `as-sql-name`, you will not need to use `partial`. If you relied on the two argument version of `quoted`, you will need to add an extra `( )` for the one argument call. I'd be fairly surprised if anyone is using `as-sql-name` at all since it is really an implementation detail. I'd also be surprised if anyone was using the two argument version of `quoted` since the natural usage is `:entities (quoted [\[ \]])` to create a naming strategy (that provides SQL entity quoting). -* Clarified that `insert-multi!` with a sequence of row maps may be substantially slower than with a sequence of row value vectors (the former performs an insert for each row, the latter performs a single insert for all the data together) [JDBC-147](http://dev.clojure.org/jira/browse/JDBC-147). -* All options are passed through all function calls, expanding the range of options you can pass into high-level functions such as `insert!` and `update!` [JDBC-144](http://dev.clojure.org/jira/browse/JDBC-144). -* Added `get-isolation-level` to return the current transaction's isolation level, if any [JDBC-141](http://dev.clojure.org/jira/browse/JDBC-141). -* Added support for `read-columns` option to allow more flexible customization of reading column values from a result set (particularly in a multi-database application). Also expands `set-parameters` support to options (previously it was just part of the db-spec) [JDBC-137](http://dev.clojure.org/jira/browse/JDBC-137). +* Clarified that `insert-multi!` with a sequence of row maps may be substantially slower than with a sequence of row value vectors (the former performs an insert for each row, the latter performs a single insert for all the data together) [JDBC-147](http://clojure.atlassian.net/browse/JDBC-147). +* All options are passed through all function calls, expanding the range of options you can pass into high-level functions such as `insert!` and `update!` [JDBC-144](http://clojure.atlassian.net/browse/JDBC-144). +* Added `get-isolation-level` to return the current transaction's isolation level, if any [JDBC-141](http://clojure.atlassian.net/browse/JDBC-141). +* Added support for `read-columns` option to allow more flexible customization of reading column values from a result set (particularly in a multi-database application). Also expands `set-parameters` support to options (previously it was just part of the db-spec) [JDBC-137](http://clojure.atlassian.net/browse/JDBC-137). * Expanded optional `clojure.spec` coverage to almost the whole library API. Changes in 0.6.2-alpha3 -* Fixed bad interaction between `:qualifier` and existing `:identifiers` functionality [JDBC-140](http://dev.clojure.org/jira/browse/JDBC-140). -* Updated the README and docstrings to reflect that `:dbtype` is the easiest / preferred way to write `db-spec` maps [JDBC-139](http://dev.clojure.org/jira/browse/JDBC-139). -* Fixed postgres / postgresql alias support [JDBC-138](http://dev.clojure.org/jira/browse/JDBC-138). +* Fixed bad interaction between `:qualifier` and existing `:identifiers` functionality [JDBC-140](http://clojure.atlassian.net/browse/JDBC-140). +* Updated the README and docstrings to reflect that `:dbtype` is the easiest / preferred way to write `db-spec` maps [JDBC-139](http://clojure.atlassian.net/browse/JDBC-139). +* Fixed postgres / postgresql alias support [JDBC-138](http://clojure.atlassian.net/browse/JDBC-138). This also adds aliases for mssql (sqlserver), jtds (jtds:sqlserver), oracle (oracle:thin), and hsql (hsqldb). Changes in 0.6.2-alpha2 @@ -125,46 +125,46 @@ Changes in 0.6.2-alpha2 Changes in 0.6.2-alpha1 * Experimental support for `clojure.spec` via the new `clojure.java.jdbc.spec` namespace. Requires Clojure 1.9.0 Alpha 8 (or later). -* All options to all functions can now have defaults within the `db-spec` itself [JDBC-136](http://dev.clojure.org/jira/browse/JDBC-136). -* `query` (and by extension `find-by-keys` and `get-by-id`) now support `:explain?` and `:explain-fn` options to help support basic performance analysis [JDBC-135](http://dev.clojure.org/jira/browse/JDBC-135). -* `insert!` and `insert-multi!` now respect `:identifiers` and `:qualifier` because inserting rows on PostgreSQL returns full rows, not just the newly inserted keys [JDBC-134](http://dev.clojure.org/jira/browse/JDBC-134). -* In addition to the `:identifiers` option, you can now use `:qualifier` to specify a namespace qualifier (string) to be used when constructing keywords from SQL column names [JDBC-133](http://dev.clojure.org/jira/browse/JDBC-133). +* All options to all functions can now have defaults within the `db-spec` itself [JDBC-136](http://clojure.atlassian.net/browse/JDBC-136). +* `query` (and by extension `find-by-keys` and `get-by-id`) now support `:explain?` and `:explain-fn` options to help support basic performance analysis [JDBC-135](http://clojure.atlassian.net/browse/JDBC-135). +* `insert!` and `insert-multi!` now respect `:identifiers` and `:qualifier` because inserting rows on PostgreSQL returns full rows, not just the newly inserted keys [JDBC-134](http://clojure.atlassian.net/browse/JDBC-134). +* In addition to the `:identifiers` option, you can now use `:qualifier` to specify a namespace qualifier (string) to be used when constructing keywords from SQL column names [JDBC-133](http://clojure.atlassian.net/browse/JDBC-133). Changes in 0.6.1 -* `insert!` and `insert-multi!` now default `:transaction?` to `true` (as they should have done in 0.6.0!) [JDBC-128](http://dev.clojure.org/jira/browse/JDBC-128). These two functions also have improved docstrings to clarify the difference in behavior between inserting rows as maps compared to inserting rows as a series of column values. -* PostgreSQL support has been improved: java.jdbc is now tested against PostgreSQL locally (as well as SQLite, Apache Derby, HSQLDB, H2, MySQL, MS SQL Server (both MS Type 4 driver and jTDS driver). [JDBC-127](http://dev.clojure.org/jira/browse/JDBC-127) and [JDBC-129](http://dev.clojure.org/jira/browse/JDBC-129). +* `insert!` and `insert-multi!` now default `:transaction?` to `true` (as they should have done in 0.6.0!) [JDBC-128](http://clojure.atlassian.net/browse/JDBC-128). These two functions also have improved docstrings to clarify the difference in behavior between inserting rows as maps compared to inserting rows as a series of column values. +* PostgreSQL support has been improved: java.jdbc is now tested against PostgreSQL locally (as well as SQLite, Apache Derby, HSQLDB, H2, MySQL, MS SQL Server (both MS Type 4 driver and jTDS driver). [JDBC-127](http://clojure.atlassian.net/browse/JDBC-127) and [JDBC-129](http://clojure.atlassian.net/browse/JDBC-129). Changes in 0.6.0 -* `find-by-keys` now correctly handles `nil` values [JDBC-126](http://dev.clojure.org/jira/browse/JDBC-126). +* `find-by-keys` now correctly handles `nil` values [JDBC-126](http://clojure.atlassian.net/browse/JDBC-126). * `find-by-keys` calls `seq` on `:order-by` to treat `[]` as no `ORDER BY` clause. Changes in 0.6.0-rc2 -* `db-query-with-resultset` now accepts an options map and passes it to `prepare-statement` [JDBC-125](http://dev.clojure.org/jira/browse/JDBC-125). +* `db-query-with-resultset` now accepts an options map and passes it to `prepare-statement` [JDBC-125](http://clojure.atlassian.net/browse/JDBC-125). - Passing the `prepare-statement` options map as the first element of the `[sql & params]` vector is no longer supported and will throw an `IllegalArgumentException`. It was always very poorly documented and almost never used, as far as I can tell. * `db-query-with-resultset` no longer requires the `sql-params` argument to be a vector: a sequence is acceptable. This is in line with other functions that accept a sequence. * `db-query-with-resultset` now accepts a bare SQL string or `PreparedStatement` as the `sql-params` argument, when there are no parameters needed. This is in line with other functions that accept SQL or a `PreparedStatement`. -* `query`'s options map now is passed to `db-query-with-resultset` and thus can contain options to be used to construct the `PreparedStatement` [JDBC-125](http://dev.clojure.org/jira/browse/JDBC-125). +* `query`'s options map now is passed to `db-query-with-resultset` and thus can contain options to be used to construct the `PreparedStatement` [JDBC-125](http://clojure.atlassian.net/browse/JDBC-125). * `find-by-keys` now accepts an `:order-by` option that specifies a sequence of orderings; an ordering is either a column (to sort ascending) or a map from column name to direct (`:asc` or `:desc`). Changes in 0.6.0-rc1 * Adds `get-by-id` and `find-by-keys` convenience functions (these were easy to add after the API changes in 0.6.0 and we rely very heavily on them at World Singles so putting them in the core for everyone seemed reasonable). -* REMINDER: ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://dev.clojure.org/jira/browse/JDBC-118). +* REMINDER: ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://clojure.atlassian.net/browse/JDBC-118). - See alpha2 / alpha1 below for more details. Changes in 0.6.0-alpha2 -* ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://dev.clojure.org/jira/browse/JDBC-118). +* ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://clojure.atlassian.net/browse/JDBC-118). - This removes deprecated functionality from db-do-commands and db-do-prepared* which should have been removed in Alpha 1. -* Ensures SQL / params are actually vectors prior to destructuring (this addresses an interop edge case from other languages) [JDBC-124](http://dev.clojure.org/jira/browse/JDBC-124). -* Fix typo in `insert-multi!` argument validation exception [JDBC-123](http://dev.clojure.org/jira/browse/JDBC-123). +* Ensures SQL / params are actually vectors prior to destructuring (this addresses an interop edge case from other languages) [JDBC-124](http://clojure.atlassian.net/browse/JDBC-124). +* Fix typo in `insert-multi!` argument validation exception [JDBC-123](http://clojure.atlassian.net/browse/JDBC-123). Changes in 0.6.0-alpha1 -* (ALMOST) ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://dev.clojure.org/jira/browse/JDBC-118). +* (ALMOST) ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://clojure.atlassian.net/browse/JDBC-118). - See changes described in versions 0.5.5 through 0.5.8 for what was deprecated - Use version 0.5.8 as a bridge to identify any deprecated API calls on which your code relies! - `db-transaction` (deprecated in version 0.3.0) has been removed @@ -172,73 +172,73 @@ Changes in 0.6.0-alpha1 Changes in 0.5.8 -* `db-do-commands` now expects multiple commands to be be wrapped in a vector [JDBC-122](http://dev.clojure.org/jira/browse/JDBC-123). The single command form is unchanged (but may be wrapped in a vector). Calling `db-do-commands` with multiple commands (not wrapped in a single vector) will produce a "DEPRECATED" warning printed to the console. -* `db-do-prepared` and `db-do-prepared-return-keys` now expect to receive a `db-spec`, an optional `transaction?` boolean, a `sql-params` argument, and an optional options map. `sql-params` is a vector containing a SQL string or `PreparedStatement` followed by parameters -- like other APIs in this library. In addition, like the `:multi? true` version of `execute!`, `db-do-prepared` can accept a vector that has parameter groups: multiple vectors containing groups of parameter values [JDBC-122](http://dev.clojure.org/jira/browse/JDBC-123). Calling `db-do-prepared` with unrolled arguments -- the SQL string / statement followed by parameter groups -- is deprecated and will produce "DEPRECATED" warnings printed to the console. +* `db-do-commands` now expects multiple commands to be be wrapped in a vector [JDBC-122](http://clojure.atlassian.net/browse/JDBC-123). The single command form is unchanged (but may be wrapped in a vector). Calling `db-do-commands` with multiple commands (not wrapped in a single vector) will produce a "DEPRECATED" warning printed to the console. +* `db-do-prepared` and `db-do-prepared-return-keys` now expect to receive a `db-spec`, an optional `transaction?` boolean, a `sql-params` argument, and an optional options map. `sql-params` is a vector containing a SQL string or `PreparedStatement` followed by parameters -- like other APIs in this library. In addition, like the `:multi? true` version of `execute!`, `db-do-prepared` can accept a vector that has parameter groups: multiple vectors containing groups of parameter values [JDBC-122](http://clojure.atlassian.net/browse/JDBC-123). Calling `db-do-prepared` with unrolled arguments -- the SQL string / statement followed by parameter groups -- is deprecated and will produce "DEPRECATED" warnings printed to the console. Changes in 0.5.7 -* `(insert! db table [:col] ["val"] {})` syntax, introduced in 0.5.6, threw an exception [JDBC-121](http://dev.clojure.org/jira/browse/JDBC-121). +* `(insert! db table [:col] ["val"] {})` syntax, introduced in 0.5.6, threw an exception [JDBC-121](http://clojure.atlassian.net/browse/JDBC-121). Changes in 0.5.6 -* `create-table-ddl` now expects the column specs to be wrapped in a single vector and no longer needs the `:options` delimiter to specify the options map [JDBC-120](http://dev.clojure.org/jira/browse/JDBC-120). If column specs are not wrapped in a vector, you will get a "DEPRECATED" warning printed to the console. -* `insert!` now supports only single row insertion; multi-row insertion is deprecated. `insert-multi!` has been added for multi-row insertion. `:options` is no longer needed as a delimiter for the options map [JDBC-119](http://dev.clojure.org/jira/browse/JDBC-119). If `insert!` is called with multiple rows, or `:options` is specified, you will get a "DEPRECATED" warning printed to the console. +* `create-table-ddl` now expects the column specs to be wrapped in a single vector and no longer needs the `:options` delimiter to specify the options map [JDBC-120](http://clojure.atlassian.net/browse/JDBC-120). If column specs are not wrapped in a vector, you will get a "DEPRECATED" warning printed to the console. +* `insert!` now supports only single row insertion; multi-row insertion is deprecated. `insert-multi!` has been added for multi-row insertion. `:options` is no longer needed as a delimiter for the options map [JDBC-119](http://clojure.atlassian.net/browse/JDBC-119). If `insert!` is called with multiple rows, or `:options` is specified, you will get a "DEPRECATED" warning printed to the console. * NOTE: all deprecated functionality will go away in version 0.6.0! Changes in 0.5.5 -* Allow options map in all calls that previously took optional keyword arguments [JDBC-117](http://dev.clojure.org/jira/browse/JDBC-117). The unrolled keyword argument forms of call are deprecated -- and print a "DEPRECATED" message to the console! -- and will go away in 0.6.0. +* Allow options map in all calls that previously took optional keyword arguments [JDBC-117](http://clojure.atlassian.net/browse/JDBC-117). The unrolled keyword argument forms of call are deprecated -- and print a "DEPRECATED" message to the console! -- and will go away in 0.6.0. Changes in 0.5.0 -* Allow PreparedStatement in db-do-prepared-return-keys [JDBC-115](http://dev.clojure.org/jira/browse/JDBC-115). -* Remove exception wrapping [JDBC-114](http://dev.clojure.org/jira/browse/JDBC-114). +* Allow PreparedStatement in db-do-prepared-return-keys [JDBC-115](http://clojure.atlassian.net/browse/JDBC-115). +* Remove exception wrapping [JDBC-114](http://clojure.atlassian.net/browse/JDBC-114). * Drop Clojure 1.3 compatibility. Changes in 0.4.2 -* Remove redundant type hints [JDBC-113](http://dev.clojure.org/jira/browse/JDBC-113) - Michael Blume. -* Avoid reflection on `.prepareStatement` [JDBC-112](http://dev.clojure.org/jira/browse/JDBC-112) - Michael Blume. -* Add `metadata-query` macro to make metadata query / results easier to work with for [JDBC-107](http://dev.clojure.org/jira/browse/JDBC-107). -* `prepare-statement` `:return-keys` may now be a vector of (auto-generated) column names to return, in addition to just being truthy or falsey. This allows keys to be returned for more databases. [JDBC-104](http://dev.clojure.org/jira/browse/JDBC-104). -* Officially support H2 (and test against it) to support [JDBC-91](http://dev.clojure.org/jira/browse/JDBC-91) and clarify docstrings to improve debugging driver-specific restrictions on SQL. +* Remove redundant type hints [JDBC-113](http://clojure.atlassian.net/browse/JDBC-113) - Michael Blume. +* Avoid reflection on `.prepareStatement` [JDBC-112](http://clojure.atlassian.net/browse/JDBC-112) - Michael Blume. +* Add `metadata-query` macro to make metadata query / results easier to work with for [JDBC-107](http://clojure.atlassian.net/browse/JDBC-107). +* `prepare-statement` `:return-keys` may now be a vector of (auto-generated) column names to return, in addition to just being truthy or falsey. This allows keys to be returned for more databases. [JDBC-104](http://clojure.atlassian.net/browse/JDBC-104). +* Officially support H2 (and test against it) to support [JDBC-91](http://clojure.atlassian.net/browse/JDBC-91) and clarify docstrings to improve debugging driver-specific restrictions on SQL. Changes in 0.4.0 / 0.4.1 -* `db-do-prepared` now allows `transaction?` to be omitted when a `PreparedStatement` is passed as the second argument [JDBC-111](http://dev.clojure.org/jira/browse/JDBC-111) - Stefan Kamphausen. -* Nested transaction checks isolation level is the same [JDBC-110](http://dev.clojure.org/jira/browse/JDBC-110) - Donald Ball. -* Default PostgreSQL port; Support more dbtype/dbname variants [JDBC-109](http://dev.clojure.org/jira/browse/JDBC-109). +* `db-do-prepared` now allows `transaction?` to be omitted when a `PreparedStatement` is passed as the second argument [JDBC-111](http://clojure.atlassian.net/browse/JDBC-111) - Stefan Kamphausen. +* Nested transaction checks isolation level is the same [JDBC-110](http://clojure.atlassian.net/browse/JDBC-110) - Donald Ball. +* Default PostgreSQL port; Support more dbtype/dbname variants [JDBC-109](http://clojure.atlassian.net/browse/JDBC-109). * Drop Clojure 1.2 compatibility. Changes in 0.3.7 * Bump all driver versions in `project.clj` and re-test. -* Remove duplicate `count` calls in `insert-sql` [JDBC-108](http://dev.clojure.org/jira/browse/JDBC-108) - Earl St Sauver. -* Remove driver versions from README and link to Maven Central [JDBC-106](http://dev.clojure.org/jira/browse/JDBC-106). -* Fix links in CHANGES and README [JDBC-103](http://dev.clojure.org/jira/browse/JDBC-103) - John Walker. +* Remove duplicate `count` calls in `insert-sql` [JDBC-108](http://clojure.atlassian.net/browse/JDBC-108) - Earl St Sauver. +* Remove driver versions from README and link to Maven Central [JDBC-106](http://clojure.atlassian.net/browse/JDBC-106). +* Fix links in CHANGES and README [JDBC-103](http://clojure.atlassian.net/browse/JDBC-103) - John Walker. Changes in 0.3.6 -* Arbitrary values allowed for `:cursors`, `:concurrency`, `:result-type` arguments to `prepare-statement` [JDBC-102](http://dev.clojure.org/jira/browse/JDBC-102). -* Allow `:as-arrays? :cols-as-is` to omit column name uniqueness when returning result sets as arrrays [JDBC-101](http://dev.clojure.org/jira/browse/JDBC-101). -* Add `:timeout` argument to `prepare-statement` [JDBC-100](http://dev.clojure.org/jira/browse/JDBC-100). +* Arbitrary values allowed for `:cursors`, `:concurrency`, `:result-type` arguments to `prepare-statement` [JDBC-102](http://clojure.atlassian.net/browse/JDBC-102). +* Allow `:as-arrays? :cols-as-is` to omit column name uniqueness when returning result sets as arrrays [JDBC-101](http://clojure.atlassian.net/browse/JDBC-101). +* Add `:timeout` argument to `prepare-statement` [JDBC-100](http://clojure.atlassian.net/browse/JDBC-100). Changes in 0.3.5 * Reflection warnings on executeUpdate addressed. -* HSQLDB and SQLite in-memory strings are now accepted [JDBC-94](http://dev.clojure.org/jira/browse/JDBC-94). -* Add support for readonly transactions via :read-only? [JDBC-93](http://dev.clojure.org/jira/browse/JDBC-93). +* HSQLDB and SQLite in-memory strings are now accepted [JDBC-94](http://clojure.atlassian.net/browse/JDBC-94). +* Add support for readonly transactions via :read-only? [JDBC-93](http://clojure.atlassian.net/browse/JDBC-93). Changes in 0.3.4 -* execute! can now accept a PreparedStatement [JDBC-96](http://dev.clojure.org/jira/browse/JDBC-96). +* execute! can now accept a PreparedStatement [JDBC-96](http://clojure.atlassian.net/browse/JDBC-96). * Support simpler db-spec with :dbtype and :dbname (and optional :host and :port etc) [JDBC-92](http://dev.clojure.org/jire/browse/JDBC-92). -* Support oracle:oci and oracle:thin subprotocols [JDBC-90](http://dev.clojure.org/jira/browse/JDBC-90). +* Support oracle:oci and oracle:thin subprotocols [JDBC-90](http://clojure.atlassian.net/browse/JDBC-90). Changes in 0.3.3 -* Prevent exception/crash when query called with bare SQL string [JDBC-89](http://dev.clojure.org/jira/browse/JDBC-89). -* Add :row-fn and :result-set-fn to metadata-result function [JDBC-87](http://dev.clojure.org/jira/browse/JDBC-87). +* Prevent exception/crash when query called with bare SQL string [JDBC-89](http://clojure.atlassian.net/browse/JDBC-89). +* Add :row-fn and :result-set-fn to metadata-result function [JDBC-87](http://clojure.atlassian.net/browse/JDBC-87). * Support key/value configuration from URI (Phil Hagelberg). Changes in 0.3.2 @@ -249,25 +249,25 @@ Changes in 0.3.1 (broken) * Improve docstrings and add :arglists for better auto-generated documentation. * Make insert-sql private - technically a breaking change but it should never have been public: sorry folks! -* Provide better protocol for setting parameters in prepared statements [JDBC-86](http://dev.clojure.org/jira/browse/JDBC-86). -* Fix parens in two deprecated tests [JDBC-85](http://dev.clojure.org/jira/browse/JDBC-85). +* Provide better protocol for setting parameters in prepared statements [JDBC-86](http://clojure.atlassian.net/browse/JDBC-86). +* Fix parens in two deprecated tests [JDBC-85](http://clojure.atlassian.net/browse/JDBC-85). * Made create-table-ddl less aggressive about applying as-sql-name so only first name in a column spec is affected. Changes in 0.3.0 -* Ensure canonical Boolean to workaround strange behavior in some JDBC drivers [JDBC-84](http://dev.clojure.org/jira/browse/JDBC-84). -* Rename recently introduced test to ensure unique names [JDBC-83](http://dev.clojure.org/jira/browse/JDBC-83). -* Rename unused arguments in protocol implementation to support Android [JDBC-82](http://dev.clojure.org/jira/browse/JDBC-82). -* Correctly handle empty param group sequence in execute! (which only seemed to affect SQLite) [JDBC-65](http://dev.clojure.org/jira/browse/JDBC-65). +* Ensure canonical Boolean to workaround strange behavior in some JDBC drivers [JDBC-84](http://clojure.atlassian.net/browse/JDBC-84). +* Rename recently introduced test to ensure unique names [JDBC-83](http://clojure.atlassian.net/browse/JDBC-83). +* Rename unused arguments in protocol implementation to support Android [JDBC-82](http://clojure.atlassian.net/browse/JDBC-82). +* Correctly handle empty param group sequence in execute! (which only seemed to affect SQLite) [JDBC-65](http://clojure.atlassian.net/browse/JDBC-65). Changes in 0.3.0-rc1 -* Deprecate db-transaction (new in 0.3.0) in favor of with-db-transaction [JDBC-81](http://dev.clojure.org/jira/browse/JDBC-81). -* Add with-db-metadata macro and metadata-result function to make it easier to work with SQL metadata [JDBC-80](http://dev.clojure.org/jira/browse/JDBC-80). -* Add with-db-connection macro to make it easier to run groups of operations against a single open connection [JDBC-79](http://dev.clojure.org/jira/browse/JDBC-79). -* Add ISQLValue protocol to make it easier to support custom SQL types for parameters in SQL statements [JDBC-77](http://dev.clojure.org/jira/browse/JDBC-77). -* Add support for :isolation in with-db-transaction [JDBC-75](http://dev.clojure.org/jira/browse/JDBC-75). -* Add :user as an alias for :username for DataSource connections [JDBC-74](http://dev.clojure.org/jira/browse/JDBC-74). +* Deprecate db-transaction (new in 0.3.0) in favor of with-db-transaction [JDBC-81](http://clojure.atlassian.net/browse/JDBC-81). +* Add with-db-metadata macro and metadata-result function to make it easier to work with SQL metadata [JDBC-80](http://clojure.atlassian.net/browse/JDBC-80). +* Add with-db-connection macro to make it easier to run groups of operations against a single open connection [JDBC-79](http://clojure.atlassian.net/browse/JDBC-79). +* Add ISQLValue protocol to make it easier to support custom SQL types for parameters in SQL statements [JDBC-77](http://clojure.atlassian.net/browse/JDBC-77). +* Add support for :isolation in with-db-transaction [JDBC-75](http://clojure.atlassian.net/browse/JDBC-75). +* Add :user as an alias for :username for DataSource connections [JDBC-74](http://clojure.atlassian.net/browse/JDBC-74). Changes in 0.3.0-beta2 @@ -276,48 +276,48 @@ Changes in 0.3.0-beta2 Changes in 0.3.0-beta1 -* query as-arrays? now allows you to leverage lazy result fetching [JDBC-72](http://dev.clojure.org/jira/browse/JDBC-72). +* query as-arrays? now allows you to leverage lazy result fetching [JDBC-72](http://clojure.atlassian.net/browse/JDBC-72). * "h2" is recognized as a protocol shorthand for org.h2.Driver -* Tests no longer use :1 literal [JDBC-71](http://dev.clojure.org/jira/browse/JDBC-71). -* Conditional use of javax.naming.InitialContext so it can be compiled on Android [JDBC-69](http://dev.clojure.org/jira/browse/JDBC-69). -* New db-query-with-resultset function replaces private db-with-query-results* and processes a raw ResultSet object [JDBC-63](http://dev.clojure.org/jira/browse/JDBC-63). -* Allow :set-parameters in db-spec to override set-parameters internal function to allow per-DB special handling of SQL parameters values (such as null for Teradata) [JDBC-40](http://dev.clojure.org/jira/browse/JDBC-40). +* Tests no longer use :1 literal [JDBC-71](http://clojure.atlassian.net/browse/JDBC-71). +* Conditional use of javax.naming.InitialContext so it can be compiled on Android [JDBC-69](http://clojure.atlassian.net/browse/JDBC-69). +* New db-query-with-resultset function replaces private db-with-query-results* and processes a raw ResultSet object [JDBC-63](http://clojure.atlassian.net/browse/JDBC-63). +* Allow :set-parameters in db-spec to override set-parameters internal function to allow per-DB special handling of SQL parameters values (such as null for Teradata) [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40). Changes in 0.3.0-alpha5 -* DDL now supports entities naming strategy [JDBC-53](http://dev.clojure.org/jira/browse/JDBC-53). +* DDL now supports entities naming strategy [JDBC-53](http://clojure.atlassian.net/browse/JDBC-53). * Attempt to address potential memory leaks due to closures - see [Christophe Grand's blog post on Macros, closures and unexpected object retention](http://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/). * Documentation has moved to [Using java.jdbc on Clojure-Doc.org](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) * Added Leiningen support for easier development/testing (Maven is still the primary build tool). -* Added create-index / drop-index DDL [JDBC-62](http://dev.clojure.org/jira/browse/JDBC-62) - moquist +* Added create-index / drop-index DDL [JDBC-62](http://clojure.atlassian.net/browse/JDBC-62) - moquist * Make transaction? boolean optional in various db-do-* functions * Create clojure.java.jdbc.ddl namespace * Add create-table, drop-table, create-index and drop-index * Deprecate create-table, create-table-ddl and drop-table in main namespace * Update README to clarify PostgreSQL instructions. -* Fix test suite for PostgreSQL [JDBC-59](http://dev.clojure.org/jira/browser/JDBC-59) -* Improve hooks for Oracle data type handling [JDBC-57](http://dev.clojure.org/jira/browser/JDBC-57) -* Fix reflection warnings [JDBC-55](http://dev.clojure.org/jira/browser/JDBC-55) +* Fix test suite for PostgreSQL [JDBC-59](http://clojure.atlassian.net/browser/JDBC-59) +* Improve hooks for Oracle data type handling [JDBC-57](http://clojure.atlassian.net/browser/JDBC-57) +* Fix reflection warnings [JDBC-55](http://clojure.atlassian.net/browser/JDBC-55) -* DDL now supports entities naming strategy [JDBC-53](http://dev.clojure.org/jira/browse/JDBC-53). +* DDL now supports entities naming strategy [JDBC-53](http://clojure.atlassian.net/browse/JDBC-53). * Attempt to address potential memory leaks due to closures - see [Christophe Grand's blog post on Macros, closures and unexpected object retention](http://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/). * Documentation has moved to [Using java.jdbc on Clojure-Doc.org](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) * Added Leiningen support for easier development/testing (Maven is still the primary build tool). -* Added create-index / drop-index DDL [JDBC-62](http://dev.clojure.org/jira/browse/JDBC-62) - moquist +* Added create-index / drop-index DDL [JDBC-62](http://clojure.atlassian.net/browse/JDBC-62) - moquist * Make transaction? boolean optional in various db-do-* functions - * It will ultimately change to a function argument I think when [JDBC-37](http://dev.clojure.org/jira/browser/JDBC-37) is dealt with + * It will ultimately change to a function argument I think when [JDBC-37](http://clojure.atlassian.net/browser/JDBC-37) is dealt with * Create clojure.java.jdbc.ddl namespace * Add create-table and drop-table * Deprecate create-table, create-table-ddl and drop-table in main namespace * More DDL is coming soon * Update README to clarify PostgreSQL instructions. -* Fix test suite for PostgreSQL [JDBC-59](http://dev.clojure.org/jira/browser/JDBC-59) -* Improve hooks for Oracle data type handling [JDBC-57](http://dev.clojure.org/jira/browser/JDBC-57) -* Fix reflection warnings [JDBC-55](http://dev.clojure.org/jira/browser/JDBC-55) +* Fix test suite for PostgreSQL [JDBC-59](http://clojure.atlassian.net/browser/JDBC-59) +* Improve hooks for Oracle data type handling [JDBC-57](http://clojure.atlassian.net/browser/JDBC-57) +* Fix reflection warnings [JDBC-55](http://clojure.atlassian.net/browser/JDBC-55) Changes in 0.3.0-alpha4 -* Fix connection leaks [JDBC-54](http://dev.clojure.org/jira/browser/JDBC-54) +* Fix connection leaks [JDBC-54](http://clojure.atlassian.net/browser/JDBC-54) * Allow order-by to accept empty sequence (and return empty string) Changes in 0.3.0-alpha3 @@ -326,10 +326,10 @@ Changes in 0.3.0-alpha3 Changes in 0.3.0-alpha2 -* Address [JDBC-51](http://dev.clojure.org/jira/browse/JDBC-51) by declaring get-connection returns java.sql.Connection -* Add IResultSetReadColumn protocol extension point for custom read conversions [JDBC-46](http://dev.clojure.org/jira/browse/JDBC-46) -* Add :multi? to execute! so it can be used for repeated operations [JDBC-52](http://dev.clojure.org/jira/browse/JDBC-52) -* Reverted specialized handling of NULL values (reopens [JDBC-40](http://dev.clojure.org/jira/browse/JDBC-40)) +* Address [JDBC-51](http://clojure.atlassian.net/browse/JDBC-51) by declaring get-connection returns java.sql.Connection +* Add IResultSetReadColumn protocol extension point for custom read conversions [JDBC-46](http://clojure.atlassian.net/browse/JDBC-46) +* Add :multi? to execute! so it can be used for repeated operations [JDBC-52](http://clojure.atlassian.net/browse/JDBC-52) +* Reverted specialized handling of NULL values (reopens [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40)) * Rename :as-arrays to :as-arrays? since it is boolean * Add curried version of clojure.java.jdbc.sql/as-quoted-str * Officially deprecate resultset-seq @@ -339,7 +339,7 @@ Changes in 0.3.0-alpha1 Major overhaul of the API and deprecation of most of the old API! * Add insert!, query, update!, delete! and execute! high-level API - [JDBC-20](http://dev.clojure.org/jira/browse/JDBC-20) + [JDBC-20](http://clojure.atlassian.net/browse/JDBC-20) * Add optional SQL-generating DSL in clojure.java.jdbc.sql (implied by JDBC-20) * Add db- prefixed versions of low-level API * Add db-transaction macro: @@ -351,16 +351,16 @@ Major overhaul of the API and deprecation of most of the old API! * Add result-set-seq as replacement for resultset-seq (which will be deprecated) * Transaction now correctly rollback on non-Exception Throwables - [JDBC-43](http://dev.clojure.org/jira/browse/JDBC-43) + [JDBC-43](http://clojure.atlassian.net/browse/JDBC-43) * Rewrite old API functions in terms of new API, and deprecate old API - [JDBC-43](http://dev.clojure.org/jira/browse/JDBC-43) + [JDBC-43](http://clojure.atlassian.net/browse/JDBC-43) * Add :as-arrays to query / result-set-seq - [JDBC-41](http://dev.clojure.org/jira/browse/JDBC-41) -* Better handling of NULL values [JDBC-40](http://dev.clojure.org/jira/browse/JDBC-40) - and [JDBC-18](http://dev.clojure.org/jira/browse/JDBC-18) + [JDBC-41](http://clojure.atlassian.net/browse/JDBC-41) +* Better handling of NULL values [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40) + and [JDBC-18](http://clojure.atlassian.net/browse/JDBC-18) Note: JDBC-40 has been reverted in 0.3.0-alpha2 because it introduced regressions for PostgreSQL * db-do-commands allows you to execute SQL without a transaction wrapping it - [JDBC-38](http://dev.clojure.org/jira/browse/JDBC-38) + [JDBC-38](http://clojure.atlassian.net/browse/JDBC-38) * Remove reflection warning from execute-batch * Add notes to README about 3rd party database driver dependencies * Add optional :identifiers argument to resultset-seq so you can explicitly pass in the naming strategy @@ -368,18 +368,18 @@ Major overhaul of the API and deprecation of most of the old API! Changes in 0.2.3: * as-str now treats a.b as two identifiers separated by . so quoting produces [a].[b] instead of [a.b] -* Add :connection-uri option [JDBC-34](http://dev.clojure.org/jira/browse/JDBC-34) +* Add :connection-uri option [JDBC-34](http://clojure.atlassian.net/browse/JDBC-34) Changes in 0.2.2: -* Handle Oracle unknown row count affected [JDBC-33](http://dev.clojure.org/jira/browse/JDBC-33) -* Handle jdbc: prefix in string db-specs [JDBC-32](http://dev.clojure.org/jira/browse/JDBC-32) -* Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](http://dev.clojure.org/jira/browse/JDBC-31) +* Handle Oracle unknown row count affected [JDBC-33](http://clojure.atlassian.net/browse/JDBC-33) +* Handle jdbc: prefix in string db-specs [JDBC-32](http://clojure.atlassian.net/browse/JDBC-32) +* Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](http://clojure.atlassian.net/browse/JDBC-31) Changes in 0.2.1: -* Result set performance enhancement (Juergen Hoetzel) [JDBC-29](http://dev.clojure.org/jira/browse/JDBC-29) -* Make do-prepared-return-keys (for Korma team) [JDBC-30](http://dev.clojure.org/jira/browse/JDBC-30) +* Result set performance enhancement (Juergen Hoetzel) [JDBC-29](http://clojure.atlassian.net/browse/JDBC-29) +* Make do-prepared-return-keys (for Korma team) [JDBC-30](http://clojure.atlassian.net/browse/JDBC-30) Changes in 0.2.0: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 820a2754..d1017074 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,5 +8,5 @@ more information on how to contribute. [Clojure contrib]: https://clojure.org/community/contrib_libs [Contributing]: https://clojure.org/community/contributing -[JIRA]: http://dev.clojure.org/jira/browse/JDBC +[JIRA]: http://clojure.atlassian.net/browse/JDBC [guidelines]: https://clojure.org/community/contrib_howto diff --git a/README.md b/README.md index 0124a8ac..5a6e721c 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ Developer Information * [GitHub project](https://github.com/clojure/java.jdbc) -* [Bug Tracker](http://dev.clojure.org/jira/browse/JDBC) +* [Bug Tracker](http://clojure.atlassian.net/browse/JDBC) * [Continuous Integration](http://build.clojure.org/job/java.jdbc/) @@ -139,57 +139,57 @@ Change Log ==================== * Release 0.7.9 on 2019-02-21 - * Fix behavior of multi-inserts when database does not support generated keys [JDBC-176](https://dev.clojure.org/jira/browse/JDBC-176). - * Added _highly experimental_ support for `datafy`/`nav` (in `clojure.java.jdbc.datafy` namespace). This includes a convention-based approach to foreign keys with some assistance from a `:schema` option. This is subject to change and is provided mostly for informational purposes, as an example of the new functionality in Clojure 1.10. This includes a fix for the conventions from [JDBC-175](https://dev.clojure.org/jira/browse/JDBC-175). - * Add note about rewriting batched operations to `insert-multi!` for some drivers [JDBC-174](https://dev.clojure.org/jira/browse/JDBC-174). - * Support Oracle SID style URLs (`dbtype` can be `oracle:sid` which maps to `oracle:thin` and uses `:` as the separator before the `dbname` value) [JDBC-173](https://dev.clojure.org/jira/browse/JDBC-173). + * Fix behavior of multi-inserts when database does not support generated keys [JDBC-176](https://clojure.atlassian.net/browse/JDBC-176). + * Added _highly experimental_ support for `datafy`/`nav` (in `clojure.java.jdbc.datafy` namespace). This includes a convention-based approach to foreign keys with some assistance from a `:schema` option. This is subject to change and is provided mostly for informational purposes, as an example of the new functionality in Clojure 1.10. This includes a fix for the conventions from [JDBC-175](https://clojure.atlassian.net/browse/JDBC-175). + * Add note about rewriting batched operations to `insert-multi!` for some drivers [JDBC-174](https://clojure.atlassian.net/browse/JDBC-174). + * Support Oracle SID style URLs (`dbtype` can be `oracle:sid` which maps to `oracle:thin` and uses `:` as the separator before the `dbname` value) [JDBC-173](https://clojure.atlassian.net/browse/JDBC-173). * Release 0.7.8 on 2018-08-13 - * Support multiple JDBC driver class names (MySQL introduced a new driver class name with its 6.x connector) [JDBC-172](https://dev.clojure.org/jira/browse/JDBC-172). - * Allow `with-db-connection` and `with-db-metadata` to nest [JDBC-171](https://dev.clojure.org/jira/browse/JDBC-171). + * Support multiple JDBC driver class names (MySQL introduced a new driver class name with its 6.x connector) [JDBC-172](https://clojure.atlassian.net/browse/JDBC-172). + * Allow `with-db-connection` and `with-db-metadata` to nest [JDBC-171](https://clojure.atlassian.net/browse/JDBC-171). * Release 0.7.7 on 2018-06-23 - * Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://dev.clojure.org/jira/browse/JDBC-169). + * Support `:as-arrays?`, `:result-set-fn`, and `:row-fn` in operations that return generated keys as a result set (`execute!`, `insert!`, and `insert-multi!`) [JDBC-169](https://clojure.atlassian.net/browse/JDBC-169). * `get-connection` provides much better feedback if you accidentally call a function that expects a `db-spec` but pass a `java.sql.Connection` object instead (which is only required for `prepare-statement`). * Release 0.7.6 on 2018-04-24 - * `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://dev.clojure.org/jira/browse/JDBC-166). + * `execute!` now supports `:return-keys` as a vector of column names, rather than just a simple Boolean value, for drivers that support that [JDBC-166](https://clojure.atlassian.net/browse/JDBC-166). * Add built-in support for H2 in-memory database (`:dbtype "h2:mem"`). * Add missing spec for `db-spec` being a `java.net.URI` object. * Fix `add-connection` handling of string `db-spec` (becomes `:connection-uri`, not `:connection-string`). - * Fix specs for `with-db-*` functions, to support options in the binding form [JDBC-165](https://dev.clojure.org/jira/browse/JDBC-165). + * Fix specs for `with-db-*` functions, to support options in the binding form [JDBC-165](https://clojure.atlassian.net/browse/JDBC-165). * Update tests so they work properly with string `db-spec` test databases. * Ensure no reflection warnings are present. * Switched local test infrastructure over to CLI and `deps.edn` (from Leiningen) as an example of multi-version testing without a "build tool". * Release 0.7.5 on 2017-12-29 - * Add support for `:return-keys` in `execute!` and `:multi?` in `db-do-prepared-return-keys` [JDBC-163](https://dev.clojure.org/jira/browse/JDBC-163). + * Add support for `:return-keys` in `execute!` and `:multi?` in `db-do-prepared-return-keys` [JDBC-163](https://clojure.atlassian.net/browse/JDBC-163). * Release 0.7.4 on 2017-12-14 - * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://dev.clojure.org/jira/browse/JDBC-160). + * Improved discoverability of other `java.jdbc` documentation [JDBC-160](https://clojure.atlassian.net/browse/JDBC-160). * Optional specs updated with `:keywordize?` and `:connection-uri` changes from 0.7.2 and 0.7.3 releases. * Performance improvements, primarily in `query` and `reducible-query`. * Experimental `:raw?` result set handling in `reducible-query`. * `modify-connection` is more robust in the face of `null` connections and bad option values. * Release 0.7.3 on 2017-10-05 - * Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://dev.clojure.org/jira/browse/JDBC-159). - * If an exception occurs during a transaction, and then rollback fails with another exception, both exceptions will now be combined into an `ex-info`. Previously the rollback exception obscured the transaction exception [JDBC-158](https://dev.clojure.org/jira/browse/JDBC-158). + * Added `:keywordize?` option alongside `:identifiers` that defaults to `true` but can be set to `false` to opt-out of converting identifiers to keywords (so column names etc will only be processed by the function passed as `:identifiers`) [JDBC-159](https://clojure.atlassian.net/browse/JDBC-159). + * If an exception occurs during a transaction, and then rollback fails with another exception, both exceptions will now be combined into an `ex-info`. Previously the rollback exception obscured the transaction exception [JDBC-158](https://clojure.atlassian.net/browse/JDBC-158). * Release 0.7.2 on 2017-10-02 - * `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://dev.clojure.org/jira/browse/JDBC-156). + * `connection-uri` was incorrectly spec'd as a `java.net.URI` but should be `string?` [JDBC-156](https://clojure.atlassian.net/browse/JDBC-156). * Allow for `:user` and `:password` to be passed with `:connection-uri`, so credentials can be omitted from the connection string. * Clarified docstring for `get-connection` to show where `:user` and `:password` can be passed. * Release 0.7.1 on 2017-08-30 - * Connection strings with empty values were not parsed correctly [JDBC-155](https://dev.clojure.org/jira/browse/JDBC-155). + * Connection strings with empty values were not parsed correctly [JDBC-155](https://clojure.atlassian.net/browse/JDBC-155). * Release 0.7.0 on 2017-07-16 * `:conditional?` option for `create-table-ddl` and `drop-table-ddl` to provide for existence check (or a function to manipulate the generated DDL). * Add better support for Oracle connections (default port to `1521`, support `:dbtype "oracle"` -- as `"oracle:thin"` -- and `:dbtype "oracle:oci"`, with `@` instead of `//` before host). * Release 0.7.0-beta5 on 2017-07-05 - * `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://dev.clojure.org/jira/browse/JDBC-153). + * `get-connection` now accepts an `opts` map with `:auto-commit?` and `:read-only?` options. If present, the appropriate methods will be called on the connection obtained. These options are valid in any function call that may call `get-connection` under the hood. This should allow for streaming results in a query for most databases [JDBC-153](https://clojure.atlassian.net/browse/JDBC-153). * Additional validation of options is performed in `prepared-statement` to avoid silently ignoring invalid combinations of `:concurrency`, `:cursors`, `:result-type`, and `:return-keys`. * Release 0.7.0-beta4 on 2017-07-04 @@ -197,7 +197,7 @@ Change Log * Updated the `::query-options` spec to make it clear that `::prepare-options` are also acceptable there. * Release 0.7.0-beta3 on 2017-07-04 - * Reflection warnings removed in `reducible-result-set` [JDBC-152](https://dev.clojure.org/jira/browse/JDBC-152). + * Reflection warnings removed in `reducible-result-set` [JDBC-152](https://clojure.atlassian.net/browse/JDBC-152). * Release 0.7.0-beta2 on 2017-06-30 (a.k.a The Reducible Saga, Part 2) * Support for Clojure 1.5 and 1.6 has been dropped -- breaking change. @@ -208,30 +208,30 @@ Change Log * Release 0.7.0-beta1 on 2017-06-29 * Support for Clojure 1.4.0 has been dropped -- breaking change. * Optional spec support now uses `clojure.spec.alpha`. - * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://dev.clojure.org/jira/browse/JDBC-99). + * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://clojure.atlassian.net/browse/JDBC-99). * Release 0.7.0-alpha3 on 2017-03-23 - * `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](http://dev.clojure.org/jira/browse/JDBC-151). + * `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](http://clojure.atlassian.net/browse/JDBC-151). * `redshift` has been added as a `dbtype` with `com.amazon.redshift.jdbc.Driver` as the driver name. * Release 0.7.0-alpha2 on 2017-03-01 - * `pgsql` and the Impossibl PostgresSQL 'NG' driver are now supported (note that `:max-rows` does not work with this driver!); also, providing unknown `dbtype` or `subprotocol` in a `db-spec` should now throw a better exception [JDBC-150](http://dev.clojure.org/jira/browse/JDBC-150). - * `quoted` now accepts keywords for database / dialect (`:ansi` (including PostgresSQL), `:mysql`, `:oracle`, `:sqlserver` -- these match the keywords used in HoneySQL which is the recommended third party SQL DSL for java.jdbc) [JDBC-149](http://dev.clojure.org/jira/browse/JDBC-149). - * Reorder `get-connection` clauses to make it easier to combine keys in a `db-spec` [JDBC-148](http://dev.clojure.org/jira/browse/JDBC-148). - * Force load `DriverManager` before `classForName` call on drivers to avoid potential race condition on initialization [JDBC-145](http://dev.clojure.org/jira/browse/JDBC-145). + * `pgsql` and the Impossibl PostgresSQL 'NG' driver are now supported (note that `:max-rows` does not work with this driver!); also, providing unknown `dbtype` or `subprotocol` in a `db-spec` should now throw a better exception [JDBC-150](http://clojure.atlassian.net/browse/JDBC-150). + * `quoted` now accepts keywords for database / dialect (`:ansi` (including PostgresSQL), `:mysql`, `:oracle`, `:sqlserver` -- these match the keywords used in HoneySQL which is the recommended third party SQL DSL for java.jdbc) [JDBC-149](http://clojure.atlassian.net/browse/JDBC-149). + * Reorder `get-connection` clauses to make it easier to combine keys in a `db-spec` [JDBC-148](http://clojure.atlassian.net/browse/JDBC-148). + * Force load `DriverManager` before `classForName` call on drivers to avoid potential race condition on initialization [JDBC-145](http://clojure.atlassian.net/browse/JDBC-145). * Release 0.7.0-alpha1 on 2016-11-12 -- potentially breaking changes * The signatures of `as-sql-name` and `quoted` have changed slightly: the former no longer has the curried (single argument) version, and the latter no longer has the two argument version. This change came out of a discussion on Slack which indicated curried functions are non-idiomatic. If you relied on the curried version of `as-sql-name`, you will not need to use `partial`. If you relied on the two argument version of `quoted`, you will need to add an extra `( )` for the one argument call. I'd be fairly surprised if anyone is using `as-sql-name` at all since it is really an implementation detail. I'd also be surprised if anyone was using the two argument version of `quoted` since the natural usage is `:entities (quoted [\[ \]])` to create a naming strategy (that provides SQL entity quoting). - * Clarified that `insert-multi!` with a sequence of row maps may be substantially slower than with a sequence of row value vectors (the former performs an insert for each row, the latter performs a single insert for all the data together) [JDBC-147](http://dev.clojure.org/jira/browse/JDBC-147). - * All options are passed through all function calls, expanding the range of options you can pass into high-level functions such as `insert!` and `update!` [JDBC-144](http://dev.clojure.org/jira/browse/JDBC-144). - * Added `get-isolation-level` to return the current transaction's isolation level, if any [JDBC-141](http://dev.clojure.org/jira/browse/JDBC-141). - * Added support for `read-columns` option to allow more flexible customization of reading column values from a result set (particularly in a multi-database application). Also expands `set-parameters` support to options (previously it was just part of the db-spec) [JDBC-137](http://dev.clojure.org/jira/browse/JDBC-137). + * Clarified that `insert-multi!` with a sequence of row maps may be substantially slower than with a sequence of row value vectors (the former performs an insert for each row, the latter performs a single insert for all the data together) [JDBC-147](http://clojure.atlassian.net/browse/JDBC-147). + * All options are passed through all function calls, expanding the range of options you can pass into high-level functions such as `insert!` and `update!` [JDBC-144](http://clojure.atlassian.net/browse/JDBC-144). + * Added `get-isolation-level` to return the current transaction's isolation level, if any [JDBC-141](http://clojure.atlassian.net/browse/JDBC-141). + * Added support for `read-columns` option to allow more flexible customization of reading column values from a result set (particularly in a multi-database application). Also expands `set-parameters` support to options (previously it was just part of the db-spec) [JDBC-137](http://clojure.atlassian.net/browse/JDBC-137). * Expanded optional `clojure.spec` coverage to almost the whole library API. * Release 0.6.2-alpha3 on 2016-08-25 - * Fixed bad interaction between `:qualifier` and existing `:identifiers` functionality [JDBC-140](http://dev.clojure.org/jira/browse/JDBC-140). - * Updated the README and docstrings to reflect that `:dbtype` is the easiest / preferred way to write `db-spec` maps [JDBC-139](http://dev.clojure.org/jira/browse/JDBC-139). - * Fixed postgres / postgresql alias support [JDBC-138](http://dev.clojure.org/jira/browse/JDBC-138). + * Fixed bad interaction between `:qualifier` and existing `:identifiers` functionality [JDBC-140](http://clojure.atlassian.net/browse/JDBC-140). + * Updated the README and docstrings to reflect that `:dbtype` is the easiest / preferred way to write `db-spec` maps [JDBC-139](http://clojure.atlassian.net/browse/JDBC-139). + * Fixed postgres / postgresql alias support [JDBC-138](http://clojure.atlassian.net/browse/JDBC-138). This also adds aliases for mssql (sqlserver), jtds (jtds:sqlserver), oracle (oracle:thin), and hsql (hsqldb). * Release 0.6.2-alpha2 on 2016-07-21 @@ -239,94 +239,94 @@ Change Log * Release 0.6.2-alpha1 on 2016-07-05 * Experimental support for `clojure.spec` via the new `clojure.java.jdbc.spec` namespace. Requires Clojure 1.9.0 Alpha 8 (or later). - * All options to all functions can now have defaults within the `db-spec` itself [JDBC-136](http://dev.clojure.org/jira/browse/JDBC-136). - * `query` (and by extension `find-by-keys` and `get-by-id`) now support `:explain?` and `:explain-fn` options to help support basic performance analysis [JDBC-135](http://dev.clojure.org/jira/browse/JDBC-135). - * `insert!` and `insert-multi!` now respect `:identifiers` and `:qualifier` because inserting rows on PostgreSQL returns full rows, not just the newly inserted keys [JDBC-134](http://dev.clojure.org/jira/browse/JDBC-134). - * In addition to the `:identifiers` option, you can now use `:qualifier` to specify a namespace qualifier (string) to be used when constructing keywords from SQL column names [JDBC-133](http://dev.clojure.org/jira/browse/JDBC-133). + * All options to all functions can now have defaults within the `db-spec` itself [JDBC-136](http://clojure.atlassian.net/browse/JDBC-136). + * `query` (and by extension `find-by-keys` and `get-by-id`) now support `:explain?` and `:explain-fn` options to help support basic performance analysis [JDBC-135](http://clojure.atlassian.net/browse/JDBC-135). + * `insert!` and `insert-multi!` now respect `:identifiers` and `:qualifier` because inserting rows on PostgreSQL returns full rows, not just the newly inserted keys [JDBC-134](http://clojure.atlassian.net/browse/JDBC-134). + * In addition to the `:identifiers` option, you can now use `:qualifier` to specify a namespace qualifier (string) to be used when constructing keywords from SQL column names [JDBC-133](http://clojure.atlassian.net/browse/JDBC-133). * Release 0.6.1 on 2016-05-12 -- **IMPORTANT BUG FIX!** - * `insert!` and `insert-multi!` now default `:transaction?` to `true` (as they should have done in 0.6.0!) [JDBC-128](http://dev.clojure.org/jira/browse/JDBC-128). These two functions also have improved docstrings to clarify the difference in behavior between inserting rows as maps compared to inserting rows as a series of column values. - * PostgreSQL support has been improved: java.jdbc is now tested against PostgreSQL locally (as well as SQLite, Apache Derby, HSQLDB, H2, MySQL, MS SQL Server (both MS Type 4 driver and jTDS driver). [JDBC-127](http://dev.clojure.org/jira/browse/JDBC-127) and [JDBC-129](http://dev.clojure.org/jira/browse/JDBC-129). + * `insert!` and `insert-multi!` now default `:transaction?` to `true` (as they should have done in 0.6.0!) [JDBC-128](http://clojure.atlassian.net/browse/JDBC-128). These two functions also have improved docstrings to clarify the difference in behavior between inserting rows as maps compared to inserting rows as a series of column values. + * PostgreSQL support has been improved: java.jdbc is now tested against PostgreSQL locally (as well as SQLite, Apache Derby, HSQLDB, H2, MySQL, MS SQL Server (both MS Type 4 driver and jTDS driver). [JDBC-127](http://clojure.atlassian.net/browse/JDBC-127) and [JDBC-129](http://clojure.atlassian.net/browse/JDBC-129). * Release 0.6.0 on 2016-05-11 -- **BREAKING RELEASE! DEPRECATED FUNCTIONALITY REMOVED!** - * `find-by-keys` now correctly handles `nil` values [JDBC-126](http://dev.clojure.org/jira/browse/JDBC-126). 0.6.0 / 2016-05-11. + * `find-by-keys` now correctly handles `nil` values [JDBC-126](http://clojure.atlassian.net/browse/JDBC-126). 0.6.0 / 2016-05-11. * `find-by-keys` calls `seq` on `:order-by` to treat `[]` as no `ORDER BY` clause. 0.6.0 / 2016-05-11. - * `db-query-with-resultset` now accepts an options map and passes it to `prepare-statement` [JDBC-125](http://dev.clojure.org/jira/browse/JDBC-125). 0.6.0-rc2 / 2016-05-07. + * `db-query-with-resultset` now accepts an options map and passes it to `prepare-statement` [JDBC-125](http://clojure.atlassian.net/browse/JDBC-125). 0.6.0-rc2 / 2016-05-07. - Passing the `prepare-statement` options map as the first element of the `[sql & params]` vector is no longer supported and will throw an `IllegalArgumentException`. It was always very poorly documented and almost never used, as far as I can tell. * `db-query-with-resultset` no longer requires the `sql-params` argument to be a vector: a sequence is acceptable. This is in line with other functions that accept a sequence. 0.6.0-rc2 / 2016-05-07. * `db-query-with-resultset` now accepts a bare SQL string or `PreparedStatement` as the `sql-params` argument, when there are no parameters needed. This is in line with other functions that accept SQL or a `PreparedStatement`. 0.6.0-rc2 / 2016-05-07. - * `query`'s options map now is passed to `db-query-with-resultset` and thus can contain options to be used to construct the `PreparedStatement` [JDBC-125](http://dev.clojure.org/jira/browse/JDBC-125). 0.6.0-rc2 / 2016-05-07. + * `query`'s options map now is passed to `db-query-with-resultset` and thus can contain options to be used to construct the `PreparedStatement` [JDBC-125](http://clojure.atlassian.net/browse/JDBC-125). 0.6.0-rc2 / 2016-05-07. * Adds `get-by-id` and `find-by-keys` convenience functions (these were easy to add after the API changes in 0.6.0 and we rely very heavily on them at World Singles so putting them in the core for everyone seemed reasonable). 0.6.0-rc1 / 2016-05-04. - `find-by-keys` accepts an `:order-by` option that expects a sequence of orderings; an ordering is a column name (keyword) or a map from column name (keyword) to direction (`:asc` or `:desc`). 0.6.0-rc2 / 2016-05-07. - * Ensures SQL / params are actually vectors prior to destructuring (this addresses an interop edge case from other languages) [JDBC-124](http://dev.clojure.org/jira/browse/JDBC-124). 0.6.0-alpha2 / 2016-04-18. - * Fix typo in `insert-multi!` argument validation exception [JDBC-123](http://dev.clojure.org/jira/browse/JDBC-123). 0.6.0-alpha2 / 2016-04-18. - * ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://dev.clojure.org/jira/browse/JDBC-118). 0.6.0-alpha1 / 2016-04-13 + * Ensures SQL / params are actually vectors prior to destructuring (this addresses an interop edge case from other languages) [JDBC-124](http://clojure.atlassian.net/browse/JDBC-124). 0.6.0-alpha2 / 2016-04-18. + * Fix typo in `insert-multi!` argument validation exception [JDBC-123](http://clojure.atlassian.net/browse/JDBC-123). 0.6.0-alpha2 / 2016-04-18. + * ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://clojure.atlassian.net/browse/JDBC-118). 0.6.0-alpha1 / 2016-04-13 - See changes described in versions 0.5.5 through 0.5.8 for what was deprecated - Use version 0.5.8 as a bridge to identify any deprecated API calls on which your code relies! - `db-transaction` (deprecated in version 0.3.0) has been removed - The `java.jdbc.deprecated` namespace has been removed * Release 0.5.8 on 2016-04-12 - * `db-do-commands` now expects multiple commands to be be wrapped in a vector [JDBC-122](http://dev.clojure.org/jira/browse/JDBC-123). The single command form is unchanged (but may be wrapped in a vector). Calling `db-do-commands` with multiple commands (not wrapped in a single vector) will produce a "DEPRECATED" warning printed to the console. - * `db-do-prepared` and `db-do-prepared-return-keys` now expect to receive a `db-spec`, an optional `transaction?` boolean, a `sql-params` argument, and an optional options map. `sql-params` is a vector containing a SQL string or `PreparedStatement` followed by parameters -- like other APIs in this library. In addition, like the `:multi? true` version of `execute!`, `db-do-prepared` can accept a vector that has parameter groups: multiple vectors containing groups of parameter values [JDBC-122](http://dev.clojure.org/jira/browse/JDBC-123). Calling `db-do-prepared` with unrolled arguments -- the SQL string / statement followed by parameter groups -- is deprecated and will produce "DEPRECATED" warnings printed to the console. + * `db-do-commands` now expects multiple commands to be be wrapped in a vector [JDBC-122](http://clojure.atlassian.net/browse/JDBC-123). The single command form is unchanged (but may be wrapped in a vector). Calling `db-do-commands` with multiple commands (not wrapped in a single vector) will produce a "DEPRECATED" warning printed to the console. + * `db-do-prepared` and `db-do-prepared-return-keys` now expect to receive a `db-spec`, an optional `transaction?` boolean, a `sql-params` argument, and an optional options map. `sql-params` is a vector containing a SQL string or `PreparedStatement` followed by parameters -- like other APIs in this library. In addition, like the `:multi? true` version of `execute!`, `db-do-prepared` can accept a vector that has parameter groups: multiple vectors containing groups of parameter values [JDBC-122](http://clojure.atlassian.net/browse/JDBC-123). Calling `db-do-prepared` with unrolled arguments -- the SQL string / statement followed by parameter groups -- is deprecated and will produce "DEPRECATED" warnings printed to the console. * Release 0.5.7 on 2016-04-10 - * `(insert! db table [:col] ["val"] {})` syntax, introduced in 0.5.6, threw an exception [JDBC-121](http://dev.clojure.org/jira/browse/JDBC-121). + * `(insert! db table [:col] ["val"] {})` syntax, introduced in 0.5.6, threw an exception [JDBC-121](http://clojure.atlassian.net/browse/JDBC-121). * Release 0.5.6 on 2016-04-10 - * `create-table-ddl` now expects the column specs to be wrapped in a single vector and no longer needs the `:options` delimiter to specify the options map [JDBC-120](http://dev.clojure.org/jira/browse/JDBC-120). + * `create-table-ddl` now expects the column specs to be wrapped in a single vector and no longer needs the `:options` delimiter to specify the options map [JDBC-120](http://clojure.atlassian.net/browse/JDBC-120). - If column specs are not wrapped in a vector, you will get a "DEPRECATED" warning printed to the console. - * `insert!` now supports only single row insertion; multi-row insertion is deprecated. `insert-multi!` has been added for multi-row insertion. `:options` is no longer needed as a delimiter for the options map [JDBC-119](http://dev.clojure.org/jira/browse/JDBC-119). + * `insert!` now supports only single row insertion; multi-row insertion is deprecated. `insert-multi!` has been added for multi-row insertion. `:options` is no longer needed as a delimiter for the options map [JDBC-119](http://clojure.atlassian.net/browse/JDBC-119). - If `insert!` is called with multiple rows, or `:options` is specified, you will get a "DEPRECATED" warning printed to the console. * NOTE: all deprecated functionality will go away in version 0.6.0! * Release 0.5.5 on 2016-04-09 - * Allow options map in all calls that previously took optional keyword arguments [JDBC-117](http://dev.clojure.org/jira/browse/JDBC-117). + * Allow options map in all calls that previously took optional keyword arguments [JDBC-117](http://clojure.atlassian.net/browse/JDBC-117). - The unrolled keyword argument forms of call are deprecated -- and print a "DEPRECATED" message to the console! -- and will go away in 0.6.0. * Release 0.5.0 on 2016-03-27 - * Allow PreparedStatement in db-do-prepared-return-keys [JDBC-115](http://dev.clojure.org/jira/browse/JDBC-115). - * Remove exception wrapping [JDBC-114](http://dev.clojure.org/jira/browse/JDBC-114). + * Allow PreparedStatement in db-do-prepared-return-keys [JDBC-115](http://clojure.atlassian.net/browse/JDBC-115). + * Remove exception wrapping [JDBC-114](http://clojure.atlassian.net/browse/JDBC-114). * Drop Clojure 1.3 compatibility. * Release 0.4.2 on 2015-09-15 - * Remove redundant type hints [JDBC-113](http://dev.clojure.org/jira/browse/JDBC-113) - Michael Blume. - * Avoid reflection on `.prepareStatement` [JDBC-112](http://dev.clojure.org/jira/browse/JDBC-112) - Michael Blume. - * Add `metadata-query` macro to make metadata query / results easier to work with for [JDBC-107](http://dev.clojure.org/jira/browse/JDBC-107). - * `prepare-statement` `:return-keys` may now be a vector of (auto-generated) column names to return, in addition to just being truthy or falsey. This allows keys to be returned for more databases. [JDBC-104](http://dev.clojure.org/jira/browse/JDBC-104). - * Officially support H2 (and test against it) to support [JDBC-91](http://dev.clojure.org/jira/browse/JDBC-91) and clarify docstrings to improve debugging driver-specific restrictions on SQL. + * Remove redundant type hints [JDBC-113](http://clojure.atlassian.net/browse/JDBC-113) - Michael Blume. + * Avoid reflection on `.prepareStatement` [JDBC-112](http://clojure.atlassian.net/browse/JDBC-112) - Michael Blume. + * Add `metadata-query` macro to make metadata query / results easier to work with for [JDBC-107](http://clojure.atlassian.net/browse/JDBC-107). + * `prepare-statement` `:return-keys` may now be a vector of (auto-generated) column names to return, in addition to just being truthy or falsey. This allows keys to be returned for more databases. [JDBC-104](http://clojure.atlassian.net/browse/JDBC-104). + * Officially support H2 (and test against it) to support [JDBC-91](http://clojure.atlassian.net/browse/JDBC-91) and clarify docstrings to improve debugging driver-specific restrictions on SQL. * Release 0.4.0 / 0.4.1 on 2015-07-26 - * `db-do-prepared` now allows `transaction?` to be omitted when a `PreparedStatement` is passed as the second argument [JDBC-111](http://dev.clojure.org/jira/browse/JDBC-111) - Stefan Kamphausen. - * Nested transaction checks isolation level is the same [JDBC-110](http://dev.clojure.org/jira/browse/JDBC-110) - Donald Ball. - * Default PostgreSQL port; Support more dbtype/dbname variants [JDBC-109](http://dev.clojure.org/jira/browse/JDBC-109). + * `db-do-prepared` now allows `transaction?` to be omitted when a `PreparedStatement` is passed as the second argument [JDBC-111](http://clojure.atlassian.net/browse/JDBC-111) - Stefan Kamphausen. + * Nested transaction checks isolation level is the same [JDBC-110](http://clojure.atlassian.net/browse/JDBC-110) - Donald Ball. + * Default PostgreSQL port; Support more dbtype/dbname variants [JDBC-109](http://clojure.atlassian.net/browse/JDBC-109). * Drop Clojure 1.2 compatibility. * Release 0.3.7 on 2015-05-18 * Bump all driver versions in `project.clj` and re-test. - * Remove duplicate `count` calls in `insert-sql` [JDBC-108](http://dev.clojure.org/jira/browse/JDBC-108) - Earl St Sauver. - * Remove driver versions from README and link to Maven Central [JDBC-106](http://dev.clojure.org/jira/browse/JDBC-106). - * Fix links in CHANGES and README [JDBC-103](http://dev.clojure.org/jira/browse/JDBC-103) - John Walker. + * Remove duplicate `count` calls in `insert-sql` [JDBC-108](http://clojure.atlassian.net/browse/JDBC-108) - Earl St Sauver. + * Remove driver versions from README and link to Maven Central [JDBC-106](http://clojure.atlassian.net/browse/JDBC-106). + * Fix links in CHANGES and README [JDBC-103](http://clojure.atlassian.net/browse/JDBC-103) - John Walker. * Release 0.3.6 on 2014-10-28 - * Arbitrary values allowed for `:cursors`, `:concurrency`, `:result-type` arguments to `prepare-statement` [JDBC-102](http://dev.clojure.org/jira/browse/JDBC-102). - * Allow `:as-arrays? :cols-as-is` to omit column name uniqueness when returning result sets as arrrays [JDBC-101](http://dev.clojure.org/jira/browse/JDBC-101). - * Add `:timeout` argument to `prepare-statement` [JDBC-100](http://dev.clojure.org/jira/browse/JDBC-100). + * Arbitrary values allowed for `:cursors`, `:concurrency`, `:result-type` arguments to `prepare-statement` [JDBC-102](http://clojure.atlassian.net/browse/JDBC-102). + * Allow `:as-arrays? :cols-as-is` to omit column name uniqueness when returning result sets as arrrays [JDBC-101](http://clojure.atlassian.net/browse/JDBC-101). + * Add `:timeout` argument to `prepare-statement` [JDBC-100](http://clojure.atlassian.net/browse/JDBC-100). * Release 0.3.5 on 2014-08-01 * Reflection warnings on executeUpdate addressed. - * HSQLDB and SQLite in-memory strings are now accepted [JDBC-94](http://dev.clojure.org/jira/browse/JDBC-94). - * Add support for readonly transactions via :read-only? [JDBC-93](http://dev.clojure.org/jira/browse/JDBC-93). + * HSQLDB and SQLite in-memory strings are now accepted [JDBC-94](http://clojure.atlassian.net/browse/JDBC-94). + * Add support for readonly transactions via :read-only? [JDBC-93](http://clojure.atlassian.net/browse/JDBC-93). * Release 0.3.4 on 2014-06-30 - * execute! can now accept a PreparedStatement [JDBC-96](http://dev.clojure.org/jira/browse/JDBC-96). + * execute! can now accept a PreparedStatement [JDBC-96](http://clojure.atlassian.net/browse/JDBC-96). * Support simpler db-spec with :dbtype and :dbname (and optional :host and :port etc) [JDBC-92](http://dev.clojure.org/jire/browse/JDBC-92). - * Support oracle:oci and oracle:thin subprotocols [JDBC-90](http://dev.clojure.org/jira/browse/JDBC-90). + * Support oracle:oci and oracle:thin subprotocols [JDBC-90](http://clojure.atlassian.net/browse/JDBC-90). * Release 0.3.3 on 2014-01-30 - * Prevent exception/crash when query called with bare SQL string [JDBC-89](http://dev.clojure.org/jira/browse/JDBC-89). - * Add :row-fn and :result-set-fn to metadata-result function [JDBC-87](http://dev.clojure.org/jira/browse/JDBC-87). + * Prevent exception/crash when query called with bare SQL string [JDBC-89](http://clojure.atlassian.net/browse/JDBC-89). + * Add :row-fn and :result-set-fn to metadata-result function [JDBC-87](http://clojure.atlassian.net/browse/JDBC-87). * Support key/value configuration from URI (Phil Hagelberg). * Release 0.3.2 on 2013-12-30 @@ -335,23 +335,23 @@ Change Log * Release 0.3.1 on 2013-12-29 (broken; use 0.3.2 instead) * Improve docstrings and add :arglists for better auto-generated documentation. * Make insert-sql private - technically a breaking change but it should never have been public: sorry folks! - * Provide better protocol for setting parameters in prepared statements [JDBC-86](http://dev.clojure.org/jira/browse/JDBC-86). - * Fix parens in two deprecated tests [JDBC-85](http://dev.clojure.org/jira/browse/JDBC-85). + * Provide better protocol for setting parameters in prepared statements [JDBC-86](http://clojure.atlassian.net/browse/JDBC-86). + * Fix parens in two deprecated tests [JDBC-85](http://clojure.atlassian.net/browse/JDBC-85). * Made create-table-ddl less aggressive about applying as-sql-name so only first name in a column spec is affected. * Release 0.3.0 on 2013-12-16 - * Ensure canonical Boolean to workaround strange behavior in some JDBC drivers [JDBC-84](http://dev.clojure.org/jira/browse/JDBC-84). - * Rename recently introduced test to ensure unique names [JDBC-83](http://dev.clojure.org/jira/browse/JDBC-83). - * Rename unused arguments in protocol implementation to support Android [JDBC-82](http://dev.clojure.org/jira/browse/JDBC-82). - * Correctly handle empty param group sequence in execute! (which only seemed to affect SQLite) [JDBC-65](http://dev.clojure.org/jira/browse/JDBC-65). + * Ensure canonical Boolean to workaround strange behavior in some JDBC drivers [JDBC-84](http://clojure.atlassian.net/browse/JDBC-84). + * Rename recently introduced test to ensure unique names [JDBC-83](http://clojure.atlassian.net/browse/JDBC-83). + * Rename unused arguments in protocol implementation to support Android [JDBC-82](http://clojure.atlassian.net/browse/JDBC-82). + * Correctly handle empty param group sequence in execute! (which only seemed to affect SQLite) [JDBC-65](http://clojure.atlassian.net/browse/JDBC-65). * Release 0.3.0-rc1 on 2013-12-12 - * Deprecate db-transaction (new in 0.3.0) in favor of with-db-transaction [JDBC-81](http://dev.clojure.org/jira/browse/JDBC-81). - * Add with-db-metadata macro and metadata-result function to make it easier to work with SQL metadata [JDBC-80](http://dev.clojure.org/jira/browse/JDBC-80). - * Add with-db-connection macro to make it easier to run groups of operations against a single open connection [JDBC-79](http://dev.clojure.org/jira/browse/JDBC-79). - * Add ISQLValue protocol to make it easier to support custom SQL types for parameters in SQL statements [JDBC-77](http://dev.clojure.org/jira/browse/JDBC-77). - * Add support for :isolation in with-db-transaction [JDBC-75](http://dev.clojure.org/jira/browse/JDBC-75). - * Add :user as an alias for :username for DataSource connections [JDBC-74](http://dev.clojure.org/jira/browse/JDBC-74). + * Deprecate db-transaction (new in 0.3.0) in favor of with-db-transaction [JDBC-81](http://clojure.atlassian.net/browse/JDBC-81). + * Add with-db-metadata macro and metadata-result function to make it easier to work with SQL metadata [JDBC-80](http://clojure.atlassian.net/browse/JDBC-80). + * Add with-db-connection macro to make it easier to run groups of operations against a single open connection [JDBC-79](http://clojure.atlassian.net/browse/JDBC-79). + * Add ISQLValue protocol to make it easier to support custom SQL types for parameters in SQL statements [JDBC-77](http://clojure.atlassian.net/browse/JDBC-77). + * Add support for :isolation in with-db-transaction [JDBC-75](http://clojure.atlassian.net/browse/JDBC-75). + * Add :user as an alias for :username for DataSource connections [JDBC-74](http://clojure.atlassian.net/browse/JDBC-74). * Release 0.3.0-beta2 on 2013-11-24 * **BREAKING CHANGES!** @@ -359,40 +359,40 @@ Change Log * The older API (0.2.3) which was deprecated in earlier 0.3.0 builds has moved to `clojure.java.jdbc.deprecated` to help streamline the API for 0.3.0 and clean up the documentation. * Release 0.3.0-beta1 on 2013-11-03 - * query as-arrays? now allows you to leverage lazy result fetching [JDBC-72](http://dev.clojure.org/jira/browse/JDBC-72). + * query as-arrays? now allows you to leverage lazy result fetching [JDBC-72](http://clojure.atlassian.net/browse/JDBC-72). * "h2" is recognized as a protocol shorthand for org.h2.Driver - * Tests no longer use :1 literal [JDBC-71](http://dev.clojure.org/jira/browse/JDBC-71). - * Conditional use of javax.naming.InitialContext so it can be compiled on Android [JDBC-69](http://dev.clojure.org/jira/browse/JDBC-69). - * New db-query-with-resultset function replaces private db-with-query-results* and processes a raw ResultSet object [JDBC-63](http://dev.clojure.org/jira/browse/JDBC-63). - * Allow :set-parameters in db-spec to override set-parameters internal function to allow per-DB special handling of SQL parameters values (such as null for Teradata) [JDBC-40](http://dev.clojure.org/jira/browse/JDBC-40). + * Tests no longer use :1 literal [JDBC-71](http://clojure.atlassian.net/browse/JDBC-71). + * Conditional use of javax.naming.InitialContext so it can be compiled on Android [JDBC-69](http://clojure.atlassian.net/browse/JDBC-69). + * New db-query-with-resultset function replaces private db-with-query-results* and processes a raw ResultSet object [JDBC-63](http://clojure.atlassian.net/browse/JDBC-63). + * Allow :set-parameters in db-spec to override set-parameters internal function to allow per-DB special handling of SQL parameters values (such as null for Teradata) [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40). * Release 0.3.0-alpha5 on 2013-09-15 - * DDL now supports entities naming strategy [JDBC-53](http://dev.clojure.org/jira/browse/JDBC-53). + * DDL now supports entities naming strategy [JDBC-53](http://clojure.atlassian.net/browse/JDBC-53). * Attempt to address potential memory leaks due to closures - see [Christophe Grand's blog post on Macros, closures and unexpected object retention](http://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/). * Documentation has moved to [Using java.jdbc on Clojure-Doc.org](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) * Added Leiningen support for easier development/testing (Maven is still the primary build tool). - * Added create-index / drop-index DDL [JDBC-62](http://dev.clojure.org/jira/browse/JDBC-62) - moquist + * Added create-index / drop-index DDL [JDBC-62](http://clojure.atlassian.net/browse/JDBC-62) - moquist * Make transaction? boolean optional in various db-do-* functions * Create clojure.java.jdbc.ddl namespace * Add create-table, drop-table, create-index and drop-index * Deprecate create-table, create-table-ddl and drop-table in main namespace * Update README to clarify PostgreSQL instructions. - * Fix test suite for PostgreSQL [JDBC-59](http://dev.clojure.org/jira/browser/JDBC-59) - * Improve hooks for Oracle data type handling [JDBC-57](http://dev.clojure.org/jira/browser/JDBC-57) - * Fix reflection warnings [JDBC-55](http://dev.clojure.org/jira/browser/JDBC-55) + * Fix test suite for PostgreSQL [JDBC-59](http://clojure.atlassian.net/browser/JDBC-59) + * Improve hooks for Oracle data type handling [JDBC-57](http://clojure.atlassian.net/browser/JDBC-57) + * Fix reflection warnings [JDBC-55](http://clojure.atlassian.net/browser/JDBC-55) * Release 0.3.0-alpha4 on 2013-05-11 - * Fix connection leaks [JDBC-54](http://dev.clojure.org/jira/browser/JDBC-54) + * Fix connection leaks [JDBC-54](http://clojure.atlassian.net/browser/JDBC-54) * Allow order-by to accept empty sequence (and return empty string) * Release 0.3.0-alpha3 on 2013-05-04 * Fix macro / import interaction by fully qualifying Connection type. * Release 0.3.0-alpha2 on 2013-05-03 - * Address [JDBC-51](http://dev.clojure.org/jira/browse/JDBC-51) by declaring get-connection returns java.sql.Connection - * Add IResultSetReadColumn protocol extension point for custom read conversions [JDBC-46](http://dev.clojure.org/jira/browse/JDBC-46) - * Add :multi? to execute! so it can be used for repeated operations [JDBC-52](http://dev.clojure.org/jira/browse/JDBC-52) - * Reverted specialized handling of NULL values (reopens [JDBC-40](http://dev.clojure.org/jira/browse/JDBC-40)) + * Address [JDBC-51](http://clojure.atlassian.net/browse/JDBC-51) by declaring get-connection returns java.sql.Connection + * Add IResultSetReadColumn protocol extension point for custom read conversions [JDBC-46](http://clojure.atlassian.net/browse/JDBC-46) + * Add :multi? to execute! so it can be used for repeated operations [JDBC-52](http://clojure.atlassian.net/browse/JDBC-52) + * Reverted specialized handling of NULL values (reopens [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40)) * Rename :as-arrays to :as-arrays? since it is boolean * Add curried version of clojure.java.jdbc.sql/as-quoted-str * Officially deprecate resultset-seq @@ -401,36 +401,36 @@ Change Log * MAJOR API OVERHAUL! * Most of the old 0.2.x API has been deprecated and a new, more idiomatic API introduced, along with a minimal DSL to generate basic SQL * Specifics: - * Add insert!, query, update!, delete! and execute! high-level API [JDBC-20](http://dev.clojure.org/jira/browse/JDBC-20) + * Add insert!, query, update!, delete! and execute! high-level API [JDBC-20](http://clojure.atlassian.net/browse/JDBC-20) * Add optional SQL-generating DSL in clojure.java.jdbc.sql (implied by JDBC-20) * Add db- prefixed versions of low-level API * Add db-transaction macro * Add result-set-seq as replacement for resultset-seq (which will be deprecated) - * Transaction now correctly rollback on non-Exception Throwables [JDBC-43](http://dev.clojure.org/jira/browse/JDBC-43) - * Rewrite old API functions in terms of new API, and deprecate old API [JDBC-43](http://dev.clojure.org/jira/browse/JDBC-43) - * Add :as-arrays to query / result-set-seq [JDBC-41](http://dev.clojure.org/jira/browse/JDBC-41) - * Better handling of NULL values [JDBC-40](http://dev.clojure.org/jira/browse/JDBC-40) and [JDBC-18](http://dev.clojure.org/jira/browse/JDBC-18) + * Transaction now correctly rollback on non-Exception Throwables [JDBC-43](http://clojure.atlassian.net/browse/JDBC-43) + * Rewrite old API functions in terms of new API, and deprecate old API [JDBC-43](http://clojure.atlassian.net/browse/JDBC-43) + * Add :as-arrays to query / result-set-seq [JDBC-41](http://clojure.atlassian.net/browse/JDBC-41) + * Better handling of NULL values [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40) and [JDBC-18](http://clojure.atlassian.net/browse/JDBC-18) Note: JDBC-40 is being reverted in 0.3.0-alpha2 because it introduces regressions in PostgreSQL - * db-do-commands allows you to execute SQL without a transaction wrapping it [JDBC-38](http://dev.clojure.org/jira/browse/JDBC-38) + * db-do-commands allows you to execute SQL without a transaction wrapping it [JDBC-38](http://clojure.atlassian.net/browse/JDBC-38) * Remove reflection warning from execute-batch * Add notes to README about 3rd party database driver dependencies * Add optional :identifiers argument to resultset-seq so you can explicitly pass in the naming strategy * Release 0.2.3 on 2012-06-18 * as-str now treats a.b as two identifiers separated by . so quoting produces [a].[b] instead of [a.b] - * Add :connection-uri option [JDBC-34](http://dev.clojure.org/jira/browse/JDBC-34) + * Add :connection-uri option [JDBC-34](http://clojure.atlassian.net/browse/JDBC-34) * Release 0.2.2 on 2012-06-10 - * Handle Oracle unknown row count affected [JDBC-33](http://dev.clojure.org/jira/browse/JDBC-33) - * Handle jdbc: prefix in string db-specs [JDBC-32](http://dev.clojure.org/jira/browse/JDBC-32) - * Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](http://dev.clojure.org/jira/browse/JDBC-31) + * Handle Oracle unknown row count affected [JDBC-33](http://clojure.atlassian.net/browse/JDBC-33) + * Handle jdbc: prefix in string db-specs [JDBC-32](http://clojure.atlassian.net/browse/JDBC-32) + * Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](http://clojure.atlassian.net/browse/JDBC-31) * Release 0.2.1 on 2012-05-10 - * Result set performance enhancement (Juergen Hoetzel) [JDBC-29](http://dev.clojure.org/jira/browse/JDBC-29) - * Make do-prepared-return-keys (for Korma team) [JDBC-30](http://dev.clojure.org/jira/browse/JDBC-30) + * Result set performance enhancement (Juergen Hoetzel) [JDBC-29](http://clojure.atlassian.net/browse/JDBC-29) + * Make do-prepared-return-keys (for Korma team) [JDBC-30](http://clojure.atlassian.net/browse/JDBC-30) * Release 0.2.0 on 2012-04-23 - * Merge internal namespace into main jdbc namespace [JDBC-19](http://dev.clojure.org/jira/browse/JDBC-19) + * Merge internal namespace into main jdbc namespace [JDBC-19](http://clojure.atlassian.net/browse/JDBC-19) * Release 0.1.4 on 2012-04-15 * Unwrap RTE for nested transaction exceptions (we already @@ -438,26 +438,26 @@ Change Log * Remove reflection warning unwrapping RunTimeException (Alan Malloy) * Release 0.1.3 on 2012-02-29 - * Fix generated keys inside transactions for SQLite3 [JDBC-26](http://dev.clojure.org/jira/browse/JDBC-26) + * Fix generated keys inside transactions for SQLite3 [JDBC-26](http://clojure.atlassian.net/browse/JDBC-26) * Release 0.1.2 on 2012-02-29 - * Handle prepared statement params correctly [JDBC-23](http://dev.clojure.org/jira/browse/JDBC-23) - * Add support for SQLite3 [JDBC-26](http://dev.clojure.org/jira/browse/JDBC-26) - * Replace replicate (deprecated) with repeat [JDBC-27](http://dev.clojure.org/jira/browse/JDBC-27) + * Handle prepared statement params correctly [JDBC-23](http://clojure.atlassian.net/browse/JDBC-23) + * Add support for SQLite3 [JDBC-26](http://clojure.atlassian.net/browse/JDBC-26) + * Replace replicate (deprecated) with repeat [JDBC-27](http://clojure.atlassian.net/browse/JDBC-27) * Ensure MS SQL Server passes tests with both Microsoft and jTDS drivers * Build server now tests derby, hsqldb and sqlite by default * Update README per Stuart Sierra's outline for contrib projects * Release 0.1.1 on 2011-11-02 - * Accept string or URI in connection definition [JDBC-21](http://dev.clojure.org/jira/browse/JDBC-21) - * Allow driver, port and subprotocol to be deduced [JDBC-22](http://dev.clojure.org/jira/browse/JDBC-22) + * Accept string or URI in connection definition [JDBC-21](http://clojure.atlassian.net/browse/JDBC-21) + * Allow driver, port and subprotocol to be deduced [JDBC-22](http://clojure.atlassian.net/browse/JDBC-22) * Release 0.1.0 on 2011-10-16 - * Remove dependence on deprecated structmap [JDBC-15](http://dev.clojure.org/jira/browse/JDBC-15) + * Remove dependence on deprecated structmap [JDBC-15](http://clojure.atlassian.net/browse/JDBC-15) * Release 0.0.7 on 2011-10-11 - * Rename duplicate columns [JDBC-9](http://dev.clojure.org/jira/browse/JDBC-9) - * Ensure do-preared traps invalid SQL [JDBC-16](http://dev.clojure.org/jira/browse/JDBC-16) + * Rename duplicate columns [JDBC-9](http://clojure.atlassian.net/browse/JDBC-9) + * Ensure do-preared traps invalid SQL [JDBC-16](http://clojure.atlassian.net/browse/JDBC-16) * Release 0.0.6 on 2011-08-04 * Improve exception handling (unwrap RTE) @@ -475,10 +475,10 @@ Change Log * Ensure transactions are not committed when Error occurs [JDBC-11](http://dev.clojure.org/jire/JDBC-11) * Release 0.0.3 on 2011-07-01 - * Key generation compatibility with MS SQL Server, PostgreSQL [JDBC-10](http://dev.clojure.org/jira/browse/JDBC-10) + * Key generation compatibility with MS SQL Server, PostgreSQL [JDBC-10](http://clojure.atlassian.net/browse/JDBC-10) * Release 0.0.2 on 2011-06-07 - * Clojure 1.2 compatibility [JDBC-7](http://dev.clojure.org/jira/browse/JDBC-7) + * Clojure 1.2 compatibility [JDBC-7](http://clojure.atlassian.net/browse/JDBC-7) * Release 0.0.1 on 2011-05-07 * Initial release From d0ecdb427badc3971e2a8d03ba93bd426c1c7ee9 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 26 May 2019 16:40:59 -0700 Subject: [PATCH 135/175] Add note about next.jdbc --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5a6e721c..f1e0730c 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ For higher level DSLs and migration libraries that are compatible, see the [docu Formerly known as `clojure.contrib.sql`. +This library is mature and stable. It is widely used and its use is described in many books and tutorials. It will continue to get bug fixes and minor releases. Based on my experience using and maintaining this library, I've created a faster, more modern JDBC wrapper called [next.jdbc](https://github.com/seancorfield/next-jdbc). I consider it to be the "next generation" of `clojure.java.jdbc` but it exposes a different API -- a better API, I think. + Documentation ======================================== * [API Reference](http://clojure.github.com/java.jdbc/) (Autogenerated) From 1f1ff8144b4a9a4447b2a2fbb9e592f90ccd2659 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 8 Aug 2019 17:08:41 -0700 Subject: [PATCH 136/175] Address Turkish lower-case issue --- CHANGES.md | 1 + src/main/clojure/clojure/java/jdbc.clj | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0cc5737a..efab13da 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ Changes coming in 0.7.10 +* Use a US-locale `lower-case` function to avoid problems in certain locales (e.g., Turkish). A similar issue has been fixed recently in both HoneySQL and `next.jdbc`. * Clean up `db-spec` options that are passed to the JDBC connection manager as properties [JDBC-178](https://clojure.atlassian.net/browse/JDBC-178). * Relax restriction on `create-table-ddl` column specs to allow numbers (as well as keywords and strings) [JDBC-177](https://clojure.atlassian.net/browse/JDBC-177). diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 7193c18e..477996a7 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -45,7 +45,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (java.sql BatchUpdateException DriverManager PreparedStatement ResultSet ResultSetMetaData SQLException Statement Types) - (java.util Hashtable Map Properties) + (java.util Hashtable Locale Map Properties) (javax.sql DataSource))) (set! *warn-on-reflection* true) @@ -535,6 +535,13 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} :else identifiers)) +(defn- lower-case + "Converts a string to lower case in the US locale to avoid problems in + locales where the lower case version of a character is not a valid SQL + entity name (e.g., Turkish)." + [^String s] + (.toLowerCase s (Locale/US))) + (defn result-set-seq "Creates and returns a lazy sequence of maps corresponding to the rows in the java.sql.ResultSet rs. Loosely based on clojure.core/resultset-seq but it @@ -551,7 +558,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} ([rs] (result-set-seq rs {})) ([^ResultSet rs {:keys [as-arrays? identifiers keywordize? qualifier read-columns] - :or {identifiers str/lower-case + :or {identifiers lower-case keywordize? true read-columns dft-read-columns}}] (let [rsmeta (.getMetaData rs) @@ -1213,7 +1220,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} Compiled with Clojure 1.7 or later -- uses clojure.lang.IReduce Note: :as-arrays? is not accepted here." [^ResultSet rs {:keys [identifiers keywordize? qualifier read-columns] - :or {identifiers str/lower-case + :or {identifiers lower-case keywordize? true read-columns dft-read-columns}}] (let [rsmeta (.getMetaData rs) @@ -1312,7 +1319,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} ([db sql-params] (reducible-query db sql-params {})) ([db sql-params opts] (let [{:keys [identifiers keywordize? qualifier read-columns] :as opts} - (merge {:identifiers str/lower-case :keywordize? true + (merge {:identifiers lower-case :keywordize? true :read-columns dft-read-columns} (when (map? db) db) opts) @@ -1551,7 +1558,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} map, insert the rows into the database." [db table rows opts] (let [{:keys [entities transaction?] :as opts} - (merge {:entities identity :identifiers str/lower-case + (merge {:entities identity :identifiers lower-case :keywordize? true :transaction? true} (when (map? db) db) opts) From da5fd9d432d35c6a1d7fc7c872b1bcdd6e9a6a6c Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 12 Aug 2019 10:43:15 -0700 Subject: [PATCH 137/175] Update test.check to 0.10.0 --- deps.edn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps.edn b/deps.edn index b09bc738..540e15d9 100644 --- a/deps.edn +++ b/deps.edn @@ -5,7 +5,7 @@ {:paths ["src/main/clojure"] :aliases {:test {:extra-paths ["src/test/clojure"] - :extra-deps {org.clojure/test.check {:mvn/version "0.9.0"} + :extra-deps {org.clojure/test.check {:mvn/version "0.10.0"} org.apache.derby/derby {:mvn/version "10.14.2.0"} org.hsqldb/hsqldb {:mvn/version "2.4.1"} com.h2database/h2 {:mvn/version "1.4.197"} From 19b1e6dc6f0f4e5c6cb2a7918f73283628bf666b Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 24 Aug 2019 11:29:38 -0700 Subject: [PATCH 138/175] Prep for 0.7.10 release --- CHANGES.md | 2 +- README.md | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index efab13da..9f5519cc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes coming in 0.7.10 +Changes in 0.7.10 * Use a US-locale `lower-case` function to avoid problems in certain locales (e.g., Turkish). A similar issue has been fixed recently in both HoneySQL and `next.jdbc`. * Clean up `db-spec` options that are passed to the JDBC connection manager as properties [JDBC-178](https://clojure.atlassian.net/browse/JDBC-178). diff --git a/README.md b/README.md index f1e0730c..60ec92b3 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Support Releases and Dependency Information ======================================== -Latest stable release: 0.7.9 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.10 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -34,14 +34,14 @@ Latest stable release: 0.7.9 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.9"] +[org.clojure/java.jdbc "0.7.10"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.9 + 0.7.10 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -140,6 +140,11 @@ Developer Information Change Log ==================== +* Release 0.7.10 on 2019-08-24 + * Use a US-locale `lower-case` function to avoid problems in certain locales (e.g., Turkish). A similar issue has been fixed recently in both HoneySQL and `next.jdbc`. + * Clean up `db-spec` options that are passed to the JDBC connection manager as properties [JDBC-178](https://clojure.atlassian.net/browse/JDBC-178). + * Relax restriction on `create-table-ddl` column specs to allow numbers (as well as keywords and strings) [JDBC-177](https://clojure.atlassian.net/browse/JDBC-177). + * Release 0.7.9 on 2019-02-21 * Fix behavior of multi-inserts when database does not support generated keys [JDBC-176](https://clojure.atlassian.net/browse/JDBC-176). * Added _highly experimental_ support for `datafy`/`nav` (in `clojure.java.jdbc.datafy` namespace). This includes a convention-based approach to foreign keys with some assistance from a `:schema` option. This is subject to change and is provided mostly for informational purposes, as an example of the new functionality in Clojure 1.10. This includes a fix for the conventions from [JDBC-175](https://clojure.atlassian.net/browse/JDBC-175). From 5344e1551bffbe8d07810bd4bb06757f41d31a5b Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Sat, 24 Aug 2019 13:31:25 -0500 Subject: [PATCH 139/175] [maven-release-plugin] prepare release java.jdbc-0.7.10 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2e00343f..71db0892 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.10-SNAPSHOT + 0.7.10 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.10 From 71a85763eb2f5cfaae17d5bdf1aca05e5220fb24 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Sat, 24 Aug 2019 13:31:25 -0500 Subject: [PATCH 140/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 71db0892..7313c02d 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.10 + 0.7.11-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.10 + HEAD From 7c382525cd15fd04d706c285b756e502d53d5212 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 6 Sep 2019 01:24:04 -0700 Subject: [PATCH 141/175] Note Stable status Also note that it is superseded by `next.jdbc`. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 60ec92b3..b5ab870f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ clojure.java.jdbc ======================================== -A low-level Clojure wrapper for JDBC-based access to databases. +A low-level Clojure wrapper for JDBC-based access to databases. This project is "Stable" (no longer "Active"). It has effectively been superseded by [seancorfield/next.jdbc](https://github.com/seancorfield/next-jdbc). For higher level DSLs and migration libraries that are compatible, see the [documentation](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html). From 0cc48ceb0ad3a5064a8baabd063b3cab4a643563 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 10 Oct 2019 17:51:32 -0700 Subject: [PATCH 142/175] Add test for reported slow prepared statement code with PostgreSQL --- src/test/clojure/clojure/java/jdbc_test.clj | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index f01de14c..94412c44 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -1254,6 +1254,32 @@ {:id (generated-key db 3) :name "Orange" :appearance "round" :cost nil :grade nil} {:id (generated-key db 2) :name "Pear" :appearance "yellow" :cost nil :grade nil}] rows))))) +(comment + (def ones (repeat 100 1)) + (def select-1 (str "select " (str/join ", " ones))) + (def select-? (into [(str "select " (str/join ", " (map (fn [_] "?") ones)))] ones)) + (time (jdbc/execute-one! (-> a :database :pooled-db :datasource) [select-1])) + (time (jdbc/execute-one! (-> a :database :pooled-db :datasource) select-?)) + (require '[clojure.java.jdbc :as j]) + (time (j/query (-> a :database :pooled-db) [select-1])) + (time (j/query (-> a :database :pooled-db) select-?))) + +(deftest check-prepared-performance + (let [ones (repeat 100 1) + select-1 [(str "select " (str/join ", " ones))] + select-? (into [(str "select " + (str/join ", " (map (fn [_] "?") ones)))] + ones)] + (println "\nSanity check on prepared statement parameter performance.") + (doseq [db (test-specs) + :when (not (or (derby? db) (hsqldb? db)))] + (println " " db) + (time (dotimes [n 100] (sql/query db select-1))) + (time (dotimes [n 100] (sql/query db select-?))) + (sql/with-db-connection [con db] + (time (dotimes [n 100] (sql/query con select-1))) + (time (dotimes [n 100] (sql/query con select-?))))))) + (deftest test-create-table-ddl (is (re-find #"`foo` int default 0" (sql/create-table-ddl :table From 47a87036376ffa69f64cfa18d1f91cbb7e301199 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 10 Oct 2019 17:53:00 -0700 Subject: [PATCH 143/175] Remove Rich Comment Form now it's a real test --- src/test/clojure/clojure/java/jdbc_test.clj | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index 94412c44..80800afe 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -1254,16 +1254,6 @@ {:id (generated-key db 3) :name "Orange" :appearance "round" :cost nil :grade nil} {:id (generated-key db 2) :name "Pear" :appearance "yellow" :cost nil :grade nil}] rows))))) -(comment - (def ones (repeat 100 1)) - (def select-1 (str "select " (str/join ", " ones))) - (def select-? (into [(str "select " (str/join ", " (map (fn [_] "?") ones)))] ones)) - (time (jdbc/execute-one! (-> a :database :pooled-db :datasource) [select-1])) - (time (jdbc/execute-one! (-> a :database :pooled-db :datasource) select-?)) - (require '[clojure.java.jdbc :as j]) - (time (j/query (-> a :database :pooled-db) [select-1])) - (time (j/query (-> a :database :pooled-db) select-?))) - (deftest check-prepared-performance (let [ones (repeat 100 1) select-1 [(str "select " (str/join ", " ones))] From 2acbf2736a30634c9c75e6a1853e970ffa6bdbb4 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Tue, 24 Dec 2019 17:40:37 -0800 Subject: [PATCH 144/175] Fix JDBC-179 by only resetting auto-commit conditionally --- CHANGES.md | 4 ++++ README.md | 11 +++++++---- src/main/clojure/clojure/java/jdbc.clj | 23 +++++++++++++++++------ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9f5519cc..443e3252 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes in 0.7.11 + +* Address edge case in transaction rollback failure [JDBC-179](https://clojure.atlassian.net/browse/JDBC-179). + Changes in 0.7.10 * Use a US-locale `lower-case` function to avoid problems in certain locales (e.g., Turkish). A similar issue has been fixed recently in both HoneySQL and `next.jdbc`. diff --git a/README.md b/README.md index b5ab870f..2b8a38fe 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ clojure.java.jdbc ======================================== -A low-level Clojure wrapper for JDBC-based access to databases. This project is "Stable" (no longer "Active"). It has effectively been superseded by [seancorfield/next.jdbc](https://github.com/seancorfield/next-jdbc). +A low-level Clojure wrapper for JDBC-based access to databases. This project is "Stable" (no longer "Active"). It has effectively been superseded by [seancorfield/next.jdbc](https://github.com/seancorfield/next-jdbc). For higher level DSLs and migration libraries that are compatible, see the [documentation](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html). @@ -26,7 +26,7 @@ Support Releases and Dependency Information ======================================== -Latest stable release: 0.7.10 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.11 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -34,14 +34,14 @@ Latest stable release: 0.7.10 -- requires Clojure 1.7 or later! [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.10"] +[org.clojure/java.jdbc "0.7.11"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.10 + 0.7.11 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -140,6 +140,9 @@ Developer Information Change Log ==================== +* Release 0.7.11 on 2019-12-24 + * Address edge case in transaction rollback failure [JDBC-179](https://clojure.atlassian.net/browse/JDBC-179). + * Release 0.7.10 on 2019-08-24 * Use a US-locale `lower-case` function to avoid problems in certain locales (e.g., Turkish). A similar issue has been fixed recently in both HoneySQL and `next.jdbc`. * Clean up `db-spec` options that are passed to the JDBC connection manager as properties [JDBC-178](https://clojure.atlassian.net/browse/JDBC-178). diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 477996a7..5b8fb94b 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -793,9 +793,10 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (if (zero? (get-level db)) (if-let [con (db-find-connection db)] (let [nested-db (inc-level db) - auto-commit (.getAutoCommit con) + auto-commit (.getAutoCommit con) old-isolation (.getTransactionIsolation con) - old-readonly (.isReadOnly con)] + old-readonly (.isReadOnly con) + restore-ac? (volatile! true)] (io! (when isolation (.setTransactionIsolation con (isolation isolation-levels))) @@ -805,12 +806,21 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (try (let [result (func nested-db)] (if (db-is-rollback-only nested-db) - (.rollback con) + (do + ;; per JDBC-179: if the rollback operation fails, we do not + ;; want to try to restore auto-commit... + (vreset! restore-ac? false) + (.rollback con) + (vreset! restore-ac? true)) (.commit con)) result) (catch Throwable t (try + ;; per JDBC-179: if the rollback operation fails, we do not + ;; want to try to restore auto-commit... + (vreset! restore-ac? false) (.rollback con) + (vreset! restore-ac? true) (catch Throwable rb ;; combine both exceptions (throw (ex-info (str "Rollback failed handling \"" @@ -825,9 +835,10 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} ;; want those to replace any exception currently being ;; handled -- and if the connection got closed, we just ;; want to ignore exceptions here anyway - (try - (.setAutoCommit con auto-commit) - (catch Exception _)) + (when @restore-ac? + (try ; only restore auto-commit if rollback did not fail + (.setAutoCommit con auto-commit) + (catch Exception _))) (when isolation (try (.setTransactionIsolation con old-isolation) From 36159f259cc51a1dcb9b64ea08f02f0e98c22b5e Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 24 Dec 2019 19:42:05 -0600 Subject: [PATCH 145/175] [maven-release-plugin] prepare release java.jdbc-0.7.11 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7313c02d..65303d76 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.11-SNAPSHOT + 0.7.11 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.11 From 54d2339e16066aed84319c0fb5111f4b2ff22611 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Tue, 24 Dec 2019 19:42:05 -0600 Subject: [PATCH 146/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 65303d76..4d3ac482 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.11 + 0.7.12-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.11 + HEAD From c5cf6719411683b86d310010a1eeca4fcc337314 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 18 Jun 2020 13:25:24 -0500 Subject: [PATCH 147/175] add LICENSE text file --- LICENSE | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..e246f6a2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,205 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' + from a Contributor if it was added to the Program by such Contributor + itself or anyone acting on such Contributor's behalf. Contributions do not + include additions to the Program which: (i) are separate modules of + software distributed in conjunction with the Program under their own + license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly + perform, distribute and sublicense the Contribution of such Contributor, + if any, and such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of + the Contribution and the Program if, at the time the Contribution is + added by the Contributor, such addition of the Contribution causes such + combination to be covered by the Licensed Patents. The patent license + shall not apply to any other combinations which include the Contribution. + No hardware per se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses + to its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other + entity based on infringement of intellectual property rights or + otherwise. As a condition to exercising the rights and licenses granted + hereunder, each Recipient hereby assumes sole responsibility to secure + any other intellectual property rights needed, if any. For example, if a + third party patent license is required to allow Recipient to distribute + the Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained + within the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, +if a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits and +other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such Commercial +Contributor in connection with its distribution of the Program in a commercial +product offering. The obligations in this section do not apply to any claims +or Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: +a) promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such claim at +its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to the +risks and costs of program errors, compliance with applicable laws, damage to +or loss of data, programs or equipment, and unavailability or interruption of +operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of the +Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in the +Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. + + From c99db253ea85f2ade4b71eff20476f6c6061d571 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Tue, 1 Sep 2020 13:10:26 -0700 Subject: [PATCH 148/175] Add deps.edn style coordinates --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 2b8a38fe..35909d3f 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,10 @@ Latest stable release: 0.7.11 -- requires Clojure 1.7 or later! * [Development Snapshot Versions](https://oss.sonatype.org/index.html#nexus-search;gav~org.clojure~java.jdbc~~~) +[CLI/`deps.edn`](https://clojure.org/guides/deps_and_cli) dependency information: +```clojure +org.clojure/java.jdbc {:mvn/version "0.7.11"} +``` [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure [org.clojure/java.jdbc "0.7.11"] From 7c2cda1d8510e76408dea9a4b047233034e1d066 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Tue, 1 Sep 2020 13:19:54 -0700 Subject: [PATCH 149/175] Link to CLI/deps.edn reference --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35909d3f..cdb2ef78 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Latest stable release: 0.7.11 -- requires Clojure 1.7 or later! * [Development Snapshot Versions](https://oss.sonatype.org/index.html#nexus-search;gav~org.clojure~java.jdbc~~~) -[CLI/`deps.edn`](https://clojure.org/guides/deps_and_cli) dependency information: +[CLI/`deps.edn`](https://clojure.org/reference/deps_and_cli) dependency information: ```clojure org.clojure/java.jdbc {:mvn/version "0.7.11"} ``` From 21512f86a1abd0a42e2174b7239329af6e3c9d9e Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 1 Feb 2021 09:45:25 -0800 Subject: [PATCH 150/175] Prep for 0.7.12 with metadata-extensible protocols --- CHANGES.md | 4 ++++ README.md | 11 +++++++---- src/main/clojure/clojure/java/jdbc.clj | 6 +++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 443e3252..b0d25b80 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes in 0.7.12 + +* Make the protocols `ISQLValue`, `ISQLParameter`, and `IResultSetReadColumn` extensible via metadata. + Changes in 0.7.11 * Address edge case in transaction rollback failure [JDBC-179](https://clojure.atlassian.net/browse/JDBC-179). diff --git a/README.md b/README.md index cdb2ef78..52ea9bfa 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Support Releases and Dependency Information ======================================== -Latest stable release: 0.7.11 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.12 -- requires Clojure 1.7 or later! * [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) @@ -34,18 +34,18 @@ Latest stable release: 0.7.11 -- requires Clojure 1.7 or later! [CLI/`deps.edn`](https://clojure.org/reference/deps_and_cli) dependency information: ```clojure -org.clojure/java.jdbc {:mvn/version "0.7.11"} +org.clojure/java.jdbc {:mvn/version "0.7.12"} ``` [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.11"] +[org.clojure/java.jdbc "0.7.12"] ``` [Maven](http://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.11 + 0.7.12 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -144,6 +144,9 @@ Developer Information Change Log ==================== +* Release 0.7.12 on 2021-02-01 + * Make the protocols `ISQLValue`, `ISQLParameter`, and `IResultSetReadColumn` extensible via metadata. + * Release 0.7.11 on 2019-12-24 * Address edge case in transaction rollback failure [JDBC-179](https://clojure.atlassian.net/browse/JDBC-179). diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index 5b8fb94b..e90c8850 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -454,7 +454,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (vswap! seen assoc input 2) (xf result input))))))) -(defprotocol ISQLValue +(defprotocol ISQLValue :extend-via-metadata true "Protocol for creating SQL values from Clojure values. Default implementations (for Object and nil) just return the argument, but it can be extended to provide custom behavior to support @@ -468,7 +468,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} nil (sql-value [_] nil)) -(defprotocol ISQLParameter +(defprotocol ISQLParameter :extend-via-metadata true "Protocol for setting SQL parameters in statement objects, which can convert from Clojure values. The default implementation just delegates the conversion to ISQLValue's sql-value conversion and @@ -495,7 +495,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (set-parameter (first values) stmt ix) (recur (inc ix) (rest values))))) -(defprotocol IResultSetReadColumn +(defprotocol IResultSetReadColumn :extend-via-metadata true "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, From ccf6236c2a1905cd8e4a3f7edffcf3dd18ccce12 Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 1 Feb 2021 22:02:37 -0600 Subject: [PATCH 151/175] [maven-release-plugin] prepare release java.jdbc-0.7.12 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4d3ac482..f4049449 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.12-SNAPSHOT + 0.7.12 java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - HEAD + java.jdbc-0.7.12 From 51b1870ff7f236d47b63bca9e2e3b1b14d0128db Mon Sep 17 00:00:00 2001 From: "Hudson @ build.clojure.org" Date: Mon, 1 Feb 2021 22:02:37 -0600 Subject: [PATCH 152/175] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f4049449..7dd53757 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 java.jdbc - 0.7.12 + 0.7.13-SNAPSHOT java.jdbc @@ -24,7 +24,7 @@ scm:git:git@github.com:clojure/java.jdbc.git scm:git:git@github.com:clojure/java.jdbc.git git@github.com:clojure/java.jdbc.git - java.jdbc-0.7.12 + HEAD From acffd9f5f216f8b8c1fc960c1d47b0b5feb56730 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 18 Feb 2021 09:31:27 -0600 Subject: [PATCH 153/175] update old links --- CHANGES.md | 214 +++++++++++++++++++++---------------------- README.md | 263 ++++++++++++++++++++++++++--------------------------- 2 files changed, 236 insertions(+), 241 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b0d25b80..4a6aba18 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -101,30 +101,30 @@ Changes in 0.7.0-beta1 Changes in 0.7.0-alpha3 -* `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](http://clojure.atlassian.net/browse/JDBC-151). +* `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](https://clojure.atlassian.net/browse/JDBC-151). * `redshift` has been added as a `dbtype` with `com.amazon.redshift.jdbc.Driver` as the driver name. Changes in 0.7.0-alpha2 -* `pgsql` and the Impossibl PostgresSQL 'NG' driver are now supported (note that `:max-rows` does not work with this driver!); also, providing unknown `dbtype` or `subprotocol` in a `db-spec` should now throw a better exception [JDBC-150](http://clojure.atlassian.net/browse/JDBC-150). -* `quoted` now accepts keywords for database / dialect (`:ansi` (including PostgresSQL), `:mysql`, `:oracle`, `:sqlserver` -- these match the keywords used in HoneySQL which is the recommended third party SQL DSL for java.jdbc) [JDBC-149](http://clojure.atlassian.net/browse/JDBC-149). -* Reorder `get-connection` clauses to make it easier to combine keys in a `db-spec` [JDBC-148](http://clojure.atlassian.net/browse/JDBC-148). -* Force load `DriverManager` before `classForName` call on drivers to avoid potential race condition on initialization [JDBC-145](http://clojure.atlassian.net/browse/JDBC-145). +* `pgsql` and the Impossibl PostgresSQL 'NG' driver are now supported (note that `:max-rows` does not work with this driver!); also, providing unknown `dbtype` or `subprotocol` in a `db-spec` should now throw a better exception [JDBC-150](https://clojure.atlassian.net/browse/JDBC-150). +* `quoted` now accepts keywords for database / dialect (`:ansi` (including PostgresSQL), `:mysql`, `:oracle`, `:sqlserver` -- these match the keywords used in HoneySQL which is the recommended third party SQL DSL for java.jdbc) [JDBC-149](https://clojure.atlassian.net/browse/JDBC-149). +* Reorder `get-connection` clauses to make it easier to combine keys in a `db-spec` [JDBC-148](https://clojure.atlassian.net/browse/JDBC-148). +* Force load `DriverManager` before `classForName` call on drivers to avoid potential race condition on initialization [JDBC-145](https://clojure.atlassian.net/browse/JDBC-145). Changes in 0.7.0-alpha1 -- potentially breaking changes * The signatures of `as-sql-name` and `quoted` have changed slightly: the former no longer has the curried (single argument) version, and the latter no longer has the two argument version. This change came out of a discussion on Slack which indicated curried functions are non-idiomatic. If you relied on the curried version of `as-sql-name`, you will not need to use `partial`. If you relied on the two argument version of `quoted`, you will need to add an extra `( )` for the one argument call. I'd be fairly surprised if anyone is using `as-sql-name` at all since it is really an implementation detail. I'd also be surprised if anyone was using the two argument version of `quoted` since the natural usage is `:entities (quoted [\[ \]])` to create a naming strategy (that provides SQL entity quoting). -* Clarified that `insert-multi!` with a sequence of row maps may be substantially slower than with a sequence of row value vectors (the former performs an insert for each row, the latter performs a single insert for all the data together) [JDBC-147](http://clojure.atlassian.net/browse/JDBC-147). -* All options are passed through all function calls, expanding the range of options you can pass into high-level functions such as `insert!` and `update!` [JDBC-144](http://clojure.atlassian.net/browse/JDBC-144). -* Added `get-isolation-level` to return the current transaction's isolation level, if any [JDBC-141](http://clojure.atlassian.net/browse/JDBC-141). -* Added support for `read-columns` option to allow more flexible customization of reading column values from a result set (particularly in a multi-database application). Also expands `set-parameters` support to options (previously it was just part of the db-spec) [JDBC-137](http://clojure.atlassian.net/browse/JDBC-137). +* Clarified that `insert-multi!` with a sequence of row maps may be substantially slower than with a sequence of row value vectors (the former performs an insert for each row, the latter performs a single insert for all the data together) [JDBC-147](https://clojure.atlassian.net/browse/JDBC-147). +* All options are passed through all function calls, expanding the range of options you can pass into high-level functions such as `insert!` and `update!` [JDBC-144](https://clojure.atlassian.net/browse/JDBC-144). +* Added `get-isolation-level` to return the current transaction's isolation level, if any [JDBC-141](https://clojure.atlassian.net/browse/JDBC-141). +* Added support for `read-columns` option to allow more flexible customization of reading column values from a result set (particularly in a multi-database application). Also expands `set-parameters` support to options (previously it was just part of the db-spec) [JDBC-137](https://clojure.atlassian.net/browse/JDBC-137). * Expanded optional `clojure.spec` coverage to almost the whole library API. Changes in 0.6.2-alpha3 -* Fixed bad interaction between `:qualifier` and existing `:identifiers` functionality [JDBC-140](http://clojure.atlassian.net/browse/JDBC-140). -* Updated the README and docstrings to reflect that `:dbtype` is the easiest / preferred way to write `db-spec` maps [JDBC-139](http://clojure.atlassian.net/browse/JDBC-139). -* Fixed postgres / postgresql alias support [JDBC-138](http://clojure.atlassian.net/browse/JDBC-138). +* Fixed bad interaction between `:qualifier` and existing `:identifiers` functionality [JDBC-140](https://clojure.atlassian.net/browse/JDBC-140). +* Updated the README and docstrings to reflect that `:dbtype` is the easiest / preferred way to write `db-spec` maps [JDBC-139](https://clojure.atlassian.net/browse/JDBC-139). +* Fixed postgres / postgresql alias support [JDBC-138](https://clojure.atlassian.net/browse/JDBC-138). This also adds aliases for mssql (sqlserver), jtds (jtds:sqlserver), oracle (oracle:thin), and hsql (hsqldb). Changes in 0.6.2-alpha2 @@ -134,46 +134,46 @@ Changes in 0.6.2-alpha2 Changes in 0.6.2-alpha1 * Experimental support for `clojure.spec` via the new `clojure.java.jdbc.spec` namespace. Requires Clojure 1.9.0 Alpha 8 (or later). -* All options to all functions can now have defaults within the `db-spec` itself [JDBC-136](http://clojure.atlassian.net/browse/JDBC-136). -* `query` (and by extension `find-by-keys` and `get-by-id`) now support `:explain?` and `:explain-fn` options to help support basic performance analysis [JDBC-135](http://clojure.atlassian.net/browse/JDBC-135). -* `insert!` and `insert-multi!` now respect `:identifiers` and `:qualifier` because inserting rows on PostgreSQL returns full rows, not just the newly inserted keys [JDBC-134](http://clojure.atlassian.net/browse/JDBC-134). -* In addition to the `:identifiers` option, you can now use `:qualifier` to specify a namespace qualifier (string) to be used when constructing keywords from SQL column names [JDBC-133](http://clojure.atlassian.net/browse/JDBC-133). +* All options to all functions can now have defaults within the `db-spec` itself [JDBC-136](https://clojure.atlassian.net/browse/JDBC-136). +* `query` (and by extension `find-by-keys` and `get-by-id`) now support `:explain?` and `:explain-fn` options to help support basic performance analysis [JDBC-135](https://clojure.atlassian.net/browse/JDBC-135). +* `insert!` and `insert-multi!` now respect `:identifiers` and `:qualifier` because inserting rows on PostgreSQL returns full rows, not just the newly inserted keys [JDBC-134](https://clojure.atlassian.net/browse/JDBC-134). +* In addition to the `:identifiers` option, you can now use `:qualifier` to specify a namespace qualifier (string) to be used when constructing keywords from SQL column names [JDBC-133](https://clojure.atlassian.net/browse/JDBC-133). Changes in 0.6.1 -* `insert!` and `insert-multi!` now default `:transaction?` to `true` (as they should have done in 0.6.0!) [JDBC-128](http://clojure.atlassian.net/browse/JDBC-128). These two functions also have improved docstrings to clarify the difference in behavior between inserting rows as maps compared to inserting rows as a series of column values. -* PostgreSQL support has been improved: java.jdbc is now tested against PostgreSQL locally (as well as SQLite, Apache Derby, HSQLDB, H2, MySQL, MS SQL Server (both MS Type 4 driver and jTDS driver). [JDBC-127](http://clojure.atlassian.net/browse/JDBC-127) and [JDBC-129](http://clojure.atlassian.net/browse/JDBC-129). +* `insert!` and `insert-multi!` now default `:transaction?` to `true` (as they should have done in 0.6.0!) [JDBC-128](https://clojure.atlassian.net/browse/JDBC-128). These two functions also have improved docstrings to clarify the difference in behavior between inserting rows as maps compared to inserting rows as a series of column values. +* PostgreSQL support has been improved: java.jdbc is now tested against PostgreSQL locally (as well as SQLite, Apache Derby, HSQLDB, H2, MySQL, MS SQL Server (both MS Type 4 driver and jTDS driver). [JDBC-127](https://clojure.atlassian.net/browse/JDBC-127) and [JDBC-129](https://clojure.atlassian.net/browse/JDBC-129). Changes in 0.6.0 -* `find-by-keys` now correctly handles `nil` values [JDBC-126](http://clojure.atlassian.net/browse/JDBC-126). +* `find-by-keys` now correctly handles `nil` values [JDBC-126](https://clojure.atlassian.net/browse/JDBC-126). * `find-by-keys` calls `seq` on `:order-by` to treat `[]` as no `ORDER BY` clause. Changes in 0.6.0-rc2 -* `db-query-with-resultset` now accepts an options map and passes it to `prepare-statement` [JDBC-125](http://clojure.atlassian.net/browse/JDBC-125). +* `db-query-with-resultset` now accepts an options map and passes it to `prepare-statement` [JDBC-125](https://clojure.atlassian.net/browse/JDBC-125). - Passing the `prepare-statement` options map as the first element of the `[sql & params]` vector is no longer supported and will throw an `IllegalArgumentException`. It was always very poorly documented and almost never used, as far as I can tell. * `db-query-with-resultset` no longer requires the `sql-params` argument to be a vector: a sequence is acceptable. This is in line with other functions that accept a sequence. * `db-query-with-resultset` now accepts a bare SQL string or `PreparedStatement` as the `sql-params` argument, when there are no parameters needed. This is in line with other functions that accept SQL or a `PreparedStatement`. -* `query`'s options map now is passed to `db-query-with-resultset` and thus can contain options to be used to construct the `PreparedStatement` [JDBC-125](http://clojure.atlassian.net/browse/JDBC-125). +* `query`'s options map now is passed to `db-query-with-resultset` and thus can contain options to be used to construct the `PreparedStatement` [JDBC-125](https://clojure.atlassian.net/browse/JDBC-125). * `find-by-keys` now accepts an `:order-by` option that specifies a sequence of orderings; an ordering is either a column (to sort ascending) or a map from column name to direct (`:asc` or `:desc`). Changes in 0.6.0-rc1 * Adds `get-by-id` and `find-by-keys` convenience functions (these were easy to add after the API changes in 0.6.0 and we rely very heavily on them at World Singles so putting them in the core for everyone seemed reasonable). -* REMINDER: ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://clojure.atlassian.net/browse/JDBC-118). +* REMINDER: ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](https://clojure.atlassian.net/browse/JDBC-118). - See alpha2 / alpha1 below for more details. Changes in 0.6.0-alpha2 -* ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://clojure.atlassian.net/browse/JDBC-118). - - This removes deprecated functionality from db-do-commands and db-do-prepared* which should have been removed in Alpha 1. -* Ensures SQL / params are actually vectors prior to destructuring (this addresses an interop edge case from other languages) [JDBC-124](http://clojure.atlassian.net/browse/JDBC-124). -* Fix typo in `insert-multi!` argument validation exception [JDBC-123](http://clojure.atlassian.net/browse/JDBC-123). +* ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](https://clojure.atlassian.net/browse/JDBC-118). + - This removes deprecated functionality from db-do-commands and `db-do-prepared*` which should have been removed in Alpha 1. +* Ensures SQL / params are actually vectors prior to destructuring (this addresses an interop edge case from other languages) [JDBC-124](https://clojure.atlassian.net/browse/JDBC-124). +* Fix typo in `insert-multi!` argument validation exception [JDBC-123](https://clojure.atlassian.net/browse/JDBC-123). Changes in 0.6.0-alpha1 -* (ALMOST) ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://clojure.atlassian.net/browse/JDBC-118). +* (ALMOST) ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](https://clojure.atlassian.net/browse/JDBC-118). - See changes described in versions 0.5.5 through 0.5.8 for what was deprecated - Use version 0.5.8 as a bridge to identify any deprecated API calls on which your code relies! - `db-transaction` (deprecated in version 0.3.0) has been removed @@ -181,73 +181,73 @@ Changes in 0.6.0-alpha1 Changes in 0.5.8 -* `db-do-commands` now expects multiple commands to be be wrapped in a vector [JDBC-122](http://clojure.atlassian.net/browse/JDBC-123). The single command form is unchanged (but may be wrapped in a vector). Calling `db-do-commands` with multiple commands (not wrapped in a single vector) will produce a "DEPRECATED" warning printed to the console. -* `db-do-prepared` and `db-do-prepared-return-keys` now expect to receive a `db-spec`, an optional `transaction?` boolean, a `sql-params` argument, and an optional options map. `sql-params` is a vector containing a SQL string or `PreparedStatement` followed by parameters -- like other APIs in this library. In addition, like the `:multi? true` version of `execute!`, `db-do-prepared` can accept a vector that has parameter groups: multiple vectors containing groups of parameter values [JDBC-122](http://clojure.atlassian.net/browse/JDBC-123). Calling `db-do-prepared` with unrolled arguments -- the SQL string / statement followed by parameter groups -- is deprecated and will produce "DEPRECATED" warnings printed to the console. +* `db-do-commands` now expects multiple commands to be be wrapped in a vector [JDBC-122](https://clojure.atlassian.net/browse/JDBC-123). The single command form is unchanged (but may be wrapped in a vector). Calling `db-do-commands` with multiple commands (not wrapped in a single vector) will produce a "DEPRECATED" warning printed to the console. +* `db-do-prepared` and `db-do-prepared-return-keys` now expect to receive a `db-spec`, an optional `transaction?` boolean, a `sql-params` argument, and an optional options map. `sql-params` is a vector containing a SQL string or `PreparedStatement` followed by parameters -- like other APIs in this library. In addition, like the `:multi? true` version of `execute!`, `db-do-prepared` can accept a vector that has parameter groups: multiple vectors containing groups of parameter values [JDBC-122](https://clojure.atlassian.net/browse/JDBC-123). Calling `db-do-prepared` with unrolled arguments -- the SQL string / statement followed by parameter groups -- is deprecated and will produce "DEPRECATED" warnings printed to the console. Changes in 0.5.7 -* `(insert! db table [:col] ["val"] {})` syntax, introduced in 0.5.6, threw an exception [JDBC-121](http://clojure.atlassian.net/browse/JDBC-121). +* `(insert! db table [:col] ["val"] {})` syntax, introduced in 0.5.6, threw an exception [JDBC-121](https://clojure.atlassian.net/browse/JDBC-121). Changes in 0.5.6 -* `create-table-ddl` now expects the column specs to be wrapped in a single vector and no longer needs the `:options` delimiter to specify the options map [JDBC-120](http://clojure.atlassian.net/browse/JDBC-120). If column specs are not wrapped in a vector, you will get a "DEPRECATED" warning printed to the console. -* `insert!` now supports only single row insertion; multi-row insertion is deprecated. `insert-multi!` has been added for multi-row insertion. `:options` is no longer needed as a delimiter for the options map [JDBC-119](http://clojure.atlassian.net/browse/JDBC-119). If `insert!` is called with multiple rows, or `:options` is specified, you will get a "DEPRECATED" warning printed to the console. +* `create-table-ddl` now expects the column specs to be wrapped in a single vector and no longer needs the `:options` delimiter to specify the options map [JDBC-120](https://clojure.atlassian.net/browse/JDBC-120). If column specs are not wrapped in a vector, you will get a "DEPRECATED" warning printed to the console. +* `insert!` now supports only single row insertion; multi-row insertion is deprecated. `insert-multi!` has been added for multi-row insertion. `:options` is no longer needed as a delimiter for the options map [JDBC-119](https://clojure.atlassian.net/browse/JDBC-119). If `insert!` is called with multiple rows, or `:options` is specified, you will get a "DEPRECATED" warning printed to the console. * NOTE: all deprecated functionality will go away in version 0.6.0! Changes in 0.5.5 -* Allow options map in all calls that previously took optional keyword arguments [JDBC-117](http://clojure.atlassian.net/browse/JDBC-117). The unrolled keyword argument forms of call are deprecated -- and print a "DEPRECATED" message to the console! -- and will go away in 0.6.0. +* Allow options map in all calls that previously took optional keyword arguments [JDBC-117](https://clojure.atlassian.net/browse/JDBC-117). The unrolled keyword argument forms of call are deprecated -- and print a "DEPRECATED" message to the console! -- and will go away in 0.6.0. Changes in 0.5.0 -* Allow PreparedStatement in db-do-prepared-return-keys [JDBC-115](http://clojure.atlassian.net/browse/JDBC-115). -* Remove exception wrapping [JDBC-114](http://clojure.atlassian.net/browse/JDBC-114). +* Allow PreparedStatement in db-do-prepared-return-keys [JDBC-115](https://clojure.atlassian.net/browse/JDBC-115). +* Remove exception wrapping [JDBC-114](https://clojure.atlassian.net/browse/JDBC-114). * Drop Clojure 1.3 compatibility. Changes in 0.4.2 -* Remove redundant type hints [JDBC-113](http://clojure.atlassian.net/browse/JDBC-113) - Michael Blume. -* Avoid reflection on `.prepareStatement` [JDBC-112](http://clojure.atlassian.net/browse/JDBC-112) - Michael Blume. -* Add `metadata-query` macro to make metadata query / results easier to work with for [JDBC-107](http://clojure.atlassian.net/browse/JDBC-107). -* `prepare-statement` `:return-keys` may now be a vector of (auto-generated) column names to return, in addition to just being truthy or falsey. This allows keys to be returned for more databases. [JDBC-104](http://clojure.atlassian.net/browse/JDBC-104). -* Officially support H2 (and test against it) to support [JDBC-91](http://clojure.atlassian.net/browse/JDBC-91) and clarify docstrings to improve debugging driver-specific restrictions on SQL. +* Remove redundant type hints [JDBC-113](https://clojure.atlassian.net/browse/JDBC-113) - Michael Blume. +* Avoid reflection on `.prepareStatement` [JDBC-112](https://clojure.atlassian.net/browse/JDBC-112) - Michael Blume. +* Add `metadata-query` macro to make metadata query / results easier to work with for [JDBC-107](https://clojure.atlassian.net/browse/JDBC-107). +* `prepare-statement` `:return-keys` may now be a vector of (auto-generated) column names to return, in addition to just being truthy or falsey. This allows keys to be returned for more databases. [JDBC-104](https://clojure.atlassian.net/browse/JDBC-104). +* Officially support H2 (and test against it) to support [JDBC-91](https://clojure.atlassian.net/browse/JDBC-91) and clarify docstrings to improve debugging driver-specific restrictions on SQL. Changes in 0.4.0 / 0.4.1 -* `db-do-prepared` now allows `transaction?` to be omitted when a `PreparedStatement` is passed as the second argument [JDBC-111](http://clojure.atlassian.net/browse/JDBC-111) - Stefan Kamphausen. -* Nested transaction checks isolation level is the same [JDBC-110](http://clojure.atlassian.net/browse/JDBC-110) - Donald Ball. -* Default PostgreSQL port; Support more dbtype/dbname variants [JDBC-109](http://clojure.atlassian.net/browse/JDBC-109). +* `db-do-prepared` now allows `transaction?` to be omitted when a `PreparedStatement` is passed as the second argument [JDBC-111](https://clojure.atlassian.net/browse/JDBC-111) - Stefan Kamphausen. +* Nested transaction checks isolation level is the same [JDBC-110](https://clojure.atlassian.net/browse/JDBC-110) - Donald Ball. +* Default PostgreSQL port; Support more dbtype/dbname variants [JDBC-109](https://clojure.atlassian.net/browse/JDBC-109). * Drop Clojure 1.2 compatibility. Changes in 0.3.7 * Bump all driver versions in `project.clj` and re-test. -* Remove duplicate `count` calls in `insert-sql` [JDBC-108](http://clojure.atlassian.net/browse/JDBC-108) - Earl St Sauver. -* Remove driver versions from README and link to Maven Central [JDBC-106](http://clojure.atlassian.net/browse/JDBC-106). -* Fix links in CHANGES and README [JDBC-103](http://clojure.atlassian.net/browse/JDBC-103) - John Walker. +* Remove duplicate `count` calls in `insert-sql` [JDBC-108](https://clojure.atlassian.net/browse/JDBC-108) - Earl St Sauver. +* Remove driver versions from README and link to Maven Central [JDBC-106](https://clojure.atlassian.net/browse/JDBC-106). +* Fix links in CHANGES and README [JDBC-103](https://clojure.atlassian.net/browse/JDBC-103) - John Walker. Changes in 0.3.6 -* Arbitrary values allowed for `:cursors`, `:concurrency`, `:result-type` arguments to `prepare-statement` [JDBC-102](http://clojure.atlassian.net/browse/JDBC-102). -* Allow `:as-arrays? :cols-as-is` to omit column name uniqueness when returning result sets as arrrays [JDBC-101](http://clojure.atlassian.net/browse/JDBC-101). -* Add `:timeout` argument to `prepare-statement` [JDBC-100](http://clojure.atlassian.net/browse/JDBC-100). +* Arbitrary values allowed for `:cursors`, `:concurrency`, `:result-type` arguments to `prepare-statement` [JDBC-102](https://clojure.atlassian.net/browse/JDBC-102). +* Allow `:as-arrays? :cols-as-is` to omit column name uniqueness when returning result sets as arrrays [JDBC-101](https://clojure.atlassian.net/browse/JDBC-101). +* Add `:timeout` argument to `prepare-statement` [JDBC-100](https://clojure.atlassian.net/browse/JDBC-100). Changes in 0.3.5 * Reflection warnings on executeUpdate addressed. -* HSQLDB and SQLite in-memory strings are now accepted [JDBC-94](http://clojure.atlassian.net/browse/JDBC-94). -* Add support for readonly transactions via :read-only? [JDBC-93](http://clojure.atlassian.net/browse/JDBC-93). +* HSQLDB and SQLite in-memory strings are now accepted [JDBC-94](https://clojure.atlassian.net/browse/JDBC-94). +* Add support for readonly transactions via :read-only? [JDBC-93](https://clojure.atlassian.net/browse/JDBC-93). Changes in 0.3.4 -* execute! can now accept a PreparedStatement [JDBC-96](http://clojure.atlassian.net/browse/JDBC-96). -* Support simpler db-spec with :dbtype and :dbname (and optional :host and :port etc) [JDBC-92](http://dev.clojure.org/jire/browse/JDBC-92). -* Support oracle:oci and oracle:thin subprotocols [JDBC-90](http://clojure.atlassian.net/browse/JDBC-90). +* execute! can now accept a PreparedStatement [JDBC-96](https://clojure.atlassian.net/browse/JDBC-96). +* Support simpler db-spec with :dbtype and :dbname (and optional :host and :port etc) [JDBC-92](https://clojure.atlassian.net/browse/JDBC-92). +* Support oracle:oci and oracle:thin subprotocols [JDBC-90](https://clojure.atlassian.net/browse/JDBC-90). Changes in 0.3.3 -* Prevent exception/crash when query called with bare SQL string [JDBC-89](http://clojure.atlassian.net/browse/JDBC-89). -* Add :row-fn and :result-set-fn to metadata-result function [JDBC-87](http://clojure.atlassian.net/browse/JDBC-87). +* Prevent exception/crash when query called with bare SQL string [JDBC-89](https://clojure.atlassian.net/browse/JDBC-89). +* Add :row-fn and :result-set-fn to metadata-result function [JDBC-87](https://clojure.atlassian.net/browse/JDBC-87). * Support key/value configuration from URI (Phil Hagelberg). Changes in 0.3.2 @@ -258,25 +258,25 @@ Changes in 0.3.1 (broken) * Improve docstrings and add :arglists for better auto-generated documentation. * Make insert-sql private - technically a breaking change but it should never have been public: sorry folks! -* Provide better protocol for setting parameters in prepared statements [JDBC-86](http://clojure.atlassian.net/browse/JDBC-86). -* Fix parens in two deprecated tests [JDBC-85](http://clojure.atlassian.net/browse/JDBC-85). +* Provide better protocol for setting parameters in prepared statements [JDBC-86](https://clojure.atlassian.net/browse/JDBC-86). +* Fix parens in two deprecated tests [JDBC-85](https://clojure.atlassian.net/browse/JDBC-85). * Made create-table-ddl less aggressive about applying as-sql-name so only first name in a column spec is affected. Changes in 0.3.0 -* Ensure canonical Boolean to workaround strange behavior in some JDBC drivers [JDBC-84](http://clojure.atlassian.net/browse/JDBC-84). -* Rename recently introduced test to ensure unique names [JDBC-83](http://clojure.atlassian.net/browse/JDBC-83). -* Rename unused arguments in protocol implementation to support Android [JDBC-82](http://clojure.atlassian.net/browse/JDBC-82). -* Correctly handle empty param group sequence in execute! (which only seemed to affect SQLite) [JDBC-65](http://clojure.atlassian.net/browse/JDBC-65). +* Ensure canonical Boolean to workaround strange behavior in some JDBC drivers [JDBC-84](https://clojure.atlassian.net/browse/JDBC-84). +* Rename recently introduced test to ensure unique names [JDBC-83](https://clojure.atlassian.net/browse/JDBC-83). +* Rename unused arguments in protocol implementation to support Android [JDBC-82](https://clojure.atlassian.net/browse/JDBC-82). +* Correctly handle empty param group sequence in execute! (which only seemed to affect SQLite) [JDBC-65](https://clojure.atlassian.net/browse/JDBC-65). Changes in 0.3.0-rc1 -* Deprecate db-transaction (new in 0.3.0) in favor of with-db-transaction [JDBC-81](http://clojure.atlassian.net/browse/JDBC-81). -* Add with-db-metadata macro and metadata-result function to make it easier to work with SQL metadata [JDBC-80](http://clojure.atlassian.net/browse/JDBC-80). -* Add with-db-connection macro to make it easier to run groups of operations against a single open connection [JDBC-79](http://clojure.atlassian.net/browse/JDBC-79). -* Add ISQLValue protocol to make it easier to support custom SQL types for parameters in SQL statements [JDBC-77](http://clojure.atlassian.net/browse/JDBC-77). -* Add support for :isolation in with-db-transaction [JDBC-75](http://clojure.atlassian.net/browse/JDBC-75). -* Add :user as an alias for :username for DataSource connections [JDBC-74](http://clojure.atlassian.net/browse/JDBC-74). +* Deprecate db-transaction (new in 0.3.0) in favor of with-db-transaction [JDBC-81](https://clojure.atlassian.net/browse/JDBC-81). +* Add with-db-metadata macro and metadata-result function to make it easier to work with SQL metadata [JDBC-80](https://clojure.atlassian.net/browse/JDBC-80). +* Add with-db-connection macro to make it easier to run groups of operations against a single open connection [JDBC-79](https://clojure.atlassian.net/browse/JDBC-79). +* Add ISQLValue protocol to make it easier to support custom SQL types for parameters in SQL statements [JDBC-77](https://clojure.atlassian.net/browse/JDBC-77). +* Add support for :isolation in with-db-transaction [JDBC-75](https://clojure.atlassian.net/browse/JDBC-75). +* Add :user as an alias for :username for DataSource connections [JDBC-74](https://clojure.atlassian.net/browse/JDBC-74). Changes in 0.3.0-beta2 @@ -285,48 +285,48 @@ Changes in 0.3.0-beta2 Changes in 0.3.0-beta1 -* query as-arrays? now allows you to leverage lazy result fetching [JDBC-72](http://clojure.atlassian.net/browse/JDBC-72). +* query as-arrays? now allows you to leverage lazy result fetching [JDBC-72](https://clojure.atlassian.net/browse/JDBC-72). * "h2" is recognized as a protocol shorthand for org.h2.Driver -* Tests no longer use :1 literal [JDBC-71](http://clojure.atlassian.net/browse/JDBC-71). -* Conditional use of javax.naming.InitialContext so it can be compiled on Android [JDBC-69](http://clojure.atlassian.net/browse/JDBC-69). -* New db-query-with-resultset function replaces private db-with-query-results* and processes a raw ResultSet object [JDBC-63](http://clojure.atlassian.net/browse/JDBC-63). -* Allow :set-parameters in db-spec to override set-parameters internal function to allow per-DB special handling of SQL parameters values (such as null for Teradata) [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40). +* Tests no longer use :1 literal [JDBC-71](https://clojure.atlassian.net/browse/JDBC-71). +* Conditional use of javax.naming.InitialContext so it can be compiled on Android [JDBC-69](https://clojure.atlassian.net/browse/JDBC-69). +* New db-query-with-resultset function replaces private `db-with-query-results*` and processes a raw ResultSet object [JDBC-63](https://clojure.atlassian.net/browse/JDBC-63). +* Allow :set-parameters in db-spec to override set-parameters internal function to allow per-DB special handling of SQL parameters values (such as null for Teradata) [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40). Changes in 0.3.0-alpha5 -* DDL now supports entities naming strategy [JDBC-53](http://clojure.atlassian.net/browse/JDBC-53). +* DDL now supports entities naming strategy [JDBC-53](https://clojure.atlassian.net/browse/JDBC-53). * Attempt to address potential memory leaks due to closures - see [Christophe Grand's blog post on Macros, closures and unexpected object retention](http://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/). * Documentation has moved to [Using java.jdbc on Clojure-Doc.org](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) * Added Leiningen support for easier development/testing (Maven is still the primary build tool). -* Added create-index / drop-index DDL [JDBC-62](http://clojure.atlassian.net/browse/JDBC-62) - moquist -* Make transaction? boolean optional in various db-do-* functions +* Added create-index / drop-index DDL [JDBC-62](https://clojure.atlassian.net/browse/JDBC-62) - moquist +* Make transaction? boolean optional in various `db-do-*` functions * Create clojure.java.jdbc.ddl namespace * Add create-table, drop-table, create-index and drop-index * Deprecate create-table, create-table-ddl and drop-table in main namespace * Update README to clarify PostgreSQL instructions. -* Fix test suite for PostgreSQL [JDBC-59](http://clojure.atlassian.net/browser/JDBC-59) -* Improve hooks for Oracle data type handling [JDBC-57](http://clojure.atlassian.net/browser/JDBC-57) -* Fix reflection warnings [JDBC-55](http://clojure.atlassian.net/browser/JDBC-55) +* Fix test suite for PostgreSQL [JDBC-59](https://clojure.atlassian.net/browser/JDBC-59) +* Improve hooks for Oracle data type handling [JDBC-57](https://clojure.atlassian.net/browser/JDBC-57) +* Fix reflection warnings [JDBC-55](https://clojure.atlassian.net/browser/JDBC-55) -* DDL now supports entities naming strategy [JDBC-53](http://clojure.atlassian.net/browse/JDBC-53). +* DDL now supports entities naming strategy [JDBC-53](https://clojure.atlassian.net/browse/JDBC-53). * Attempt to address potential memory leaks due to closures - see [Christophe Grand's blog post on Macros, closures and unexpected object retention](http://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/). * Documentation has moved to [Using java.jdbc on Clojure-Doc.org](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) * Added Leiningen support for easier development/testing (Maven is still the primary build tool). -* Added create-index / drop-index DDL [JDBC-62](http://clojure.atlassian.net/browse/JDBC-62) - moquist -* Make transaction? boolean optional in various db-do-* functions - * It will ultimately change to a function argument I think when [JDBC-37](http://clojure.atlassian.net/browser/JDBC-37) is dealt with +* Added create-index / drop-index DDL [JDBC-62](https://clojure.atlassian.net/browse/JDBC-62) - moquist +* Make transaction? boolean optional in various `db-do-*` functions + * It will ultimately change to a function argument I think when [JDBC-37](https://clojure.atlassian.net/browser/JDBC-37) is dealt with * Create clojure.java.jdbc.ddl namespace * Add create-table and drop-table * Deprecate create-table, create-table-ddl and drop-table in main namespace * More DDL is coming soon * Update README to clarify PostgreSQL instructions. -* Fix test suite for PostgreSQL [JDBC-59](http://clojure.atlassian.net/browser/JDBC-59) -* Improve hooks for Oracle data type handling [JDBC-57](http://clojure.atlassian.net/browser/JDBC-57) -* Fix reflection warnings [JDBC-55](http://clojure.atlassian.net/browser/JDBC-55) +* Fix test suite for PostgreSQL [JDBC-59](https://clojure.atlassian.net/browser/JDBC-59) +* Improve hooks for Oracle data type handling [JDBC-57](https://clojure.atlassian.net/browser/JDBC-57) +* Fix reflection warnings [JDBC-55](https://clojure.atlassian.net/browser/JDBC-55) Changes in 0.3.0-alpha4 -* Fix connection leaks [JDBC-54](http://clojure.atlassian.net/browser/JDBC-54) +* Fix connection leaks [JDBC-54](https://clojure.atlassian.net/browser/JDBC-54) * Allow order-by to accept empty sequence (and return empty string) Changes in 0.3.0-alpha3 @@ -335,10 +335,10 @@ Changes in 0.3.0-alpha3 Changes in 0.3.0-alpha2 -* Address [JDBC-51](http://clojure.atlassian.net/browse/JDBC-51) by declaring get-connection returns java.sql.Connection -* Add IResultSetReadColumn protocol extension point for custom read conversions [JDBC-46](http://clojure.atlassian.net/browse/JDBC-46) -* Add :multi? to execute! so it can be used for repeated operations [JDBC-52](http://clojure.atlassian.net/browse/JDBC-52) -* Reverted specialized handling of NULL values (reopens [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40)) +* Address [JDBC-51](https://clojure.atlassian.net/browse/JDBC-51) by declaring get-connection returns java.sql.Connection +* Add IResultSetReadColumn protocol extension point for custom read conversions [JDBC-46](https://clojure.atlassian.net/browse/JDBC-46) +* Add :multi? to execute! so it can be used for repeated operations [JDBC-52](https://clojure.atlassian.net/browse/JDBC-52) +* Reverted specialized handling of NULL values (reopens [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40)) * Rename :as-arrays to :as-arrays? since it is boolean * Add curried version of clojure.java.jdbc.sql/as-quoted-str * Officially deprecate resultset-seq @@ -348,7 +348,7 @@ Changes in 0.3.0-alpha1 Major overhaul of the API and deprecation of most of the old API! * Add insert!, query, update!, delete! and execute! high-level API - [JDBC-20](http://clojure.atlassian.net/browse/JDBC-20) + [JDBC-20](https://clojure.atlassian.net/browse/JDBC-20) * Add optional SQL-generating DSL in clojure.java.jdbc.sql (implied by JDBC-20) * Add db- prefixed versions of low-level API * Add db-transaction macro: @@ -360,16 +360,16 @@ Major overhaul of the API and deprecation of most of the old API! * Add result-set-seq as replacement for resultset-seq (which will be deprecated) * Transaction now correctly rollback on non-Exception Throwables - [JDBC-43](http://clojure.atlassian.net/browse/JDBC-43) + [JDBC-43](https://clojure.atlassian.net/browse/JDBC-43) * Rewrite old API functions in terms of new API, and deprecate old API - [JDBC-43](http://clojure.atlassian.net/browse/JDBC-43) + [JDBC-43](https://clojure.atlassian.net/browse/JDBC-43) * Add :as-arrays to query / result-set-seq - [JDBC-41](http://clojure.atlassian.net/browse/JDBC-41) -* Better handling of NULL values [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40) - and [JDBC-18](http://clojure.atlassian.net/browse/JDBC-18) + [JDBC-41](https://clojure.atlassian.net/browse/JDBC-41) +* Better handling of NULL values [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40) + and [JDBC-18](https://clojure.atlassian.net/browse/JDBC-18) Note: JDBC-40 has been reverted in 0.3.0-alpha2 because it introduced regressions for PostgreSQL * db-do-commands allows you to execute SQL without a transaction wrapping it - [JDBC-38](http://clojure.atlassian.net/browse/JDBC-38) + [JDBC-38](https://clojure.atlassian.net/browse/JDBC-38) * Remove reflection warning from execute-batch * Add notes to README about 3rd party database driver dependencies * Add optional :identifiers argument to resultset-seq so you can explicitly pass in the naming strategy @@ -377,18 +377,18 @@ Major overhaul of the API and deprecation of most of the old API! Changes in 0.2.3: * as-str now treats a.b as two identifiers separated by . so quoting produces [a].[b] instead of [a.b] -* Add :connection-uri option [JDBC-34](http://clojure.atlassian.net/browse/JDBC-34) +* Add :connection-uri option [JDBC-34](https://clojure.atlassian.net/browse/JDBC-34) Changes in 0.2.2: -* Handle Oracle unknown row count affected [JDBC-33](http://clojure.atlassian.net/browse/JDBC-33) -* Handle jdbc: prefix in string db-specs [JDBC-32](http://clojure.atlassian.net/browse/JDBC-32) -* Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](http://clojure.atlassian.net/browse/JDBC-31) +* Handle Oracle unknown row count affected [JDBC-33](https://clojure.atlassian.net/browse/JDBC-33) +* Handle jdbc: prefix in string db-specs [JDBC-32](https://clojure.atlassian.net/browse/JDBC-32) +* Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](https://clojure.atlassian.net/browse/JDBC-31) Changes in 0.2.1: -* Result set performance enhancement (Juergen Hoetzel) [JDBC-29](http://clojure.atlassian.net/browse/JDBC-29) -* Make do-prepared-return-keys (for Korma team) [JDBC-30](http://clojure.atlassian.net/browse/JDBC-30) +* Result set performance enhancement (Juergen Hoetzel) [JDBC-29](https://clojure.atlassian.net/browse/JDBC-29) +* Make do-prepared-return-keys (for Korma team) [JDBC-30](https://clojure.atlassian.net/browse/JDBC-30) Changes in 0.2.0: @@ -462,7 +462,7 @@ Changes in 0.0.4: * Fix JDBC-8 by removing all reflection warnings * Fix JDBC-11 by no longer committing the transaction when an Error occurs * Clean up as-... functions to reduce use of (binding) -* Refactor do-prepared*, separating out return keys logic and parameter setting logic +* Refactor `do-prepared*`, separating out return keys logic and parameter setting logic - in preparation for exposing more hooks in PreparedStatement creation / manipulation Changes in 0.0.3: @@ -475,9 +475,9 @@ Changes in 0.0.2: Changes in 0.0.1 (compared to clojure.contrib.sql): -* Exposed print-... functions for exception printing; no longer writes exceptions to *out* +* Exposed print-... functions for exception printing; no longer writes exceptions to `*out*` * Add clojure.java.jdbc/resultset-seq (to replace clojure.core/resultset-seq which should be deprecated) -* Add support for naming and quoting strategies - see http://clojure.github.com/java.jdbc/doc/clojure/java/jdbc/NameMapping.html +* Add support for naming and quoting strategies - see https://clojure.github.io/java.jdbc/doc/clojure/java/jdbc/NameMapping.html - The formatting is a bit borked, Tom F knows about this and is working on an enhancement to auto-doc to improve it * Add ability to return generated keys from single insert operations, add insert-record function * Clojure 1.3 compatibility diff --git a/README.md b/README.md index 52ea9bfa..338996f7 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,7 @@ This library is mature and stable. It is widely used and its use is described in Documentation ======================================== -* [API Reference](http://clojure.github.com/java.jdbc/) (Autogenerated) - +* [API Reference](https://clojure.github.io/java.jdbc/) (Autogenerated) * [Overview](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) * [Manipulating Data with SQL](http://clojure-doc.org/articles/ecosystem/java_jdbc/using_sql.html) * [How to Reuse Database Connections](http://clojure-doc.org/articles/ecosystem/java_jdbc/reusing_connections.html) @@ -28,8 +27,7 @@ Releases and Dependency Information Latest stable release: 0.7.12 -- requires Clojure 1.7 or later! -* [All Released Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) - +* [All Released Versions](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) * [Development Snapshot Versions](https://oss.sonatype.org/index.html#nexus-search;gav~org.clojure~java.jdbc~~~) [CLI/`deps.edn`](https://clojure.org/reference/deps_and_cli) dependency information: @@ -40,7 +38,7 @@ org.clojure/java.jdbc {:mvn/version "0.7.12"} ```clojure [org.clojure/java.jdbc "0.7.12"] ``` -[Maven](http://maven.apache.org/) dependency information: +[Maven](https://maven.apache.org/) dependency information: ```xml org.clojure @@ -52,14 +50,14 @@ _Note: Earlier versions of Clojure are supported by older versions of `clojure.j You will also need to add dependencies for the JDBC driver you intend to use. Here are links (to Maven Central) for each of the common database drivers that clojure.java.jdbc is known to be used with: -* [Apache Derby](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.apache.derby%22%20AND%20a%3A%22derby%22) -* [H2](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.h2database%22%20AND%20a%3A%22h2%22) -* [HSQLDB](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22hsqldb%22%20AND%20a%3A%22hsqldb%22) -* [Microsoft SQL Server jTDS](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22net.sourceforge.jtds%22%20AND%20a%3A%22jtds%22) -* [Microsoft SQL Server -- Official MS Version](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.microsoft.sqlserver%22%20AND%20a%3A%22mssql-jdbc%22) -* [MySQL](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22mysql%22%20AND%20a%3A%22mysql-connector-java%22) -* [PostgreSQL](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.postgresql%22%20AND%20a%3A%22postgresql%22) -* [SQLite](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.xerial%22%20AND%20a%3A%22sqlite-jdbc%22) +* [Apache Derby](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.apache.derby%22%20AND%20a%3A%22derby%22) +* [H2](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.h2database%22%20AND%20a%3A%22h2%22) +* [HSQLDB](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22hsqldb%22%20AND%20a%3A%22hsqldb%22) +* [Microsoft SQL Server jTDS](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22net.sourceforge.jtds%22%20AND%20a%3A%22jtds%22) +* [Microsoft SQL Server -- Official MS Version](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.microsoft.sqlserver%22%20AND%20a%3A%22mssql-jdbc%22) +* [MySQL](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22mysql%22%20AND%20a%3A%22mysql-connector-java%22) +* [PostgreSQL](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.postgresql%22%20AND%20a%3A%22postgresql%22) +* [SQLite](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.xerial%22%20AND%20a%3A%22sqlite-jdbc%22) Note: different versions of various database drivers have different Java/JVM version requirements. In particular, recent versions of Apache Derby require at least Java 8 and recent versions of H2 require at least Java 7. Clojure's Continuous Integration system uses older versions so tests can be run on Java 6 (see `pom.xml`); local testing is done with more recent versions on Java 8. @@ -108,18 +106,15 @@ Example Usage {:row-fn :cost}) ;; (24) ``` -For more detail see the [API reference](http://clojure.github.com/java.jdbc/) or [documentation](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html). +For more detail see the [API reference](https://clojure.github.io/java.jdbc/) or [documentation](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html). Developer Information ======================================== * [GitHub project](https://github.com/clojure/java.jdbc) - -* [Bug Tracker](http://clojure.atlassian.net/browse/JDBC) - -* [Continuous Integration](http://build.clojure.org/job/java.jdbc/) - -* [Compatibility Test Matrix](http://build.clojure.org/job/java.jdbc-test-matrix/) +* [Bug Tracker](https://clojure.atlassian.net/browse/JDBC) +* [Continuous Integration](https://build.clojure.org/job/java.jdbc/) +* [Compatibility Test Matrix](https://build.clojure.org/job/java.jdbc-test-matrix/) * Testing: * Currently by default tests run only against Derby and HSQLDB, the in-process databases. @@ -228,27 +223,27 @@ Change Log * `reducible-query` accepts a `db-spec` and a SQL/parameters vector and returns a reducible (`IReduce` on Clojure 1.7 or later; `CollReduce` on Clojure 1.5/1.6): when reduced, it runs the query, obtains a reducible result set, and then reduces that. A reducible query will run the query each time it is reduced. The helper function `reducible-result-set` is public: it accepts a `ResultSet` and produces a reducible that offers a single pass reduce over the rows. Both functions honor `reduced` values to short-circuit the process [JDBC-99](https://clojure.atlassian.net/browse/JDBC-99). * Release 0.7.0-alpha3 on 2017-03-23 - * `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](http://clojure.atlassian.net/browse/JDBC-151). + * `classname` is now accepted with `dbtype` / `dbname` so you can easily specify a JDBC driver class name for a database type that is not known [JDBC-151](https://clojure.atlassian.net/browse/JDBC-151). * `redshift` has been added as a `dbtype` with `com.amazon.redshift.jdbc.Driver` as the driver name. * Release 0.7.0-alpha2 on 2017-03-01 - * `pgsql` and the Impossibl PostgresSQL 'NG' driver are now supported (note that `:max-rows` does not work with this driver!); also, providing unknown `dbtype` or `subprotocol` in a `db-spec` should now throw a better exception [JDBC-150](http://clojure.atlassian.net/browse/JDBC-150). - * `quoted` now accepts keywords for database / dialect (`:ansi` (including PostgresSQL), `:mysql`, `:oracle`, `:sqlserver` -- these match the keywords used in HoneySQL which is the recommended third party SQL DSL for java.jdbc) [JDBC-149](http://clojure.atlassian.net/browse/JDBC-149). - * Reorder `get-connection` clauses to make it easier to combine keys in a `db-spec` [JDBC-148](http://clojure.atlassian.net/browse/JDBC-148). - * Force load `DriverManager` before `classForName` call on drivers to avoid potential race condition on initialization [JDBC-145](http://clojure.atlassian.net/browse/JDBC-145). + * `pgsql` and the Impossibl PostgresSQL 'NG' driver are now supported (note that `:max-rows` does not work with this driver!); also, providing unknown `dbtype` or `subprotocol` in a `db-spec` should now throw a better exception [JDBC-150](https://clojure.atlassian.net/browse/JDBC-150). + * `quoted` now accepts keywords for database / dialect (`:ansi` (including PostgresSQL), `:mysql`, `:oracle`, `:sqlserver` -- these match the keywords used in HoneySQL which is the recommended third party SQL DSL for java.jdbc) [JDBC-149](https://clojure.atlassian.net/browse/JDBC-149). + * Reorder `get-connection` clauses to make it easier to combine keys in a `db-spec` [JDBC-148](https://clojure.atlassian.net/browse/JDBC-148). + * Force load `DriverManager` before `classForName` call on drivers to avoid potential race condition on initialization [JDBC-145](https://clojure.atlassian.net/browse/JDBC-145). * Release 0.7.0-alpha1 on 2016-11-12 -- potentially breaking changes * The signatures of `as-sql-name` and `quoted` have changed slightly: the former no longer has the curried (single argument) version, and the latter no longer has the two argument version. This change came out of a discussion on Slack which indicated curried functions are non-idiomatic. If you relied on the curried version of `as-sql-name`, you will not need to use `partial`. If you relied on the two argument version of `quoted`, you will need to add an extra `( )` for the one argument call. I'd be fairly surprised if anyone is using `as-sql-name` at all since it is really an implementation detail. I'd also be surprised if anyone was using the two argument version of `quoted` since the natural usage is `:entities (quoted [\[ \]])` to create a naming strategy (that provides SQL entity quoting). - * Clarified that `insert-multi!` with a sequence of row maps may be substantially slower than with a sequence of row value vectors (the former performs an insert for each row, the latter performs a single insert for all the data together) [JDBC-147](http://clojure.atlassian.net/browse/JDBC-147). - * All options are passed through all function calls, expanding the range of options you can pass into high-level functions such as `insert!` and `update!` [JDBC-144](http://clojure.atlassian.net/browse/JDBC-144). - * Added `get-isolation-level` to return the current transaction's isolation level, if any [JDBC-141](http://clojure.atlassian.net/browse/JDBC-141). - * Added support for `read-columns` option to allow more flexible customization of reading column values from a result set (particularly in a multi-database application). Also expands `set-parameters` support to options (previously it was just part of the db-spec) [JDBC-137](http://clojure.atlassian.net/browse/JDBC-137). + * Clarified that `insert-multi!` with a sequence of row maps may be substantially slower than with a sequence of row value vectors (the former performs an insert for each row, the latter performs a single insert for all the data together) [JDBC-147](https://clojure.atlassian.net/browse/JDBC-147). + * All options are passed through all function calls, expanding the range of options you can pass into high-level functions such as `insert!` and `update!` [JDBC-144](https://clojure.atlassian.net/browse/JDBC-144). + * Added `get-isolation-level` to return the current transaction's isolation level, if any [JDBC-141](https://clojure.atlassian.net/browse/JDBC-141). + * Added support for `read-columns` option to allow more flexible customization of reading column values from a result set (particularly in a multi-database application). Also expands `set-parameters` support to options (previously it was just part of the db-spec) [JDBC-137](https://clojure.atlassian.net/browse/JDBC-137). * Expanded optional `clojure.spec` coverage to almost the whole library API. * Release 0.6.2-alpha3 on 2016-08-25 - * Fixed bad interaction between `:qualifier` and existing `:identifiers` functionality [JDBC-140](http://clojure.atlassian.net/browse/JDBC-140). - * Updated the README and docstrings to reflect that `:dbtype` is the easiest / preferred way to write `db-spec` maps [JDBC-139](http://clojure.atlassian.net/browse/JDBC-139). - * Fixed postgres / postgresql alias support [JDBC-138](http://clojure.atlassian.net/browse/JDBC-138). + * Fixed bad interaction between `:qualifier` and existing `:identifiers` functionality [JDBC-140](https://clojure.atlassian.net/browse/JDBC-140). + * Updated the README and docstrings to reflect that `:dbtype` is the easiest / preferred way to write `db-spec` maps [JDBC-139](https://clojure.atlassian.net/browse/JDBC-139). + * Fixed postgres / postgresql alias support [JDBC-138](https://clojure.atlassian.net/browse/JDBC-138). This also adds aliases for mssql (sqlserver), jtds (jtds:sqlserver), oracle (oracle:thin), and hsql (hsqldb). * Release 0.6.2-alpha2 on 2016-07-21 @@ -256,94 +251,94 @@ Change Log * Release 0.6.2-alpha1 on 2016-07-05 * Experimental support for `clojure.spec` via the new `clojure.java.jdbc.spec` namespace. Requires Clojure 1.9.0 Alpha 8 (or later). - * All options to all functions can now have defaults within the `db-spec` itself [JDBC-136](http://clojure.atlassian.net/browse/JDBC-136). - * `query` (and by extension `find-by-keys` and `get-by-id`) now support `:explain?` and `:explain-fn` options to help support basic performance analysis [JDBC-135](http://clojure.atlassian.net/browse/JDBC-135). - * `insert!` and `insert-multi!` now respect `:identifiers` and `:qualifier` because inserting rows on PostgreSQL returns full rows, not just the newly inserted keys [JDBC-134](http://clojure.atlassian.net/browse/JDBC-134). - * In addition to the `:identifiers` option, you can now use `:qualifier` to specify a namespace qualifier (string) to be used when constructing keywords from SQL column names [JDBC-133](http://clojure.atlassian.net/browse/JDBC-133). + * All options to all functions can now have defaults within the `db-spec` itself [JDBC-136](https://clojure.atlassian.net/browse/JDBC-136). + * `query` (and by extension `find-by-keys` and `get-by-id`) now support `:explain?` and `:explain-fn` options to help support basic performance analysis [JDBC-135](https://clojure.atlassian.net/browse/JDBC-135). + * `insert!` and `insert-multi!` now respect `:identifiers` and `:qualifier` because inserting rows on PostgreSQL returns full rows, not just the newly inserted keys [JDBC-134](https://clojure.atlassian.net/browse/JDBC-134). + * In addition to the `:identifiers` option, you can now use `:qualifier` to specify a namespace qualifier (string) to be used when constructing keywords from SQL column names [JDBC-133](https://clojure.atlassian.net/browse/JDBC-133). * Release 0.6.1 on 2016-05-12 -- **IMPORTANT BUG FIX!** - * `insert!` and `insert-multi!` now default `:transaction?` to `true` (as they should have done in 0.6.0!) [JDBC-128](http://clojure.atlassian.net/browse/JDBC-128). These two functions also have improved docstrings to clarify the difference in behavior between inserting rows as maps compared to inserting rows as a series of column values. - * PostgreSQL support has been improved: java.jdbc is now tested against PostgreSQL locally (as well as SQLite, Apache Derby, HSQLDB, H2, MySQL, MS SQL Server (both MS Type 4 driver and jTDS driver). [JDBC-127](http://clojure.atlassian.net/browse/JDBC-127) and [JDBC-129](http://clojure.atlassian.net/browse/JDBC-129). + * `insert!` and `insert-multi!` now default `:transaction?` to `true` (as they should have done in 0.6.0!) [JDBC-128](https://clojure.atlassian.net/browse/JDBC-128). These two functions also have improved docstrings to clarify the difference in behavior between inserting rows as maps compared to inserting rows as a series of column values. + * PostgreSQL support has been improved: java.jdbc is now tested against PostgreSQL locally (as well as SQLite, Apache Derby, HSQLDB, H2, MySQL, MS SQL Server (both MS Type 4 driver and jTDS driver). [JDBC-127](https://clojure.atlassian.net/browse/JDBC-127) and [JDBC-129](https://clojure.atlassian.net/browse/JDBC-129). * Release 0.6.0 on 2016-05-11 -- **BREAKING RELEASE! DEPRECATED FUNCTIONALITY REMOVED!** - * `find-by-keys` now correctly handles `nil` values [JDBC-126](http://clojure.atlassian.net/browse/JDBC-126). 0.6.0 / 2016-05-11. + * `find-by-keys` now correctly handles `nil` values [JDBC-126](https://clojure.atlassian.net/browse/JDBC-126). 0.6.0 / 2016-05-11. * `find-by-keys` calls `seq` on `:order-by` to treat `[]` as no `ORDER BY` clause. 0.6.0 / 2016-05-11. - * `db-query-with-resultset` now accepts an options map and passes it to `prepare-statement` [JDBC-125](http://clojure.atlassian.net/browse/JDBC-125). 0.6.0-rc2 / 2016-05-07. + * `db-query-with-resultset` now accepts an options map and passes it to `prepare-statement` [JDBC-125](https://clojure.atlassian.net/browse/JDBC-125). 0.6.0-rc2 / 2016-05-07. - Passing the `prepare-statement` options map as the first element of the `[sql & params]` vector is no longer supported and will throw an `IllegalArgumentException`. It was always very poorly documented and almost never used, as far as I can tell. * `db-query-with-resultset` no longer requires the `sql-params` argument to be a vector: a sequence is acceptable. This is in line with other functions that accept a sequence. 0.6.0-rc2 / 2016-05-07. * `db-query-with-resultset` now accepts a bare SQL string or `PreparedStatement` as the `sql-params` argument, when there are no parameters needed. This is in line with other functions that accept SQL or a `PreparedStatement`. 0.6.0-rc2 / 2016-05-07. - * `query`'s options map now is passed to `db-query-with-resultset` and thus can contain options to be used to construct the `PreparedStatement` [JDBC-125](http://clojure.atlassian.net/browse/JDBC-125). 0.6.0-rc2 / 2016-05-07. + * `query`'s options map now is passed to `db-query-with-resultset` and thus can contain options to be used to construct the `PreparedStatement` [JDBC-125](https://clojure.atlassian.net/browse/JDBC-125). 0.6.0-rc2 / 2016-05-07. * Adds `get-by-id` and `find-by-keys` convenience functions (these were easy to add after the API changes in 0.6.0 and we rely very heavily on them at World Singles so putting them in the core for everyone seemed reasonable). 0.6.0-rc1 / 2016-05-04. - `find-by-keys` accepts an `:order-by` option that expects a sequence of orderings; an ordering is a column name (keyword) or a map from column name (keyword) to direction (`:asc` or `:desc`). 0.6.0-rc2 / 2016-05-07. - * Ensures SQL / params are actually vectors prior to destructuring (this addresses an interop edge case from other languages) [JDBC-124](http://clojure.atlassian.net/browse/JDBC-124). 0.6.0-alpha2 / 2016-04-18. - * Fix typo in `insert-multi!` argument validation exception [JDBC-123](http://clojure.atlassian.net/browse/JDBC-123). 0.6.0-alpha2 / 2016-04-18. - * ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](http://clojure.atlassian.net/browse/JDBC-118). 0.6.0-alpha1 / 2016-04-13 + * Ensures SQL / params are actually vectors prior to destructuring (this addresses an interop edge case from other languages) [JDBC-124](https://clojure.atlassian.net/browse/JDBC-124). 0.6.0-alpha2 / 2016-04-18. + * Fix typo in `insert-multi!` argument validation exception [JDBC-123](https://clojure.atlassian.net/browse/JDBC-123). 0.6.0-alpha2 / 2016-04-18. + * ALL DEPRECATED FUNCTIONALITY HAS BEEN REMOVED! [JDBC-118](https://clojure.atlassian.net/browse/JDBC-118). 0.6.0-alpha1 / 2016-04-13 - See changes described in versions 0.5.5 through 0.5.8 for what was deprecated - Use version 0.5.8 as a bridge to identify any deprecated API calls on which your code relies! - `db-transaction` (deprecated in version 0.3.0) has been removed - The `java.jdbc.deprecated` namespace has been removed * Release 0.5.8 on 2016-04-12 - * `db-do-commands` now expects multiple commands to be be wrapped in a vector [JDBC-122](http://clojure.atlassian.net/browse/JDBC-123). The single command form is unchanged (but may be wrapped in a vector). Calling `db-do-commands` with multiple commands (not wrapped in a single vector) will produce a "DEPRECATED" warning printed to the console. - * `db-do-prepared` and `db-do-prepared-return-keys` now expect to receive a `db-spec`, an optional `transaction?` boolean, a `sql-params` argument, and an optional options map. `sql-params` is a vector containing a SQL string or `PreparedStatement` followed by parameters -- like other APIs in this library. In addition, like the `:multi? true` version of `execute!`, `db-do-prepared` can accept a vector that has parameter groups: multiple vectors containing groups of parameter values [JDBC-122](http://clojure.atlassian.net/browse/JDBC-123). Calling `db-do-prepared` with unrolled arguments -- the SQL string / statement followed by parameter groups -- is deprecated and will produce "DEPRECATED" warnings printed to the console. + * `db-do-commands` now expects multiple commands to be be wrapped in a vector [JDBC-122](https://clojure.atlassian.net/browse/JDBC-123). The single command form is unchanged (but may be wrapped in a vector). Calling `db-do-commands` with multiple commands (not wrapped in a single vector) will produce a "DEPRECATED" warning printed to the console. + * `db-do-prepared` and `db-do-prepared-return-keys` now expect to receive a `db-spec`, an optional `transaction?` boolean, a `sql-params` argument, and an optional options map. `sql-params` is a vector containing a SQL string or `PreparedStatement` followed by parameters -- like other APIs in this library. In addition, like the `:multi? true` version of `execute!`, `db-do-prepared` can accept a vector that has parameter groups: multiple vectors containing groups of parameter values [JDBC-122](https://clojure.atlassian.net/browse/JDBC-123). Calling `db-do-prepared` with unrolled arguments -- the SQL string / statement followed by parameter groups -- is deprecated and will produce "DEPRECATED" warnings printed to the console. * Release 0.5.7 on 2016-04-10 - * `(insert! db table [:col] ["val"] {})` syntax, introduced in 0.5.6, threw an exception [JDBC-121](http://clojure.atlassian.net/browse/JDBC-121). + * `(insert! db table [:col] ["val"] {})` syntax, introduced in 0.5.6, threw an exception [JDBC-121](https://clojure.atlassian.net/browse/JDBC-121). * Release 0.5.6 on 2016-04-10 - * `create-table-ddl` now expects the column specs to be wrapped in a single vector and no longer needs the `:options` delimiter to specify the options map [JDBC-120](http://clojure.atlassian.net/browse/JDBC-120). + * `create-table-ddl` now expects the column specs to be wrapped in a single vector and no longer needs the `:options` delimiter to specify the options map [JDBC-120](https://clojure.atlassian.net/browse/JDBC-120). - If column specs are not wrapped in a vector, you will get a "DEPRECATED" warning printed to the console. - * `insert!` now supports only single row insertion; multi-row insertion is deprecated. `insert-multi!` has been added for multi-row insertion. `:options` is no longer needed as a delimiter for the options map [JDBC-119](http://clojure.atlassian.net/browse/JDBC-119). + * `insert!` now supports only single row insertion; multi-row insertion is deprecated. `insert-multi!` has been added for multi-row insertion. `:options` is no longer needed as a delimiter for the options map [JDBC-119](https://clojure.atlassian.net/browse/JDBC-119). - If `insert!` is called with multiple rows, or `:options` is specified, you will get a "DEPRECATED" warning printed to the console. * NOTE: all deprecated functionality will go away in version 0.6.0! * Release 0.5.5 on 2016-04-09 - * Allow options map in all calls that previously took optional keyword arguments [JDBC-117](http://clojure.atlassian.net/browse/JDBC-117). + * Allow options map in all calls that previously took optional keyword arguments [JDBC-117](https://clojure.atlassian.net/browse/JDBC-117). - The unrolled keyword argument forms of call are deprecated -- and print a "DEPRECATED" message to the console! -- and will go away in 0.6.0. * Release 0.5.0 on 2016-03-27 - * Allow PreparedStatement in db-do-prepared-return-keys [JDBC-115](http://clojure.atlassian.net/browse/JDBC-115). - * Remove exception wrapping [JDBC-114](http://clojure.atlassian.net/browse/JDBC-114). + * Allow PreparedStatement in db-do-prepared-return-keys [JDBC-115](https://clojure.atlassian.net/browse/JDBC-115). + * Remove exception wrapping [JDBC-114](https://clojure.atlassian.net/browse/JDBC-114). * Drop Clojure 1.3 compatibility. * Release 0.4.2 on 2015-09-15 - * Remove redundant type hints [JDBC-113](http://clojure.atlassian.net/browse/JDBC-113) - Michael Blume. - * Avoid reflection on `.prepareStatement` [JDBC-112](http://clojure.atlassian.net/browse/JDBC-112) - Michael Blume. - * Add `metadata-query` macro to make metadata query / results easier to work with for [JDBC-107](http://clojure.atlassian.net/browse/JDBC-107). - * `prepare-statement` `:return-keys` may now be a vector of (auto-generated) column names to return, in addition to just being truthy or falsey. This allows keys to be returned for more databases. [JDBC-104](http://clojure.atlassian.net/browse/JDBC-104). - * Officially support H2 (and test against it) to support [JDBC-91](http://clojure.atlassian.net/browse/JDBC-91) and clarify docstrings to improve debugging driver-specific restrictions on SQL. + * Remove redundant type hints [JDBC-113](https://clojure.atlassian.net/browse/JDBC-113) - Michael Blume. + * Avoid reflection on `.prepareStatement` [JDBC-112](https://clojure.atlassian.net/browse/JDBC-112) - Michael Blume. + * Add `metadata-query` macro to make metadata query / results easier to work with for [JDBC-107](https://clojure.atlassian.net/browse/JDBC-107). + * `prepare-statement` `:return-keys` may now be a vector of (auto-generated) column names to return, in addition to just being truthy or falsey. This allows keys to be returned for more databases. [JDBC-104](https://clojure.atlassian.net/browse/JDBC-104). + * Officially support H2 (and test against it) to support [JDBC-91](https://clojure.atlassian.net/browse/JDBC-91) and clarify docstrings to improve debugging driver-specific restrictions on SQL. * Release 0.4.0 / 0.4.1 on 2015-07-26 - * `db-do-prepared` now allows `transaction?` to be omitted when a `PreparedStatement` is passed as the second argument [JDBC-111](http://clojure.atlassian.net/browse/JDBC-111) - Stefan Kamphausen. - * Nested transaction checks isolation level is the same [JDBC-110](http://clojure.atlassian.net/browse/JDBC-110) - Donald Ball. - * Default PostgreSQL port; Support more dbtype/dbname variants [JDBC-109](http://clojure.atlassian.net/browse/JDBC-109). + * `db-do-prepared` now allows `transaction?` to be omitted when a `PreparedStatement` is passed as the second argument [JDBC-111](https://clojure.atlassian.net/browse/JDBC-111) - Stefan Kamphausen. + * Nested transaction checks isolation level is the same [JDBC-110](https://clojure.atlassian.net/browse/JDBC-110) - Donald Ball. + * Default PostgreSQL port; Support more dbtype/dbname variants [JDBC-109](https://clojure.atlassian.net/browse/JDBC-109). * Drop Clojure 1.2 compatibility. * Release 0.3.7 on 2015-05-18 * Bump all driver versions in `project.clj` and re-test. - * Remove duplicate `count` calls in `insert-sql` [JDBC-108](http://clojure.atlassian.net/browse/JDBC-108) - Earl St Sauver. - * Remove driver versions from README and link to Maven Central [JDBC-106](http://clojure.atlassian.net/browse/JDBC-106). - * Fix links in CHANGES and README [JDBC-103](http://clojure.atlassian.net/browse/JDBC-103) - John Walker. + * Remove duplicate `count` calls in `insert-sql` [JDBC-108](https://clojure.atlassian.net/browse/JDBC-108) - Earl St Sauver. + * Remove driver versions from README and link to Maven Central [JDBC-106](https://clojure.atlassian.net/browse/JDBC-106). + * Fix links in CHANGES and README [JDBC-103](https://clojure.atlassian.net/browse/JDBC-103) - John Walker. * Release 0.3.6 on 2014-10-28 - * Arbitrary values allowed for `:cursors`, `:concurrency`, `:result-type` arguments to `prepare-statement` [JDBC-102](http://clojure.atlassian.net/browse/JDBC-102). - * Allow `:as-arrays? :cols-as-is` to omit column name uniqueness when returning result sets as arrrays [JDBC-101](http://clojure.atlassian.net/browse/JDBC-101). - * Add `:timeout` argument to `prepare-statement` [JDBC-100](http://clojure.atlassian.net/browse/JDBC-100). + * Arbitrary values allowed for `:cursors`, `:concurrency`, `:result-type` arguments to `prepare-statement` [JDBC-102](https://clojure.atlassian.net/browse/JDBC-102). + * Allow `:as-arrays? :cols-as-is` to omit column name uniqueness when returning result sets as arrrays [JDBC-101](https://clojure.atlassian.net/browse/JDBC-101). + * Add `:timeout` argument to `prepare-statement` [JDBC-100](https://clojure.atlassian.net/browse/JDBC-100). * Release 0.3.5 on 2014-08-01 * Reflection warnings on executeUpdate addressed. - * HSQLDB and SQLite in-memory strings are now accepted [JDBC-94](http://clojure.atlassian.net/browse/JDBC-94). - * Add support for readonly transactions via :read-only? [JDBC-93](http://clojure.atlassian.net/browse/JDBC-93). + * HSQLDB and SQLite in-memory strings are now accepted [JDBC-94](https://clojure.atlassian.net/browse/JDBC-94). + * Add support for readonly transactions via :read-only? [JDBC-93](https://clojure.atlassian.net/browse/JDBC-93). * Release 0.3.4 on 2014-06-30 - * execute! can now accept a PreparedStatement [JDBC-96](http://clojure.atlassian.net/browse/JDBC-96). - * Support simpler db-spec with :dbtype and :dbname (and optional :host and :port etc) [JDBC-92](http://dev.clojure.org/jire/browse/JDBC-92). - * Support oracle:oci and oracle:thin subprotocols [JDBC-90](http://clojure.atlassian.net/browse/JDBC-90). + * execute! can now accept a PreparedStatement [JDBC-96](https://clojure.atlassian.net/browse/JDBC-96). + * Support simpler db-spec with :dbtype and :dbname (and optional :host and :port etc) [JDBC-92](https://clojure.atlassian.net/browse/JDBC-92). + * Support oracle:oci and oracle:thin subprotocols [JDBC-90](https://clojure.atlassian.net/browse/JDBC-90). * Release 0.3.3 on 2014-01-30 - * Prevent exception/crash when query called with bare SQL string [JDBC-89](http://clojure.atlassian.net/browse/JDBC-89). - * Add :row-fn and :result-set-fn to metadata-result function [JDBC-87](http://clojure.atlassian.net/browse/JDBC-87). + * Prevent exception/crash when query called with bare SQL string [JDBC-89](https://clojure.atlassian.net/browse/JDBC-89). + * Add :row-fn and :result-set-fn to metadata-result function [JDBC-87](https://clojure.atlassian.net/browse/JDBC-87). * Support key/value configuration from URI (Phil Hagelberg). * Release 0.3.2 on 2013-12-30 @@ -352,23 +347,23 @@ Change Log * Release 0.3.1 on 2013-12-29 (broken; use 0.3.2 instead) * Improve docstrings and add :arglists for better auto-generated documentation. * Make insert-sql private - technically a breaking change but it should never have been public: sorry folks! - * Provide better protocol for setting parameters in prepared statements [JDBC-86](http://clojure.atlassian.net/browse/JDBC-86). - * Fix parens in two deprecated tests [JDBC-85](http://clojure.atlassian.net/browse/JDBC-85). + * Provide better protocol for setting parameters in prepared statements [JDBC-86](https://clojure.atlassian.net/browse/JDBC-86). + * Fix parens in two deprecated tests [JDBC-85](https://clojure.atlassian.net/browse/JDBC-85). * Made create-table-ddl less aggressive about applying as-sql-name so only first name in a column spec is affected. * Release 0.3.0 on 2013-12-16 - * Ensure canonical Boolean to workaround strange behavior in some JDBC drivers [JDBC-84](http://clojure.atlassian.net/browse/JDBC-84). - * Rename recently introduced test to ensure unique names [JDBC-83](http://clojure.atlassian.net/browse/JDBC-83). - * Rename unused arguments in protocol implementation to support Android [JDBC-82](http://clojure.atlassian.net/browse/JDBC-82). - * Correctly handle empty param group sequence in execute! (which only seemed to affect SQLite) [JDBC-65](http://clojure.atlassian.net/browse/JDBC-65). + * Ensure canonical Boolean to workaround strange behavior in some JDBC drivers [JDBC-84](https://clojure.atlassian.net/browse/JDBC-84). + * Rename recently introduced test to ensure unique names [JDBC-83](https://clojure.atlassian.net/browse/JDBC-83). + * Rename unused arguments in protocol implementation to support Android [JDBC-82](https://clojure.atlassian.net/browse/JDBC-82). + * Correctly handle empty param group sequence in execute! (which only seemed to affect SQLite) [JDBC-65](https://clojure.atlassian.net/browse/JDBC-65). * Release 0.3.0-rc1 on 2013-12-12 - * Deprecate db-transaction (new in 0.3.0) in favor of with-db-transaction [JDBC-81](http://clojure.atlassian.net/browse/JDBC-81). - * Add with-db-metadata macro and metadata-result function to make it easier to work with SQL metadata [JDBC-80](http://clojure.atlassian.net/browse/JDBC-80). - * Add with-db-connection macro to make it easier to run groups of operations against a single open connection [JDBC-79](http://clojure.atlassian.net/browse/JDBC-79). - * Add ISQLValue protocol to make it easier to support custom SQL types for parameters in SQL statements [JDBC-77](http://clojure.atlassian.net/browse/JDBC-77). - * Add support for :isolation in with-db-transaction [JDBC-75](http://clojure.atlassian.net/browse/JDBC-75). - * Add :user as an alias for :username for DataSource connections [JDBC-74](http://clojure.atlassian.net/browse/JDBC-74). + * Deprecate db-transaction (new in 0.3.0) in favor of with-db-transaction [JDBC-81](https://clojure.atlassian.net/browse/JDBC-81). + * Add with-db-metadata macro and metadata-result function to make it easier to work with SQL metadata [JDBC-80](https://clojure.atlassian.net/browse/JDBC-80). + * Add with-db-connection macro to make it easier to run groups of operations against a single open connection [JDBC-79](https://clojure.atlassian.net/browse/JDBC-79). + * Add ISQLValue protocol to make it easier to support custom SQL types for parameters in SQL statements [JDBC-77](https://clojure.atlassian.net/browse/JDBC-77). + * Add support for :isolation in with-db-transaction [JDBC-75](https://clojure.atlassian.net/browse/JDBC-75). + * Add :user as an alias for :username for DataSource connections [JDBC-74](https://clojure.atlassian.net/browse/JDBC-74). * Release 0.3.0-beta2 on 2013-11-24 * **BREAKING CHANGES!** @@ -376,40 +371,40 @@ Change Log * The older API (0.2.3) which was deprecated in earlier 0.3.0 builds has moved to `clojure.java.jdbc.deprecated` to help streamline the API for 0.3.0 and clean up the documentation. * Release 0.3.0-beta1 on 2013-11-03 - * query as-arrays? now allows you to leverage lazy result fetching [JDBC-72](http://clojure.atlassian.net/browse/JDBC-72). + * query as-arrays? now allows you to leverage lazy result fetching [JDBC-72](https://clojure.atlassian.net/browse/JDBC-72). * "h2" is recognized as a protocol shorthand for org.h2.Driver - * Tests no longer use :1 literal [JDBC-71](http://clojure.atlassian.net/browse/JDBC-71). - * Conditional use of javax.naming.InitialContext so it can be compiled on Android [JDBC-69](http://clojure.atlassian.net/browse/JDBC-69). - * New db-query-with-resultset function replaces private db-with-query-results* and processes a raw ResultSet object [JDBC-63](http://clojure.atlassian.net/browse/JDBC-63). - * Allow :set-parameters in db-spec to override set-parameters internal function to allow per-DB special handling of SQL parameters values (such as null for Teradata) [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40). + * Tests no longer use :1 literal [JDBC-71](https://clojure.atlassian.net/browse/JDBC-71). + * Conditional use of javax.naming.InitialContext so it can be compiled on Android [JDBC-69](https://clojure.atlassian.net/browse/JDBC-69). + * New db-query-with-resultset function replaces private `db-with-query-results*` and processes a raw ResultSet object [JDBC-63](https://clojure.atlassian.net/browse/JDBC-63). + * Allow :set-parameters in db-spec to override set-parameters internal function to allow per-DB special handling of SQL parameters values (such as null for Teradata) [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40). * Release 0.3.0-alpha5 on 2013-09-15 - * DDL now supports entities naming strategy [JDBC-53](http://clojure.atlassian.net/browse/JDBC-53). - * Attempt to address potential memory leaks due to closures - see [Christophe Grand's blog post on Macros, closures and unexpected object retention](http://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/). - * Documentation has moved to [Using java.jdbc on Clojure-Doc.org](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) + * DDL now supports entities naming strategy [JDBC-53](https://clojure.atlassian.net/browse/JDBC-53). + * Attempt to address potential memory leaks due to closures - see [Christophe Grand's blog post on Macros, closures and unexpected object retention](https://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/). + * Documentation has moved to [Using java.jdbc on Clojure-Doc.org](https://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) * Added Leiningen support for easier development/testing (Maven is still the primary build tool). - * Added create-index / drop-index DDL [JDBC-62](http://clojure.atlassian.net/browse/JDBC-62) - moquist - * Make transaction? boolean optional in various db-do-* functions + * Added create-index / drop-index DDL [JDBC-62](https://clojure.atlassian.net/browse/JDBC-62) - moquist + * Make transaction? boolean optional in various `db-do-*` functions * Create clojure.java.jdbc.ddl namespace * Add create-table, drop-table, create-index and drop-index * Deprecate create-table, create-table-ddl and drop-table in main namespace * Update README to clarify PostgreSQL instructions. - * Fix test suite for PostgreSQL [JDBC-59](http://clojure.atlassian.net/browser/JDBC-59) - * Improve hooks for Oracle data type handling [JDBC-57](http://clojure.atlassian.net/browser/JDBC-57) - * Fix reflection warnings [JDBC-55](http://clojure.atlassian.net/browser/JDBC-55) + * Fix test suite for PostgreSQL [JDBC-59](https://clojure.atlassian.net/browser/JDBC-59) + * Improve hooks for Oracle data type handling [JDBC-57](https://clojure.atlassian.net/browser/JDBC-57) + * Fix reflection warnings [JDBC-55](https://clojure.atlassian.net/browser/JDBC-55) * Release 0.3.0-alpha4 on 2013-05-11 - * Fix connection leaks [JDBC-54](http://clojure.atlassian.net/browser/JDBC-54) + * Fix connection leaks [JDBC-54](https://clojure.atlassian.net/browser/JDBC-54) * Allow order-by to accept empty sequence (and return empty string) * Release 0.3.0-alpha3 on 2013-05-04 * Fix macro / import interaction by fully qualifying Connection type. * Release 0.3.0-alpha2 on 2013-05-03 - * Address [JDBC-51](http://clojure.atlassian.net/browse/JDBC-51) by declaring get-connection returns java.sql.Connection - * Add IResultSetReadColumn protocol extension point for custom read conversions [JDBC-46](http://clojure.atlassian.net/browse/JDBC-46) - * Add :multi? to execute! so it can be used for repeated operations [JDBC-52](http://clojure.atlassian.net/browse/JDBC-52) - * Reverted specialized handling of NULL values (reopens [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40)) + * Address [JDBC-51](https://clojure.atlassian.net/browse/JDBC-51) by declaring get-connection returns java.sql.Connection + * Add IResultSetReadColumn protocol extension point for custom read conversions [JDBC-46](https://clojure.atlassian.net/browse/JDBC-46) + * Add :multi? to execute! so it can be used for repeated operations [JDBC-52](https://clojure.atlassian.net/browse/JDBC-52) + * Reverted specialized handling of NULL values (reopens [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40)) * Rename :as-arrays to :as-arrays? since it is boolean * Add curried version of clojure.java.jdbc.sql/as-quoted-str * Officially deprecate resultset-seq @@ -418,36 +413,36 @@ Change Log * MAJOR API OVERHAUL! * Most of the old 0.2.x API has been deprecated and a new, more idiomatic API introduced, along with a minimal DSL to generate basic SQL * Specifics: - * Add insert!, query, update!, delete! and execute! high-level API [JDBC-20](http://clojure.atlassian.net/browse/JDBC-20) + * Add insert!, query, update!, delete! and execute! high-level API [JDBC-20](https://clojure.atlassian.net/browse/JDBC-20) * Add optional SQL-generating DSL in clojure.java.jdbc.sql (implied by JDBC-20) * Add db- prefixed versions of low-level API * Add db-transaction macro * Add result-set-seq as replacement for resultset-seq (which will be deprecated) - * Transaction now correctly rollback on non-Exception Throwables [JDBC-43](http://clojure.atlassian.net/browse/JDBC-43) - * Rewrite old API functions in terms of new API, and deprecate old API [JDBC-43](http://clojure.atlassian.net/browse/JDBC-43) - * Add :as-arrays to query / result-set-seq [JDBC-41](http://clojure.atlassian.net/browse/JDBC-41) - * Better handling of NULL values [JDBC-40](http://clojure.atlassian.net/browse/JDBC-40) and [JDBC-18](http://clojure.atlassian.net/browse/JDBC-18) + * Transaction now correctly rollback on non-Exception Throwables [JDBC-43](https://clojure.atlassian.net/browse/JDBC-43) + * Rewrite old API functions in terms of new API, and deprecate old API [JDBC-43](https://clojure.atlassian.net/browse/JDBC-43) + * Add :as-arrays to query / result-set-seq [JDBC-41](https://clojure.atlassian.net/browse/JDBC-41) + * Better handling of NULL values [JDBC-40](https://clojure.atlassian.net/browse/JDBC-40) and [JDBC-18](https://clojure.atlassian.net/browse/JDBC-18) Note: JDBC-40 is being reverted in 0.3.0-alpha2 because it introduces regressions in PostgreSQL - * db-do-commands allows you to execute SQL without a transaction wrapping it [JDBC-38](http://clojure.atlassian.net/browse/JDBC-38) + * db-do-commands allows you to execute SQL without a transaction wrapping it [JDBC-38](https://clojure.atlassian.net/browse/JDBC-38) * Remove reflection warning from execute-batch * Add notes to README about 3rd party database driver dependencies * Add optional :identifiers argument to resultset-seq so you can explicitly pass in the naming strategy * Release 0.2.3 on 2012-06-18 * as-str now treats a.b as two identifiers separated by . so quoting produces [a].[b] instead of [a.b] - * Add :connection-uri option [JDBC-34](http://clojure.atlassian.net/browse/JDBC-34) + * Add :connection-uri option [JDBC-34](https://clojure.atlassian.net/browse/JDBC-34) * Release 0.2.2 on 2012-06-10 - * Handle Oracle unknown row count affected [JDBC-33](http://clojure.atlassian.net/browse/JDBC-33) - * Handle jdbc: prefix in string db-specs [JDBC-32](http://clojure.atlassian.net/browse/JDBC-32) - * Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](http://clojure.atlassian.net/browse/JDBC-31) + * Handle Oracle unknown row count affected [JDBC-33](https://clojure.atlassian.net/browse/JDBC-33) + * Handle jdbc: prefix in string db-specs [JDBC-32](https://clojure.atlassian.net/browse/JDBC-32) + * Handle empty columns in make column unique (Juergen Hoetzel) [JDBC-31](https://clojure.atlassian.net/browse/JDBC-31) * Release 0.2.1 on 2012-05-10 - * Result set performance enhancement (Juergen Hoetzel) [JDBC-29](http://clojure.atlassian.net/browse/JDBC-29) - * Make do-prepared-return-keys (for Korma team) [JDBC-30](http://clojure.atlassian.net/browse/JDBC-30) + * Result set performance enhancement (Juergen Hoetzel) [JDBC-29](https://clojure.atlassian.net/browse/JDBC-29) + * Make do-prepared-return-keys (for Korma team) [JDBC-30](https://clojure.atlassian.net/browse/JDBC-30) * Release 0.2.0 on 2012-04-23 - * Merge internal namespace into main jdbc namespace [JDBC-19](http://clojure.atlassian.net/browse/JDBC-19) + * Merge internal namespace into main jdbc namespace [JDBC-19](https://clojure.atlassian.net/browse/JDBC-19) * Release 0.1.4 on 2012-04-15 * Unwrap RTE for nested transaction exceptions (we already @@ -455,30 +450,30 @@ Change Log * Remove reflection warning unwrapping RunTimeException (Alan Malloy) * Release 0.1.3 on 2012-02-29 - * Fix generated keys inside transactions for SQLite3 [JDBC-26](http://clojure.atlassian.net/browse/JDBC-26) + * Fix generated keys inside transactions for SQLite3 [JDBC-26](https://clojure.atlassian.net/browse/JDBC-26) * Release 0.1.2 on 2012-02-29 - * Handle prepared statement params correctly [JDBC-23](http://clojure.atlassian.net/browse/JDBC-23) - * Add support for SQLite3 [JDBC-26](http://clojure.atlassian.net/browse/JDBC-26) - * Replace replicate (deprecated) with repeat [JDBC-27](http://clojure.atlassian.net/browse/JDBC-27) + * Handle prepared statement params correctly [JDBC-23](https://clojure.atlassian.net/browse/JDBC-23) + * Add support for SQLite3 [JDBC-26](https://clojure.atlassian.net/browse/JDBC-26) + * Replace replicate (deprecated) with repeat [JDBC-27](https://clojure.atlassian.net/browse/JDBC-27) * Ensure MS SQL Server passes tests with both Microsoft and jTDS drivers * Build server now tests derby, hsqldb and sqlite by default * Update README per Stuart Sierra's outline for contrib projects * Release 0.1.1 on 2011-11-02 - * Accept string or URI in connection definition [JDBC-21](http://clojure.atlassian.net/browse/JDBC-21) - * Allow driver, port and subprotocol to be deduced [JDBC-22](http://clojure.atlassian.net/browse/JDBC-22) + * Accept string or URI in connection definition [JDBC-21](https://clojure.atlassian.net/browse/JDBC-21) + * Allow driver, port and subprotocol to be deduced [JDBC-22](https://clojure.atlassian.net/browse/JDBC-22) * Release 0.1.0 on 2011-10-16 - * Remove dependence on deprecated structmap [JDBC-15](http://clojure.atlassian.net/browse/JDBC-15) + * Remove dependence on deprecated structmap [JDBC-15](https://clojure.atlassian.net/browse/JDBC-15) * Release 0.0.7 on 2011-10-11 - * Rename duplicate columns [JDBC-9](http://clojure.atlassian.net/browse/JDBC-9) - * Ensure do-preared traps invalid SQL [JDBC-16](http://clojure.atlassian.net/browse/JDBC-16) + * Rename duplicate columns [JDBC-9](https://clojure.atlassian.net/browse/JDBC-9) + * Ensure do-preared traps invalid SQL [JDBC-16](https://clojure.atlassian.net/browse/JDBC-16) * Release 0.0.6 on 2011-08-04 * Improve exception handling (unwrap RTE) - * Don't use batch for update (causes exceptions on Apache Derby) [JDBC-12](http://dev.clojure.org/jire/JDBC-12) + * Don't use batch for update (causes exceptions on Apache Derby) [JDBC-12](https://clojure.atlassian.net/browse/JDBC-12) * Add test suite * Release 0.0.5 on 2011-07-18 @@ -487,23 +482,23 @@ Change Log * Support databases that cannot return generated keys * Release 0.0.4 on 2011-07-17 - * Allow :table-spec {string} in create-table [JDBC-4](http://dev.clojure.org/jire/JDBC-4) - * Remove reflection warnings [JDBC-8](http://dev.clojure.org/jire/JDBC-8) - * Ensure transactions are not committed when Error occurs [JDBC-11](http://dev.clojure.org/jire/JDBC-11) + * Allow :table-spec {string} in create-table [JDBC-4](https://clojure.atlassian.net/browse/JDBC-4) + * Remove reflection warnings [JDBC-8](https://clojure.atlassian.net/browse/JDBC-8) + * Ensure transactions are not committed when Error occurs [JDBC-11](https://clojure.atlassian.net/browse/JDBC-11) * Release 0.0.3 on 2011-07-01 - * Key generation compatibility with MS SQL Server, PostgreSQL [JDBC-10](http://clojure.atlassian.net/browse/JDBC-10) + * Key generation compatibility with MS SQL Server, PostgreSQL [JDBC-10](https://clojure.atlassian.net/browse/JDBC-10) * Release 0.0.2 on 2011-06-07 - * Clojure 1.2 compatibility [JDBC-7](http://clojure.atlassian.net/browse/JDBC-7) + * Clojure 1.2 compatibility [JDBC-7](https://clojure.atlassian.net/browse/JDBC-7) * Release 0.0.1 on 2011-05-07 * Initial release * Changes from clojure.contrib.sql: - * Expose print-... functions; no longer write exceptions to **\*out\*** + * Expose print-... functions; no longer write exceptions to `\*out\*` * Define resultset-seq to replace clojure.core/resultset-seq - * Add naming / quoting strategies (see [name mapping documentation](http://clojure.github.com/java.jdbc/doc/clojure/java/jdbc/NameMapping.html) + * Add naming / quoting strategies (see [name mapping documentation](https://clojure.github.io/java.jdbc/doc/clojure/java/jdbc/NameMapping.html) * Return generated keys from insert operations, where possible * Add insert-record function * Clojure 1.3 compatibility @@ -513,7 +508,7 @@ Copyright and License Copyright (c) Sean Corfield, Stephen Gilardi, 2011-2014. 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 +License 1.0 (https://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 From 6d61891318dfbd9983ef6f35ee25660db3405854 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 15 Apr 2021 15:38:46 -0700 Subject: [PATCH 154/175] Update parent pom to 1.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7dd53757..cce28fd3 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.clojure pom.contrib - 0.2.2 + 1.1.0 From a0c5bd1a9d52080f320e966199d6c8446a85f1dd Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 4 Apr 2022 11:24:21 -0500 Subject: [PATCH 155/175] fix links to clojure-docs --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 338996f7..97e1c82f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ clojure.java.jdbc A low-level Clojure wrapper for JDBC-based access to databases. This project is "Stable" (no longer "Active"). It has effectively been superseded by [seancorfield/next.jdbc](https://github.com/seancorfield/next-jdbc). -For higher level DSLs and migration libraries that are compatible, see the [documentation](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html). +For higher level DSLs and migration libraries that are compatible, see the [documentation](https://clojure-doc.org/articles/ecosystem/java_jdbc/home). Formerly known as `clojure.contrib.sql`. @@ -12,10 +12,10 @@ This library is mature and stable. It is widely used and its use is described in Documentation ======================================== * [API Reference](https://clojure.github.io/java.jdbc/) (Autogenerated) -* [Overview](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html) -* [Manipulating Data with SQL](http://clojure-doc.org/articles/ecosystem/java_jdbc/using_sql.html) -* [How to Reuse Database Connections](http://clojure-doc.org/articles/ecosystem/java_jdbc/reusing_connections.html) -* [Using DDL and Metadata](http://clojure-doc.org/articles/ecosystem/java_jdbc/using_ddl.html) +* [Overview](https://clojure-doc.org/articles/ecosystem/java_jdbc/home) +* [Manipulating Data with SQL](https://clojure-doc.org/articles/ecosystem/java_jdbc/using_sql) +* [How to Reuse Database Connections](https://clojure-doc.org/articles/ecosystem/java_jdbc/reusing_connections) +* [Using DDL and Metadata](https://clojure-doc.org/articles/ecosystem/java_jdbc/using_ddl) Support ======================================== @@ -106,7 +106,7 @@ Example Usage {:row-fn :cost}) ;; (24) ``` -For more detail see the [API reference](https://clojure.github.io/java.jdbc/) or [documentation](http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html). +For more detail see the [API reference](https://clojure.github.io/java.jdbc/) or [documentation](https://clojure-doc.org/articles/ecosystem/java_jdbc/home). Developer Information ======================================== From af72059bdc161bde3436dab2eaefdef0c0111499 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 7 Oct 2022 07:21:21 -0700 Subject: [PATCH 156/175] ignore more dev env stuff --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 51cb74c5..1ba3bb61 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ *.jar .classpath +.clj-kondo/.cache .cpcache +.lsp/.cache +.portal .project .rebl .settings From a7f14dc607b339ca87e2d1a047be7dfefc8fb0c7 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 26 May 2023 15:21:37 -0500 Subject: [PATCH 157/175] add actions --- .github/workflows/release.yml | 19 +++++++++++++++++++ .github/workflows/snapshot.yml | 8 ++++++++ .github/workflows/test.yml | 7 +++++++ README.md | 5 ++--- 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/snapshot.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..e2718bd3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,19 @@ +name: Release on demand + +on: + workflow_dispatch: + inputs: + releaseVersion: + description: "Version to release" + required: true + snapshotVersion: + description: "Snapshot version after release" + required: true + +jobs: + call-release: + uses: clojure/build.ci/.github/workflows/release.yml@master + with: + releaseVersion: ${{ github.event.inputs.releaseVersion }} + snapshotVersion: ${{ github.event.inputs.snapshotVersion }} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml new file mode 100644 index 00000000..24729578 --- /dev/null +++ b/.github/workflows/snapshot.yml @@ -0,0 +1,8 @@ +name: Snapshot on demand + +on: [workflow_dispatch] + +jobs: + call-snapshot: + uses: clojure/build.ci/.github/workflows/snapshot.yml@master + secrets: inherit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..1fa127c9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,7 @@ +name: Test + +on: [push] + +jobs: + call-test: + uses: clojure/build.ci/.github/workflows/test.yml@master diff --git a/README.md b/README.md index 97e1c82f..69ff3ed7 100644 --- a/README.md +++ b/README.md @@ -113,8 +113,7 @@ Developer Information * [GitHub project](https://github.com/clojure/java.jdbc) * [Bug Tracker](https://clojure.atlassian.net/browse/JDBC) -* [Continuous Integration](https://build.clojure.org/job/java.jdbc/) -* [Compatibility Test Matrix](https://build.clojure.org/job/java.jdbc-test-matrix/) +* [Continuous Integration](https://github.com/clojure/java.jdbc/actions/workflows/test.yml) * Testing: * Currently by default tests run only against Derby and HSQLDB, the in-process databases. @@ -506,7 +505,7 @@ Change Log Copyright and License ======================================== -Copyright (c) Sean Corfield, Stephen Gilardi, 2011-2014. All rights reserved. The use and +Copyright (c) Sean Corfield, Stephen Gilardi, 2011-2023. All rights reserved. The use and distribution terms for this software are covered by the Eclipse Public License 1.0 (https://opensource.org/licenses/eclipse-1.0.php) which can be found in the file epl-v10.html at the root of this distribution. From 7a374ac4f35d579f15e85866b721def9ae472e81 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 26 May 2023 15:22:35 -0500 Subject: [PATCH 158/175] bump version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cce28fd3..2527ee30 100644 --- a/pom.xml +++ b/pom.xml @@ -96,7 +96,7 @@ org.xerial sqlite-jdbc - 3.23.1 + 3.41.2.2 test From 528dd9feb3d5ea5b2e38e1d2e8285ed156a65506 Mon Sep 17 00:00:00 2001 From: JarrodCTaylor Date: Wed, 16 Aug 2023 12:45:30 -0500 Subject: [PATCH 159/175] Added github action to build api docs --- .github/workflows/doc-build.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/doc-build.yml diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml new file mode 100644 index 00000000..685f14ea --- /dev/null +++ b/.github/workflows/doc-build.yml @@ -0,0 +1,10 @@ +name: Build API Docs + +on: + workflow_dispatch: + +jobs: + call-doc-build-workflow: + uses: clojure/build.ci/.github/workflows/doc-build.yml@master + with: + project: clojure/java.jdbc From 8458e4892ddb4169c67b571c75377d95cc574873 Mon Sep 17 00:00:00 2001 From: JarrodCTaylor Date: Wed, 30 Aug 2023 22:26:38 -0500 Subject: [PATCH 160/175] Fix EPL license link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69ff3ed7..cb29432b 100644 --- a/README.md +++ b/README.md @@ -507,7 +507,7 @@ Copyright and License Copyright (c) Sean Corfield, Stephen Gilardi, 2011-2023. All rights reserved. The use and distribution terms for this software are covered by the Eclipse Public -License 1.0 (https://opensource.org/licenses/eclipse-1.0.php) which can +License 1.0 (https://opensource.org/license/epl-1-0/) 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 From 25c1bbb7e7d1d077be8b6906d87627fb8a82b8f9 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 19 Feb 2024 12:35:31 -0600 Subject: [PATCH 161/175] update parent pom and versions --- deps.edn | 8 ++++---- pom.xml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/deps.edn b/deps.edn index 540e15d9..7d6b7e47 100644 --- a/deps.edn +++ b/deps.edn @@ -5,7 +5,7 @@ {:paths ["src/main/clojure"] :aliases {:test {:extra-paths ["src/test/clojure"] - :extra-deps {org.clojure/test.check {:mvn/version "0.10.0"} + :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} org.apache.derby/derby {:mvn/version "10.14.2.0"} org.hsqldb/hsqldb {:mvn/version "2.4.1"} com.h2database/h2 {:mvn/version "1.4.197"} @@ -20,10 +20,10 @@ :1.7 {:override-deps {org.clojure/clojure {:mvn/version "1.7.0"}}} :1.8 {:override-deps {org.clojure/clojure {:mvn/version "1.8.0"}}} :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} - :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.0"}}} - :master {:override-deps {org.clojure/clojure {:mvn/version "1.11.0-master-SNAPSHOT"}}} + :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} + :master {:override-deps {org.clojure/clojure {:mvn/version "1.12.0-master-SNAPSHOT"}}} :perf {:extra-paths ["src/perf/clojure"] - :extra-deps {criterium {:mvn/version "0.4.4"}} + :extra-deps {criterium {:mvn/version "0.4.6"}} :jvm-opts ["-server" "-Xmx4096m" "-Dclojure.compiler.direct-linking=true"]} diff --git a/pom.xml b/pom.xml index 2527ee30..27a7fce3 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.clojure pom.contrib - 1.1.0 + 1.2.0 @@ -28,7 +28,7 @@ - 1.7.0 + 1.9.0 @@ -108,7 +108,7 @@ org.clojure test.check - 0.9.0 + 1.1.1 test From 25fcc830c70208df8827038be3c881d2517f039c Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Mon, 19 Feb 2024 12:56:09 -0600 Subject: [PATCH 162/175] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb29432b..b36cbaf9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ clojure.java.jdbc ======================================== -A low-level Clojure wrapper for JDBC-based access to databases. This project is "Stable" (no longer "Active"). It has effectively been superseded by [seancorfield/next.jdbc](https://github.com/seancorfield/next-jdbc). +A low-level Clojure wrapper for JDBC-based access to databases. This project is "Inactive". It has effectively been superseded by [seancorfield/next.jdbc](https://github.com/seancorfield/next-jdbc). For higher level DSLs and migration libraries that are compatible, see the [documentation](https://clojure-doc.org/articles/ecosystem/java_jdbc/home). From 44b9e74a2440e01469790c77a54f648f692668e6 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Wed, 27 Mar 2024 15:06:56 -0700 Subject: [PATCH 163/175] some version updates Signed-off-by: Sean Corfield --- .gitignore | 5 ++--- .joker | 3 --- CHANGES.md | 4 ++++ deps.edn | 25 ++++++++++----------- pom.xml | 7 +++--- run-tests.sh | 4 ++-- src/main/clojure/clojure/java/jdbc.clj | 5 +++-- src/test/clojure/clojure/java/jdbc_test.clj | 10 +++++---- 8 files changed, 33 insertions(+), 30 deletions(-) delete mode 100644 .joker diff --git a/.gitignore b/.gitignore index 1ba3bb61..81f14885 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -*.jar +.calva/repl.calva-repl .classpath .clj-kondo/.cache .cpcache @@ -7,6 +7,7 @@ .project .rebl .settings +*.jar /.lein-failures /.lein-repl-history /.nrepl-port @@ -18,5 +19,3 @@ derby.log settings.xml target test-all.sh -/postgres-down.sh -/postgres-up.sh diff --git a/.joker b/.joker deleted file mode 100644 index 0fd26273..00000000 --- a/.joker +++ /dev/null @@ -1,3 +0,0 @@ -{:known-macros [clojure.java.jdbc/with-db-connection - clojure.java.jdbc/with-db-metadata - clojure.java.jdbc/with-db-transaction]} diff --git a/CHANGES.md b/CHANGES.md index 4a6aba18..b0db8bea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +Changes not yet released +* Update most testing dependencies (and update a couple of tests to match). +* Drop support for Clojure 1.7.0. Test against 1.8, 1.9, 1.10, 1.11, and 1.12 (Alpha 9). + Changes in 0.7.12 * Make the protocols `ISQLValue`, `ISQLParameter`, and `IResultSetReadColumn` extensible via metadata. diff --git a/deps.edn b/deps.edn index 7d6b7e47..4b1a182a 100644 --- a/deps.edn +++ b/deps.edn @@ -1,35 +1,34 @@ ;; You can run clojure.java.jdbc tests with: clj -A:test:runner ;; You can also specify an alias to select which version of Clojure to test -;; against: :1.7 :1.8 :1.9 :master +;; against: :1.8 :1.9 :1.10 :1.11 :1.12 {:paths ["src/main/clojure"] :aliases {:test {:extra-paths ["src/test/clojure"] :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} org.apache.derby/derby {:mvn/version "10.14.2.0"} - org.hsqldb/hsqldb {:mvn/version "2.4.1"} + org.hsqldb/hsqldb$jdk8 {:mvn/version "2.7.2"} com.h2database/h2 {:mvn/version "1.4.197"} net.sourceforge.jtds/jtds {:mvn/version "1.3.1"} ;; Note: Tests fail with 6.0.2+ driver mysql/mysql-connector-java {:mvn/version "5.1.41"} - org.postgresql/postgresql {:mvn/version "42.2.2.jre7"} - com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.7.1"} - org.xerial/sqlite-jdbc {:mvn/version "3.23.1"} - ;; Note: Assumes Java 8; there's a .jre7 version as well - com.microsoft.sqlserver/mssql-jdbc {:mvn/version "6.2.2.jre8"}}} - :1.7 {:override-deps {org.clojure/clojure {:mvn/version "1.7.0"}}} + org.postgresql/postgresql {:mvn/version "42.7.3"} + com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.8.9"} + org.xerial/sqlite-jdbc {:mvn/version "3.45.2.0"} + ;; Note: Assumes Java 8; there's a .jre11 version as well + com.microsoft.sqlserver/mssql-jdbc {:mvn/version "12.6.1.jre8"}}} :1.8 {:override-deps {org.clojure/clojure {:mvn/version "1.8.0"}}} :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} - :master {:override-deps {org.clojure/clojure {:mvn/version "1.12.0-master-SNAPSHOT"}}} + :1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.2"}}} + :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0-alpha9"}}} :perf {:extra-paths ["src/perf/clojure"] - :extra-deps {criterium {:mvn/version "0.4.6"}} + :extra-deps {criterium/criterium {:mvn/version "0.4.6"}} :jvm-opts ["-server" "-Xmx4096m" "-Dclojure.compiler.direct-linking=true"]} :runner - {:extra-deps {com.cognitect/test-runner - {:git/url "https://github.com/cognitect-labs/test-runner" - :sha "76568540e7f40268ad2b646110f237a60295fa3c"}} + {:extra-deps {io.github.cognitect-labs/test-runner + {:git/tag "v0.5.1" :git/sha "dfb30dd"}} :main-opts ["-m" "cognitect.test-runner" "-d" "src/test/clojure"]}}} diff --git a/pom.xml b/pom.xml index 27a7fce3..9104128f 100644 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,8 @@ org.hsqldb hsqldb - 2.4.1 + 2.7.2 + jdk8 test @@ -90,13 +91,13 @@ org.postgresql postgresql - 42.2.2.jre7 + 42.7.3 test org.xerial sqlite-jdbc - 3.41.2.2 + 3.45.2.0 test diff --git a/run-tests.sh b/run-tests.sh index f4325f09..ed2a4f69 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -44,10 +44,10 @@ dbs="derby h2 hsqldb sqlite" # Start with clean databases each time to avoid slowdown rm -rf clojure_test_* -versions="1.7 1.8 1.9 1.10 master" +versions="1.8 1.9 1.10 1.11 1.12" for v in $versions do - TEST_DBS="$dbs $*" clj -A:test:runner:$v + TEST_DBS="$dbs $*" clj -M:test:runner:$v if test $? -ne 0 then exit $? diff --git a/src/main/clojure/clojure/java/jdbc.clj b/src/main/clojure/clojure/java/jdbc.clj index e90c8850..5bcf5236 100644 --- a/src/main/clojure/clojure/java/jdbc.clj +++ b/src/main/clojure/clojure/java/jdbc.clj @@ -540,7 +540,7 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} locales where the lower case version of a character is not a valid SQL entity name (e.g., Turkish)." [^String s] - (.toLowerCase s (Locale/US))) + (.toLowerCase s Locale/US)) (defn result-set-seq "Creates and returns a lazy sequence of maps corresponding to the rows in the @@ -1507,7 +1507,8 @@ http://clojure-doc.org/articles/ecosystem/java_jdbc/home.html"} (map second rs))) result-set-fn (throw (ex-info (str "Cannot apply result-set-fn to" - " non-homogeneous generated keys array") rs)) + " non-homogeneous generated keys array") + {:generated-keys rs})) :else ;; non-non-homogeneous generated keys array - return as-is rs)) diff --git a/src/test/clojure/clojure/java/jdbc_test.clj b/src/test/clojure/clojure/java/jdbc_test.clj index 80800afe..f8b11819 100644 --- a/src/test/clojure/clojure/java/jdbc_test.clj +++ b/src/test/clojure/clojure/java/jdbc_test.clj @@ -974,8 +974,8 @@ []) (execute-multi-insert db)))] (case (db-type db) - ;; SQLite only returns the last key inserted in a batch - "sqlite" (is (= [(returned-key db 2)] new-keys)) + ;; SQLite returns nothing useful now + "sqlite" (is (= [] new-keys)) ;; Derby returns a single row count "derby" (is (= [(returned-key db 1)] new-keys)) ;; H2 returns dummy keys @@ -1015,8 +1015,8 @@ 0) (execute-multi-insert db))] (case (db-type db) - ;; SQLite only returns the last key inserted in a batch - "sqlite" (is (= 1 n)) + ;; SQLite returns nothing useful now + "sqlite" (is (= 0 n)) ;; Derby returns a single row count "derby" (is (= 1 n)) ;; H2 returns (zero) keys now @@ -1215,6 +1215,7 @@ (is (= [1] delete-result)) (is (= [] rows)))))) +#_{:clj-kondo/ignore [:invalid-arity]} (deftest illegal-insert-arguments (doseq [db (test-specs)] (illegal-arg-or-spec "insert!" (sql/insert! db)) @@ -1276,6 +1277,7 @@ [[:foo :int :default 0]] {:entities (sql/quoted :mysql)})))) +#_{:clj-kondo/ignore [:unresolved-symbol]} (comment db (sql/create-table-ddl table From 507bd16f625ec2ef44d848a23c712b6174544c5b Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 23 May 2024 19:27:52 -0700 Subject: [PATCH 164/175] clojure 1.11.3, 1.12 alpha 12 Signed-off-by: Sean Corfield --- CHANGES.md | 2 +- deps.edn | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b0db8bea..f9f2310b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ Changes not yet released * Update most testing dependencies (and update a couple of tests to match). -* Drop support for Clojure 1.7.0. Test against 1.8, 1.9, 1.10, 1.11, and 1.12 (Alpha 9). +* Drop support for Clojure 1.7.0. Test against 1.8, 1.9, 1.10, 1.11, and 1.12 (Alpha 12). Changes in 0.7.12 diff --git a/deps.edn b/deps.edn index 4b1a182a..250ebf8e 100644 --- a/deps.edn +++ b/deps.edn @@ -20,8 +20,8 @@ :1.8 {:override-deps {org.clojure/clojure {:mvn/version "1.8.0"}}} :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} - :1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.2"}}} - :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0-alpha9"}}} + :1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.3"}}} + :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0-alpha12"}}} :perf {:extra-paths ["src/perf/clojure"] :extra-deps {criterium/criterium {:mvn/version "0.4.6"}} :jvm-opts ["-server" From c679817ec1ed5a60a73067aedd55f882dc02187c Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 3 Aug 2024 12:31:32 -0700 Subject: [PATCH 165/175] clojure 1.11.4 & 1.12.0 rc 1 Signed-off-by: Sean Corfield --- CHANGES.md | 2 +- deps.edn | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f9f2310b..f3aa0a95 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ Changes not yet released * Update most testing dependencies (and update a couple of tests to match). -* Drop support for Clojure 1.7.0. Test against 1.8, 1.9, 1.10, 1.11, and 1.12 (Alpha 12). +* Drop support for Clojure 1.7.0. Test against 1.8, 1.9, 1.10, 1.11, and 1.12 (RC 1). Changes in 0.7.12 diff --git a/deps.edn b/deps.edn index 250ebf8e..89393dc6 100644 --- a/deps.edn +++ b/deps.edn @@ -20,8 +20,8 @@ :1.8 {:override-deps {org.clojure/clojure {:mvn/version "1.8.0"}}} :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} - :1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.3"}}} - :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0-alpha12"}}} + :1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.4"}}} + :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0-rc1"}}} :perf {:extra-paths ["src/perf/clojure"] :extra-deps {criterium/criterium {:mvn/version "0.4.6"}} :jvm-opts ["-server" From 69d466e3d506d0fb360abf6449a882e96ab8743d Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 5 Sep 2024 22:45:38 -0700 Subject: [PATCH 166/175] update test deps to 1.12.0 Signed-off-by: Sean Corfield --- CHANGES.md | 2 +- deps.edn | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f3aa0a95..5691aebb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ Changes not yet released * Update most testing dependencies (and update a couple of tests to match). -* Drop support for Clojure 1.7.0. Test against 1.8, 1.9, 1.10, 1.11, and 1.12 (RC 1). +* Drop support for Clojure 1.7.0. Test against 1.8, 1.9, 1.10, 1.11, and 1.12. Changes in 0.7.12 diff --git a/deps.edn b/deps.edn index 89393dc6..d23ec9af 100644 --- a/deps.edn +++ b/deps.edn @@ -21,7 +21,7 @@ :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} :1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.4"}}} - :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0-rc1"}}} + :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0"}}} :perf {:extra-paths ["src/perf/clojure"] :extra-deps {criterium/criterium {:mvn/version "0.4.6"}} :jvm-opts ["-server" From 006901a443c8e67ecac803dd8102d38d41e02d4d Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 4 Oct 2024 13:40:11 -0700 Subject: [PATCH 167/175] update multi-version testing to match official matrix Signed-off-by: Sean Corfield --- CHANGES.md | 2 +- deps.edn | 3 +-- run-tests.sh | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5691aebb..d6d50aab 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ Changes not yet released * Update most testing dependencies (and update a couple of tests to match). -* Drop support for Clojure 1.7.0. Test against 1.8, 1.9, 1.10, 1.11, and 1.12. +* Drop support for Clojure 1.7 & 1.8. Test against 1.9, 1.10, 1.11, and 1.12. Changes in 0.7.12 diff --git a/deps.edn b/deps.edn index d23ec9af..48b6ef53 100644 --- a/deps.edn +++ b/deps.edn @@ -1,6 +1,6 @@ ;; You can run clojure.java.jdbc tests with: clj -A:test:runner ;; You can also specify an alias to select which version of Clojure to test -;; against: :1.8 :1.9 :1.10 :1.11 :1.12 +;; against: :1.9 :1.10 :1.11 :1.12 {:paths ["src/main/clojure"] :aliases {:test @@ -17,7 +17,6 @@ org.xerial/sqlite-jdbc {:mvn/version "3.45.2.0"} ;; Note: Assumes Java 8; there's a .jre11 version as well com.microsoft.sqlserver/mssql-jdbc {:mvn/version "12.6.1.jre8"}}} - :1.8 {:override-deps {org.clojure/clojure {:mvn/version "1.8.0"}}} :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} :1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.4"}}} diff --git a/run-tests.sh b/run-tests.sh index ed2a4f69..5c4032a2 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -44,7 +44,7 @@ dbs="derby h2 hsqldb sqlite" # Start with clean databases each time to avoid slowdown rm -rf clojure_test_* -versions="1.8 1.9 1.10 1.11 1.12" +versions="1.9 1.10 1.11 1.12" for v in $versions do TEST_DBS="$dbs $*" clj -M:test:runner:$v From 12503991bf08b3b0d3cd6b196920f43d0137cb70 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 30 May 2025 15:06:24 -0500 Subject: [PATCH 168/175] update to new parent pom --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9104128f..2d6da37e 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.clojure pom.contrib - 1.2.0 + 1.3.0 From c541f1a6c4d732baf9fa19d8dcaf6ca7e384a2a7 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 28 Aug 2025 09:36:47 -0400 Subject: [PATCH 169/175] Clarify future support for clojure.java.jdbc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b36cbaf9..e78b88ba 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ For higher level DSLs and migration libraries that are compatible, see the [docu Formerly known as `clojure.contrib.sql`. -This library is mature and stable. It is widely used and its use is described in many books and tutorials. It will continue to get bug fixes and minor releases. Based on my experience using and maintaining this library, I've created a faster, more modern JDBC wrapper called [next.jdbc](https://github.com/seancorfield/next-jdbc). I consider it to be the "next generation" of `clojure.java.jdbc` but it exposes a different API -- a better API, I think. +This library is mature and stable. It is widely used and its use is described in many books and tutorials. It will only get critical bug fixes (e.g., security). Based on my experience using and maintaining this library, I've created a faster, more modern JDBC wrapper called [next.jdbc](https://github.com/seancorfield/next-jdbc). I consider it to be the "next generation" of `clojure.java.jdbc` but it exposes a different API -- a better API, I think. Documentation ======================================== From 2b5bb19c6feb2d9e40b9d8a94d09fd32eeeee7b2 Mon Sep 17 00:00:00 2001 From: Fogus Date: Tue, 21 Oct 2025 08:19:13 -0400 Subject: [PATCH 170/175] Add description for java.jdbc pom.xml --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 2d6da37e..acfbd83a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,6 +4,7 @@ java.jdbc 0.7.13-SNAPSHOT java.jdbc + JDBC utilities for Clojure. org.clojure From ef8447a58a81ba2e67fcdc875c3fe976fea879df Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 2 Jan 2026 08:41:43 -0600 Subject: [PATCH 171/175] update permissions in workflows --- .github/workflows/doc-build.yml | 3 +++ .github/workflows/release.yml | 3 +++ .github/workflows/snapshot.yml | 3 +++ .github/workflows/test.yml | 3 +++ 4 files changed, 12 insertions(+) diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml index 685f14ea..c7a04465 100644 --- a/.github/workflows/doc-build.yml +++ b/.github/workflows/doc-build.yml @@ -1,5 +1,8 @@ name: Build API Docs +permissions: + contents: write + on: workflow_dispatch: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2718bd3..286cf956 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,8 @@ name: Release on demand +permissions: + contents: write + on: workflow_dispatch: inputs: diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 24729578..9fdad8c6 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -1,5 +1,8 @@ name: Snapshot on demand +permissions: + contents: read + on: [workflow_dispatch] jobs: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1fa127c9..2cc441ac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,8 @@ name: Test +permissions: + contents: read + on: [push] jobs: From 38f9fecb6b9066ca8479fe1ac8e6b325dac5ce30 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 2 Jan 2026 08:55:14 -0600 Subject: [PATCH 172/175] update to latest parent pom --- CHANGES.md | 4 +++- README.md | 13 +++++++++---- deps.edn | 16 ++++++++-------- pom.xml | 16 ++++++++-------- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d6d50aab..2becdae8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,6 @@ -Changes not yet released +Changes in 0.8.0 + +* Update to latest parent pom and Clojure 1.11.4 as dep * Update most testing dependencies (and update a couple of tests to match). * Drop support for Clojure 1.7 & 1.8. Test against 1.9, 1.10, 1.11, and 1.12. diff --git a/README.md b/README.md index e78b88ba..d06ae4fe 100644 --- a/README.md +++ b/README.md @@ -25,25 +25,25 @@ Support Releases and Dependency Information ======================================== -Latest stable release: 0.7.12 -- requires Clojure 1.7 or later! +Latest stable release: 0.8.0 -- requires Clojure 1.7 or later! * [All Released Versions](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) * [Development Snapshot Versions](https://oss.sonatype.org/index.html#nexus-search;gav~org.clojure~java.jdbc~~~) [CLI/`deps.edn`](https://clojure.org/reference/deps_and_cli) dependency information: ```clojure -org.clojure/java.jdbc {:mvn/version "0.7.12"} +org.clojure/java.jdbc {:mvn/version "0.8.0"} ``` [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.7.12"] +[org.clojure/java.jdbc "0.8.0"] ``` [Maven](https://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.7.12 + 0.8.0 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -138,6 +138,11 @@ Developer Information Change Log ==================== +* Release 0.8.0 on 2025-01-02 + * Update to latest parent pom and Clojure 1.11.4 as dep + * Update most testing dependencies (and update a couple of tests to match). + * Drop support for Clojure 1.7 & 1.8. Test against 1.9, 1.10, 1.11, and 1.12. + * Release 0.7.12 on 2021-02-01 * Make the protocols `ISQLValue`, `ISQLParameter`, and `IResultSetReadColumn` extensible via metadata. diff --git a/deps.edn b/deps.edn index 48b6ef53..4d8878e7 100644 --- a/deps.edn +++ b/deps.edn @@ -5,22 +5,22 @@ {:paths ["src/main/clojure"] :aliases {:test {:extra-paths ["src/test/clojure"] - :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} - org.apache.derby/derby {:mvn/version "10.14.2.0"} - org.hsqldb/hsqldb$jdk8 {:mvn/version "2.7.2"} - com.h2database/h2 {:mvn/version "1.4.197"} + :extra-deps {org.clojure/test.check {:mvn/version "1.1.3"} + org.apache.derby/derby {:mvn/version "10.17.1.0"} + org.hsqldb/hsqldb$jdk8 {:mvn/version "2.7.4"} + com.h2database/h2 {:mvn/version "2.4.240"} net.sourceforge.jtds/jtds {:mvn/version "1.3.1"} ;; Note: Tests fail with 6.0.2+ driver mysql/mysql-connector-java {:mvn/version "5.1.41"} - org.postgresql/postgresql {:mvn/version "42.7.3"} + org.postgresql/postgresql {:mvn/version "42.7.8"} com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.8.9"} - org.xerial/sqlite-jdbc {:mvn/version "3.45.2.0"} + org.xerial/sqlite-jdbc {:mvn/version "3.51.1.0"} ;; Note: Assumes Java 8; there's a .jre11 version as well - com.microsoft.sqlserver/mssql-jdbc {:mvn/version "12.6.1.jre8"}}} + com.microsoft.sqlserver/mssql-jdbc {:mvn/version "13.2.0.jre8"}}} :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} :1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.4"}}} - :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0"}}} + :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.4"}}} :perf {:extra-paths ["src/perf/clojure"] :extra-deps {criterium/criterium {:mvn/version "0.4.6"}} :jvm-opts ["-server" diff --git a/pom.xml b/pom.xml index acfbd83a..77993a5a 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.clojure pom.contrib - 1.3.0 + 1.4.0 @@ -29,7 +29,7 @@ - 1.9.0 + 1.11.4 @@ -73,32 +73,32 @@ org.apache.derby derby - 10.14.2.0 + 10.17.1.0 test org.hsqldb hsqldb - 2.7.2 + 2.7.4 jdk8 test com.h2database h2 - 1.4.197 + 2.4.240 test org.postgresql postgresql - 42.7.3 + 42.7.8 test org.xerial sqlite-jdbc - 3.45.2.0 + 3.51.1.0 test @@ -110,7 +110,7 @@ org.clojure test.check - 1.1.1 + 1.1.3 test From 8e029074acd8bff7045b1cb39573047fdf2c1647 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 2 Jan 2026 09:02:03 -0600 Subject: [PATCH 173/175] revert derby version --- deps.edn | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deps.edn b/deps.edn index 4d8878e7..004c697a 100644 --- a/deps.edn +++ b/deps.edn @@ -6,7 +6,7 @@ :aliases {:test {:extra-paths ["src/test/clojure"] :extra-deps {org.clojure/test.check {:mvn/version "1.1.3"} - org.apache.derby/derby {:mvn/version "10.17.1.0"} + org.apache.derby/derby {:mvn/version "10.14.2.0"} org.hsqldb/hsqldb$jdk8 {:mvn/version "2.7.4"} com.h2database/h2 {:mvn/version "2.4.240"} net.sourceforge.jtds/jtds {:mvn/version "1.3.1"} diff --git a/pom.xml b/pom.xml index 77993a5a..47091809 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ org.apache.derby derby - 10.17.1.0 + 10.14.2.0 test From 068ec357eef9ef514d39d75fa8e63b2c6c0b63f6 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 2 Jan 2026 09:08:15 -0600 Subject: [PATCH 174/175] revert h2 version change --- deps.edn | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deps.edn b/deps.edn index 004c697a..4670cbee 100644 --- a/deps.edn +++ b/deps.edn @@ -8,7 +8,7 @@ :extra-deps {org.clojure/test.check {:mvn/version "1.1.3"} org.apache.derby/derby {:mvn/version "10.14.2.0"} org.hsqldb/hsqldb$jdk8 {:mvn/version "2.7.4"} - com.h2database/h2 {:mvn/version "2.4.240"} + com.h2database/h2 {:mvn/version "1.4.197"} net.sourceforge.jtds/jtds {:mvn/version "1.3.1"} ;; Note: Tests fail with 6.0.2+ driver mysql/mysql-connector-java {:mvn/version "5.1.41"} diff --git a/pom.xml b/pom.xml index 47091809..17472a63 100644 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,7 @@ com.h2database h2 - 2.4.240 + 1.4.197 test From ec2e95286b894ff81726d04d57709a75f7a22c91 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 2 Jan 2026 09:16:59 -0600 Subject: [PATCH 175/175] revert release version --- CHANGES.md | 2 +- README.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2becdae8..6cb2c13b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes in 0.8.0 +Unreleased changes * Update to latest parent pom and Clojure 1.11.4 as dep * Update most testing dependencies (and update a couple of tests to match). diff --git a/README.md b/README.md index d06ae4fe..5c4dac6f 100644 --- a/README.md +++ b/README.md @@ -25,25 +25,25 @@ Support Releases and Dependency Information ======================================== -Latest stable release: 0.8.0 -- requires Clojure 1.7 or later! +Latest stable release: 0.7.12 -- requires Clojure 1.7 or later! * [All Released Versions](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22java.jdbc%22) * [Development Snapshot Versions](https://oss.sonatype.org/index.html#nexus-search;gav~org.clojure~java.jdbc~~~) [CLI/`deps.edn`](https://clojure.org/reference/deps_and_cli) dependency information: ```clojure -org.clojure/java.jdbc {:mvn/version "0.8.0"} +org.clojure/java.jdbc {:mvn/version "0.7.12"} ``` [Leiningen](https://github.com/technomancy/leiningen) dependency information: ```clojure -[org.clojure/java.jdbc "0.8.0"] +[org.clojure/java.jdbc "0.7.12"] ``` [Maven](https://maven.apache.org/) dependency information: ```xml org.clojure java.jdbc - 0.8.0 + 0.7.12 ``` _Note: Earlier versions of Clojure are supported by older versions of `clojure.java.jdbc`: e.g., version 0.6.1 supports Clojure 1.4 and later._ @@ -138,7 +138,7 @@ Developer Information Change Log ==================== -* Release 0.8.0 on 2025-01-02 +* Unreleased changes * Update to latest parent pom and Clojure 1.11.4 as dep * Update most testing dependencies (and update a couple of tests to match). * Drop support for Clojure 1.7 & 1.8. Test against 1.9, 1.10, 1.11, and 1.12.