Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add support for JDBC drivers which don't support generated keys #108

Closed
wants to merge 2 commits into from

2 participants

@andeee
  • Not every JDBC driver does support getGeneratedKeys, in my example Sybase SQL Anywhere doesn't support it and throws an SQLException when e. g. trying to conj! onto a table
  • We can make clojureql work with these drivers when calling supportsGetGeneratedKeys onto DatabaseMetaData before we create the statement and append the generated keys to the result
  • I also check for the corner case when supportsGetGeneratedKeys isn't even implemented, catching the AbstractMethodError.
    Support for generated keys was introduced with JDBC Version 3, so here we could start to support even older drivers - tested with a SQL Anywhere JDBC Driver Version 9 which only supports JDBC Version 2
Andreas Wurzer added some commits
Andreas Wurzer added tests for derby database, which doesn't support generated keys 7507fe9
Andreas Wurzer added supports-get-generated-keys? (wrapper for DatabaseMetaData.supp…
…ortsGetGeneratedKeys)

added generated-keys, prepare-statement
fixed tests for derby database
4d7a826
@andeee

follow up from #107

@bendlas
Collaborator

Committed. Thanks a lot for your help!

@bendlas bendlas closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 1, 2011
  1. added supports-get-generated-keys? (wrapper for DatabaseMetaData.supp…

    Andreas Wurzer authored
    …ortsGetGeneratedKeys)
    
    added generated-keys, prepare-statement
    fixed tests for derby database
This page is out of date. Refresh to see the latest.
View
3  project.clj
@@ -7,7 +7,8 @@
:dev-dependencies [[swank-clojure "1.3.0-SNAPSHOT"]
[mysql/mysql-connector-java "5.1.17"]
[org.xerial/sqlite-jdbc "3.7.2"]
- [postgresql/postgresql "8.4-702.jdbc4"]]
+ [postgresql/postgresql "8.4-702.jdbc4"]
+ [org.apache.derby/derby "10.1.1.0"]]
:repositories {"clojure-releases" {:url "http://build.clojure.org/releases"}})
View
30 src/clojureql/internal.clj
@@ -360,22 +360,38 @@
(with-open [rset (.executeQuery stmt)]
(func (result-seq rset))))))
+(defn supports-generated-keys? [conn]
+ "Checks if the JDBC Driver supports generated keys"
+ (try (.supportsGetGeneratedKeys (.getMetaData conn))
+ (catch AbstractMethodError _ false)))
+
+(defn prepare-statement [conn sql]
+ "When supported by the JDBC Driver, creates a new prepared statement which will return the generated keys - else returns a 'normal' prepared statement"
+ (if (supports-generated-keys? conn)
+ (jdbc/prepare-statement conn sql :return-keys true)
+ (jdbc/prepare-statement conn sql)))
+
+(defn generated-keys [stmt]
+ "When supported by the JDBC driver, returns the generated keys of the latest executed statement"
+ (when (supports-generated-keys? (.getConnection stmt))
+ (let [ks (.getGeneratedKeys stmt)]
+ {:last-index
+ (and
+ (.next ks)
+ (.getInt ks 1))})))
+
(defn exec-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."
([sql param-group]
- (with-open [stmt (jdbc/prepare-statement (:connection jdbcint/*db*)
- sql :return-keys true)]
+ (with-open [stmt (prepare-statement (:connection jdbcint/*db*) sql)]
(doseq [[idx v] (map vector (iterate inc 1) param-group)]
(.setObject stmt idx v))
(jdbc/transaction
- (let [retr (.execute stmt)
- ks (.getGeneratedKeys stmt)]
+ (let [retr (.execute stmt)]
(with-meta [(.getUpdateCount stmt)]
- {:last-index (and
- (.next ks)
- (.getInt ks 1))})))))
+ (generated-keys stmt))))))
([sql param-group & param-groups]
(with-open [stmt (jdbc/prepare-statement (:connection jdbcint/*db*) sql)]
(doseq [param-group (cons param-group param-groups)]
View
27 test/clojureql/test.clj
@@ -39,7 +39,17 @@
:subname "/tmp/cql.sqlite3"
:create true})
-(def databases [mysql postgresql sqlite3])
+(def derby
+ {:classname "org.apache.derby.jdbc.EmbeddedDriver"
+ :subprotocol "derby"
+ :subname "/tmp/cql.derby"
+ :create true})
+
+(def register-derby-driver
+ (-> (Class/forName "org.apache.derby.jdbc.EmbeddedDriver")
+ .newInstance))
+
+(def databases [mysql postgresql sqlite3 derby])
(defn mysql? []
(isa? (class (connection)) com.mysql.jdbc.JDBC4Connection))
@@ -50,6 +60,9 @@
(defn sqlite3? []
(isa? (class (connection)) org.sqlite.Conn))
+(defn derby? []
+ (isa? (class (connection)) org.apache.derby.impl.jdbc.EmbedConnection))
+
(defn drop-if [table]
(try (drop-table table) (catch Exception _)))
@@ -95,6 +108,18 @@
[:id :integer "PRIMARY KEY" "AUTOINCREMENT"]
[:wage :integer]))
+(defmethod create-schema org.apache.derby.impl.jdbc.EmbedConnection []
+ (create-table
+ :users
+ [:id :integer "PRIMARY KEY" "GENERATED ALWAYS AS IDENTITY"]
+ [:name "varchar(255)"]
+ [:title "varchar(255)"]
+ [:birthday "TIMESTAMP"])
+ (create-table
+ :salary
+ [:id :integer "PRIMARY KEY" "GENERATED ALWAYS AS IDENTITY"]
+ [:wage :integer]))
+
(def users (-> (table :users) (project [:id :name :title])))
(def salary (-> (table :salary) (project [:id :wage])))
View
106 test/clojureql/test/integration.clj
@@ -25,11 +25,14 @@
{:wage 400, :id 8}))))
(database-test test-generated-keys
- (is (= 5 (-> (conj! salary {:wage 1337})
- meta :last-index)))
- (is (= 6 (-> (update-in! salary (where (= :id 512))
+ ; old derby releases don't support generated keys
+ ; SQLite has a bug here, see http://code.google.com/p/sqlite-jdbc/issues/detail?id=10
+ (when (or (mysql?) (postgresql?))
+ (is (= 5 (-> (conj! salary {:wage 1337})
+ meta :last-index)))
+ (is (= 6 (-> (update-in! salary (where (= :id 512))
{:wage 1337})
- meta :last-index))))
+ meta :last-index)))))
(database-test test-join-explicitly
(is (= @(join users salary (where (= :users.id :salary.id)))
@@ -39,36 +42,53 @@
{:wage 400, :title "Engineer", :name "Frank", :id 4}))))
(database-test test-join-using
- (is (= @(join users salary :id)
- '({:wage 100, :title "Dev", :name "Lau Jensen", :id 1}
- {:wage 200, :title "Design Guru", :name "Christophe", :id 2}
- {:wage 300, :title "Mr. Macros", :name "sthuebner", :id 3}
- {:wage 400, :title "Engineer", :name "Frank", :id 4}))))
+ (when (not (derby?)) ; derby doesn't support using
+ (is (= @(join users salary :id)
+ '({:wage 100, :title "Dev", :name "Lau Jensen", :id 1}
+ {:wage 200, :title "Design Guru", :name "Christophe", :id 2}
+ {:wage 300, :title "Mr. Macros", :name "sthuebner", :id 3}
+ {:wage 400, :title "Engineer", :name "Frank", :id 4})))))
(database-test test-case
- (is (= @(-> (project salary
- [:id (case :wages
- (<= :wage 150) "low"
- (>= :wage 150) "high"
- :else "average")])
- (select (where (<= :id 2))))
- '({:wages "low" :id 1}
- {:wages "high" :id 2}))))
+ (when (not (derby?)) ; derby doesn't support ? in case
+ (is (= @(-> (project salary
+ [:id (case :wages
+ (<= :wage 150) "low"
+ (>= :wage 150) "high"
+ :else "average")])
+ (select (where (<= :id 2))))
+ '({:wages "low" :id 1}
+ {:wages "high" :id 2})))))
(database-test test-chained-statements
- (is (= @(-> users
- (conj! {:name "Jack"}) ; Add a single row
- (disj! (where (= :id 1))) ; Remove another
- (update-in! (where (= :id 2))
- {:name "John"}) ; Update a third
- (sort [:id#desc]) ; Prepare to sort
- (project [:id :title]) ; Returns colums id and title
- (select (where (<= :id 10))) ; Where ID is <= 10
- (join salary :id) ; Join with table salary
- (limit 10)) ; Take a maximum of 10 entries
- '({:id 4, :title "Engineer", :wage 400}
- {:id 3, :title "Mr. Macros", :wage 300}
- {:id 2, :title "Design Guru", :wage 200}))))
+ (if (derby?) ; derby doesn't support using
+ (is (= @(-> users
+ (conj! {:name "Jack"}) ; Add a single row
+ (disj! (where (= :id 1))) ; Remove another
+ (update-in! (where (= :id 2))
+ {:name "John"}) ; Update a third
+ (sort [:id#desc]) ; Prepare to sort
+ (project [:id :title]) ; Returns colums id and title
+ (select (where (<= :id 10))) ; Where ID is <= 10
+ (join salary
+ (where (= :users.id
+ :salary.id)))) ; Join with table salary explicitly
+ '({:id 4, :title "Engineer", :wage 400}
+ {:id 3, :title "Mr. Macros", :wage 300}
+ {:id 2, :title "Design Guru", :wage 200})))
+ (is (= @(-> users
+ (conj! {:name "Jack"}) ; Add a single row
+ (disj! (where (= :id 1))) ; Remove another
+ (update-in! (where (= :id 2))
+ {:name "John"}) ; Update a third
+ (sort [:id#desc]) ; Prepare to sort
+ (project [:id :title]) ; Returns colums id and title
+ (select (where (<= :id 10))) ; Where ID is <= 10
+ (join salary :id) ; Join with table salary
+ (limit 10)) ; Take a maximum of 10 entries
+ '({:id 4, :title "Engineer", :wage 400}
+ {:id 3, :title "Mr. Macros", :wage 300}
+ {:id 2, :title "Design Guru", :wage 200})))))
(database-test test-implicit-asc-sort
(is (= @(-> (disj! users (where (or (= :id 3) (= :id 4))))
@@ -76,8 +96,9 @@
'({:title "Dev", :name "Lau Jensen", :id 1}
{:title "Design Guru", :name "Christophe", :id 2}))))
-(database-test test-implicit-asc-sort
- (is (= 1 (count @(limit users 1)))))
+(database-test test-limit-1
+ (if (not (derby?))
+ (is (= 1 (count @(limit users 1))))))
(database-test test-avg
(insert-data)
@@ -88,6 +109,8 @@
'({:avg 250.0000M})))
(sqlite3?) (is (= @(-> (table :salary) (project [[:avg/wage :as :avg]]))
'({:avg 250.0})))
+ (derby?) (is (= @(-> (table :salary) (project [[:avg/wage :as :avgx]])) ; avg is a keyword in derby
+ '({:avgx 250.0})))
:else true))
(database-test test-select-with-nil-and-value
@@ -98,14 +121,14 @@
(is (empty? @(select (table :users) (where (= nil nil))))))
(database-test test-select-is-null
- (when (or (postgresql?) (mysql?)) ; (where true) not supported by sqlite3
+ (when (or (postgresql?) (mysql?)) ; (where true) not supported by sqlite3, derby
(let [[alice bob] @(-> (disj! users (where true))
(conj! [{:name "Alice" :title "Developer"}
{:name "Bob"}]))]
(is (= bob (first @(select users (where (= :title nil)))))))))
(database-test test-select-is-not-null
- (when (or (postgresql?) (mysql?)) ; (where true) not supported by sqlite3
+ (when (or (postgresql?) (mysql?)) ; (where true) not supported by sqlite3, derby
(let [[alice bob] @(-> (disj! users (where true))
(conj! [{:name "Alice" :title "Developer"} {:name "Bob"}]))]
(is (= alice (first @(select users (where (!= :title nil)))))))))
@@ -172,7 +195,7 @@
(table :users))))))))
(database-test test-union
- (when (or (postgresql?) (mysql?))
+ (when (or (postgresql?) (mysql?) (derby?))
(let [[alice bob] @(conj! users [{:name "Alice" :title "Developer"} {:name "Bob"}])]
(is (= (map :id [alice bob])
(map :id @(union (select (table :users) (where (= :id (:id alice))))
@@ -188,7 +211,9 @@
(is (= connection-info-from-var connection-info-from-fn))))
(database-test test-resultset
- (let [tbl (join users salary :id)
+ (let [tbl (if (derby?) ; derby doesn't support join using
+ (join users salary (where (= :users.id :salary.id)))
+ (join users salary :id))
no-missing? #(not
(some keyword? ; which would be :clojureql.internal/missing
(mapcat vals %)))]
@@ -198,10 +223,11 @@
(is (= res @tbl)))))
(database-test test-dupes
- (let [tbl (join
- (project users [[:name :as :dupe]])
- (project salary [[:wage :as :dupe]])
- :id)]
+ (let [users-tbl (project users [[:name :as :dupe]])
+ salary-tbl (project salary [[:wage :as :dupe]])
+ tbl (if derby? ; derby doesn't support join using
+ (join users-tbl salary-tbl (where (= :users.id :salary.id)))
+ (join users-tbl salary-tbl :id))]
(is (thrown-with-msg? Exception
#".*:dupe.*" @tbl)))
(let [tbl (project users [[:name :as :dupe]
Something went wrong with that request. Please try again.