2020-01-01 21:13:41 +00:00
|
|
|
;; copyright (c) 2019-2020 Sean Corfield, all rights reserved
|
2019-04-19 05:43:19 +00:00
|
|
|
|
|
|
|
|
(ns next.jdbc.test-fixtures
|
2019-05-26 02:16:30 +00:00
|
|
|
"Multi-database testing fixtures."
|
2019-11-15 23:38:51 +00:00
|
|
|
(:require [clojure.string :as str]
|
|
|
|
|
[next.jdbc :as jdbc]
|
2020-06-07 17:01:12 +00:00
|
|
|
[next.jdbc.prepare :as prep]
|
2019-07-25 00:32:58 +00:00
|
|
|
[next.jdbc.sql :as sql])
|
2020-06-06 01:34:08 +00:00
|
|
|
(:import (io.zonky.test.db.postgres.embedded EmbeddedPostgres)))
|
2019-04-19 05:43:19 +00:00
|
|
|
|
2019-05-29 16:04:21 +00:00
|
|
|
(set! *warn-on-reflection* true)
|
|
|
|
|
|
2019-04-22 00:10:29 +00:00
|
|
|
(def ^:private test-derby {:dbtype "derby" :dbname "clojure_test_derby" :create true})
|
|
|
|
|
|
|
|
|
|
(def ^:private test-h2-mem {:dbtype "h2:mem" :dbname "clojure_test_h2_mem"})
|
|
|
|
|
|
|
|
|
|
(def ^:private test-h2 {:dbtype "h2" :dbname "clojure_test_h2"})
|
|
|
|
|
|
|
|
|
|
(def ^:private test-hsql {:dbtype "hsqldb" :dbname "clojure_test_hsqldb"})
|
|
|
|
|
|
|
|
|
|
(def ^:private test-sqlite {:dbtype "sqlite" :dbname "clojure_test_sqlite"})
|
|
|
|
|
|
2019-07-25 00:32:58 +00:00
|
|
|
;; this is just a dummy db-spec -- it's handled in with-test-db below
|
|
|
|
|
(def ^:private test-postgres {:dbtype "embedded-postgres"})
|
2019-11-11 01:53:23 +00:00
|
|
|
;; it takes a while to spin up so we kick it off at startup
|
|
|
|
|
(defonce embedded-pg (future (EmbeddedPostgres/start)))
|
2019-07-25 00:32:58 +00:00
|
|
|
|
2019-11-16 08:50:50 +00:00
|
|
|
(def ^:private test-mysql-map
|
2020-03-16 22:19:21 +00:00
|
|
|
(merge (if (System/getenv "NEXT_JDBC_TEST_MARIADB")
|
|
|
|
|
{:dbtype "mariadb"}
|
|
|
|
|
{:dbtype "mysql" :disableMariaDbDriver true})
|
|
|
|
|
{:dbname "clojure_test" :useSSL false
|
|
|
|
|
:user "root" :password (System/getenv "MYSQL_ROOT_PASSWORD")}))
|
2019-11-15 23:38:51 +00:00
|
|
|
(def ^:private test-mysql
|
2019-11-16 08:50:50 +00:00
|
|
|
(when (System/getenv "NEXT_JDBC_TEST_MYSQL") test-mysql-map))
|
2019-11-15 23:38:51 +00:00
|
|
|
|
2019-11-16 08:50:50 +00:00
|
|
|
(def ^:private test-mssql-map
|
|
|
|
|
{:dbtype "mssql" :dbname "model"
|
|
|
|
|
:user "sa" :password (System/getenv "MSSQL_SA_PASSWORD")})
|
2019-11-16 06:37:42 +00:00
|
|
|
(def ^:private test-mssql
|
2019-11-16 08:50:50 +00:00
|
|
|
(when (System/getenv "NEXT_JDBC_TEST_MSSQL") test-mssql-map))
|
2019-11-16 06:37:42 +00:00
|
|
|
|
2020-06-06 01:34:08 +00:00
|
|
|
(def ^:private test-jtds-map
|
|
|
|
|
{:dbtype "jtds" :dbname "model"
|
|
|
|
|
:user "sa" :password (System/getenv "MSSQL_SA_PASSWORD")})
|
|
|
|
|
(def ^:private test-jtds
|
|
|
|
|
(when (System/getenv "NEXT_JDBC_TEST_MSSQL") test-jtds-map))
|
|
|
|
|
|
2019-07-25 00:32:58 +00:00
|
|
|
(def ^:private test-db-specs
|
2019-11-15 23:38:51 +00:00
|
|
|
(cond-> [test-derby test-h2-mem test-h2 test-hsql test-sqlite test-postgres]
|
2019-11-16 06:37:42 +00:00
|
|
|
test-mysql (conj test-mysql)
|
2020-06-06 01:34:08 +00:00
|
|
|
test-mssql (conj test-mssql test-jtds)))
|
2019-04-22 00:10:29 +00:00
|
|
|
|
|
|
|
|
(def ^:private test-db-spec (atom nil))
|
|
|
|
|
|
|
|
|
|
(defn derby? [] (= "derby" (:dbtype @test-db-spec)))
|
|
|
|
|
|
2020-06-06 21:30:27 +00:00
|
|
|
(defn jtds? [] (= "jtds" (:dbtype @test-db-spec)))
|
|
|
|
|
|
2020-03-16 22:19:21 +00:00
|
|
|
(defn maria? [] (= "mariadb" (:dbtype @test-db-spec)))
|
|
|
|
|
|
2020-06-06 01:34:08 +00:00
|
|
|
(defn mssql? [] (#{"jtds" "mssql"} (:dbtype @test-db-spec)))
|
2019-11-16 06:37:42 +00:00
|
|
|
|
2020-03-16 22:19:21 +00:00
|
|
|
(defn mysql? [] (#{"mariadb" "mysql"} (:dbtype @test-db-spec)))
|
2019-11-15 23:38:51 +00:00
|
|
|
|
2019-07-25 00:32:58 +00:00
|
|
|
(defn postgres? [] (= "embedded-postgres" (:dbtype @test-db-spec)))
|
|
|
|
|
|
2019-04-22 00:10:29 +00:00
|
|
|
(defn sqlite? [] (= "sqlite" (:dbtype @test-db-spec)))
|
2019-04-19 05:43:19 +00:00
|
|
|
|
2020-06-06 01:34:08 +00:00
|
|
|
(defn stored-proc? [] (not (#{"derby" "h2" "h2:mem" "sqlite"} (:dbtype @test-db-spec))))
|
|
|
|
|
|
2019-11-15 23:38:51 +00:00
|
|
|
(defn column [k]
|
|
|
|
|
(let [n (namespace k)]
|
|
|
|
|
(keyword (when n (cond (postgres?) (str/lower-case n)
|
2019-11-16 06:37:42 +00:00
|
|
|
(mssql?) (str/lower-case n)
|
2019-11-15 23:38:51 +00:00
|
|
|
(mysql?) (str/lower-case n)
|
|
|
|
|
:else n))
|
|
|
|
|
(cond (postgres?) (str/lower-case (name k))
|
|
|
|
|
:else (name k)))))
|
|
|
|
|
|
2019-11-16 06:37:42 +00:00
|
|
|
(defn default-options []
|
|
|
|
|
(if (mssql?) ; so that we get table names back from queries
|
|
|
|
|
{:result-type :scroll-insensitive :concurrency :read-only}
|
|
|
|
|
{}))
|
|
|
|
|
|
2019-04-19 05:43:19 +00:00
|
|
|
(def ^:private test-datasource (atom nil))
|
|
|
|
|
|
2019-10-02 16:19:31 +00:00
|
|
|
(defn db
|
|
|
|
|
"Tests should call this to get the db-spec to use inside a fixture."
|
|
|
|
|
[]
|
|
|
|
|
@test-db-spec)
|
|
|
|
|
|
2019-04-19 05:43:19 +00:00
|
|
|
(defn ds
|
|
|
|
|
"Tests should call this to get the DataSource to use inside a fixture."
|
|
|
|
|
[]
|
|
|
|
|
@test-datasource)
|
|
|
|
|
|
2020-06-07 17:01:12 +00:00
|
|
|
(defn- do-commands
|
|
|
|
|
"Example from migration docs: this serves as a test for it."
|
|
|
|
|
[connectable commands]
|
|
|
|
|
(if (instance? java.sql.Connection connectable)
|
|
|
|
|
(with-open [stmt (prep/statement connectable)]
|
|
|
|
|
(run! #(.addBatch stmt %) commands)
|
|
|
|
|
(into [] (.executeBatch stmt)))
|
|
|
|
|
(with-open [conn (jdbc/get-connection connectable)]
|
|
|
|
|
(do-commands conn commands))))
|
|
|
|
|
|
2019-04-19 05:43:19 +00:00
|
|
|
(defn with-test-db
|
|
|
|
|
"Given a test function (or suite), run it in the context of an in-memory
|
|
|
|
|
H2 database set up with a simple fruit table containing four rows of data.
|
|
|
|
|
|
|
|
|
|
Tests can reach into here and call ds (above) to get a DataSource for use
|
|
|
|
|
in test functions (that operate inside this fixture)."
|
|
|
|
|
[t]
|
2019-04-22 00:10:29 +00:00
|
|
|
(doseq [db test-db-specs]
|
|
|
|
|
(reset! test-db-spec db)
|
2019-07-25 00:32:58 +00:00
|
|
|
(if (= "embedded-postgres" (:dbtype db))
|
2019-11-11 01:53:23 +00:00
|
|
|
(reset! test-datasource
|
|
|
|
|
(.getPostgresDatabase ^EmbeddedPostgres @embedded-pg))
|
2019-07-25 00:32:58 +00:00
|
|
|
(reset! test-datasource (jdbc/get-datasource db)))
|
2019-11-15 23:38:51 +00:00
|
|
|
(let [fruit (if (mysql?) "fruit" "FRUIT") ; MySQL is case sensitive!
|
|
|
|
|
auto-inc-pk
|
2019-04-22 00:10:29 +00:00
|
|
|
(cond (or (derby?) (= "hsqldb" (:dbtype db)))
|
|
|
|
|
(str "GENERATED ALWAYS AS IDENTITY"
|
|
|
|
|
" (START WITH 1, INCREMENT BY 1)"
|
|
|
|
|
" PRIMARY KEY")
|
2019-07-25 00:32:58 +00:00
|
|
|
(postgres?)
|
|
|
|
|
(str "GENERATED ALWAYS AS IDENTITY"
|
|
|
|
|
" PRIMARY KEY")
|
2019-11-16 06:37:42 +00:00
|
|
|
(mssql?)
|
|
|
|
|
"IDENTITY PRIMARY KEY"
|
2019-04-22 00:10:29 +00:00
|
|
|
(sqlite?)
|
|
|
|
|
"PRIMARY KEY AUTOINCREMENT"
|
|
|
|
|
:else
|
|
|
|
|
"AUTO_INCREMENT PRIMARY KEY")]
|
|
|
|
|
(with-open [con (jdbc/get-connection (ds))]
|
2020-06-06 01:34:08 +00:00
|
|
|
(when (stored-proc?)
|
|
|
|
|
(try
|
|
|
|
|
(jdbc/execute-one! con ["DROP PROCEDURE FRUITP"])
|
|
|
|
|
(catch Throwable _)))
|
2019-04-22 00:10:29 +00:00
|
|
|
(try
|
2020-06-07 17:01:12 +00:00
|
|
|
(do-commands con [(str "DROP TABLE " fruit)])
|
2019-04-22 00:10:29 +00:00
|
|
|
(catch Exception _))
|
2020-06-24 18:23:40 +00:00
|
|
|
(when (postgres?)
|
|
|
|
|
(try
|
|
|
|
|
(do-commands con ["DROP TABLE LANG_TEST"])
|
|
|
|
|
(catch Exception _))
|
|
|
|
|
(try
|
|
|
|
|
(do-commands con ["DROP TYPE LANGUAGE"])
|
|
|
|
|
(catch Exception _))
|
|
|
|
|
(do-commands con ["CREATE TYPE LANGUAGE AS ENUM('en','fr','de')"])
|
|
|
|
|
(do-commands con ["
|
|
|
|
|
CREATE TABLE LANG_TEST (
|
|
|
|
|
LANG LANGUAGE NOT NULL
|
|
|
|
|
)"]))
|
2020-06-07 17:01:12 +00:00
|
|
|
(do-commands con [(str "
|
2019-11-15 23:38:51 +00:00
|
|
|
CREATE TABLE " fruit " (
|
2019-04-22 00:10:29 +00:00
|
|
|
ID INTEGER " auto-inc-pk ",
|
|
|
|
|
NAME VARCHAR(32),
|
2019-05-26 02:16:30 +00:00
|
|
|
APPEARANCE VARCHAR(32) DEFAULT NULL,
|
|
|
|
|
COST INT DEFAULT NULL,
|
|
|
|
|
GRADE REAL DEFAULT NULL
|
2019-04-22 00:10:29 +00:00
|
|
|
)")])
|
2020-06-06 01:34:08 +00:00
|
|
|
(when (stored-proc?)
|
|
|
|
|
(let [[begin end] (if (postgres?) ["$$" "$$"] ["BEGIN" "END"])]
|
|
|
|
|
(try
|
2020-06-07 19:13:32 +00:00
|
|
|
(do-commands con [(str "
|
2020-06-06 01:34:08 +00:00
|
|
|
CREATE PROCEDURE FRUITP" (cond (= "hsqldb" (:dbtype db)) "() READS SQL DATA DYNAMIC RESULT SETS 2 "
|
|
|
|
|
(mssql?) " AS "
|
|
|
|
|
(postgres?) "() LANGUAGE SQL AS "
|
|
|
|
|
:else "() ") "
|
|
|
|
|
" begin " " (if (= "hsqldb" (:dbtype db))
|
|
|
|
|
(str "ATOMIC
|
|
|
|
|
DECLARE result1 CURSOR WITH RETURN FOR SELECT * FROM " fruit " WHERE COST < 90;
|
|
|
|
|
DECLARE result2 CURSOR WITH RETURN FOR SELECT * FROM " fruit " WHERE GRADE >= 90.0;
|
|
|
|
|
OPEN result1;
|
|
|
|
|
OPEN result2;")
|
|
|
|
|
(str "
|
|
|
|
|
SELECT * FROM " fruit " WHERE COST < 90;
|
|
|
|
|
SELECT * FROM " fruit " WHERE GRADE >= 90.0;")) "
|
|
|
|
|
" end "
|
|
|
|
|
")])
|
|
|
|
|
(catch Throwable t
|
|
|
|
|
(println 'procedure (:dbtype db) (ex-message t))))))
|
|
|
|
|
(sql/insert-multi! con :fruit
|
|
|
|
|
[:name :appearance :cost :grade]
|
|
|
|
|
[["Apple" "red" 59 nil]
|
|
|
|
|
["Banana" "yellow" nil 92.2]
|
|
|
|
|
["Peach" nil 139 90.0]
|
|
|
|
|
["Orange" "juicy" 89 88.6]]
|
|
|
|
|
{:return-keys false})
|
|
|
|
|
(t)))))
|
2019-11-16 08:36:32 +00:00
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
;; this is a convenience to bring next.jdbc's test dependencies
|
|
|
|
|
;; into any REPL that has the add-lib branch of tools.deps.alpha
|
|
|
|
|
;; which allows me to develop and test next.jdbc inside my work's
|
|
|
|
|
;; "everything" REBL environment
|
|
|
|
|
(require '[clojure.tools.deps.alpha.repl :refer [add-lib]]
|
|
|
|
|
'[clojure.java.io :as io]
|
|
|
|
|
'[clojure.edn :as edn])
|
|
|
|
|
(def repo-path "/Developer/workspace/next.jdbc")
|
|
|
|
|
(def test-deps (-> (io/reader (str repo-path "/deps.edn"))
|
|
|
|
|
(java.io.PushbackReader.)
|
|
|
|
|
(edn/read)
|
|
|
|
|
:aliases
|
|
|
|
|
:test
|
|
|
|
|
:extra-deps))
|
|
|
|
|
(doseq [[coord version] test-deps]
|
2019-11-16 08:50:50 +00:00
|
|
|
(add-lib coord version))
|
|
|
|
|
;; now you can load this file... and then you can load other test
|
|
|
|
|
;; files and run their tests as needed... which will leave (ds)
|
|
|
|
|
;; set to the embedded PostgreSQL datasource -- reset it with this:
|
|
|
|
|
(let [db test-h2-mem #_test-mysql-map]
|
|
|
|
|
(reset! test-db-spec db)
|
|
|
|
|
(reset! test-datasource (jdbc/get-datasource db))))
|