next-jdbc/test/next/jdbc/sql_test.clj

293 lines
13 KiB
Clojure
Raw Normal View History

;; copyright (c) 2019-2025 Sean Corfield, all rights reserved
(ns next.jdbc.sql-test
2019-12-12 00:42:28 +00:00
"Tests for the syntactic sugar SQL functions."
(:require [lazytest.core :refer [around set-ns-context! throws?]]
[lazytest.experimental.interfaces.clojure-test :refer [deftest is testing]]
[next.jdbc :as jdbc]
[next.jdbc.specs :as specs]
[next.jdbc.sql :as sql]
[next.jdbc.test-fixtures
:refer [col-kw column default-options derby? ds index jtds?
maria? mssql? mysql? postgres? sqlite? with-test-db xtdb?]]
[next.jdbc.types :refer [as-other as-real as-varchar]]))
2019-04-20 06:25:03 +00:00
(set! *warn-on-reflection* true)
(set-ns-context! [(around [f] (with-test-db f))])
(specs/instrument)
2019-04-20 06:25:03 +00:00
(deftest test-query
(let [ds-opts (jdbc/with-options (ds) (default-options))
rs (sql/query ds-opts [(str "select * from fruit order by " (index))])]
2019-04-20 06:25:03 +00:00
(is (= 4 (count rs)))
(is (every? map? rs))
(is (every? meta rs))
2019-11-15 23:38:51 +00:00
(is (= 1 ((column :FRUIT/ID) (first rs))))
(is (= 4 ((column :FRUIT/ID) (last rs))))))
2019-04-20 06:25:03 +00:00
(deftest test-find-all-offset
(let [ds-opts (jdbc/with-options (ds) (default-options))
rs (sql/find-by-keys
ds-opts :fruit :all
(assoc
(if (or (mysql?) (sqlite?))
{:limit 2 :offset 1}
{:offset 1 :fetch 2})
:columns [(col-kw :ID)
["CASE WHEN grade > 91 THEN 'ok ' ELSE 'bad' END"
:QUALITY]]
:order-by [(col-kw :id)]))]
(is (= 2 (count rs)))
(is (every? map? rs))
(is (every? meta rs))
(is (every? #(= 2 (count %)) rs))
(is (= 2 ((column :FRUIT/ID) (first rs))))
(is (= "ok " ((column :QUALITY) (first rs))))
(is (= 3 ((column :FRUIT/ID) (last rs))))
(is (= "bad" ((column :QUALITY) (last rs))))))
2019-04-20 06:25:03 +00:00
(deftest test-find-by-keys
(let [ds-opts (jdbc/with-options (ds) (default-options))]
(let [rs (sql/find-by-keys ds-opts :fruit {:appearance "neon-green"})]
(is (vector? rs))
(is (= [] rs)))
(let [rs (sql/find-by-keys ds-opts :fruit {:appearance "yellow"})]
(is (= 1 (count rs)))
(is (every? map? rs))
(is (every? meta rs))
(is (= 2 ((column :FRUIT/ID) (first rs)))))))
2019-04-20 06:25:03 +00:00
(deftest test-aggregate-by-keys
(let [ds-opts (jdbc/with-options (ds) (default-options))]
(let [count-v (sql/aggregate-by-keys ds-opts :fruit "count(*)" {:appearance "neon-green"})]
(is (number? count-v))
(is (= 0 count-v)))
(let [count-v (sql/aggregate-by-keys ds-opts :fruit "count(*)" {:appearance "yellow"})]
(is (= 1 count-v)))
(let [count-v (sql/aggregate-by-keys ds-opts :fruit "count(*)" :all)]
(is (= 4 count-v)))
(let [max-id (sql/aggregate-by-keys ds-opts :fruit (str "max(" (index) ")") :all)]
(is (= 4 max-id)))
(when-not (xtdb?) ; XTDB does not support min/max on strings?
(let [min-name (sql/aggregate-by-keys ds-opts :fruit "min(name)" :all)]
(is (= "Apple" min-name))))
(is (throws? IllegalArgumentException
#(sql/aggregate-by-keys ds-opts :fruit "count(*)" :all {:columns []})))))
2019-04-20 06:25:03 +00:00
(deftest test-get-by-id
(let [ds-opts (jdbc/with-options (ds) (default-options))]
(is (nil? (sql/get-by-id ds-opts :fruit -1 (col-kw :id) {})))
(let [row (sql/get-by-id ds-opts :fruit 3 (col-kw :id) {})]
(is (map? row))
(is (= "Peach" ((column :FRUIT/NAME) row))))
(let [row (sql/get-by-id ds-opts :fruit "juicy" :appearance {})]
(is (map? row))
(is (= 4 ((column :FRUIT/ID) row)))
(is (= "Orange" ((column :FRUIT/NAME) row))))
(let [row (sql/get-by-id ds-opts :fruit "Banana" :FRUIT/NAME {})]
(is (map? row))
(is (= 2 ((column :FRUIT/ID) row))))))
2019-04-20 06:25:03 +00:00
(defn- update-count [n]
(if (xtdb?)
{:next.jdbc/update-count 0}
{:next.jdbc/update-count n}))
2019-04-20 06:25:03 +00:00
(deftest test-update!
(let [ds-opts (jdbc/with-options (ds) (default-options))]
(try
(is (= (update-count 1)
(sql/update! ds-opts :fruit {:appearance "brown"} {(col-kw :id) 2})))
(is (= "brown" ((column :FRUIT/APPEARANCE)
(sql/get-by-id ds-opts :fruit 2 (col-kw :id) {}))))
(finally
(sql/update! ds-opts :fruit {:appearance "yellow"} {(col-kw :id) 2})))
(try
(is (= (update-count 1)
(sql/update! ds-opts :fruit {:appearance "green"}
["name = ?" "Banana"])))
(is (= "green" ((column :FRUIT/APPEARANCE)
(sql/get-by-id ds-opts :fruit 2 (col-kw :id) {}))))
(finally
(sql/update! ds-opts :fruit {:appearance "yellow"} {(col-kw :id) 2})))))
2019-04-20 06:25:03 +00:00
(deftest test-insert-delete
(let [new-key (cond (derby?) :1
2020-06-06 21:30:27 +00:00
(jtds?) :ID
(maria?) :insert_id
2020-03-18 18:35:18 +00:00
(mssql?) :GENERATED_KEYS
2019-11-15 23:38:51 +00:00
(mysql?) :GENERATED_KEY
(postgres?) :fruit/id
;; XTDB does not return the generated key so we fix it
;; to be the one we insert here, and then fake it in all
;; the other tests.
(xtdb?) (constantly 5)
:else :FRUIT/ID)]
(testing "single insert/delete"
(is (== 5 (new-key (sql/insert! (ds) :fruit
(cond-> {:name (as-varchar "Kiwi")
:appearance "green & fuzzy"
:cost 100 :grade (as-real 99.9)}
(xtdb?)
(assoc :_id 5))
{:suffix
(when (sqlite?)
"RETURNING *")}))))
(is (= 5 (count (sql/query (ds) ["select * from fruit"]))))
(is (= (update-count 1)
(sql/delete! (ds) :fruit {(col-kw :id) 5})))
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "multiple insert/delete"
(is (= (cond (derby?)
[nil] ; WTF Apache Derby?
2019-11-16 06:37:42 +00:00
(mssql?)
[8M]
(maria?)
[6]
(xtdb?)
[]
:else
[6 7 8])
(mapv new-key
(sql/insert-multi! (ds) :fruit
(cond->> [:name :appearance :cost :grade]
(xtdb?) (cons :_id))
(cond->> [["Kiwi" "green & fuzzy" 100 99.9]
["Grape" "black" 10 50]
["Lemon" "yellow" 20 9.9]]
(xtdb?)
(map cons [6 7 8]))
{:suffix
(when (sqlite?)
"RETURNING *")}))))
(is (= 7 (count (sql/query (ds) ["select * from fruit"]))))
(is (= (update-count 1)
(sql/delete! (ds) :fruit {(col-kw :id) 6})))
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
(is (= (update-count 2)
(sql/delete! (ds) :fruit [(str (index) " > ?") 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?
2019-11-16 06:37:42 +00:00
(mssql?)
[11M]
(maria?)
[9]
(xtdb?)
[]
:else
[9 10 11])
(mapv new-key
(sql/insert-multi! (ds) :fruit
(cond->> '(:name :appearance :cost :grade)
(xtdb?) (cons :_id))
(cond->> '(("Kiwi" "green & fuzzy" 100 99.9)
("Grape" "black" 10 50)
("Lemon" "yellow" 20 9.9))
(xtdb?)
(map cons [9 10 11]))
{:suffix
(when (sqlite?)
"RETURNING *")}))))
(is (= 7 (count (sql/query (ds) ["select * from fruit"]))))
(is (= (update-count 1)
(sql/delete! (ds) :fruit {(col-kw :id) 9})))
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
(is (= (update-count 2)
(sql/delete! (ds) :fruit [(str (index) " > ?") 4])))
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
(testing "multiple insert/delete with maps"
(is (= (cond (derby?)
[nil] ; WTF Apache Derby?
(mssql?)
[14M]
(maria?)
[12]
(xtdb?)
[]
:else
[12 13 14])
(mapv new-key
(sql/insert-multi! (ds) :fruit
(cond->> [{:name "Kiwi"
:appearance "green & fuzzy"
:cost 100
:grade 99.9}
{:name "Grape"
:appearance "black"
:cost 10
:grade 50}
{:name "Lemon"
:appearance "yellow"
:cost 20
:grade 9.9}]
(xtdb?)
(map #(assoc %2 :_id %1) [12 13 14]))
{:suffix
(when (sqlite?)
"RETURNING *")}))))
(is (= 7 (count (sql/query (ds) ["select * from fruit"]))))
(is (= (update-count 1)
(sql/delete! (ds) :fruit {(col-kw :id) 12})))
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
(is (= (update-count 2)
(sql/delete! (ds) :fruit [(str (index) " > ?") 10])))
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
2023-11-10 21:26:21 +00:00
(testing "empty insert-multi!" ; per #44 and #264
(is (= [] (sql/insert-multi! (ds) :fruit
[:name :appearance :cost :grade]
[]
{:suffix
(when (sqlite?)
2023-11-10 21:26:21 +00:00
"RETURNING *")})))
;; per #264 the following should all be legal too:
(is (= [] (sql/insert-multi! (ds) :fruit
[]
{:suffix
(when (sqlite?)
"RETURNING *")})))
(is (= [] (sql/insert-multi! (ds) :fruit
[]
[]
{:suffix
(when (sqlite?)
"RETURNING *")})))
(is (= [] (sql/insert-multi! (ds) :fruit [])))
(is (= [] (sql/insert-multi! (ds) :fruit [] []))))))
2019-07-03 01:50:25 +00:00
(deftest no-empty-example-maps
(is (throws? clojure.lang.ExceptionInfo
#(sql/find-by-keys (ds) :fruit {})))
(is (throws? clojure.lang.ExceptionInfo
#(sql/update! (ds) :fruit {} {})))
(is (throws? clojure.lang.ExceptionInfo
#(sql/delete! (ds) :fruit {}))))
(deftest no-empty-columns
(is (throws? clojure.lang.ExceptionInfo
#(sql/insert-multi! (ds) :fruit [] [[] [] []]))))
2022-05-20 18:53:53 +00:00
(deftest no-mismatched-columns
(is (throws? IllegalArgumentException
#(sql/insert-multi! (ds) :fruit [{:name "Apple"} {:cost 1.23}]))))
2022-05-20 18:53:53 +00:00
(deftest no-empty-order-by
(is (throws? clojure.lang.ExceptionInfo
#(sql/find-by-keys (ds) :fruit
{:name "Apple"}
{:order-by []}))))
(deftest array-in
(when (postgres?)
(let [data (sql/find-by-keys (ds) :fruit [(str (index) " = any(?)") (int-array [1 2 3 4])])]
(is (= 4 (count data))))))
(deftest enum-pg
(when (postgres?)
(let [r (sql/insert! (ds) :lang-test {:lang (as-other "fr")}
jdbc/snake-kebab-opts)]
(is (= {:lang-test/lang "fr"} r)))))