wip xtdb testing

Signed-off-by: Sean Corfield <sean@corfield.org>
This commit is contained in:
Sean Corfield 2024-11-23 23:56:44 -08:00
parent ecd950d009
commit 0c50cf28b5
No known key found for this signature in database
10 changed files with 770 additions and 637 deletions

View file

@ -0,0 +1,8 @@
{:hooks
{:analyze-call
{next.jdbc/with-transaction
hooks.com.github.seancorfield.next-jdbc/with-transaction
next.jdbc/with-transaction+options
hooks.com.github.seancorfield.next-jdbc/with-transaction+options}}
:lint-as {next.jdbc/on-connection clojure.core/with-open
next.jdbc/on-connection+options clojure.core/with-open}}

View file

@ -0,0 +1,34 @@
(ns hooks.com.github.seancorfield.next-jdbc
(:require [clj-kondo.hooks-api :as api]))
(defn with-transaction
"Expands (with-transaction [tx expr opts] body)
to (let [tx expr] opts body) per clj-kondo examples."
[{:keys [:node]}]
(let [[binding-vec & body] (rest (:children node))
[sym val opts] (:children binding-vec)]
(when-not (and sym val)
(throw (ex-info "No sym and val provided" {})))
(let [new-node (api/list-node
(list*
(api/token-node 'let)
(api/vector-node [sym val])
opts
body))]
{:node new-node})))
(defn with-transaction+options
"Expands (with-transaction+options [tx expr opts] body)
to (let [tx expr] opts body) per clj-kondo examples."
[{:keys [:node]}]
(let [[binding-vec & body] (rest (:children node))
[sym val opts] (:children binding-vec)]
(when-not (and sym val)
(throw (ex-info "No sym and val provided" {})))
(let [new-node (api/list-node
(list*
(api/token-node 'let)
(api/vector-node [sym val])
opts
body))]
{:node new-node})))

View file

@ -1,4 +1,5 @@
{:mvn/repos {"sonatype" {:url "https://oss.sonatype.org/content/repositories/snapshots/"}} {:mvn/repos {"sonatype" {:url "https://oss.sonatype.org/content/repositories/snapshots/"}
"ossrh-snapshots" {:url "https://s01.oss.sonatype.org/content/repositories/snapshots"}}
:paths ["src" "resources"] :paths ["src" "resources"]
:deps {org.clojure/clojure {:mvn/version "1.10.3"} :deps {org.clojure/clojure {:mvn/version "1.10.3"}
org.clojure/java.data {:mvn/version "1.2.107"} org.clojure/java.data {:mvn/version "1.2.107"}
@ -40,6 +41,8 @@
io.zonky.test.postgres/embedded-postgres-binaries-windows-amd64 {:mvn/version "17.0.0"} io.zonky.test.postgres/embedded-postgres-binaries-windows-amd64 {:mvn/version "17.0.0"}
org.xerial/sqlite-jdbc {:mvn/version "3.46.1.3"} org.xerial/sqlite-jdbc {:mvn/version "3.46.1.3"}
com.microsoft.sqlserver/mssql-jdbc {:mvn/version "12.8.1.jre11"} com.microsoft.sqlserver/mssql-jdbc {:mvn/version "12.8.1.jre11"}
;; prerelease XTDB JDBC module:
com.xtdb/xtdb-jdbc {:mvn/version "2.0.0-SNAPSHOT"}
;; use log4j2 to reduce log noise during testing: ;; use log4j2 to reduce log noise during testing:
org.apache.logging.log4j/log4j-api {:mvn/version "2.24.0"} org.apache.logging.log4j/log4j-api {:mvn/version "2.24.0"}
;; bridge everything into log4j: ;; bridge everything into log4j:

View file

@ -14,3 +14,8 @@ services:
MSSQL_SA_PASSWORD: Str0ngP4ssw0rd MSSQL_SA_PASSWORD: Str0ngP4ssw0rd
ports: ports:
- "1433:1433" - "1433:1433"
xtdb:
image: ghcr.io/xtdb/xtdb
pull_policy: always
ports:
- "5432:5432"

View file

@ -10,7 +10,7 @@
[next.jdbc :as jdbc] [next.jdbc :as jdbc]
[next.jdbc.date-time] ; to extend SettableParameter to date/time [next.jdbc.date-time] ; to extend SettableParameter to date/time
[next.jdbc.test-fixtures :refer [with-test-db db ds [next.jdbc.test-fixtures :refer [with-test-db db ds
mssql?]] mssql? xtdb?]]
[next.jdbc.specs :as specs]) [next.jdbc.specs :as specs])
(:import (java.sql ResultSet))) (:import (java.sql ResultSet)))
@ -21,6 +21,7 @@
(specs/instrument) (specs/instrument)
(deftest issue-73 (deftest issue-73
(when-not (xtdb?)
(try (try
(jdbc/execute-one! (ds) ["drop table fruit_time"]) (jdbc/execute-one! (ds) ["drop table fruit_time"])
(catch Throwable _)) (catch Throwable _))
@ -46,4 +47,4 @@
(jdbc/execute-one! (ds) ["insert into fruit_time (id, deadline) values (?,?)" 1 (java.util.Date.)]) (jdbc/execute-one! (ds) ["insert into fruit_time (id, deadline) values (?,?)" 1 (java.util.Date.)])
(jdbc/execute-one! (ds) ["insert into fruit_time (id, deadline) values (?,?)" 2 (java.time.Instant/now)]) (jdbc/execute-one! (ds) ["insert into fruit_time (id, deadline) values (?,?)" 2 (java.time.Instant/now)])
(jdbc/execute-one! (ds) ["insert into fruit_time (id, deadline) values (?,?)" 3 (java.time.LocalDate/now)]) (jdbc/execute-one! (ds) ["insert into fruit_time (id, deadline) values (?,?)" 3 (java.time.LocalDate/now)])
(jdbc/execute-one! (ds) ["insert into fruit_time (id, deadline) values (?,?)" 4 (java.time.LocalDateTime/now)])) (jdbc/execute-one! (ds) ["insert into fruit_time (id, deadline) values (?,?)" 4 (java.time.LocalDateTime/now)])))

View file

@ -11,7 +11,7 @@
(:require [clojure.test :refer [deftest is testing use-fixtures]] (:require [clojure.test :refer [deftest is testing use-fixtures]]
[next.jdbc :as jdbc] [next.jdbc :as jdbc]
[next.jdbc.test-fixtures [next.jdbc.test-fixtures
:refer [with-test-db ds jtds? mssql? sqlite?]] :refer [with-test-db ds jtds? mssql? sqlite? xtdb?]]
[next.jdbc.prepare :as prep] [next.jdbc.prepare :as prep]
[next.jdbc.specs :as specs])) [next.jdbc.specs :as specs]))
@ -22,6 +22,7 @@
(specs/instrument) (specs/instrument)
(deftest execute-batch-tests (deftest execute-batch-tests
(when-not (xtdb?)
(testing "simple batch insert" (testing "simple batch insert"
(is (= [1 1 1 1 1 1 1 1 1 13] (is (= [1 1 1 1 1 1 1 1 1 13]
(jdbc/with-transaction [t (ds) {:rollback-only true}] (jdbc/with-transaction [t (ds) {:rollback-only true}]
@ -120,4 +121,4 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
;; Derby and SQLite only return one generated key per batch so there ;; Derby and SQLite only return one generated key per batch so there
;; are only three keys, plus the overall count here: ;; are only three keys, plus the overall count here:
(is (< 3 (count results)))) (is (< 3 (count results))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))))))

View file

@ -12,9 +12,9 @@
[next.jdbc.protocols :as p] [next.jdbc.protocols :as p]
[next.jdbc.result-set :as rs] [next.jdbc.result-set :as rs]
[next.jdbc.specs :as specs] [next.jdbc.specs :as specs]
[next.jdbc.test-fixtures :refer [with-test-db ds column [next.jdbc.test-fixtures :refer [with-test-db ds column index col-kw
default-options default-options
derby? mssql? mysql? postgres?]]) derby? mssql? mysql? postgres? xtdb?]])
(:import (java.sql ResultSet ResultSetMetaData))) (:import (java.sql ResultSet ResultSetMetaData)))
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
@ -93,7 +93,7 @@
(deftest test-map-row-builder (deftest test-map-row-builder
(testing "default row builder" (testing "default row builder"
(let [row (p/-execute-one (ds) (let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 1] [(str "select * from fruit where " (index) " = ?") 1]
(default-options))] (default-options))]
(is (map? row)) (is (map? row))
(is (contains? row (column :FRUIT/GRADE))) (is (contains? row (column :FRUIT/GRADE)))
@ -101,7 +101,7 @@
(is (= 1 ((column :FRUIT/ID) row))) (is (= 1 ((column :FRUIT/ID) row)))
(is (= "Apple" ((column :FRUIT/NAME) row)))) (is (= "Apple" ((column :FRUIT/NAME) row))))
(let [rs (p/-execute-all (ds) (let [rs (p/-execute-all (ds)
["select * from fruit order by id"] [(str "select * from fruit order by " (index))]
(default-options))] (default-options))]
(is (every? map? rs)) (is (every? map? rs))
(is (= 1 ((column :FRUIT/ID) (first rs)))) (is (= 1 ((column :FRUIT/ID) (first rs))))
@ -110,7 +110,7 @@
(is (= "Orange" ((column :FRUIT/NAME) (last rs)))))) (is (= "Orange" ((column :FRUIT/NAME) (last rs))))))
(testing "unqualified row builder" (testing "unqualified row builder"
(let [row (p/-execute-one (ds) (let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 2] [(str "select * from fruit where " (index) " = ?") 2]
{:builder-fn rs/as-unqualified-maps})] {:builder-fn rs/as-unqualified-maps})]
(is (map? row)) (is (map? row))
(is (contains? row (column :COST))) (is (contains? row (column :COST)))
@ -119,7 +119,7 @@
(is (= "Banana" ((column :NAME) row))))) (is (= "Banana" ((column :NAME) row)))))
(testing "lower-case row builder" (testing "lower-case row builder"
(let [row (p/-execute-one (ds) (let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 3] [(str "select * from fruit where " (index) " = ?") 3]
(assoc (default-options) (assoc (default-options)
:builder-fn rs/as-lower-maps))] :builder-fn rs/as-lower-maps))]
(is (map? row)) (is (map? row))
@ -129,33 +129,33 @@
(is (= "Peach" (:fruit/name row))))) (is (= "Peach" (:fruit/name row)))))
(testing "unqualified lower-case row builder" (testing "unqualified lower-case row builder"
(let [row (p/-execute-one (ds) (let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 4] [(str "select * from fruit where " (index) " = ?") 4]
{:builder-fn rs/as-unqualified-lower-maps})] {:builder-fn rs/as-unqualified-lower-maps})]
(is (map? row)) (is (map? row))
(is (= 4 (:id row))) (is (= 4 (:id row)))
(is (= "Orange" (:name row))))) (is (= "Orange" (:name row)))))
(testing "kebab-case row builder" (testing "kebab-case row builder"
(let [row (p/-execute-one (ds) (let [row (p/-execute-one (ds)
["select id,name,appearance as looks_like from fruit where id = ?" 3] [(str "select " (index) ",name,appearance as looks_like from fruit where " (index) " = ?") 3]
(assoc (default-options) (assoc (default-options)
:builder-fn rs/as-kebab-maps))] :builder-fn rs/as-kebab-maps))]
(is (map? row)) (is (map? row))
(is (contains? row :fruit/looks-like)) (is (contains? row (col-kw :fruit/looks-like)))
(is (nil? (:fruit/looks-like row))) (is (nil? ((col-kw :fruit/looks-like) row)))
(is (= 3 (:fruit/id row))) (is (= 3 ((col-kw :fruit/id) row)))
(is (= "Peach" (:fruit/name row))))) (is (= "Peach" ((col-kw :fruit/name) row)))))
(testing "unqualified kebab-case row builder" (testing "unqualified kebab-case row builder"
(let [row (p/-execute-one (ds) (let [row (p/-execute-one (ds)
["select id,name,appearance as looks_like from fruit where id = ?" 4] [(str "select " (index) ",name,appearance as looks_like from fruit where " (index) " = ?") 4]
{:builder-fn rs/as-unqualified-kebab-maps})] {:builder-fn rs/as-unqualified-kebab-maps})]
(is (map? row)) (is (map? row))
(is (contains? row :looks-like)) (is (contains? row :looks-like))
(is (= "juicy" (:looks-like row))) (is (= "juicy" (:looks-like row)))
(is (= 4 (:id row))) (is (= 4 ((col-kw :id) row)))
(is (= "Orange" (:name row))))) (is (= "Orange" (:name row)))))
(testing "custom row builder 1" (testing "custom row builder 1"
(let [row (p/-execute-one (ds) (let [row (p/-execute-one (ds)
["select fruit.*, id + 100 as newid from fruit where id = ?" 3] [(str "select fruit.*, " (index) " + 100 as newid from fruit where " (index) " = ?") 3]
(assoc (default-options) (assoc (default-options)
:builder-fn rs/as-modified-maps :builder-fn rs/as-modified-maps
:label-fn str/lower-case :label-fn str/lower-case
@ -181,7 +181,7 @@
(is (= "Peach" (:vegetable/name row))))) (is (= "Peach" (:vegetable/name row)))))
(testing "adapted row builder" (testing "adapted row builder"
(let [row (p/-execute-one (ds) (let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 3] [(str "select * from fruit where " (index) " = ?") 3]
(assoc (assoc
(default-options) (default-options)
:builder-fn (rs/as-maps-adapter :builder-fn (rs/as-maps-adapter
@ -207,7 +207,7 @@
(fn [^ResultSet rs _ ^Integer i] (fn [^ResultSet rs _ ^Integer i]
(.getObject rs i))) (.getObject rs i)))
row (p/-execute-one (ds) row (p/-execute-one (ds)
["select * from fruit where id = ?" 3] [(str "select * from fruit where " (index) " = ?") 3]
(assoc (assoc
(default-options) (default-options)
:builder-fn (rs/as-maps-adapter :builder-fn (rs/as-maps-adapter
@ -299,31 +299,31 @@
(testing "no row builder is used" (testing "no row builder is used"
(is (= [true] (is (= [true]
(into [] (map map?) ; it looks like a real map now (into [] (map map?) ; it looks like a real map now
(p/-execute (ds) ["select * from fruit where id = ?" 1] (p/-execute (ds) [(str "select * from fruit where " (index) " = ?") 1]
{:builder-fn (constantly nil)})))) {:builder-fn (constantly nil)}))))
(is (= ["Apple"] (is (= ["Apple"]
(into [] (map :name) ; keyword selection works (into [] (map :name) ; keyword selection works
(p/-execute (ds) ["select * from fruit where id = ?" 1] (p/-execute (ds) [(str "select * from fruit where " (index) " = ?") 1]
{:builder-fn (constantly nil)})))) {:builder-fn (constantly nil)}))))
(is (= [[2 [:name "Banana"]]] (is (= [[2 [:name "Banana"]]]
(into [] (map (juxt #(get % "id") ; get by string key works (into [] (map (juxt #(get % "id") ; get by string key works
#(find % :name))) ; get MapEntry works #(find % :name))) ; get MapEntry works
(p/-execute (ds) ["select * from fruit where id = ?" 2] (p/-execute (ds) [(str "select * from fruit where " (index) " = ?") 2]
{:builder-fn (constantly nil)})))) {:builder-fn (constantly nil)}))))
(is (= [{:id 3 :name "Peach"}] (is (= [{:id 3 :name "Peach"}]
(into [] (map #(select-keys % [:id :name])) ; select-keys works (into [] (map #(select-keys % [:id :name])) ; select-keys works
(p/-execute (ds) ["select * from fruit where id = ?" 3] (p/-execute (ds) [(str "select * from fruit where " (index) " = ?") 3]
{:builder-fn (constantly nil)})))) {:builder-fn (constantly nil)}))))
(is (= [[:orange 4]] (is (= [[:orange 4]]
(into [] (map #(vector (if (contains? % :name) ; contains works (into [] (map #(vector (if (contains? % :name) ; contains works
(keyword (str/lower-case (:name %))) (keyword (str/lower-case (:name %)))
:unnamed) :unnamed)
(get % :id 0))) ; get with not-found works (get % :id 0))) ; get with not-found works
(p/-execute (ds) ["select * from fruit where id = ?" 4] (p/-execute (ds) [(str "select * from fruit where " (index) " = ?") 4]
{:builder-fn (constantly nil)})))) {:builder-fn (constantly nil)}))))
(is (= [{}] (is (= [{}]
(into [] (map empty) ; return empty map without building (into [] (map empty) ; return empty map without building
(p/-execute (ds) ["select * from fruit where id = ?" 1] (p/-execute (ds) [(str "select * from fruit where " (index) " = ?") 1]
{:builder-fn (constantly nil)}))))) {:builder-fn (constantly nil)})))))
(testing "count does not build a map" (testing "count does not build a map"
(let [count-builder (fn [_1 _2] (let [count-builder (fn [_1 _2]
@ -331,7 +331,7 @@
(column-count [_] 13)))] (column-count [_] 13)))]
(is (= [13] (is (= [13]
(into [] (map count) ; count relies on columns, not row fields (into [] (map count) ; count relies on columns, not row fields
(p/-execute (ds) ["select * from fruit where id = ?" 1] (p/-execute (ds) [(str "select * from fruit where " (index) " = ?") 1]
{:builder-fn count-builder})))))) {:builder-fn count-builder}))))))
(testing "assoc, dissoc, cons, seq, and = build maps" (testing "assoc, dissoc, cons, seq, and = build maps"
(is (map? (reduce (fn [_ row] (reduced (assoc row :x 1))) (is (map? (reduce (fn [_ row] (reduced (assoc row :x 1)))
@ -467,7 +467,7 @@
metadata)))) metadata))))
(deftest clob-reading (deftest clob-reading
(when-not (or (mssql?) (mysql?) (postgres?)) ; no clob in these (when-not (or (mssql?) (mysql?) (postgres?) (xtdb?)) ; no clob in these
(with-open [con (p/get-connection (ds) {})] (with-open [con (p/get-connection (ds) {})]
(try (try
(p/-execute-one con ["DROP TABLE CLOBBER"] {}) (p/-execute-one con ["DROP TABLE CLOBBER"] {})

View file

@ -2,13 +2,14 @@
(ns next.jdbc.sql-test (ns next.jdbc.sql-test
"Tests for the syntactic sugar SQL functions." "Tests for the syntactic sugar SQL functions."
(:require [clojure.test :refer [deftest is testing use-fixtures]] (:require
[clojure.test :refer [deftest is testing use-fixtures]]
[next.jdbc :as jdbc] [next.jdbc :as jdbc]
[next.jdbc.specs :as specs] [next.jdbc.specs :as specs]
[next.jdbc.sql :as sql] [next.jdbc.sql :as sql]
[next.jdbc.test-fixtures [next.jdbc.test-fixtures
:refer [with-test-db ds column default-options :refer [column col-kw default-options derby? ds index
derby? jtds? maria? mssql? mysql? postgres? sqlite?]] jtds? maria? mssql? mysql? postgres? sqlite? with-test-db xtdb?]]
[next.jdbc.types :refer [as-other as-real as-varchar]])) [next.jdbc.types :refer [as-other as-real as-varchar]]))
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
@ -76,8 +77,8 @@
(deftest test-get-by-id (deftest test-get-by-id
(let [ds-opts (jdbc/with-options (ds) (default-options))] (let [ds-opts (jdbc/with-options (ds) (default-options))]
(is (nil? (sql/get-by-id ds-opts :fruit -1))) (is (nil? (sql/get-by-id ds-opts :fruit -1 (col-kw :id) {})))
(let [row (sql/get-by-id ds-opts :fruit 3)] (let [row (sql/get-by-id ds-opts :fruit 3 (col-kw :id) {})]
(is (map? row)) (is (map? row))
(is (= "Peach" ((column :FRUIT/NAME) row)))) (is (= "Peach" ((column :FRUIT/NAME) row))))
(let [row (sql/get-by-id ds-opts :fruit "juicy" :appearance {})] (let [row (sql/get-by-id ds-opts :fruit "juicy" :appearance {})]
@ -92,19 +93,19 @@
(let [ds-opts (jdbc/with-options (ds) (default-options))] (let [ds-opts (jdbc/with-options (ds) (default-options))]
(try (try
(is (= {:next.jdbc/update-count 1} (is (= {:next.jdbc/update-count 1}
(sql/update! ds-opts :fruit {:appearance "brown"} {:id 2}))) (sql/update! ds-opts :fruit {:appearance "brown"} {(col-kw :id) 2})))
(is (= "brown" ((column :FRUIT/APPEARANCE) (is (= "brown" ((column :FRUIT/APPEARANCE)
(sql/get-by-id ds-opts :fruit 2)))) (sql/get-by-id ds-opts :fruit 2 (col-kw :id) {}))))
(finally (finally
(sql/update! ds-opts :fruit {:appearance "yellow"} {:id 2}))) (sql/update! ds-opts :fruit {:appearance "yellow"} {(col-kw :id) 2})))
(try (try
(is (= {:next.jdbc/update-count 1} (is (= {:next.jdbc/update-count 1}
(sql/update! ds-opts :fruit {:appearance "green"} (sql/update! ds-opts :fruit {:appearance "green"}
["name = ?" "Banana"]))) ["name = ?" "Banana"])))
(is (= "green" ((column :FRUIT/APPEARANCE) (is (= "green" ((column :FRUIT/APPEARANCE)
(sql/get-by-id ds-opts :fruit 2)))) (sql/get-by-id ds-opts :fruit 2 (col-kw :id) {}))))
(finally (finally
(sql/update! ds-opts :fruit {:appearance "yellow"} {:id 2}))))) (sql/update! ds-opts :fruit {:appearance "yellow"} {(col-kw :id) 2})))))
(deftest test-insert-delete (deftest test-insert-delete
(let [new-key (cond (derby?) :1 (let [new-key (cond (derby?) :1
@ -113,18 +114,23 @@
(mssql?) :GENERATED_KEYS (mssql?) :GENERATED_KEYS
(mysql?) :GENERATED_KEY (mysql?) :GENERATED_KEY
(postgres?) :fruit/id (postgres?) :fruit/id
(xtdb?) :_id
:else :FRUIT/ID)] :else :FRUIT/ID)]
(testing "single insert/delete" (testing "single insert/delete"
(is (== 5 (new-key (sql/insert! (ds) :fruit (is (== 5 (new-key (doto
{:name (as-varchar "Kiwi") (sql/insert! (ds) :fruit
(cond-> {:name (as-varchar "Kiwi")
:appearance "green & fuzzy" :appearance "green & fuzzy"
:cost 100 :grade (as-real 99.9)} :cost 100 :grade (as-real 99.9)}
(xtdb?)
(assoc :_id 5))
{:suffix {:suffix
(when (sqlite?) (when (sqlite?)
"RETURNING *")})))) "RETURNING *")})
(println (ds))))))
(is (= 5 (count (sql/query (ds) ["select * from fruit"])))) (is (= 5 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 1} (is (= {:next.jdbc/update-count 1}
(sql/delete! (ds) :fruit {:id 5}))) (sql/delete! (ds) :fruit {(col-kw :id) 5})))
(is (= 4 (count (sql/query (ds) ["select * from fruit"]))))) (is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "multiple insert/delete" (testing "multiple insert/delete"
(is (= (cond (derby?) (is (= (cond (derby?)
@ -137,19 +143,22 @@
[6 7 8]) [6 7 8])
(mapv new-key (mapv new-key
(sql/insert-multi! (ds) :fruit (sql/insert-multi! (ds) :fruit
[:name :appearance :cost :grade] (cond->> [:name :appearance :cost :grade]
[["Kiwi" "green & fuzzy" 100 99.9] (xtdb?) (cons :_id))
(cond->> [["Kiwi" "green & fuzzy" 100 99.9]
["Grape" "black" 10 50] ["Grape" "black" 10 50]
["Lemon" "yellow" 20 9.9]] ["Lemon" "yellow" 20 9.9]]
(xtdb?)
(map cons [6 7 8]))
{:suffix {:suffix
(when (sqlite?) (when (sqlite?)
"RETURNING *")})))) "RETURNING *")}))))
(is (= 7 (count (sql/query (ds) ["select * from fruit"])))) (is (= 7 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 1} (is (= {:next.jdbc/update-count 1}
(sql/delete! (ds) :fruit {:id 6}))) (sql/delete! (ds) :fruit {(col-kw :id) 6})))
(is (= 6 (count (sql/query (ds) ["select * from fruit"])))) (is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 2} (is (= {:next.jdbc/update-count 2}
(sql/delete! (ds) :fruit ["id > ?" 4]))) (sql/delete! (ds) :fruit [(str (index) " > ?") 4])))
(is (= 4 (count (sql/query (ds) ["select * from fruit"]))))) (is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "multiple insert/delete with sequential cols/rows" ; per #43 (testing "multiple insert/delete with sequential cols/rows" ; per #43
(is (= (cond (derby?) (is (= (cond (derby?)
@ -162,19 +171,22 @@
[9 10 11]) [9 10 11])
(mapv new-key (mapv new-key
(sql/insert-multi! (ds) :fruit (sql/insert-multi! (ds) :fruit
'(:name :appearance :cost :grade) (cond->> '(:name :appearance :cost :grade)
'(("Kiwi" "green & fuzzy" 100 99.9) (xtdb?) (cons :_id))
(cond->> '(("Kiwi" "green & fuzzy" 100 99.9)
("Grape" "black" 10 50) ("Grape" "black" 10 50)
("Lemon" "yellow" 20 9.9)) ("Lemon" "yellow" 20 9.9))
(xtdb?)
(map cons [9 10 11]))
{:suffix {:suffix
(when (sqlite?) (when (sqlite?)
"RETURNING *")})))) "RETURNING *")}))))
(is (= 7 (count (sql/query (ds) ["select * from fruit"])))) (is (= 7 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 1} (is (= {:next.jdbc/update-count 1}
(sql/delete! (ds) :fruit {:id 9}))) (sql/delete! (ds) :fruit {(col-kw :id) 9})))
(is (= 6 (count (sql/query (ds) ["select * from fruit"])))) (is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 2} (is (= {:next.jdbc/update-count 2}
(sql/delete! (ds) :fruit ["id > ?" 4]))) (sql/delete! (ds) :fruit [(str (index) " > ?") 4])))
(is (= 4 (count (sql/query (ds) ["select * from fruit"]))))) (is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "multiple insert/delete with maps" (testing "multiple insert/delete with maps"
(is (= (cond (derby?) (is (= (cond (derby?)
@ -187,7 +199,7 @@
[12 13 14]) [12 13 14])
(mapv new-key (mapv new-key
(sql/insert-multi! (ds) :fruit (sql/insert-multi! (ds) :fruit
[{:name "Kiwi" (cond->> [{:name "Kiwi"
:appearance "green & fuzzy" :appearance "green & fuzzy"
:cost 100 :cost 100
:grade 99.9} :grade 99.9}
@ -199,15 +211,17 @@
:appearance "yellow" :appearance "yellow"
:cost 20 :cost 20
:grade 9.9}] :grade 9.9}]
(xtdb?)
(map #(assoc %2 :_id %1) [12 13 14]))
{:suffix {:suffix
(when (sqlite?) (when (sqlite?)
"RETURNING *")})))) "RETURNING *")}))))
(is (= 7 (count (sql/query (ds) ["select * from fruit"])))) (is (= 7 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 1} (is (= {:next.jdbc/update-count 1}
(sql/delete! (ds) :fruit {:id 12}))) (sql/delete! (ds) :fruit {(col-kw :id) 12})))
(is (= 6 (count (sql/query (ds) ["select * from fruit"])))) (is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 2} (is (= {:next.jdbc/update-count 2}
(sql/delete! (ds) :fruit ["id > ?" 10]))) (sql/delete! (ds) :fruit [(str (index) " > ?") 10])))
(is (= 4 (count (sql/query (ds) ["select * from fruit"]))))) (is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "empty insert-multi!" ; per #44 and #264 (testing "empty insert-multi!" ; per #44 and #264
(is (= [] (sql/insert-multi! (ds) :fruit (is (= [] (sql/insert-multi! (ds) :fruit
@ -255,7 +269,7 @@
(deftest array-in (deftest array-in
(when (postgres?) (when (postgres?)
(let [data (sql/find-by-keys (ds) :fruit ["id = any(?)" (int-array [1 2 3 4])])] (let [data (sql/find-by-keys (ds) :fruit [(str (index) " = any(?)") (int-array [1 2 3 4])])]
(is (= 4 (count data)))))) (is (= 4 (count data))))))
(deftest enum-pg (deftest enum-pg

View file

@ -64,11 +64,17 @@
(def ^:private test-jtds (def ^:private test-jtds
(when (System/getenv "NEXT_JDBC_TEST_MSSQL") test-jtds-map)) (when (System/getenv "NEXT_JDBC_TEST_MSSQL") test-jtds-map))
(def ^:private test-xtdb-map {:dbtype "xtdb"})
(def ^:private test-xtdb
(when (System/getenv "NEXT_JDBC_TEST_XTDB") test-xtdb-map))
(def ^:private test-db-specs (def ^:private test-db-specs
(cond-> [test-derby test-h2-mem test-h2 test-hsql test-sqlite] (cond-> [test-derby test-h2-mem test-h2 test-hsql test-sqlite]
test-postgres (conj test-postgres) test-postgres (conj test-postgres)
test-mysql (conj test-mysql) test-mysql (conj test-mysql)
test-mssql (conj test-mssql test-jtds))) test-mssql (conj test-mssql test-jtds)
test-xtdb (conj test-xtdb)))
(def ^:private test-db-spec (atom nil)) (def ^:private test-db-spec (atom nil))
@ -86,19 +92,34 @@
(defn postgres? [] (= "embedded-postgres" (:dbtype @test-db-spec))) (defn postgres? [] (= "embedded-postgres" (:dbtype @test-db-spec)))
(defn xtdb? [] (= "xtdb" (:dbtype @test-db-spec)))
(defn sqlite? [] (= "sqlite" (:dbtype @test-db-spec))) (defn sqlite? [] (= "sqlite" (:dbtype @test-db-spec)))
(defn stored-proc? [] (not (#{"derby" "h2" "h2:mem" "sqlite"} (:dbtype @test-db-spec)))) (defn stored-proc? [] (not (#{"derby" "h2" "h2:mem" "sqlite" "xtdb"}
(:dbtype @test-db-spec))))
(defn column [k] (defn column [k]
(let [n (namespace k)] (let [n (namespace k)]
(keyword (when n (cond (postgres?) (str/lower-case n) (keyword (when n (cond (postgres?) (str/lower-case n)
(mssql?) (str/lower-case n) (mssql?) (str/lower-case n)
(mysql?) (str/lower-case n) (mysql?) (str/lower-case n)
(xtdb?) nil
:else n)) :else n))
(cond (postgres?) (str/lower-case (name k)) (cond (postgres?) (str/lower-case (name k))
(xtdb?) (let [c (str/lower-case (name k))]
(if (= "id" c) "_id" c))
:else (name k))))) :else (name k)))))
(defn index []
(if (xtdb?) "_id" "id"))
(defn col-kw [k]
(if (xtdb?)
(let [n (name k)]
(if (= "id" n) :_id (keyword n)))
k))
(defn default-options [] (defn default-options []
(if (mssql?) ; so that we get table names back from queries (if (mssql?) ; so that we get table names back from queries
{:result-type :scroll-insensitive :concurrency :read-only} {:result-type :scroll-insensitive :concurrency :read-only}
@ -156,6 +177,28 @@
:else :else
"AUTO_INCREMENT PRIMARY KEY")] "AUTO_INCREMENT PRIMARY KEY")]
(with-open [con (jdbc/get-connection (ds))] (with-open [con (jdbc/get-connection (ds))]
(if (xtdb?) ; no DDL for creation
(do
(try
(do-commands con ["DELETE FROM fruit WHERE true"])
(catch Throwable _))
(sql/insert-multi! con :fruit
[:_id :name :appearance :cost]
[[1 "Apple" "red" 59]]
{:return-keys false})
(sql/insert-multi! con :fruit
[:_id :name :appearance :grade]
[[2 "Banana" "yellow" 92.2]]
{:return-keys false})
(sql/insert-multi! con :fruit
[:_id :name :cost :grade]
[[3 "Peach" 139 90.0]]
{:return-keys false})
(sql/insert-multi! con :fruit
[:_id :name :appearance :cost :grade]
[[4 "Orange" "juicy" 89 88.6]]
{:return-keys false}))
(do
(when (stored-proc?) (when (stored-proc?)
(try (try
(jdbc/execute-one! con ["DROP PROCEDURE FRUITP"]) (jdbc/execute-one! con ["DROP PROCEDURE FRUITP"])
@ -231,7 +274,7 @@ CREATE PROCEDURE FRUITP" (cond (hsqldb?) "() READS SQL DATA DYNAMIC RESULT SETS
["Banana" "yellow" nil 92.2] ["Banana" "yellow" nil 92.2]
["Peach" nil 139 90.0] ["Peach" nil 139 90.0]
["Orange" "juicy" 89 88.6]] ["Orange" "juicy" 89 88.6]]
{:return-keys false}) {:return-keys false})))
(t))))) (t)))))
(create-clojure-test) (create-clojure-test)

View file

@ -2,21 +2,23 @@
(ns next.jdbc-test (ns next.jdbc-test
"Basic tests for the primary API of `next.jdbc`." "Basic tests for the primary API of `next.jdbc`."
(:require [clojure.core.reducers :as r] (:require
[clojure.core.reducers :as r]
[clojure.string :as str] [clojure.string :as str]
[clojure.test :refer [deftest is testing use-fixtures]] [clojure.test :refer [deftest is testing use-fixtures]]
[next.jdbc :as jdbc] [next.jdbc :as jdbc]
[next.jdbc.connection :as c] [next.jdbc.connection :as c]
[next.jdbc.test-fixtures
:refer [with-test-db db ds column
default-options stored-proc?
derby? hsqldb? jtds? mssql? mysql? postgres? sqlite?]]
[next.jdbc.prepare :as prep] [next.jdbc.prepare :as prep]
[next.jdbc.result-set :as rs] [next.jdbc.result-set :as rs]
[next.jdbc.specs :as specs] [next.jdbc.specs :as specs]
[next.jdbc.test-fixtures
:refer [col-kw column db default-options derby? ds hsqldb? index
jtds? mssql? mysql? postgres? sqlite? stored-proc?
with-test-db xtdb?]]
[next.jdbc.types :as types]) [next.jdbc.types :as types])
(:import (com.zaxxer.hikari HikariDataSource) (:import
(com.mchange.v2.c3p0 ComboPooledDataSource PooledDataSource) (com.mchange.v2.c3p0 ComboPooledDataSource PooledDataSource)
(com.zaxxer.hikari HikariDataSource)
(java.sql ResultSet ResultSetMetaData))) (java.sql ResultSet ResultSetMetaData)))
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
@ -60,27 +62,27 @@
(jdbc/execute-one! (jdbc/execute-one!
ds-opts ds-opts
["select * from fruit where appearance = ?" "red"])))) ["select * from fruit where appearance = ?" "red"]))))
(is (= "red" (:fruit/looks-like (is (= "red" ((col-kw :fruit/looks-like)
(jdbc/execute-one! (jdbc/execute-one!
ds-opts ds-opts
["select appearance as looks_like from fruit where id = ?" 1] [(str "select appearance as looks_like from fruit where " (index) " = ?") 1]
jdbc/snake-kebab-opts)))) jdbc/snake-kebab-opts))))
(let [ds' (jdbc/with-options ds-opts jdbc/snake-kebab-opts)] (let [ds' (jdbc/with-options ds-opts jdbc/snake-kebab-opts)]
(is (= "red" (:fruit/looks-like (is (= "red" ((col-kw :fruit/looks-like)
(jdbc/execute-one! (jdbc/execute-one!
ds' ds'
["select appearance as looks_like from fruit where id = ?" 1]))))) [(str "select appearance as looks_like from fruit where " (index) " = ?") 1])))))
(jdbc/with-transaction+options [ds' (jdbc/with-options ds-opts jdbc/snake-kebab-opts)] (jdbc/with-transaction+options [ds' (jdbc/with-options ds-opts jdbc/snake-kebab-opts)]
(is (= (merge (default-options) jdbc/snake-kebab-opts) (is (= (merge (default-options) jdbc/snake-kebab-opts)
(:options ds'))) (:options ds')))
(is (= "red" (:fruit/looks-like (is (= "red" ((col-kw :fruit/looks-like)
(jdbc/execute-one! (jdbc/execute-one!
ds' ds'
["select appearance as looks_like from fruit where id = ?" 1]))))) [(str "select appearance as looks_like from fruit where " (index) " = ?") 1])))))
(is (= "red" (:looks-like (is (= "red" (:looks-like
(jdbc/execute-one! (jdbc/execute-one!
ds-opts ds-opts
["select appearance as looks_like from fruit where id = ?" 1] [(str "select appearance as looks_like from fruit where " (index) " = ?") 1]
jdbc/unqualified-snake-kebab-opts))))) jdbc/unqualified-snake-kebab-opts)))))
(testing "execute!" (testing "execute!"
(let [rs (jdbc/execute! (let [rs (jdbc/execute!
@ -95,7 +97,7 @@
(is (= 1 ((column :FRUIT/ID) (first rs))))) (is (= 1 ((column :FRUIT/ID) (first rs)))))
(let [rs (jdbc/execute! (let [rs (jdbc/execute!
ds-opts ds-opts
["select * from fruit order by id"] [(str "select * from fruit order by " (index))]
{:builder-fn rs/as-maps})] {:builder-fn rs/as-maps})]
(is (every? map? rs)) (is (every? map? rs))
(is (every? meta rs)) (is (every? meta rs))
@ -104,22 +106,26 @@
(is (= 4 ((column :FRUIT/ID) (last rs))))) (is (= 4 ((column :FRUIT/ID) (last rs)))))
(let [rs (jdbc/execute! (let [rs (jdbc/execute!
ds-opts ds-opts
["select * from fruit order by id"] [(str "select * from fruit order by " (index))]
{:builder-fn rs/as-arrays})] {:builder-fn rs/as-arrays})]
(is (every? vector? rs)) (is (every? vector? rs))
(is (= 5 (count rs))) (is (= 5 (count rs)))
(is (every? #(= 5 (count %)) rs)) (is (every? #(= 5 (count %)) rs))
;; columns come first ;; columns come first
(is (every? qualified-keyword? (first rs))) (is (every? (if (xtdb?) keyword? qualified-keyword?) (first rs)))
;; :FRUIT/ID should be first column ;; :FRUIT/ID should be first column
(is (= (column :FRUIT/ID) (ffirst rs))) (is (= (column :FRUIT/ID) (ffirst rs)))
;; and all its corresponding values should be ints ;; and all its corresponding values should be ints
(is (every? int? (map first (rest rs)))) (is (every? int? (map first (rest rs))))
(is (every? string? (map second (rest rs)))))) (when (xtdb?) (println (first rs)
(.indexOf ^java.util.List (first rs) :name)
(.indexOf (first rs) :name)))
(let [n (.indexOf ^java.util.List (first rs) :name)]
(is (every? string? (map #(nth % n) (rest rs)))))))
(testing "execute! with adapter" (testing "execute! with adapter"
(let [rs (jdbc/execute! ; test again, with adapter and lower columns (let [rs (jdbc/execute! ; test again, with adapter and lower columns
ds-opts ds-opts
["select * from fruit order by id"] [(str "select * from fruit order by " (index))]
{:builder-fn (rs/as-arrays-adapter {:builder-fn (rs/as-arrays-adapter
rs/as-lower-arrays rs/as-lower-arrays
(fn [^ResultSet rs _ ^Integer i] (fn [^ResultSet rs _ ^Integer i]
@ -128,16 +134,20 @@
(is (= 5 (count rs))) (is (= 5 (count rs)))
(is (every? #(= 5 (count %)) rs)) (is (every? #(= 5 (count %)) rs))
;; columns come first ;; columns come first
(is (every? qualified-keyword? (first rs))) (is (every? (if (xtdb?) keyword? qualified-keyword?) (first rs)))
;; :fruit/id should be first column ;; :fruit/id should be first column
(is (= :fruit/id (ffirst rs))) (is (= (col-kw :fruit/id) (ffirst rs)))
;; and all its corresponding values should be ints ;; and all its corresponding values should be ints
(is (every? int? (map first (rest rs)))) (is (every? int? (map first (rest rs))))
(is (every? string? (map second (rest rs)))))) (when (xtdb?) (println (first rs)
(.indexOf ^java.util.List (first rs) :name)
(.indexOf (first rs) :name)))
(let [n (.indexOf ^java.util.List (first rs) :name)]
(is (every? string? (map #(nth % n) (rest rs)))))))
(testing "execute! with unqualified" (testing "execute! with unqualified"
(let [rs (jdbc/execute! (let [rs (jdbc/execute!
(ds) (ds)
["select * from fruit order by id"] [(str "select * from fruit order by " (index))]
{:builder-fn rs/as-unqualified-maps})] {:builder-fn rs/as-unqualified-maps})]
(is (every? map? rs)) (is (every? map? rs))
(is (every? meta rs)) (is (every? meta rs))
@ -146,7 +156,7 @@
(is (= 4 ((column :ID) (last rs))))) (is (= 4 ((column :ID) (last rs)))))
(let [rs (jdbc/execute! (let [rs (jdbc/execute!
ds-opts ds-opts
["select * from fruit order by id"] [(str "select * from fruit order by " (index))]
{:builder-fn rs/as-unqualified-arrays})] {:builder-fn rs/as-unqualified-arrays})]
(is (every? vector? rs)) (is (every? vector? rs))
(is (= 5 (count rs))) (is (= 5 (count rs)))
@ -157,11 +167,15 @@
(is (= (column :ID) (ffirst rs))) (is (= (column :ID) (ffirst rs)))
;; and all its corresponding values should be ints ;; and all its corresponding values should be ints
(is (every? int? (map first (rest rs)))) (is (every? int? (map first (rest rs))))
(is (every? string? (map second (rest rs)))))) (when (xtdb?) (println (first rs)
(.indexOf ^java.util.List (first rs) :name)
(.indexOf (first rs) :name)))
(let [n (.indexOf ^java.util.List (first rs) :name)]
(is (every? string? (map #(nth % n) (rest rs)))))))
(testing "execute! with :max-rows / :maxRows" (testing "execute! with :max-rows / :maxRows"
(let [rs (jdbc/execute! (let [rs (jdbc/execute!
ds-opts ds-opts
["select * from fruit order by id"] [(str "select * from fruit order by " (index))]
{:max-rows 2})] {:max-rows 2})]
(is (every? map? rs)) (is (every? map? rs))
(is (every? meta rs)) (is (every? meta rs))
@ -170,7 +184,7 @@
(is (= 2 ((column :FRUIT/ID) (last rs))))) (is (= 2 ((column :FRUIT/ID) (last rs)))))
(let [rs (jdbc/execute! (let [rs (jdbc/execute!
ds-opts ds-opts
["select * from fruit order by id"] [(str "select * from fruit order by " (index))]
{:statement {:maxRows 2}})] {:statement {:maxRows 2}})]
(is (every? map? rs)) (is (every? map? rs))
(is (every? meta rs)) (is (every? meta rs))
@ -182,7 +196,7 @@
(let [rs (with-open [con (jdbc/get-connection (ds)) (let [rs (with-open [con (jdbc/get-connection (ds))
ps (jdbc/prepare ps (jdbc/prepare
con con
["select * from fruit order by id"] [(str "select * from fruit order by " (index))]
(default-options))] (default-options))]
(jdbc/execute! ps))] (jdbc/execute! ps))]
(is (every? map? rs)) (is (every? map? rs))
@ -194,7 +208,7 @@
(let [rs (with-open [con (jdbc/get-connection (ds)) (let [rs (with-open [con (jdbc/get-connection (ds))
ps (jdbc/prepare ps (jdbc/prepare
con con
["select * from fruit where id = ?"] [(str "select * from fruit where " (index) " = ?")]
(default-options))] (default-options))]
(jdbc/execute! (prep/set-parameters ps [4]) nil {}))] (jdbc/execute! (prep/set-parameters ps [4]) nil {}))]
(is (every? map? rs)) (is (every? map? rs))
@ -205,7 +219,8 @@
;; default options do not flow over get-connection ;; default options do not flow over get-connection
(let [rs (with-open [con (jdbc/get-connection (ds))] (let [rs (with-open [con (jdbc/get-connection (ds))]
(jdbc/execute! (prep/statement con (default-options)) (jdbc/execute! (prep/statement con (default-options))
["select * from fruit order by id"]))] [(str "select * from fruit order by " (index))]))]
(when (xtdb?) (println rs))
(is (every? map? rs)) (is (every? map? rs))
(is (every? meta rs)) (is (every? meta rs))
(is (= 4 (count rs))) (is (= 4 (count rs)))
@ -214,11 +229,13 @@
;; default options do not flow over get-connection ;; default options do not flow over get-connection
(let [rs (with-open [con (jdbc/get-connection (ds))] (let [rs (with-open [con (jdbc/get-connection (ds))]
(jdbc/execute! (prep/statement con (default-options)) (jdbc/execute! (prep/statement con (default-options))
["select * from fruit where id = 4"]))] [(str "select * from fruit where " (index) " = 4")]))]
(when (xtdb?) (println rs))
(is (every? map? rs)) (is (every? map? rs))
(is (every? meta rs)) (is (every? meta rs))
(is (= 1 (count rs))) (is (= 1 (count rs)))
(is (= 4 ((column :FRUIT/ID) (first rs)))))) (is (= 4 ((column :FRUIT/ID) (first rs))))))
(when-not (xtdb?)
(testing "transact" (testing "transact"
(is (= [{:next.jdbc/update-count 1}] (is (= [{:next.jdbc/update-count 1}]
(jdbc/transact (ds) (jdbc/transact (ds)
@ -354,11 +371,11 @@ VALUES ('Pear', 'green', 49, 47)
(.rollback t save-point) (.rollback t save-point)
result)))) result))))
(is (= 4 (count (jdbc/execute! con ["select * from fruit"])))) (is (= 4 (count (jdbc/execute! con ["select * from fruit"]))))
(is (= ac (.getAutoCommit con))))))) (is (= ac (.getAutoCommit con))))))))
(deftest issue-146 (deftest issue-146
;; since we use an embedded PostgreSQL data source, we skip this: ;; since we use an embedded PostgreSQL data source, we skip this:
(when-not (or (postgres?) (when-not (or (postgres?) (xtdb?)
;; and now we skip MS SQL because we can't use the db-spec ;; and now we skip MS SQL because we can't use the db-spec
;; we'd need to build the jdbcUrl with encryption turned off: ;; we'd need to build the jdbcUrl with encryption turned off:
(and (mssql?) (not (jtds?)))) (and (mssql?) (not (jtds?))))
@ -495,6 +512,7 @@ VALUES ('Pear', 'green', 49, 47)
"\n\t" (ex-message t))))) "\n\t" (ex-message t)))))
(deftest bool-tests (deftest bool-tests
(when-not (xtdb?)
(doseq [[n b] [["zero" 0] ["one" 1] ["false" false] ["true" true]] (doseq [[n b] [["zero" 0] ["one" 1] ["false" false] ["true" true]]
:let [v-bit (if (number? b) b (if b 1 0)) :let [v-bit (if (number? b) b (if b 1 0))
v-bool (if (number? b) (pos? b) b)]] v-bool (if (number? b) (pos? b) b)]]
@ -548,9 +566,10 @@ VALUES ('Pear', 'green', 49, 47)
[] []
(jdbc/plan (ds) ["select * from btest"]))] (jdbc/plan (ds) ["select * from btest"]))]
(is (every? boolean? (map :is_it data))) (is (every? boolean? (map :is_it data)))
(is (every? boolean? (map :twiddle data))))) (is (every? boolean? (map :twiddle data))))))
(deftest execute-batch-tests (deftest execute-batch-tests
(when-not (xtdb?)
(testing "simple batch insert" (testing "simple batch insert"
(is (= [1 1 1 1 1 1 1 1 1 13] (is (= [1 1 1 1 1 1 1 1 1 13]
(jdbc/with-transaction [t (ds) {:rollback-only true}] (jdbc/with-transaction [t (ds) {:rollback-only true}]
@ -649,9 +668,10 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
;; Derby and SQLite only return one generated key per batch so there ;; Derby and SQLite only return one generated key per batch so there
;; are only three keys, plus the overall count here: ;; are only three keys, plus the overall count here:
(is (< 3 (count results)))) (is (< 3 (count results))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))))))
(deftest execute-batch-connectable-tests (deftest execute-batch-connectable-tests
(when-not (xtdb?)
(testing "simple batch insert" (testing "simple batch insert"
(is (= [1 1 1 1 1 1 1 1 1 13] (is (= [1 1 1 1 1 1 1 1 1 13]
(try (try
@ -669,7 +689,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
{})] {})]
(conj result (count (jdbc/execute! (ds) ["select * from fruit"])))) (conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
(finally (finally
(jdbc/execute-one! (ds) ["delete from fruit where id > 4"]))))) (jdbc/execute-one! (ds) [(str "delete from fruit where " (index) " > 4")])))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
(testing "batch with-options" (testing "batch with-options"
(is (= [1 1 1 1 1 1 1 1 1 13] (is (= [1 1 1 1 1 1 1 1 1 13]
@ -688,7 +708,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
{})] {})]
(conj result (count (jdbc/execute! (ds) ["select * from fruit"])))) (conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
(finally (finally
(jdbc/execute-one! (ds) ["delete from fruit where id > 4"]))))) (jdbc/execute-one! (ds) [(str "delete from fruit where " (index) " > 4")])))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
(testing "batch with-logging" (testing "batch with-logging"
(is (= [1 1 1 1 1 1 1 1 1 13] (is (= [1 1 1 1 1 1 1 1 1 13]
@ -707,7 +727,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
{})] {})]
(conj result (count (jdbc/execute! (ds) ["select * from fruit"])))) (conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
(finally (finally
(jdbc/execute-one! (ds) ["delete from fruit where id > 4"]))))) (jdbc/execute-one! (ds) [(str "delete from fruit where " (index) " > 4")])))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
(testing "small batch insert" (testing "small batch insert"
(is (= [1 1 1 1 1 1 1 1 1 13] (is (= [1 1 1 1 1 1 1 1 1 13]
@ -726,7 +746,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
{:batch-size 3})] {:batch-size 3})]
(conj result (count (jdbc/execute! (ds) ["select * from fruit"])))) (conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
(finally (finally
(jdbc/execute-one! (ds) ["delete from fruit where id > 4"]))))) (jdbc/execute-one! (ds) [(str "delete from fruit where " (index) " > 4")])))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
(testing "big batch insert" (testing "big batch insert"
(is (= [1 1 1 1 1 1 1 1 1 13] (is (= [1 1 1 1 1 1 1 1 1 13]
@ -745,7 +765,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
{:batch-size 8})] {:batch-size 8})]
(conj result (count (jdbc/execute! (ds) ["select * from fruit"])))) (conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
(finally (finally
(jdbc/execute-one! (ds) ["delete from fruit where id > 4"]))))) (jdbc/execute-one! (ds) [(str "delete from fruit where " (index) " > 4")])))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
(testing "large batch insert" (testing "large batch insert"
(when-not (or (jtds?) (sqlite?)) (when-not (or (jtds?) (sqlite?))
@ -766,7 +786,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
:large true})] :large true})]
(conj result (count (jdbc/execute! (ds) ["select * from fruit"])))) (conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
(finally (finally
(jdbc/execute-one! (ds) ["delete from fruit where id > 4"]))))) (jdbc/execute-one! (ds) [(str "delete from fruit where " (index) " > 4")])))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))))
(testing "return generated keys" (testing "return generated keys"
(when-not (or (mssql?) (sqlite?)) (when-not (or (mssql?) (sqlite?))
@ -790,26 +810,30 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
:return-generated-keys true})] :return-generated-keys true})]
(conj result (count (jdbc/execute! (ds) ["select * from fruit"])))) (conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
(finally (finally
(jdbc/execute-one! (ds) ["delete from fruit where id > 4"])))] (jdbc/execute-one! (ds) [(str "delete from fruit where " (index) " > 4")])))]
(is (= 13 (last results))) (is (= 13 (last results)))
(is (every? map? (butlast results))) (is (every? map? (butlast results)))
;; Derby and SQLite only return one generated key per batch so there ;; Derby and SQLite only return one generated key per batch so there
;; are only three keys, plus the overall count here: ;; are only three keys, plus the overall count here:
(is (< 3 (count results)))) (is (< 3 (count results))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))))))
(deftest folding-test (deftest folding-test
(jdbc/execute-one! (ds) ["delete from fruit"]) (jdbc/execute-one! (ds) ["delete from fruit"])
(if (xtdb?)
(with-open [con (jdbc/get-connection (ds))
ps (jdbc/prepare con ["insert into fruit(_id,name) values (?,?)"])]
(jdbc/execute-batch! ps (mapv #(vector % (str "Fruit-" %)) (range 1 1001))))
(with-open [con (jdbc/get-connection (ds)) (with-open [con (jdbc/get-connection (ds))
ps (jdbc/prepare con ["insert into fruit(name) values (?)"])] ps (jdbc/prepare con ["insert into fruit(name) values (?)"])]
(jdbc/execute-batch! ps (mapv #(vector (str "Fruit-" %)) (range 1 1001)))) (jdbc/execute-batch! ps (mapv #(vector (str "Fruit-" %)) (range 1 1001)))))
(testing "foldable result set" (testing "foldable result set"
(testing "from a Connection" (testing "from a Connection"
(let [result (let [result
(with-open [con (jdbc/get-connection (ds))] (with-open [con (jdbc/get-connection (ds))]
(r/foldcat (r/foldcat
(r/map (column :FRUIT/NAME) (r/map (column :FRUIT/NAME)
(jdbc/plan con ["select * from fruit order by id"] (jdbc/plan con [(str "select * from fruit order by " (index))]
(default-options)))))] (default-options)))))]
(is (= 1000 (count result))) (is (= 1000 (count result)))
(is (= "Fruit-1" (first result))) (is (= "Fruit-1" (first result)))
@ -821,7 +845,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
(try (try
(r/fold n r/cat r/append! (r/fold n r/cat r/append!
(r/map (column :FRUIT/NAME) (r/map (column :FRUIT/NAME)
(jdbc/plan (ds) ["select * from fruit order by id"] (jdbc/plan (ds) [(str "select * from fruit order by " (index))]
(default-options)))) (default-options))))
(catch java.util.concurrent.RejectedExecutionException _ (catch java.util.concurrent.RejectedExecutionException _
[]))] []))]
@ -832,7 +856,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
(let [result (let [result
(with-open [con (jdbc/get-connection (ds)) (with-open [con (jdbc/get-connection (ds))
stmt (jdbc/prepare con stmt (jdbc/prepare con
["select * from fruit order by id"] [(str "select * from fruit order by " (index))]
(default-options))] (default-options))]
(r/foldcat (r/foldcat
(r/map (column :FRUIT/NAME) (r/map (column :FRUIT/NAME)
@ -846,7 +870,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
stmt (prep/statement con (default-options))] stmt (prep/statement con (default-options))]
(r/foldcat (r/foldcat
(r/map (column :FRUIT/NAME) (r/map (column :FRUIT/NAME)
(jdbc/plan stmt ["select * from fruit order by id"] (jdbc/plan stmt [(str "select * from fruit order by " (index))]
(default-options)))))] (default-options)))))]
(is (= 1000 (count result))) (is (= 1000 (count result)))
(is (= "Fruit-1" (first result))) (is (= "Fruit-1" (first result)))
@ -854,7 +878,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
(deftest connection-tests (deftest connection-tests
(testing "datasource via jdbcUrl" (testing "datasource via jdbcUrl"
(when-not (postgres?) (when-not (or (postgres?) (xtdb?))
(let [[url etc] (#'c/spec->url+etc (db)) (let [[url etc] (#'c/spec->url+etc (db))
ds (jdbc/get-datasource (assoc etc :jdbcUrl url))] ds (jdbc/get-datasource (assoc etc :jdbcUrl url))]
(cond (derby?) (is (= {:create true} etc)) (cond (derby?) (is (= {:create true} etc))
@ -937,11 +961,11 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
(let [s (pr-str (into [] (take 3) (jdbc/plan (ds) ["select * from fruit"] (let [s (pr-str (into [] (take 3) (jdbc/plan (ds) ["select * from fruit"]
(default-options))))] (default-options))))]
(is (or (re-find #"missing `map` or `reduce`" s) (is (or (re-find #"missing `map` or `reduce`" s)
(re-find #"(?i)^\[#:fruit\{.*:id.*\}\]$" s)))) (re-find #"(?i)^\[(#:fruit)?\{.*:_?id.*\}\]$" s))))
(is (every? #(re-find #"(?i)^#:fruit\{.*:id.*\}$" %) (is (every? #(re-find #"(?i)^(#:fruit)?\{.*:_?id.*\}$" %)
(into [] (map str) (jdbc/plan (ds) ["select * from fruit"] (into [] (map str) (jdbc/plan (ds) ["select * from fruit"]
(default-options))))) (default-options)))))
(is (every? #(re-find #"(?i)^#:fruit\{.*:id.*\}$" %) (is (every? #(re-find #"(?i)^(#:fruit)?\{.*:_?id.*\}$" %)
(into [] (map pr-str) (jdbc/plan (ds) ["select * from fruit"] (into [] (map pr-str) (jdbc/plan (ds) ["select * from fruit"]
(default-options))))) (default-options)))))
(is (thrown? IllegalArgumentException (is (thrown? IllegalArgumentException