Fix #50 by testing against (embedded) PostgreSQL

This is the first database that returns lowercase columns in results so 
there are a lot of conditionals in the tests now. It also returns entire 
rows on inserts instead of just the generated key.
This commit is contained in:
Sean Corfield 2019-07-24 17:32:58 -07:00
parent 097cdf7d28
commit 9372ce11cd
9 changed files with 138 additions and 121 deletions

View file

@ -6,7 +6,7 @@ Only accretive/fixative changes will be made from now on.
The following changes have been committed to the **master** branch since the 1.0.3 release:
* None.
* Fix #50 by adding machinery to test against (embedded) PostgreSQL!
## Stable Builds

View file

@ -14,6 +14,7 @@
net.sourceforge.jtds/jtds {:mvn/version "1.3.1"}
mysql/mysql-connector-java {:mvn/version "5.1.41"} ; behind
org.postgresql/postgresql {:mvn/version "42.2.6"}
com.opentable.components/otj-pg-embedded {:mvn/version "0.13.1"}
com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.8.2"}
org.xerial/sqlite-jdbc {:mvn/version "3.28.0"}
com.microsoft.sqlserver/mssql-jdbc {:mvn/version "7.2.2.jre8"}}}

View file

@ -18,7 +18,7 @@ for `deps.edn` or:
```
for `project.clj` or `build.boot`.
In addition, you will need to add dependencies for the JDBC drivers you wish to use for whatever databases you are using. You can see the drivers and versions that `next.jdbc` is tested against in [the project's `deps.edn` file](https://github.com/seancorfield/next-jdbc/blob/master/deps.edn#L11-L19), but many other JDBC drivers for other databases should also work (e.g., Oracle, Red Shift).
In addition, you will need to add dependencies for the JDBC drivers you wish to use for whatever databases you are using. You can see the drivers and versions that `next.jdbc` is tested against in [the project's `deps.edn` file](https://github.com/seancorfield/next-jdbc/blob/master/deps.edn#L11-L20), but many other JDBC drivers for other databases should also work (e.g., Oracle, Red Shift).
## An Example REPL Session

View file

@ -6,7 +6,7 @@
[clojure.test :refer [deftest is testing use-fixtures]]
[next.jdbc.optional :as opt]
[next.jdbc.protocols :as p]
[next.jdbc.test-fixtures :refer [with-test-db ds]]))
[next.jdbc.test-fixtures :refer [with-test-db ds postgres?]]))
(set! *warn-on-reflection* true)
@ -18,17 +18,17 @@
["select * from fruit where id = ?" 1]
{:builder-fn opt/as-maps})]
(is (map? row))
(is (not (contains? row :FRUIT/GRADE)))
(is (= 1 (:FRUIT/ID row)))
(is (= "Apple" (:FRUIT/NAME row)))))
(is (not (contains? row (if (postgres?) :fruit/grade :FRUIT/GRADE))))
(is (= 1 ((if (postgres?) :fruit/id :FRUIT/ID) row)))
(is (= "Apple" ((if (postgres?) :fruit/name :FRUIT/NAME) row)))))
(testing "unqualified row builder"
(let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 2]
{:builder-fn opt/as-unqualified-maps})]
(is (map? row))
(is (not (contains? row :COST)))
(is (= 2 (:ID row)))
(is (= "Banana" (:NAME row)))))
(is (not (contains? row (if (postgres?) :cost :COST))))
(is (= 2 ((if (postgres?) :id :ID) row)))
(is (= "Banana" ((if (postgres?) :name :NAME) row)))))
(testing "lower-case row builder"
(let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 3]
@ -51,6 +51,6 @@
:label-fn str/lower-case
:qualifier-fn identity})]
(is (map? row))
(is (not (contains? row :FRUIT/appearance)))
(is (= 3 (:FRUIT/id row)))
(is (= "Peach" (:FRUIT/name row))))))
(is (not (contains? row (if (postgres?) :fruit/appearance :FRUIT/appearance))))
(is (= 3 ((if (postgres?) :fruit/id :FRUIT/id) row)))
(is (= "Peach" ((if (postgres?) :fruit/name :FRUIT/name) row))))))

View file

@ -8,7 +8,7 @@
`execute-batch!` here."
(:require [clojure.test :refer [deftest is testing use-fixtures]]
[next.jdbc :as jdbc]
[next.jdbc.test-fixtures :refer [with-test-db ds sqlite?]]
[next.jdbc.test-fixtures :refer [with-test-db ds postgres? sqlite?]]
[next.jdbc.prepare :as prep]
[next.jdbc.specs :as specs]))
@ -73,7 +73,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
(conj result (count (jdbc/execute! t ["select * from fruit"]))))))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
(testing "large batch insert"
(when-not (sqlite?)
(when-not (or (postgres?) (sqlite?))
(is (= [1 1 1 1 1 1 1 1 1 13]
(jdbc/with-transaction [t (ds) {:rollback-only true}]
(with-open [ps (jdbc/prepare t ["

View file

@ -11,7 +11,7 @@
[clojure.test :refer [deftest is testing use-fixtures]]
[next.jdbc.protocols :as p]
[next.jdbc.result-set :as rs]
[next.jdbc.test-fixtures :refer [with-test-db ds]])
[next.jdbc.test-fixtures :refer [with-test-db ds postgres?]])
(:import (java.sql ResultSet ResultSetMetaData)))
(set! *warn-on-reflection* true)
@ -28,9 +28,8 @@
(is (= 1 v))
(let [object (d/nav data :table/fruit_id v)]
;; check nav produces a single map with the expected key/value data
;; and remember H2 is all UPPERCASE!
(is (= 1 (:FRUIT/ID object)))
(is (= "Apple" (:FRUIT/NAME object))))))
(is (= 1 ((if (postgres?) :fruit/id :FRUIT/ID) object)))
(is (= "Apple" ((if (postgres?) :fruit/name :FRUIT/NAME) object))))))
(testing "custom schema :one"
(let [connectable (ds)
test-row (rs/datafiable-row {:foo/bar 2} connectable
@ -41,9 +40,8 @@
(is (= 2 v))
(let [object (d/nav data :foo/bar v)]
;; check nav produces a single map with the expected key/value data
;; and remember H2 is all UPPERCASE!
(is (= 2 (:FRUIT/ID object)))
(is (= "Banana" (:FRUIT/NAME object))))))
(is (= 2 ((if (postgres?) :fruit/id :FRUIT/ID) object)))
(is (= "Banana" ((if (postgres?) :fruit/name :FRUIT/NAME) object))))))
(testing "custom schema :many"
(let [connectable (ds)
test-row (rs/datafiable-row {:foo/bar 3} connectable
@ -54,10 +52,9 @@
(is (= 3 v))
(let [object (d/nav data :foo/bar v)]
;; check nav produces a result set with the expected key/value data
;; and remember H2 is all UPPERCASE!
(is (vector? object))
(is (= 3 (:FRUIT/ID (first object))))
(is (= "Peach" (:FRUIT/NAME (first object))))))))
(is (= 3 ((if (postgres?) :fruit/id :FRUIT/ID) (first object))))
(is (= "Peach" ((if (postgres?) :fruit/name :FRUIT/NAME) (first object))))))))
(deftest test-map-row-builder
(testing "default row builder"
@ -65,27 +62,27 @@
["select * from fruit where id = ?" 1]
{})]
(is (map? row))
(is (contains? row :FRUIT/GRADE))
(is (nil? (:FRUIT/GRADE row)))
(is (= 1 (:FRUIT/ID row)))
(is (= "Apple" (:FRUIT/NAME row))))
(is (contains? row (if (postgres?) :fruit/grade :FRUIT/GRADE)))
(is (nil? ((if (postgres?) :fruit/grade :FRUIT/GRADE) row)))
(is (= 1 ((if (postgres?) :fruit/id :FRUIT/ID) row)))
(is (= "Apple" ((if (postgres?) :fruit/name :FRUIT/NAME) row))))
(let [rs (p/-execute-all (ds)
["select * from fruit order by id"]
{})]
(is (every? map? rs))
(is (= 1 (:FRUIT/ID (first rs))))
(is (= "Apple" (:FRUIT/NAME (first rs))))
(is (= 4 (:FRUIT/ID (last rs))))
(is (= "Orange" (:FRUIT/NAME (last rs))))))
(is (= 1 ((if (postgres?) :fruit/id :FRUIT/ID) (first rs))))
(is (= "Apple" ((if (postgres?) :fruit/name :FRUIT/NAME) (first rs))))
(is (= 4 ((if (postgres?) :fruit/id :FRUIT/ID) (last rs))))
(is (= "Orange" ((if (postgres?) :fruit/name :FRUIT/NAME) (last rs))))))
(testing "unqualified row builder"
(let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 2]
{:builder-fn rs/as-unqualified-maps})]
(is (map? row))
(is (contains? row :COST))
(is (nil? (:COST row)))
(is (= 2 (:ID row)))
(is (= "Banana" (:NAME row)))))
(is (contains? row (if (postgres?) :cost :COST)))
(is (nil? ((if (postgres?) :cost :COST) row)))
(is (= 2 ((if (postgres?) :id :ID) row)))
(is (= "Banana" ((if (postgres?) :name :NAME) row)))))
(testing "lower-case row builder"
(let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 3]
@ -109,10 +106,10 @@
:label-fn str/lower-case
:qualifier-fn identity})]
(is (map? row))
(is (contains? row :FRUIT/appearance))
(is (nil? (:FRUIT/appearance row)))
(is (= 3 (:FRUIT/id row)))
(is (= "Peach" (:FRUIT/name row))))))
(is (contains? row (if (postgres?) :fruit/appearance :FRUIT/appearance)))
(is (nil? ((if (postgres?) :fruit/appearance :FRUIT/appearance) row)))
(is (= 3 ((if (postgres?) :fruit/id :FRUIT/id) row)))
(is (= "Peach" ((if (postgres?) :fruit/name :FRUIT/name) row))))))
(deftest test-mapify
(testing "no row builder is used"

View file

@ -9,7 +9,7 @@
[next.jdbc.specs :as specs]
[next.jdbc.sql :as sql]
[next.jdbc.test-fixtures
:refer [with-test-db ds derby? sqlite?]]))
:refer [with-test-db ds derby? postgres? sqlite?]]))
(set! *warn-on-reflection* true)
@ -82,33 +82,33 @@
(is (= 4 (count rs)))
(is (every? map? rs))
(is (every? meta rs))
(is (= 1 (:FRUIT/ID (first rs))))
(is (= 4 (:FRUIT/ID (last rs))))))
(is (= 1 ((if (postgres?) :fruit/id :FRUIT/ID) (first rs))))
(is (= 4 ((if (postgres?) :fruit/id :FRUIT/ID) (last rs))))))
(deftest test-find-by-keys
(let [rs (sql/find-by-keys (ds) :fruit {:appearance "yellow"})]
(is (= 1 (count rs)))
(is (every? map? rs))
(is (every? meta rs))
(is (= 2 (:FRUIT/ID (first rs))))))
(is (= 2 ((if (postgres?) :fruit/id :FRUIT/ID) (first rs))))))
(deftest test-get-by-id
(let [row (sql/get-by-id (ds) :fruit 3)]
(is (map? row))
(is (= "Peach" (:FRUIT/NAME row))))
(is (= "Peach" ((if (postgres?) :fruit/name :FRUIT/NAME) row))))
(let [row (sql/get-by-id (ds) :fruit "juicy" :appearance {})]
(is (map? row))
(is (= 4 (:FRUIT/ID row)))
(is (= "Orange" (:FRUIT/NAME row))))
(is (= 4 ((if (postgres?) :fruit/id :FRUIT/ID) row)))
(is (= "Orange" ((if (postgres?) :fruit/name :FRUIT/NAME) row))))
(let [row (sql/get-by-id (ds) :fruit "Banana" :FRUIT/NAME {})]
(is (map? row))
(is (= 2 (:FRUIT/ID row)))))
(is (= 2 ((if (postgres?) :fruit/id :FRUIT/ID) row)))))
(deftest test-update!
(try
(is (= {:next.jdbc/update-count 1}
(sql/update! (ds) :fruit {:appearance "brown"} {:id 2})))
(is (= "brown" (:FRUIT/APPEARANCE
(is (= "brown" ((if (postgres?) :fruit/appearance :FRUIT/APPEARANCE)
(sql/get-by-id (ds) :fruit 2))))
(finally
(sql/update! (ds) :fruit {:appearance "yellow"} {:id 2})))
@ -116,68 +116,69 @@
(is (= {:next.jdbc/update-count 1}
(sql/update! (ds) :fruit {:appearance "green"}
["name = ?" "Banana"])))
(is (= "green" (:FRUIT/APPEARANCE
(is (= "green" ((if (postgres?) :fruit/appearance :FRUIT/APPEARANCE)
(sql/get-by-id (ds) :fruit 2))))
(finally
(sql/update! (ds) :fruit {:appearance "yellow"} {:id 2}))))
(deftest test-insert-delete
(testing "single insert/delete"
(is (= (cond (derby?)
{:1 5M}
(sqlite?)
{(keyword "last_insert_rowid()") 5}
:else
{:FRUIT/ID 5})
(sql/insert! (ds) :fruit
{:name "Kiwi" :appearance "green & fuzzy"
:cost 100 :grade 99.9})))
(is (= 5 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 1}
(sql/delete! (ds) :fruit {:id 5})))
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "multiple insert/delete"
(is (= (cond (derby?)
[{:1 nil}] ; WTF Apache Derby?
(sqlite?)
[{(keyword "last_insert_rowid()") 8}]
:else
[{:FRUIT/ID 6} {:FRUIT/ID 7} {:FRUIT/ID 8}])
(sql/insert-multi! (ds) :fruit
[:name :appearance :cost :grade]
[["Kiwi" "green & fuzzy" 100 99.9]
["Grape" "black" 10 50]
["Lemon" "yellow" 20 9.9]])))
(is (= 7 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 1}
(sql/delete! (ds) :fruit {:id 6})))
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 2}
(sql/delete! (ds) :fruit ["id > ?" 4])))
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "multiple insert/delete with sequential cols/rows" ; per #43
(is (= (cond (derby?)
[{:1 nil}] ; WTF Apache Derby?
(sqlite?)
[{(keyword "last_insert_rowid()") 11}]
:else
[{:FRUIT/ID 9} {:FRUIT/ID 10} {:FRUIT/ID 11}])
(sql/insert-multi! (ds) :fruit
'(:name :appearance :cost :grade)
'(("Kiwi" "green & fuzzy" 100 99.9)
("Grape" "black" 10 50)
("Lemon" "yellow" 20 9.9)))))
(is (= 7 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 1}
(sql/delete! (ds) :fruit {:id 9})))
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 2}
(sql/delete! (ds) :fruit ["id > ?" 4])))
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "empty insert-multi!" ; per #44
(is (= [] (sql/insert-multi! (ds) :fruit
[:name :appearance :cost :grade]
[])))))
(let [new-key (cond (derby?) :1
(postgres?) :fruit/id
(sqlite?) (keyword "last_insert_rowid()")
:else :FRUIT/ID)]
(testing "single insert/delete"
(is (= (if (derby?) 5M 5)
(new-key (sql/insert! (ds) :fruit
{:name "Kiwi" :appearance "green & fuzzy"
:cost 100 :grade 99.9}))))
(is (= 5 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 1}
(sql/delete! (ds) :fruit {:id 5})))
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "multiple insert/delete"
(is (= (cond (derby?)
[nil] ; WTF Apache Derby?
(sqlite?)
[8]
:else
[6 7 8])
(mapv new-key
(sql/insert-multi! (ds) :fruit
[:name :appearance :cost :grade]
[["Kiwi" "green & fuzzy" 100 99.9]
["Grape" "black" 10 50]
["Lemon" "yellow" 20 9.9]]))))
(is (= 7 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 1}
(sql/delete! (ds) :fruit {:id 6})))
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 2}
(sql/delete! (ds) :fruit ["id > ?" 4])))
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "multiple insert/delete with sequential cols/rows" ; per #43
(is (= (cond (derby?)
[nil] ; WTF Apache Derby?
(sqlite?)
[11]
:else
[9 10 11])
(mapv new-key
(sql/insert-multi! (ds) :fruit
'(:name :appearance :cost :grade)
'(("Kiwi" "green & fuzzy" 100 99.9)
("Grape" "black" 10 50)
("Lemon" "yellow" 20 9.9))))))
(is (= 7 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 1}
(sql/delete! (ds) :fruit {:id 9})))
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 2}
(sql/delete! (ds) :fruit ["id > ?" 4])))
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "empty insert-multi!" ; per #44
(is (= [] (sql/insert-multi! (ds) :fruit
[:name :appearance :cost :grade]
[]))))))
(deftest no-empty-example-maps
(is (thrown? clojure.lang.ExceptionInfo

View file

@ -3,7 +3,9 @@
(ns next.jdbc.test-fixtures
"Multi-database testing fixtures."
(:require [next.jdbc :as jdbc]
[next.jdbc.sql :as sql]))
[next.jdbc.sql :as sql])
(:import (com.opentable.db.postgres.embedded EmbeddedPostgres)
(javax.sql DataSource)))
(set! *warn-on-reflection* true)
@ -17,12 +19,19 @@
(def ^:private test-sqlite {:dbtype "sqlite" :dbname "clojure_test_sqlite"})
(def ^:private test-db-specs [test-derby test-h2-mem test-h2 test-hsql test-sqlite])
;; this is just a dummy db-spec -- it's handled in with-test-db below
(def ^:private test-postgres {:dbtype "embedded-postgres"})
(defonce embedded-pg (atom nil))
(def ^:private test-db-specs
[test-derby test-h2-mem test-h2 test-hsql test-sqlite test-postgres])
(def ^:private test-db-spec (atom nil))
(defn derby? [] (= "derby" (:dbtype @test-db-spec)))
(defn postgres? [] (= "embedded-postgres" (:dbtype @test-db-spec)))
(defn sqlite? [] (= "sqlite" (:dbtype @test-db-spec)))
(def ^:private test-datasource (atom nil))
@ -41,12 +50,21 @@
[t]
(doseq [db test-db-specs]
(reset! test-db-spec db)
(reset! test-datasource (jdbc/get-datasource db))
(if (= "embedded-postgres" (:dbtype db))
(do
(when-not @embedded-pg
(reset! embedded-pg (EmbeddedPostgres/start)))
(reset! test-datasource
(.getPostgresDatabase ^EmbeddedPostgres @embedded-pg)))
(reset! test-datasource (jdbc/get-datasource db)))
(let [auto-inc-pk
(cond (or (derby?) (= "hsqldb" (:dbtype db)))
(str "GENERATED ALWAYS AS IDENTITY"
" (START WITH 1, INCREMENT BY 1)"
" PRIMARY KEY")
(postgres?)
(str "GENERATED ALWAYS AS IDENTITY"
" PRIMARY KEY")
(sqlite?)
"PRIMARY KEY AUTOINCREMENT"
:else

View file

@ -4,7 +4,7 @@
"Not exactly a test suite -- more a series of examples."
(:require [clojure.test :refer [deftest is testing use-fixtures]]
[next.jdbc :as jdbc]
[next.jdbc.test-fixtures :refer [with-test-db ds]]
[next.jdbc.test-fixtures :refer [with-test-db ds postgres?]]
[next.jdbc.prepare :as prep]
[next.jdbc.result-set :as rs]
[next.jdbc.specs :as specs])
@ -25,7 +25,7 @@
(ds)
["select * from fruit where appearance = ?" "red"])))))
(testing "execute-one!"
(is (= "Apple" (:FRUIT/NAME
(is (= "Apple" ((if (postgres?) :fruit/name :FRUIT/NAME)
(jdbc/execute-one!
(ds)
["select * from fruit where appearance = ?" "red"])))))
@ -34,7 +34,7 @@
(ds)
["select * from fruit where appearance = ?" "red"])]
(is (= 1 (count rs)))
(is (= 1 (:FRUIT/ID (first rs)))))
(is (= 1 ((if (postgres?) :fruit/id :FRUIT/ID) (first rs)))))
(let [rs (jdbc/execute!
(ds)
["select * from fruit order by id"]
@ -42,8 +42,8 @@
(is (every? map? rs))
(is (every? meta rs))
(is (= 4 (count rs)))
(is (= 1 (:FRUIT/ID (first rs))))
(is (= 4 (:FRUIT/ID (last rs)))))
(is (= 1 ((if (postgres?) :fruit/id :FRUIT/ID) (first rs))))
(is (= 4 ((if (postgres?) :fruit/id :FRUIT/ID) (last rs)))))
(let [rs (jdbc/execute!
(ds)
["select * from fruit order by id"]
@ -54,7 +54,7 @@
;; columns come first
(is (every? qualified-keyword? (first rs)))
;; :FRUIT/ID should be first column
(is (= :FRUIT/ID (ffirst rs)))
(is (= (if (postgres?) :fruit/id :FRUIT/ID) (ffirst rs)))
;; and all its corresponding values should be ints
(is (every? int? (map first (rest rs))))
(is (every? string? (map second (rest rs)))))
@ -65,8 +65,8 @@
(is (every? map? rs))
(is (every? meta rs))
(is (= 4 (count rs)))
(is (= 1 (:ID (first rs))))
(is (= 4 (:ID (last rs)))))
(is (= 1 ((if (postgres?) :id :ID) (first rs))))
(is (= 4 ((if (postgres?) :id :ID) (last rs)))))
(let [rs (jdbc/execute!
(ds)
["select * from fruit order by id"]
@ -77,7 +77,7 @@
;; columns come first
(is (every? simple-keyword? (first rs)))
;; :ID should be first column
(is (= :ID (ffirst rs)))
(is (= (if (postgres?) :id :ID) (ffirst rs)))
;; and all its corresponding values should be ints
(is (every? int? (map first (rest rs))))
(is (every? string? (map second (rest rs))))))
@ -90,8 +90,8 @@
(is (every? map? rs))
(is (every? meta rs))
(is (= 4 (count rs)))
(is (= 1 (:FRUIT/ID (first rs))))
(is (= 4 (:FRUIT/ID (last rs)))))
(is (= 1 ((if (postgres?) :fruit/id :FRUIT/ID) (first rs))))
(is (= 4 ((if (postgres?) :fruit/id :FRUIT/ID) (last rs)))))
(let [rs (with-open [con (jdbc/get-connection (ds))
ps (jdbc/prepare
con
@ -100,7 +100,7 @@
(is (every? map? rs))
(is (every? meta rs))
(is (= 1 (count rs)))
(is (= 4 (:FRUIT/ID (first rs))))))
(is (= 4 ((if (postgres?) :fruit/id :FRUIT/ID) (first rs))))))
(testing "transact"
(is (= [{:next.jdbc/update-count 1}]
(jdbc/transact (ds)