Merge pull request #290 from seancorfield/xtdb-testing
enable xtdb testing automatically
This commit is contained in:
commit
87f4224c22
19 changed files with 1481 additions and 770 deletions
8
.clj-kondo/imports/com.xtdb/xtdb-api/config.edn
Normal file
8
.clj-kondo/imports/com.xtdb/xtdb-api/config.edn
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{:linters {:xtql/redundant-pipeline {:level :warning}
|
||||
:xtql/redundant-unify {:level :warning}
|
||||
:xtql/unrecognized-operation {:level :error}
|
||||
:xtql/unrecognized-parameter {:level :warning}
|
||||
:xtql/missing-parameter {:level :error}
|
||||
:xtql/type-mismatch {:level :error}
|
||||
:xtql/invalid-arity {:level :error}}
|
||||
:hooks {:analyze-call {xtdb.api/q hooks.xtql/q}}}
|
||||
567
.clj-kondo/imports/com.xtdb/xtdb-api/hooks/xtql.clj
Normal file
567
.clj-kondo/imports/com.xtdb/xtdb-api/hooks/xtql.clj
Normal file
|
|
@ -0,0 +1,567 @@
|
|||
(ns ^:no-doc hooks.xtql
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(def source-op?
|
||||
#{'from 'rel 'unify})
|
||||
|
||||
(def tail-op?
|
||||
#{'aggregate
|
||||
'limit 'offset
|
||||
'where
|
||||
'order-by
|
||||
'with 'without 'return
|
||||
'unnest})
|
||||
|
||||
(def unify-clause?
|
||||
#{'from 'rel
|
||||
'join 'left-join
|
||||
'unnest
|
||||
'where
|
||||
'with})
|
||||
|
||||
(defn node-map? [node]
|
||||
(contains? #{:map :namespaced-map}
|
||||
(:tag node)))
|
||||
|
||||
(defn node-namespaced-map? [node]
|
||||
(= :namespaced-map (:tag node)))
|
||||
|
||||
(defn map-children [node]
|
||||
(->> (if (node-namespaced-map? node)
|
||||
(-> node :children first)
|
||||
node)
|
||||
:children
|
||||
(partition-all 2)))
|
||||
|
||||
(defn node-vector? [node]
|
||||
(= :vector (:tag node)))
|
||||
|
||||
(defn node-list? [node]
|
||||
(= :list (:tag node)))
|
||||
|
||||
(defn node-symbol? [node]
|
||||
(symbol? (:value node)))
|
||||
|
||||
(defn node-symbol [node]
|
||||
(:value node))
|
||||
|
||||
(defn node-keyword? [node]
|
||||
(keyword? (:k node)))
|
||||
|
||||
(defn node-keyword [node]
|
||||
(:k node))
|
||||
|
||||
(defn node-quote? [node]
|
||||
(= :quote (:tag node)))
|
||||
|
||||
(defn node-op [node]
|
||||
(-> node :children first))
|
||||
|
||||
(declare lint-query)
|
||||
|
||||
(defmulti lint-unify-clause #(-> % node-op node-symbol))
|
||||
(defmulti lint-source-op #(-> % node-op node-symbol))
|
||||
(defmulti lint-tail-op #(-> % node-op node-symbol))
|
||||
|
||||
(defn lint-not-arg-symbol [node]
|
||||
(when (= \$ (-> node node-symbol str first))
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "unexpected parameter in binding"
|
||||
:type :xtql/unrecognized-parameter))))
|
||||
|
||||
(defn lint-bind [node]
|
||||
(cond
|
||||
(node-symbol? node)
|
||||
;; TODO: Make own type, should really be a warning
|
||||
(lint-not-arg-symbol node)
|
||||
|
||||
(node-map? node)
|
||||
(doseq [[k _v] (map-children node)]
|
||||
(when-not (node-keyword? k)
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "all keys in binding maps must be keywords"
|
||||
:type :xtql/type-mismatch))))
|
||||
|
||||
:else
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "expected a symbol or map"
|
||||
:type :xtql/type-mismatch))))
|
||||
|
||||
;; TODO: Lint more unify clauses
|
||||
(defmethod lint-unify-clause :default [node]
|
||||
(when-not (unify-clause? (-> node node-op node-symbol))
|
||||
(api/reg-finding!
|
||||
(assoc (some-> node :children first meta)
|
||||
:message "unrecognized unify clause"
|
||||
:type :xtql/unrecognized-operation))))
|
||||
|
||||
(defmethod lint-unify-clause 'from [node]
|
||||
(lint-source-op node))
|
||||
|
||||
(defmethod lint-unify-clause 'rel [node]
|
||||
(lint-source-op node))
|
||||
|
||||
(defmethod lint-unify-clause 'with [node]
|
||||
(let [opts (-> node :children rest)]
|
||||
(when-not (>= (count opts) 1)
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "expected at least one argument"
|
||||
:type :xtql/invalid-arity)))
|
||||
(doseq [opt opts]
|
||||
(if (node-map? opt)
|
||||
(let [ks (->> opt
|
||||
map-children
|
||||
(map first)
|
||||
(remove node-symbol?))]
|
||||
(doseq [k ks]
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "expected all keys to be symbols in a unify"
|
||||
:type :xtql/type-mismatch))))
|
||||
(api/reg-finding!
|
||||
(assoc (meta opt)
|
||||
:message "opts must be a map"
|
||||
:type :xtql/type-mismatch))))))
|
||||
|
||||
(defn lint-join-clause [node]
|
||||
(let [args (-> node :children rest)]
|
||||
(if-not (= (count args) 2)
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "expected at exactly two arguments"
|
||||
:type :xtql/invalid-arity))
|
||||
(let [[query opts] args]
|
||||
(lint-query query)
|
||||
(cond
|
||||
(node-vector? opts)
|
||||
(->> opts :children (run! lint-bind))
|
||||
(node-map? opts)
|
||||
(let [kvs (map-children opts)
|
||||
ks (->> kvs
|
||||
(map first)
|
||||
(map node-keyword)
|
||||
(remove nil?)
|
||||
(into #{}))]
|
||||
(when-not (contains? ks :bind)
|
||||
(api/reg-finding!
|
||||
(assoc (meta opts)
|
||||
:message "Missing :bind parameter"
|
||||
:type :xtql/missing-parameter)))
|
||||
(doseq [[k v] kvs]
|
||||
(when-not (node-keyword? k)
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "All keys in 'opts' must be keywords"
|
||||
:type :xtql/type-mismatch)))
|
||||
(case (node-keyword k)
|
||||
:bind (if (node-vector? v)
|
||||
(->> v :children (run! lint-bind))
|
||||
(api/reg-finding!
|
||||
(assoc (meta v)
|
||||
:message "expected :bind value to be a vector"
|
||||
:type :xtql/type-mismatch)))
|
||||
:args (if (node-vector? v)
|
||||
;; TODO: Make args specific
|
||||
(->> v :children (run! lint-bind))
|
||||
(api/reg-finding!
|
||||
(assoc (meta v)
|
||||
:message "expected :args value to be a vector"
|
||||
:type :xtql/type-mismatch)))
|
||||
; else
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "unrecognized parameter"
|
||||
:type :xtql/unrecognized-parameter)))))
|
||||
:else
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "opts must be a map or vector"
|
||||
:type :xtql/type-mismatch)))))))
|
||||
|
||||
(defmethod lint-unify-clause 'join [node]
|
||||
(lint-join-clause node))
|
||||
|
||||
(defmethod lint-unify-clause 'inner-join [node]
|
||||
(lint-join-clause node))
|
||||
|
||||
(defmethod lint-unify-clause 'unnest [node]
|
||||
(let [opts (-> node :children rest)]
|
||||
(when-not (= 1 (count opts))
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "expected at exactly one argument"
|
||||
:type :xtql/invalid-arity)))
|
||||
(let [opt (first opts)]
|
||||
(if (node-map? opt)
|
||||
(doseq [[k _v] (map-children opt)]
|
||||
(when-not (node-symbol? k)
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "expected all columns to be symbols"
|
||||
:type :xtql/type-mismatch))))
|
||||
(api/reg-finding!
|
||||
(assoc (meta opt)
|
||||
:message "expected opt to be a map"
|
||||
:type :xtql/type-mismatch))))))
|
||||
|
||||
|
||||
(defmethod lint-source-op :default [node]
|
||||
(let [op (-> node node-op node-symbol)]
|
||||
(if (tail-op? op)
|
||||
(api/reg-finding!
|
||||
(assoc (some-> node :children first meta)
|
||||
:message "tail op in source position"
|
||||
:type :xtql/unrecognized-operation))
|
||||
(when-not (source-op? op)
|
||||
(api/reg-finding!
|
||||
(assoc (some-> node :children first meta)
|
||||
:message "unrecognized source operation"
|
||||
:type :xtql/unrecognized-operation))))))
|
||||
|
||||
(defmethod lint-source-op 'from [node]
|
||||
(let [[_ table opts] (some-> node :children)]
|
||||
(when-not (node-keyword? table)
|
||||
(api/reg-finding!
|
||||
(assoc (meta table)
|
||||
:message "expected 'table' to be a keyword"
|
||||
:type :xtql/type-mismatch)))
|
||||
(case (:tag opts)
|
||||
:vector (->> opts :children (run! lint-bind))
|
||||
:map
|
||||
(let [kvs (map-children opts)
|
||||
ks (->> kvs
|
||||
(map first)
|
||||
(map node-keyword)
|
||||
(remove nil?)
|
||||
(into #{}))]
|
||||
(when-not (contains? ks :bind)
|
||||
(api/reg-finding!
|
||||
(assoc (meta opts)
|
||||
:message "Missing :bind parameter"
|
||||
:type :xtql/missing-parameter)))
|
||||
(doseq [[k v] kvs]
|
||||
(when-not (node-keyword? k)
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "All keys in 'opts' must be keywords"
|
||||
:type :xtql/type-mismatch)))
|
||||
(case (node-keyword k)
|
||||
:bind (if (node-vector? v)
|
||||
(->> v :children (run! lint-bind))
|
||||
(api/reg-finding!
|
||||
(assoc (meta opts)
|
||||
:message "expected :bind value to be a vector"
|
||||
:type :xtql/type-mismatch)))
|
||||
;; TODO
|
||||
:for-valid-time nil
|
||||
;; TODO
|
||||
:for-system-time nil
|
||||
; else
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "unrecognized parameter"
|
||||
:type :xtql/unrecognized-parameter)))))
|
||||
(api/reg-finding!
|
||||
(assoc (meta opts)
|
||||
:message "expected 'opts' to be either a map or vector"
|
||||
:type :xtql/type-mismatch)))))
|
||||
|
||||
(defmethod lint-source-op 'unify [node]
|
||||
(let [[_ & clauses] (some-> node :children)]
|
||||
(doseq [bad-op (remove node-list? clauses)]
|
||||
(api/reg-finding!
|
||||
(assoc (meta bad-op)
|
||||
:message "all operations in a unify must be lists"
|
||||
:type :xtql/type-mismatch)))
|
||||
(when (= (count clauses) 1)
|
||||
(let [clause (first clauses)
|
||||
clause-op (-> clause node-op node-symbol)
|
||||
unify-node (some-> node :children first)]
|
||||
(case clause-op
|
||||
from (api/reg-finding!
|
||||
(assoc (meta unify-node)
|
||||
:message "redundant unify"
|
||||
:type :xtql/redundant-unify))
|
||||
rel (api/reg-finding!
|
||||
(assoc (meta unify-node)
|
||||
:message "redundant unify"
|
||||
:type :xtql/redundant-unify))
|
||||
;; TODO: Cover other operators
|
||||
nil)))
|
||||
(->> clauses
|
||||
(filter node-list?)
|
||||
(run! lint-unify-clause))))
|
||||
|
||||
(defmethod lint-source-op 'rel [node]
|
||||
(let [[_ _expr binds] (some-> node :children)]
|
||||
(if (node-vector? binds)
|
||||
(->> binds :children (run! lint-bind))
|
||||
(api/reg-finding!
|
||||
(assoc (meta binds)
|
||||
:message "expected rel binding to be a vector"
|
||||
:type :xtql/type-mismatch)))))
|
||||
|
||||
;; TODO: Lint more tail ops
|
||||
(defmethod lint-tail-op :default [node]
|
||||
(let [op (-> node node-op node-symbol)]
|
||||
(if (source-op? op)
|
||||
(api/reg-finding!
|
||||
(assoc (some-> node :children first meta)
|
||||
:message "source op in tail position"
|
||||
:type :xtql/unrecognized-operation))
|
||||
(when-not (tail-op? op)
|
||||
(api/reg-finding!
|
||||
(assoc (some-> node :children first meta)
|
||||
:message "unrecognized tail operation"
|
||||
:type :xtql/unrecognized-operation))))))
|
||||
|
||||
(defn lint-keyword [node name]
|
||||
(when-not (node-keyword? node)
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message (str "expected '" name "' to be a keyword")
|
||||
:type :xtql/type-mismatch))))
|
||||
|
||||
(defn lint-enum [node name values]
|
||||
;; TODO: Expand to more than just keywords?
|
||||
;; Maybe a `node-value` function?
|
||||
(when-not (contains? values (node-keyword node))
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message (str "expected '" name "' to be one of " values)
|
||||
;; TODO: change to different type?
|
||||
:type :xtql/type-mismatch))))
|
||||
|
||||
(defmethod lint-tail-op 'limit [node]
|
||||
(let [opts (-> node :children rest)]
|
||||
(when-not (= 1 (count opts))
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "expected exactly one argument"
|
||||
:type :xtql/invalid-arity)))
|
||||
(when-let [opt (first opts)]
|
||||
(when-not (some-> opt :value int?)
|
||||
(api/reg-finding!
|
||||
(assoc (meta opt)
|
||||
:message "expected limit to be an integer"
|
||||
:type :xtql/type-mismatch))))))
|
||||
|
||||
(defmethod lint-tail-op 'offset [node]
|
||||
(let [opts (-> node :children rest)]
|
||||
(when-not (= 1 (count opts))
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "expected exactly one argument"
|
||||
:type :xtql/invalid-arity)))
|
||||
(when-let [opt (first opts)]
|
||||
(when-not (some-> opt :value int?)
|
||||
(api/reg-finding!
|
||||
(assoc (meta opt)
|
||||
:message "expected offset to be an integer"
|
||||
:type :xtql/type-mismatch))))))
|
||||
|
||||
(defmethod lint-tail-op 'with [node]
|
||||
(let [opts (-> node :children rest)]
|
||||
(when-not (>= (count opts) 1)
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "expected at least one argument"
|
||||
:type :xtql/invalid-arity)))
|
||||
(doseq [opt opts]
|
||||
(cond
|
||||
(node-symbol? opt)
|
||||
(lint-not-arg-symbol opt)
|
||||
(node-map? opt)
|
||||
(let [ks (->> opt
|
||||
map-children
|
||||
(map first)
|
||||
(remove node-keyword?))]
|
||||
(doseq [k ks]
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "expected all keys to be keywords"
|
||||
:type :xtql/type-mismatch))))
|
||||
:else
|
||||
(api/reg-finding!
|
||||
(assoc (meta opt)
|
||||
:message "opts must be a symbol or map"
|
||||
:type :xtql/type-mismatch))))))
|
||||
|
||||
(defmethod lint-tail-op 'return [node]
|
||||
(let [opts (-> node :children rest)]
|
||||
(when-not (>= (count opts) 1)
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "expected at least one argument"
|
||||
:type :xtql/invalid-arity)))
|
||||
(doseq [opt opts]
|
||||
(cond
|
||||
(node-symbol? opt)
|
||||
(lint-not-arg-symbol opt)
|
||||
(node-map? opt)
|
||||
(let [ks (->> opt
|
||||
map-children
|
||||
(map first)
|
||||
(remove node-keyword?))]
|
||||
(doseq [k ks]
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "expected all keys to be keywords"
|
||||
:type :xtql/type-mismatch))))
|
||||
:else
|
||||
(api/reg-finding!
|
||||
(assoc (meta opt)
|
||||
:message "opts must be a symbol or map"
|
||||
:type :xtql/type-mismatch))))))
|
||||
|
||||
(defmethod lint-tail-op 'order-by [node]
|
||||
(doseq [opt (-> node :children rest)]
|
||||
(cond
|
||||
(node-symbol? opt)
|
||||
(lint-not-arg-symbol opt)
|
||||
|
||||
(node-map? opt)
|
||||
(let [kvs (map-children opt)
|
||||
ks (->> kvs
|
||||
(map first)
|
||||
(map node-keyword)
|
||||
(remove nil?)
|
||||
(into #{}))]
|
||||
(when-not (contains? ks :val)
|
||||
(api/reg-finding!
|
||||
(assoc (meta opt)
|
||||
:message "Missing :val parameter"
|
||||
:type :xtql/missing-parameter)))
|
||||
(doseq [[k v] kvs]
|
||||
(when-not (node-keyword? k)
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "All keys in 'opts' must be keywords"
|
||||
:type :xtql/type-mismatch)))
|
||||
(case (node-keyword k)
|
||||
:val
|
||||
(cond
|
||||
(node-symbol? v)
|
||||
(lint-not-arg-symbol v)
|
||||
(node-keyword? v)
|
||||
(api/reg-finding!
|
||||
(assoc (meta v)
|
||||
:message "expected :val value to be a symbol or an expression"
|
||||
:type :xtql/type-mismatch)))
|
||||
; else do nothing
|
||||
:dir
|
||||
(if (node-keyword? v)
|
||||
(lint-enum v :dir #{:asc :desc})
|
||||
(lint-keyword v ":dir value"))
|
||||
:nulls
|
||||
(if (node-keyword? v)
|
||||
(lint-enum v :nulls #{:first :last})
|
||||
(lint-keyword v ":nulls value"))
|
||||
; else
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "unrecognized parameter"
|
||||
:type :xtql/unrecognized-parameter)))))
|
||||
|
||||
:else
|
||||
(api/reg-finding!
|
||||
(assoc (meta opt)
|
||||
:message "opts must be a symbol or map"
|
||||
:type :xtql/type-mismatch)))))
|
||||
|
||||
(defmethod lint-tail-op 'without [node]
|
||||
(let [columns (-> node :children rest)]
|
||||
(when-not (>= (count columns) 1)
|
||||
;; TODO: Should be a warning really
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "expected at least one column"
|
||||
:type :xtql/invalid-arity)))
|
||||
(doseq [column columns]
|
||||
(when-not (node-keyword? column)
|
||||
(api/reg-finding!
|
||||
(assoc (meta column)
|
||||
:message "expected column to be a keyword"
|
||||
:type :xtql/type-mismatch))))))
|
||||
|
||||
(defmethod lint-tail-op 'aggregate [node]
|
||||
(let [opts (-> node :children rest)]
|
||||
(when-not (>= (count opts) 1)
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "expected at least one argument"
|
||||
:type :xtql/invalid-arity)))
|
||||
(doseq [opt opts]
|
||||
(cond
|
||||
(node-symbol? opt)
|
||||
(lint-not-arg-symbol opt)
|
||||
(node-map? opt)
|
||||
(doseq [[k _v] (map-children opt)]
|
||||
(when-not (node-keyword? k)
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "expected all keys to be keywords"
|
||||
:type :xtql/type-mismatch))))
|
||||
|
||||
:else
|
||||
(api/reg-finding!
|
||||
(assoc (meta opt)
|
||||
:message "expected opts to be a symbol or map"
|
||||
:type :xtql/type-mismatch))))))
|
||||
|
||||
(defmethod lint-tail-op 'unnest [node]
|
||||
(let [opts (-> node :children rest)]
|
||||
(when-not (= 1 (count opts))
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "expected at exactly one argument"
|
||||
:type :xtql/invalid-arity)))
|
||||
(let [opt (first opts)]
|
||||
(if (node-map? opt)
|
||||
(doseq [[k _v] (map-children opt)]
|
||||
(when-not (node-keyword? k)
|
||||
(api/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message "expected all columns to be keywords"
|
||||
:type :xtql/type-mismatch))))
|
||||
(api/reg-finding!
|
||||
(assoc (meta opt)
|
||||
:message "expected opt to be a map"
|
||||
:type :xtql/type-mismatch))))))
|
||||
|
||||
(defn lint-pipeline [node]
|
||||
(let [[_ & ops] (some-> node :children)]
|
||||
(doseq [bad-op (remove node-list? ops)]
|
||||
(api/reg-finding!
|
||||
(assoc (meta bad-op)
|
||||
:message "all operations in a pipeline must be lists"
|
||||
:type :xtql/type-mismatch)))
|
||||
(when (= 1 (count ops))
|
||||
(api/reg-finding!
|
||||
(assoc (-> node :children first meta)
|
||||
:message "redundant pipeline"
|
||||
:type :xtql/redundant-pipeline)))
|
||||
(let [first-op (first ops)]
|
||||
(when (node-list? first-op)
|
||||
(lint-source-op (first ops))))
|
||||
(->> ops
|
||||
(drop 1)
|
||||
(filter node-list?)
|
||||
(run! lint-tail-op))))
|
||||
|
||||
(defn lint-query [node]
|
||||
(if (= '-> (node-symbol (-> node :children first)))
|
||||
(lint-pipeline node)
|
||||
(lint-source-op node)))
|
||||
|
||||
;; TODO: Lint other functions that take queries
|
||||
|
||||
(defn q [{:keys [node]}]
|
||||
(let [[_ _node quoted-query] (some-> node :children)]
|
||||
(when (node-quote? quoted-query)
|
||||
(let [query (-> quoted-query :children first)]
|
||||
(lint-query query)))))
|
||||
1
.github/workflows/test-and-release.yml
vendored
1
.github/workflows/test-and-release.yml
vendored
|
|
@ -44,6 +44,7 @@ jobs:
|
|||
env:
|
||||
MYSQL_ROOT_PASSWORD: testing
|
||||
NEXT_JDBC_TEST_MYSQL: yes
|
||||
NEXT_JDBC_TEST_XTDB: yes
|
||||
NEXT_JDBC_TEST_MSSQL: yes
|
||||
MSSQL_SA_PASSWORD: Str0ngP4ssw0rd
|
||||
- name: Deploy Release
|
||||
|
|
|
|||
1
.github/workflows/test-and-snapshot.yml
vendored
1
.github/workflows/test-and-snapshot.yml
vendored
|
|
@ -42,6 +42,7 @@ jobs:
|
|||
env:
|
||||
MYSQL_ROOT_PASSWORD: testing
|
||||
NEXT_JDBC_TEST_MYSQL: yes
|
||||
NEXT_JDBC_TEST_XTDB: yes
|
||||
NEXT_JDBC_TEST_MSSQL: yes
|
||||
MSSQL_SA_PASSWORD: Str0ngP4ssw0rd
|
||||
- name: Deploy Snapshot
|
||||
|
|
|
|||
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
|
|
@ -42,5 +42,6 @@ jobs:
|
|||
env:
|
||||
MYSQL_ROOT_PASSWORD: testing
|
||||
NEXT_JDBC_TEST_MYSQL: yes
|
||||
NEXT_JDBC_TEST_XTDB: yes
|
||||
NEXT_JDBC_TEST_MSSQL: yes
|
||||
MSSQL_SA_PASSWORD: Str0ngP4ssw0rd
|
||||
|
|
|
|||
5
deps.edn
5
deps.edn
|
|
@ -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"]
|
||||
:deps {org.clojure/clojure {:mvn/version "1.10.3"}
|
||||
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.2.0"}
|
||||
org.xerial/sqlite-jdbc {:mvn/version "3.47.1.0"}
|
||||
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:
|
||||
org.apache.logging.log4j/log4j-api {:mvn/version "2.24.2"}
|
||||
;; bridge everything into log4j:
|
||||
|
|
|
|||
|
|
@ -14,3 +14,8 @@ services:
|
|||
MSSQL_SA_PASSWORD: Str0ngP4ssw0rd
|
||||
ports:
|
||||
- "1433:1433"
|
||||
xtdb:
|
||||
image: ghcr.io/xtdb/xtdb
|
||||
pull_policy: always
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
|
|
|||
|
|
@ -12,12 +12,15 @@
|
|||
(System/exit exit))))
|
||||
|
||||
(let [maria? (some #(= "maria" %) *command-line-args*)
|
||||
xtdb? (some #(= "xtdb" %) *command-line-args*)
|
||||
all? (some #(= "all" %) *command-line-args*)
|
||||
env
|
||||
(cond-> {"NEXT_JDBC_TEST_MSSQL" "yes"
|
||||
"NEXT_JDBC_TEST_MYSQL" "yes"
|
||||
"MSSQL_SA_PASSWORD" "Str0ngP4ssw0rd"}
|
||||
maria?
|
||||
(assoc "NEXT_JDBC_TEST_MARIADB" "yes"))]
|
||||
(assoc "NEXT_JDBC_TEST_MARIADB" "yes")
|
||||
xtdb?
|
||||
(assoc "NEXT_JDBC_TEST_XTDB" "yes"))]
|
||||
(doseq [v (if all? ["1.10" "1.11" "1.12"] [nil])]
|
||||
(run-tests env v)))
|
||||
|
|
|
|||
|
|
@ -278,6 +278,10 @@
|
|||
:qualifier-fn ->kebab-case
|
||||
:label-fn ->kebab-case)))
|
||||
|
||||
(comment
|
||||
(->kebab-case "_id") ;;=> "id"!!
|
||||
)
|
||||
|
||||
(defn as-unqualified-kebab-maps
|
||||
"Given a `ResultSet` and options, return a `RowBuilder` / `ResultSetBuilder`
|
||||
that produces bare vectors of hash map rows, with simple, kebab-case keys."
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
[next.jdbc.result-set :as rs]
|
||||
[next.jdbc.specs :as specs]
|
||||
[next.jdbc.test-fixtures
|
||||
:refer [with-test-db db ds
|
||||
derby? jtds? mysql? postgres? sqlite?]]))
|
||||
:refer [db derby? ds jtds? mysql? postgres? sqlite? with-test-db
|
||||
xtdb?]]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
|
|
@ -83,6 +83,26 @@
|
|||
:rowIdLifetime/exception))
|
||||
(postgres?) (-> (disj :rowIdLifetime)
|
||||
(conj :rowIdLifetime/exception))
|
||||
(xtdb?) (-> (disj :clientInfoProperties
|
||||
:defaultTransactionIsolation
|
||||
:maxCatalogNameLength
|
||||
:maxColumnNameLength
|
||||
:maxCursorNameLength
|
||||
:maxProcedureNameLength
|
||||
:maxSchemaNameLength
|
||||
:maxTableNameLength
|
||||
:maxUserNameLength
|
||||
:rowIdLifetime)
|
||||
(conj :clientInfoProperties/exception
|
||||
:defaultTransactionIsolation/exception
|
||||
:maxCatalogNameLength/exception
|
||||
:maxColumnNameLength/exception
|
||||
:maxCursorNameLength/exception
|
||||
:maxProcedureNameLength/exception
|
||||
:maxSchemaNameLength/exception
|
||||
:maxTableNameLength/exception
|
||||
:maxUserNameLength/exception
|
||||
:rowIdLifetime/exception))
|
||||
(sqlite?) (-> (disj :clientInfoProperties :rowIdLifetime)
|
||||
(conj :clientInfoProperties/exception
|
||||
:rowIdLifetime/exception)))
|
||||
|
|
@ -97,7 +117,8 @@
|
|||
(let [data (d/datafy (.getMetaData con))]
|
||||
(doseq [k (cond-> #{:catalogs :clientInfoProperties :schemas :tableTypes :typeInfo}
|
||||
(jtds?) (disj :clientInfoProperties)
|
||||
(sqlite?) (disj :clientInfoProperties))]
|
||||
(sqlite?) (disj :clientInfoProperties)
|
||||
(xtdb?) (disj :clientInfoProperties))]
|
||||
(let [rs (d/nav data k nil)]
|
||||
(is (vector? rs))
|
||||
(is (every? map? rs))))))))
|
||||
|
|
@ -122,4 +143,5 @@
|
|||
(.execute ps)
|
||||
(.getResultSet ps)
|
||||
(.close ps)
|
||||
(.close con))
|
||||
(.close con)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
[next.jdbc :as jdbc]
|
||||
[next.jdbc.date-time] ; to extend SettableParameter to date/time
|
||||
[next.jdbc.test-fixtures :refer [with-test-db db ds
|
||||
mssql?]]
|
||||
mssql? xtdb?]]
|
||||
[next.jdbc.specs :as specs])
|
||||
(:import (java.sql ResultSet)))
|
||||
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
(specs/instrument)
|
||||
|
||||
(deftest issue-73
|
||||
(when-not (xtdb?)
|
||||
(try
|
||||
(jdbc/execute-one! (ds) ["drop table fruit_time"])
|
||||
(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 (?,?)" 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 (?,?)" 4 (java.time.LocalDateTime/now)]))
|
||||
(jdbc/execute-one! (ds) ["insert into fruit_time (id, deadline) values (?,?)" 4 (java.time.LocalDateTime/now)])))
|
||||
|
|
|
|||
|
|
@ -15,13 +15,14 @@
|
|||
[next.jdbc :as jdbc]
|
||||
[next.jdbc.defer :as sut]
|
||||
[next.jdbc.test-fixtures
|
||||
:refer [ds with-test-db]]))
|
||||
:refer [ds with-test-db xtdb?]]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(use-fixtures :once with-test-db)
|
||||
|
||||
(deftest basic-test
|
||||
(when-not (xtdb?)
|
||||
(testing "data structures"
|
||||
(is (= [{:sql-p ["INSERT INTO foo (name) VALUES (?)" "Sean"]
|
||||
:key-fn :GENERATED_KEY
|
||||
|
|
@ -50,4 +51,4 @@
|
|||
(catch Exception _ :failed))))
|
||||
(is (= 0 (count (jdbc/execute! (ds)
|
||||
["select * from fruit where name = ?"
|
||||
"Grapefruit"])))))))
|
||||
"Grapefruit"]))))))))
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@
|
|||
[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 column
|
||||
default-options]])
|
||||
(:import (java.sql ResultSet ResultSetMetaData)))
|
||||
[next.jdbc.test-fixtures :refer [col-kw column default-options ds index
|
||||
with-test-db]])
|
||||
(:import
|
||||
(java.sql ResultSet ResultSetMetaData)))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
|
|
@ -17,7 +18,7 @@
|
|||
(deftest test-map-row-builder
|
||||
(testing "default row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 1]
|
||||
[(str "select * from fruit where " (index) " = ?") 1]
|
||||
(assoc (default-options)
|
||||
:builder-fn opt/as-maps))]
|
||||
(is (map? row))
|
||||
|
|
@ -26,7 +27,7 @@
|
|||
(is (= "Apple" ((column :FRUIT/NAME) row)))))
|
||||
(testing "unqualified row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 2]
|
||||
[(str "select * from fruit where " (index) " = ?") 2]
|
||||
{:builder-fn opt/as-unqualified-maps})]
|
||||
(is (map? row))
|
||||
(is (not (contains? row (column :COST))))
|
||||
|
|
@ -34,23 +35,23 @@
|
|||
(is (= "Banana" ((column :NAME) row)))))
|
||||
(testing "lower-case row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 3]
|
||||
[(str "select * from fruit where " (index) " = ?") 3]
|
||||
(assoc (default-options)
|
||||
:builder-fn opt/as-lower-maps))]
|
||||
(is (map? row))
|
||||
(is (not (contains? row :fruit/appearance)))
|
||||
(is (= 3 (:fruit/id row)))
|
||||
(is (= "Peach" (:fruit/name row)))))
|
||||
(is (not (contains? row (col-kw :fruit/appearance))))
|
||||
(is (= 3 ((col-kw :fruit/id) row)))
|
||||
(is (= "Peach" ((col-kw :fruit/name) row)))))
|
||||
(testing "unqualified lower-case row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 4]
|
||||
[(str "select * from fruit where " (index) " = ?") 4]
|
||||
{:builder-fn opt/as-unqualified-lower-maps})]
|
||||
(is (map? row))
|
||||
(is (= 4 (:id row)))
|
||||
(is (= "Orange" (:name row)))))
|
||||
(is (= 4 ((col-kw :id) row)))
|
||||
(is (= "Orange" ((col-kw :name) row)))))
|
||||
(testing "custom row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 3]
|
||||
[(str "select * from fruit where " (index) " = ?") 3]
|
||||
(assoc (default-options)
|
||||
:builder-fn opt/as-modified-maps
|
||||
:label-fn str/lower-case
|
||||
|
|
@ -67,7 +68,7 @@
|
|||
(deftest test-map-row-adapter
|
||||
(testing "default row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 1]
|
||||
[(str "select * from fruit where " (index) " = ?") 1]
|
||||
(assoc (default-options)
|
||||
:builder-fn (opt/as-maps-adapter
|
||||
opt/as-maps
|
||||
|
|
@ -78,7 +79,7 @@
|
|||
(is (= "Apple" ((column :FRUIT/NAME) row)))))
|
||||
(testing "unqualified row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 2]
|
||||
[(str "select * from fruit where " (index) " = ?") 2]
|
||||
{:builder-fn (opt/as-maps-adapter
|
||||
opt/as-unqualified-maps
|
||||
default-column-reader)})]
|
||||
|
|
@ -88,27 +89,27 @@
|
|||
(is (= "Banana" ((column :NAME) row)))))
|
||||
(testing "lower-case row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 3]
|
||||
[(str "select * from fruit where " (index) " = ?") 3]
|
||||
(assoc (default-options)
|
||||
:builder-fn (opt/as-maps-adapter
|
||||
opt/as-lower-maps
|
||||
default-column-reader)))]
|
||||
(is (map? row))
|
||||
(is (not (contains? row :fruit/appearance)))
|
||||
(is (= 3 (:fruit/id row)))
|
||||
(is (= "Peach" (:fruit/name row)))))
|
||||
(is (not (contains? row (col-kw :fruit/appearance))))
|
||||
(is (= 3 ((col-kw :fruit/id) row)))
|
||||
(is (= "Peach" ((col-kw :fruit/name) row)))))
|
||||
(testing "unqualified lower-case row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 4]
|
||||
[(str "select * from fruit where " (index) " = ?") 4]
|
||||
{:builder-fn (opt/as-maps-adapter
|
||||
opt/as-unqualified-lower-maps
|
||||
default-column-reader)})]
|
||||
(is (map? row))
|
||||
(is (= 4 (:id row)))
|
||||
(is (= "Orange" (:name row)))))
|
||||
(is (= 4 ((col-kw :id) row)))
|
||||
(is (= "Orange" ((col-kw :name) row)))))
|
||||
(testing "custom row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 3]
|
||||
[(str "select * from fruit where " (index) " = ?") 3]
|
||||
(assoc (default-options)
|
||||
:builder-fn (opt/as-maps-adapter
|
||||
opt/as-modified-maps
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
[next.jdbc.plan :as plan]
|
||||
[next.jdbc.specs :as specs]
|
||||
[next.jdbc.test-fixtures
|
||||
:refer [with-test-db ds]]
|
||||
:refer [with-test-db ds col-kw index]]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
|
@ -17,56 +17,56 @@
|
|||
(specs/instrument)
|
||||
|
||||
(deftest select-one!-tests
|
||||
(is (= {:id 1}
|
||||
(plan/select-one! (ds) [:id] ["select * from fruit order by id"])))
|
||||
(is (= {(col-kw :id) 1}
|
||||
(plan/select-one! (ds) [(col-kw :id)] [(str "select * from fruit order by " (index))])))
|
||||
(is (= 1
|
||||
(plan/select-one! (ds) :id ["select * from fruit order by id"])))
|
||||
(plan/select-one! (ds) (col-kw :id) [(str "select * from fruit order by " (index))])))
|
||||
(is (= "Banana"
|
||||
(plan/select-one! (ds) :name ["select * from fruit where id = ?" 2])))
|
||||
(plan/select-one! (ds) :name [(str "select * from fruit where " (index) " = ?") 2])))
|
||||
(is (= [1 "Apple"]
|
||||
(plan/select-one! (ds) (juxt :id :name)
|
||||
["select * from fruit order by id"])))
|
||||
(is (= {:id 1 :name "Apple"}
|
||||
(plan/select-one! (ds) #(select-keys % [:id :name])
|
||||
["select * from fruit order by id"]))))
|
||||
(plan/select-one! (ds) (juxt (col-kw :id) :name)
|
||||
[(str "select * from fruit order by " (index))])))
|
||||
(is (= {(col-kw :id) 1 :name "Apple"}
|
||||
(plan/select-one! (ds) #(select-keys % [(col-kw :id) :name])
|
||||
[(str "select * from fruit order by " (index))]))))
|
||||
|
||||
(deftest select-vector-tests
|
||||
(is (= [{:id 1} {:id 2} {:id 3} {:id 4}]
|
||||
(plan/select! (ds) [:id] ["select * from fruit order by id"])))
|
||||
(is (= [{(col-kw :id) 1} {(col-kw :id) 2} {(col-kw :id) 3} {(col-kw :id) 4}]
|
||||
(plan/select! (ds) [(col-kw :id)] [(str "select * from fruit order by " (index))])))
|
||||
(is (= [1 2 3 4]
|
||||
(plan/select! (ds) :id ["select * from fruit order by id"])))
|
||||
(plan/select! (ds) (col-kw :id) [(str "select * from fruit order by " (index))])))
|
||||
(is (= ["Banana"]
|
||||
(plan/select! (ds) :name ["select * from fruit where id = ?" 2])))
|
||||
(plan/select! (ds) :name [(str "select * from fruit where " (index) " = ?") 2])))
|
||||
(is (= [[2 "Banana"]]
|
||||
(plan/select! (ds) (juxt :id :name)
|
||||
["select * from fruit where id = ?" 2])))
|
||||
(is (= [{:id 2 :name "Banana"}]
|
||||
(plan/select! (ds) [:id :name]
|
||||
["select * from fruit where id = ?" 2]))))
|
||||
(plan/select! (ds) (juxt (col-kw :id) :name)
|
||||
[(str "select * from fruit where " (index) " = ?") 2])))
|
||||
(is (= [{(col-kw :id) 2 :name "Banana"}]
|
||||
(plan/select! (ds) [(col-kw :id) :name]
|
||||
[(str "select * from fruit where " (index) " = ?") 2]))))
|
||||
|
||||
(deftest select-set-tests
|
||||
(is (= #{{:id 1} {:id 2} {:id 3} {:id 4}}
|
||||
(plan/select! (ds) [:id] ["select * from fruit order by id"]
|
||||
(is (= #{{(col-kw :id) 1} {(col-kw :id) 2} {(col-kw :id) 3} {(col-kw :id) 4}}
|
||||
(plan/select! (ds) [(col-kw :id)] [(str "select * from fruit order by " (index))]
|
||||
{:into #{}})))
|
||||
(is (= #{1 2 3 4}
|
||||
(plan/select! (ds) :id ["select * from fruit order by id"]
|
||||
(plan/select! (ds) (col-kw :id) [(str "select * from fruit order by " (index))]
|
||||
{:into #{}}))))
|
||||
|
||||
(deftest select-map-tests
|
||||
(is (= {1 "Apple", 2 "Banana", 3 "Peach", 4 "Orange"}
|
||||
(plan/select! (ds) (juxt :id :name) ["select * from fruit order by id"]
|
||||
(plan/select! (ds) (juxt (col-kw :id) :name) [(str "select * from fruit order by " (index))]
|
||||
{:into {}}))))
|
||||
|
||||
(deftest select-issue-227
|
||||
(is (= ["Apple"]
|
||||
(plan/select! (ds) :name ["select * from fruit where id = ?" 1]
|
||||
(plan/select! (ds) :name [(str "select * from fruit where " (index) " = ?") 1]
|
||||
{:column-fn #(str/replace % "-" "_")})))
|
||||
(is (= ["Apple"]
|
||||
(plan/select! (ds) :foo/name ["select * from fruit where id = ?" 1]
|
||||
(plan/select! (ds) :foo/name [(str "select * from fruit where " (index) " = ?") 1]
|
||||
{:column-fn #(str/replace % "-" "_")})))
|
||||
(is (= ["Apple"]
|
||||
(plan/select! (ds) #(get % "name") ["select * from fruit where id = ?" 1]
|
||||
(plan/select! (ds) #(get % "name") [(str "select * from fruit where " (index) " = ?") 1]
|
||||
{:column-fn #(str/replace % "-" "_")})))
|
||||
(is (= [["Apple"]]
|
||||
(plan/select! (ds) (juxt :name) ["select * from fruit where id = ?" 1]
|
||||
(plan/select! (ds) (juxt :name) [(str "select * from fruit where " (index) " = ?") 1]
|
||||
{:column-fn #(str/replace % "-" "_")}))))
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
(:require [clojure.test :refer [deftest is testing use-fixtures]]
|
||||
[next.jdbc :as jdbc]
|
||||
[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.specs :as specs]))
|
||||
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
(specs/instrument)
|
||||
|
||||
(deftest execute-batch-tests
|
||||
(when-not (xtdb?)
|
||||
(testing "simple batch insert"
|
||||
(is (= [1 1 1 1 1 1 1 1 1 13]
|
||||
(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
|
||||
;; are only three keys, plus the overall count here:
|
||||
(is (< 3 (count results))))
|
||||
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))))
|
||||
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))))))
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@
|
|||
[next.jdbc.protocols :as p]
|
||||
[next.jdbc.result-set :as rs]
|
||||
[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
|
||||
derby? mssql? mysql? postgres?]])
|
||||
derby? mssql? mysql? postgres? xtdb?]])
|
||||
(:import (java.sql ResultSet ResultSetMetaData)))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
|
@ -27,7 +27,9 @@
|
|||
(testing "default schema"
|
||||
(let [connectable (ds)
|
||||
test-row (rs/datafiable-row {:TABLE/FRUIT_ID 1} connectable
|
||||
(default-options))
|
||||
(cond-> (default-options)
|
||||
(xtdb?)
|
||||
(assoc :schema-opts {:pk "_id"})))
|
||||
data (d/datafy test-row)
|
||||
v (get data :TABLE/FRUIT_ID)]
|
||||
;; check datafication is sane
|
||||
|
|
@ -40,7 +42,10 @@
|
|||
(let [connectable (ds)
|
||||
test-row (rs/datafiable-row {:foo/bar 2} connectable
|
||||
(assoc (default-options)
|
||||
:schema {:foo/bar :fruit/id}))
|
||||
:schema {:foo/bar
|
||||
(if (xtdb?)
|
||||
:fruit/_id
|
||||
:fruit/id)}))
|
||||
data (d/datafy test-row)
|
||||
v (get data :foo/bar)]
|
||||
;; check datafication is sane
|
||||
|
|
@ -53,7 +58,10 @@
|
|||
(let [connectable (ds)
|
||||
test-row (rs/datafiable-row {:foo/bar 3} connectable
|
||||
(assoc (default-options)
|
||||
:schema {:foo/bar [:fruit/id]}))
|
||||
:schema {:foo/bar
|
||||
[(if (xtdb?)
|
||||
:fruit/_id
|
||||
:fruit/id)]}))
|
||||
data (d/datafy test-row)
|
||||
v (get data :foo/bar)]
|
||||
;; check datafication is sane
|
||||
|
|
@ -67,7 +75,7 @@
|
|||
(let [connectable (ds)
|
||||
test-row (rs/datafiable-row {:foo/bar 2} connectable
|
||||
(assoc (default-options)
|
||||
:schema {:foo/bar [:fruit :id]}))
|
||||
:schema {:foo/bar [:fruit (col-kw :id)]}))
|
||||
data (d/datafy test-row)
|
||||
v (get data :foo/bar)]
|
||||
;; check datafication is sane
|
||||
|
|
@ -79,7 +87,7 @@
|
|||
(let [connectable (ds)
|
||||
test-row (rs/datafiable-row {:foo/bar 3} connectable
|
||||
(assoc (default-options)
|
||||
:schema {:foo/bar [:fruit :id :many]}))
|
||||
:schema {:foo/bar [:fruit (col-kw :id) :many]}))
|
||||
data (d/datafy test-row)
|
||||
v (get data :foo/bar)]
|
||||
;; check datafication is sane
|
||||
|
|
@ -93,7 +101,7 @@
|
|||
(deftest test-map-row-builder
|
||||
(testing "default row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 1]
|
||||
[(str "select * from fruit where " (index) " = ?") 1]
|
||||
(default-options))]
|
||||
(is (map? row))
|
||||
(is (contains? row (column :FRUIT/GRADE)))
|
||||
|
|
@ -101,7 +109,7 @@
|
|||
(is (= 1 ((column :FRUIT/ID) row)))
|
||||
(is (= "Apple" ((column :FRUIT/NAME) row))))
|
||||
(let [rs (p/-execute-all (ds)
|
||||
["select * from fruit order by id"]
|
||||
[(str "select * from fruit order by " (index))]
|
||||
(default-options))]
|
||||
(is (every? map? rs))
|
||||
(is (= 1 ((column :FRUIT/ID) (first rs))))
|
||||
|
|
@ -110,7 +118,7 @@
|
|||
(is (= "Orange" ((column :FRUIT/NAME) (last rs))))))
|
||||
(testing "unqualified row builder"
|
||||
(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})]
|
||||
(is (map? row))
|
||||
(is (contains? row (column :COST)))
|
||||
|
|
@ -119,34 +127,35 @@
|
|||
(is (= "Banana" ((column :NAME) row)))))
|
||||
(testing "lower-case row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 3]
|
||||
[(str "select * from fruit where " (index) " = ?") 3]
|
||||
(assoc (default-options)
|
||||
:builder-fn rs/as-lower-maps))]
|
||||
(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 (col-kw :fruit/appearance)))
|
||||
(is (nil? ((col-kw :fruit/appearance) row)))
|
||||
(is (= 3 ((col-kw :fruit/id) row)))
|
||||
(is (= "Peach" ((col-kw :fruit/name) row)))))
|
||||
(testing "unqualified lower-case row builder"
|
||||
(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})]
|
||||
(is (map? row))
|
||||
(is (= 4 (:id row)))
|
||||
(is (= "Orange" (:name row)))))
|
||||
(is (= 4 ((col-kw :id) row)))
|
||||
(is (= "Orange" ((col-kw :name) row)))))
|
||||
(testing "kebab-case row builder"
|
||||
(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)
|
||||
:builder-fn rs/as-kebab-maps))]
|
||||
(is (map? row))
|
||||
(is (contains? row :fruit/looks-like))
|
||||
(is (nil? (:fruit/looks-like row)))
|
||||
(is (= 3 (:fruit/id row)))
|
||||
(is (= "Peach" (:fruit/name row)))))
|
||||
(is (contains? row (col-kw :fruit/looks-like)))
|
||||
(is (nil? ((col-kw :fruit/looks-like) row)))
|
||||
;; kebab-case strips leading _ from _id (XTDB):
|
||||
(is (= 3 ((if (xtdb?) :id :fruit/id) row)))
|
||||
(is (= "Peach" ((col-kw :fruit/name) row)))))
|
||||
(testing "unqualified kebab-case row builder"
|
||||
(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})]
|
||||
(is (map? row))
|
||||
(is (contains? row :looks-like))
|
||||
|
|
@ -155,7 +164,7 @@
|
|||
(is (= "Orange" (:name row)))))
|
||||
(testing "custom row builder 1"
|
||||
(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)
|
||||
:builder-fn rs/as-modified-maps
|
||||
:label-fn str/lower-case
|
||||
|
|
@ -168,7 +177,7 @@
|
|||
(is (= "Peach" ((column :FRUIT/name) row)))))
|
||||
(testing "custom row builder 2"
|
||||
(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)
|
||||
:builder-fn rs/as-modified-maps
|
||||
:label-fn str/lower-case
|
||||
|
|
@ -176,12 +185,12 @@
|
|||
(is (map? row))
|
||||
(is (contains? row :vegetable/appearance))
|
||||
(is (nil? (:vegetable/appearance row)))
|
||||
(is (= 3 (:vegetable/id row)))
|
||||
(is (= 3 ((if (xtdb?) :vegetable/_id :vegetable/id) row)))
|
||||
(is (= 103 (:vegetable/newid row))) ; constant qualifier here
|
||||
(is (= "Peach" (:vegetable/name row)))))
|
||||
(testing "adapted row builder"
|
||||
(let [row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 3]
|
||||
[(str "select * from fruit where " (index) " = ?") 3]
|
||||
(assoc
|
||||
(default-options)
|
||||
:builder-fn (rs/as-maps-adapter
|
||||
|
|
@ -207,7 +216,7 @@
|
|||
(fn [^ResultSet rs _ ^Integer i]
|
||||
(.getObject rs i)))
|
||||
row (p/-execute-one (ds)
|
||||
["select * from fruit where id = ?" 3]
|
||||
[(str "select * from fruit where " (index) " = ?") 3]
|
||||
(assoc
|
||||
(default-options)
|
||||
:builder-fn (rs/as-maps-adapter
|
||||
|
|
@ -236,7 +245,7 @@
|
|||
(testing "row-numbers on bare abstraction"
|
||||
(is (= [1 2 3]
|
||||
(into [] (map rs/row-number)
|
||||
(p/-execute (ds) ["select * from fruit where id < ?" 4]
|
||||
(p/-execute (ds) [(str "select * from fruit where " (index) " < ?") 4]
|
||||
;; we do not need a real builder here...
|
||||
(cond-> {:builder-fn (constantly nil)}
|
||||
(derby?)
|
||||
|
|
@ -247,7 +256,7 @@
|
|||
(is (= [1 2 3]
|
||||
(into [] (comp (map #(rs/datafiable-row % (ds) {}))
|
||||
(map rs/row-number))
|
||||
(p/-execute (ds) ["select * from fruit where id < ?" 4]
|
||||
(p/-execute (ds) [(str "select * from fruit where " (index) " < ?") 4]
|
||||
;; ...but datafiable-row requires a real builder
|
||||
(cond-> {:builder-fn rs/as-arrays}
|
||||
(derby?)
|
||||
|
|
@ -257,7 +266,7 @@
|
|||
|
||||
(deftest test-column-names
|
||||
(testing "column-names on bare abstraction"
|
||||
(is (= #{"id" "appearance" "grade" "cost" "name"}
|
||||
(is (= #{(index) "appearance" "grade" "cost" "name"}
|
||||
(reduce (fn [_ row]
|
||||
(-> row
|
||||
(->> (rs/column-names)
|
||||
|
|
@ -265,11 +274,11 @@
|
|||
(set)
|
||||
(reduced))))
|
||||
nil
|
||||
(p/-execute (ds) ["select * from fruit where id < ?" 4]
|
||||
(p/-execute (ds) [(str "select * from fruit where " (index) " < ?") 4]
|
||||
;; column-names require a real builder
|
||||
{:builder-fn rs/as-arrays})))))
|
||||
(testing "column-names on realized row"
|
||||
(is (= #{"id" "appearance" "grade" "cost" "name"}
|
||||
(is (= #{(index) "appearance" "grade" "cost" "name"}
|
||||
(reduce (fn [_ row]
|
||||
(-> row
|
||||
(rs/datafiable-row (ds) {})
|
||||
|
|
@ -278,7 +287,7 @@
|
|||
(set)
|
||||
(reduced))))
|
||||
nil
|
||||
(p/-execute (ds) ["select * from fruit where id < ?" 4]
|
||||
(p/-execute (ds) [(str "select * from fruit where " (index) " < ?") 4]
|
||||
{:builder-fn rs/as-arrays}))))))
|
||||
|
||||
(deftest test-over-partition-all
|
||||
|
|
@ -299,31 +308,31 @@
|
|||
(testing "no row builder is used"
|
||||
(is (= [true]
|
||||
(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)}))))
|
||||
(is (= ["Apple"]
|
||||
(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)}))))
|
||||
(is (= [[2 [:name "Banana"]]]
|
||||
(into [] (map (juxt #(get % "id") ; get by string key works
|
||||
(into [] (map (juxt #(get % (index)) ; get by string key 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)}))))
|
||||
(is (= [{:id 3 :name "Peach"}]
|
||||
(into [] (map #(select-keys % [:id :name])) ; select-keys works
|
||||
(p/-execute (ds) ["select * from fruit where id = ?" 3]
|
||||
(is (= [{(col-kw :id) 3 :name "Peach"}]
|
||||
(into [] (map #(select-keys % [(col-kw :id) :name])) ; select-keys works
|
||||
(p/-execute (ds) [(str "select * from fruit where " (index) " = ?") 3]
|
||||
{:builder-fn (constantly nil)}))))
|
||||
(is (= [[:orange 4]]
|
||||
(into [] (map #(vector (if (contains? % :name) ; contains works
|
||||
(keyword (str/lower-case (:name %)))
|
||||
:unnamed)
|
||||
(get % :id 0))) ; get with not-found works
|
||||
(p/-execute (ds) ["select * from fruit where id = ?" 4]
|
||||
(get % (col-kw :id) 0))) ; get with not-found works
|
||||
(p/-execute (ds) [(str "select * from fruit where " (index) " = ?") 4]
|
||||
{:builder-fn (constantly nil)}))))
|
||||
(is (= [{}]
|
||||
(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)})))))
|
||||
(testing "count does not build a map"
|
||||
(let [count-builder (fn [_1 _2]
|
||||
|
|
@ -331,7 +340,7 @@
|
|||
(column-count [_] 13)))]
|
||||
(is (= [13]
|
||||
(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}))))))
|
||||
(testing "assoc, dissoc, cons, seq, and = build maps"
|
||||
(is (map? (reduce (fn [_ row] (reduced (assoc row :x 1)))
|
||||
|
|
@ -417,7 +426,7 @@
|
|||
(defn fruit-builder [^ResultSet rs ^ResultSetMetaData rsmeta]
|
||||
(reify
|
||||
rs/RowBuilder
|
||||
(->row [_] (->Fruit (.getObject rs "id")
|
||||
(->row [_] (->Fruit (.getObject rs ^String (index))
|
||||
(.getObject rs "name")
|
||||
(.getObject rs "appearance")
|
||||
(.getObject rs "cost")
|
||||
|
|
@ -434,7 +443,7 @@
|
|||
(valAt [this k] (get this k nil))
|
||||
(valAt [this k not-found]
|
||||
(case k
|
||||
:cols [:id :name :appearance :cost :grade]
|
||||
:cols [(col-kw :id) :name :appearance :cost :grade]
|
||||
:rsmeta rsmeta
|
||||
not-found))))
|
||||
|
||||
|
|
@ -467,7 +476,7 @@
|
|||
metadata))))
|
||||
|
||||
(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) {})]
|
||||
(try
|
||||
(p/-execute-one con ["DROP TABLE CLOBBER"] {})
|
||||
|
|
@ -497,10 +506,10 @@ CREATE TABLE CLOBBER (
|
|||
(testing "get n on bare abstraction over arrays"
|
||||
(is (= [1 2 3]
|
||||
(into [] (map #(get % 0))
|
||||
(p/-execute (ds) ["select id from fruit where id < ?" 4]
|
||||
(p/-execute (ds) [(str "select " (index) " from fruit where " (index) " < ? order by " (index)) 4]
|
||||
{:builder-fn rs/as-arrays})))))
|
||||
(testing "nth on bare abstraction over arrays"
|
||||
(is (= [1 2 3]
|
||||
(into [] (map #(nth % 0))
|
||||
(p/-execute (ds) ["select id from fruit where id < ?" 4]
|
||||
(p/-execute (ds) [(str "select " (index) " from fruit where " (index) " < ? order by " (index)) 4]
|
||||
{:builder-fn rs/as-arrays}))))))
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
[next.jdbc.specs :as specs]
|
||||
[next.jdbc.sql :as sql]
|
||||
[next.jdbc.test-fixtures
|
||||
:refer [with-test-db ds column default-options
|
||||
derby? jtds? maria? mssql? mysql? postgres? sqlite?]]
|
||||
:refer [column col-kw 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]]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
(deftest test-query
|
||||
(let [ds-opts (jdbc/with-options (ds) (default-options))
|
||||
rs (sql/query ds-opts ["select * from fruit order by id"])]
|
||||
rs (sql/query ds-opts [(str "select * from fruit order by " (index))])]
|
||||
(is (= 4 (count rs)))
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
|
|
@ -34,10 +34,10 @@
|
|||
(if (or (mysql?) (sqlite?))
|
||||
{:limit 2 :offset 1}
|
||||
{:offset 1 :fetch 2})
|
||||
:columns [:ID
|
||||
:columns [(col-kw :ID)
|
||||
["CASE WHEN grade > 91 THEN 'ok ' ELSE 'bad' END"
|
||||
:QUALITY]]
|
||||
:order-by [:id]))]
|
||||
:order-by [(col-kw :id)]))]
|
||||
(is (= 2 (count rs)))
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
|
|
@ -67,17 +67,18 @@
|
|||
(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 "max(id)" :all)]
|
||||
(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 (= "Apple" min-name))))
|
||||
(is (thrown? IllegalArgumentException
|
||||
(sql/aggregate-by-keys ds-opts :fruit "count(*)" :all {:columns []})))))
|
||||
|
||||
(deftest test-get-by-id
|
||||
(let [ds-opts (jdbc/with-options (ds) (default-options))]
|
||||
(is (nil? (sql/get-by-id ds-opts :fruit -1)))
|
||||
(let [row (sql/get-by-id ds-opts :fruit 3)]
|
||||
(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 {})]
|
||||
|
|
@ -88,23 +89,28 @@
|
|||
(is (map? row))
|
||||
(is (= 2 ((column :FRUIT/ID) row))))))
|
||||
|
||||
(defn- update-count [n]
|
||||
(if (xtdb?)
|
||||
{:next.jdbc/update-count 0}
|
||||
{:next.jdbc/update-count n}))
|
||||
|
||||
(deftest test-update!
|
||||
(let [ds-opts (jdbc/with-options (ds) (default-options))]
|
||||
(try
|
||||
(is (= {:next.jdbc/update-count 1}
|
||||
(sql/update! ds-opts :fruit {:appearance "brown"} {:id 2})))
|
||||
(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))))
|
||||
(sql/get-by-id ds-opts :fruit 2 (col-kw :id) {}))))
|
||||
(finally
|
||||
(sql/update! ds-opts :fruit {:appearance "yellow"} {:id 2})))
|
||||
(sql/update! ds-opts :fruit {:appearance "yellow"} {(col-kw :id) 2})))
|
||||
(try
|
||||
(is (= {:next.jdbc/update-count 1}
|
||||
(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))))
|
||||
(sql/get-by-id ds-opts :fruit 2 (col-kw :id) {}))))
|
||||
(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
|
||||
(let [new-key (cond (derby?) :1
|
||||
|
|
@ -113,18 +119,24 @@
|
|||
(mssql?) :GENERATED_KEYS
|
||||
(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
|
||||
{:name (as-varchar "Kiwi")
|
||||
(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 (= {:next.jdbc/update-count 1}
|
||||
(sql/delete! (ds) :fruit {:id 5})))
|
||||
(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?)
|
||||
|
|
@ -133,23 +145,28 @@
|
|||
[8M]
|
||||
(maria?)
|
||||
[6]
|
||||
(xtdb?)
|
||||
[]
|
||||
:else
|
||||
[6 7 8])
|
||||
(mapv new-key
|
||||
(sql/insert-multi! (ds) :fruit
|
||||
[:name :appearance :cost :grade]
|
||||
[["Kiwi" "green & fuzzy" 100 99.9]
|
||||
(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 (= {:next.jdbc/update-count 1}
|
||||
(sql/delete! (ds) :fruit {:id 6})))
|
||||
(is (= (update-count 1)
|
||||
(sql/delete! (ds) :fruit {(col-kw :id) 6})))
|
||||
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
|
||||
(is (= {:next.jdbc/update-count 2}
|
||||
(sql/delete! (ds) :fruit ["id > ?" 4])))
|
||||
(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?)
|
||||
|
|
@ -158,23 +175,28 @@
|
|||
[11M]
|
||||
(maria?)
|
||||
[9]
|
||||
(xtdb?)
|
||||
[]
|
||||
:else
|
||||
[9 10 11])
|
||||
(mapv new-key
|
||||
(sql/insert-multi! (ds) :fruit
|
||||
'(:name :appearance :cost :grade)
|
||||
'(("Kiwi" "green & fuzzy" 100 99.9)
|
||||
(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 (= {:next.jdbc/update-count 1}
|
||||
(sql/delete! (ds) :fruit {:id 9})))
|
||||
(is (= (update-count 1)
|
||||
(sql/delete! (ds) :fruit {(col-kw :id) 9})))
|
||||
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
|
||||
(is (= {:next.jdbc/update-count 2}
|
||||
(sql/delete! (ds) :fruit ["id > ?" 4])))
|
||||
(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?)
|
||||
|
|
@ -183,11 +205,13 @@
|
|||
[14M]
|
||||
(maria?)
|
||||
[12]
|
||||
(xtdb?)
|
||||
[]
|
||||
:else
|
||||
[12 13 14])
|
||||
(mapv new-key
|
||||
(sql/insert-multi! (ds) :fruit
|
||||
[{:name "Kiwi"
|
||||
(cond->> [{:name "Kiwi"
|
||||
:appearance "green & fuzzy"
|
||||
:cost 100
|
||||
:grade 99.9}
|
||||
|
|
@ -199,15 +223,17 @@
|
|||
: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 (= {:next.jdbc/update-count 1}
|
||||
(sql/delete! (ds) :fruit {:id 12})))
|
||||
(is (= (update-count 1)
|
||||
(sql/delete! (ds) :fruit {(col-kw :id) 12})))
|
||||
(is (= 6 (count (sql/query (ds) ["select * from fruit"]))))
|
||||
(is (= {:next.jdbc/update-count 2}
|
||||
(sql/delete! (ds) :fruit ["id > ?" 10])))
|
||||
(is (= (update-count 2)
|
||||
(sql/delete! (ds) :fruit [(str (index) " > ?") 10])))
|
||||
(is (= 4 (count (sql/query (ds) ["select * from fruit"])))))
|
||||
(testing "empty insert-multi!" ; per #44 and #264
|
||||
(is (= [] (sql/insert-multi! (ds) :fruit
|
||||
|
|
@ -255,7 +281,7 @@
|
|||
|
||||
(deftest array-in
|
||||
(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))))))
|
||||
|
||||
(deftest enum-pg
|
||||
|
|
|
|||
|
|
@ -64,11 +64,17 @@
|
|||
(def ^:private test-jtds
|
||||
(when (System/getenv "NEXT_JDBC_TEST_MSSQL") test-jtds-map))
|
||||
|
||||
(def ^:private test-xtdb-map {:dbtype "xtdb" :dbname "xtdb"})
|
||||
|
||||
(def ^:private test-xtdb
|
||||
(when (System/getenv "NEXT_JDBC_TEST_XTDB") test-xtdb-map))
|
||||
|
||||
(def ^:private test-db-specs
|
||||
(cond-> [test-derby test-h2-mem test-h2 test-hsql test-sqlite]
|
||||
test-postgres (conj test-postgres)
|
||||
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))
|
||||
|
||||
|
|
@ -86,19 +92,34 @@
|
|||
|
||||
(defn postgres? [] (= "embedded-postgres" (:dbtype @test-db-spec)))
|
||||
|
||||
(defn xtdb? [] (= "xtdb" (: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]
|
||||
(let [n (namespace k)]
|
||||
(keyword (when n (cond (postgres?) (str/lower-case n)
|
||||
(mssql?) (str/lower-case n)
|
||||
(mysql?) (str/lower-case n)
|
||||
(xtdb?) nil
|
||||
:else n))
|
||||
(cond (postgres?) (str/lower-case (name k))
|
||||
(xtdb?) (let [c (str/lower-case (name k))]
|
||||
(if (= "id" c) "_id" c))
|
||||
:else (name k)))))
|
||||
|
||||
(defn index []
|
||||
(if (xtdb?) "_id" "id"))
|
||||
|
||||
(defn col-kw [k]
|
||||
(if (xtdb?)
|
||||
(let [n (name k)]
|
||||
(if (= "id" (str/lower-case n)) :_id (keyword n)))
|
||||
k))
|
||||
|
||||
(defn default-options []
|
||||
(if (mssql?) ; so that we get table names back from queries
|
||||
{:result-type :scroll-insensitive :concurrency :read-only}
|
||||
|
|
@ -156,6 +177,28 @@
|
|||
:else
|
||||
"AUTO_INCREMENT PRIMARY KEY")]
|
||||
(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?)
|
||||
(try
|
||||
(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]
|
||||
["Peach" nil 139 90.0]
|
||||
["Orange" "juicy" 89 88.6]]
|
||||
{:return-keys false})
|
||||
{:return-keys false})))
|
||||
(t)))))
|
||||
|
||||
(create-clojure-test)
|
||||
|
|
|
|||
|
|
@ -2,21 +2,23 @@
|
|||
|
||||
(ns next.jdbc-test
|
||||
"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.test :refer [deftest is testing use-fixtures]]
|
||||
[next.jdbc :as jdbc]
|
||||
[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.result-set :as rs]
|
||||
[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])
|
||||
(:import (com.zaxxer.hikari HikariDataSource)
|
||||
(:import
|
||||
(com.mchange.v2.c3p0 ComboPooledDataSource PooledDataSource)
|
||||
(com.zaxxer.hikari HikariDataSource)
|
||||
(java.sql ResultSet ResultSetMetaData)))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
|
@ -60,27 +62,27 @@
|
|||
(jdbc/execute-one!
|
||||
ds-opts
|
||||
["select * from fruit where appearance = ?" "red"]))))
|
||||
(is (= "red" (:fruit/looks-like
|
||||
(is (= "red" ((col-kw :fruit/looks-like)
|
||||
(jdbc/execute-one!
|
||||
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))))
|
||||
(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!
|
||||
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)]
|
||||
(is (= (merge (default-options) jdbc/snake-kebab-opts)
|
||||
(:options ds')))
|
||||
(is (= "red" (:fruit/looks-like
|
||||
(is (= "red" ((col-kw :fruit/looks-like)
|
||||
(jdbc/execute-one!
|
||||
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
|
||||
(jdbc/execute-one!
|
||||
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)))))
|
||||
(testing "execute!"
|
||||
(let [rs (jdbc/execute!
|
||||
|
|
@ -95,7 +97,7 @@
|
|||
(is (= 1 ((column :FRUIT/ID) (first rs)))))
|
||||
(let [rs (jdbc/execute!
|
||||
ds-opts
|
||||
["select * from fruit order by id"]
|
||||
[(str "select * from fruit order by " (index))]
|
||||
{:builder-fn rs/as-maps})]
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
|
|
@ -104,22 +106,23 @@
|
|||
(is (= 4 ((column :FRUIT/ID) (last rs)))))
|
||||
(let [rs (jdbc/execute!
|
||||
ds-opts
|
||||
["select * from fruit order by id"]
|
||||
[(str "select * from fruit order by " (index))]
|
||||
{:builder-fn rs/as-arrays})]
|
||||
(is (every? vector? rs))
|
||||
(is (= 5 (count rs)))
|
||||
(is (every? #(= 5 (count %)) rs))
|
||||
;; columns come first
|
||||
(is (every? qualified-keyword? (first rs)))
|
||||
(is (every? (if (xtdb?) keyword? qualified-keyword?) (first rs)))
|
||||
;; :FRUIT/ID should be first column
|
||||
(is (= (column :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))))))
|
||||
(let [n (max (.indexOf ^java.util.List (first rs) :name) 1)]
|
||||
(is (every? string? (map #(nth % n) (rest rs)))))))
|
||||
(testing "execute! with adapter"
|
||||
(let [rs (jdbc/execute! ; test again, with adapter and lower columns
|
||||
ds-opts
|
||||
["select * from fruit order by id"]
|
||||
[(str "select * from fruit order by " (index))]
|
||||
{:builder-fn (rs/as-arrays-adapter
|
||||
rs/as-lower-arrays
|
||||
(fn [^ResultSet rs _ ^Integer i]
|
||||
|
|
@ -128,16 +131,17 @@
|
|||
(is (= 5 (count rs)))
|
||||
(is (every? #(= 5 (count %)) rs))
|
||||
;; columns come first
|
||||
(is (every? qualified-keyword? (first rs)))
|
||||
(is (every? (if (xtdb?) keyword? qualified-keyword?) (first rs)))
|
||||
;; :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
|
||||
(is (every? int? (map first (rest rs))))
|
||||
(is (every? string? (map second (rest rs))))))
|
||||
(let [n (max (.indexOf ^java.util.List (first rs) :name) 1)]
|
||||
(is (every? string? (map #(nth % n) (rest rs)))))))
|
||||
(testing "execute! with unqualified"
|
||||
(let [rs (jdbc/execute!
|
||||
(ds)
|
||||
["select * from fruit order by id"]
|
||||
[(str "select * from fruit order by " (index))]
|
||||
{:builder-fn rs/as-unqualified-maps})]
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
|
|
@ -146,7 +150,7 @@
|
|||
(is (= 4 ((column :ID) (last rs)))))
|
||||
(let [rs (jdbc/execute!
|
||||
ds-opts
|
||||
["select * from fruit order by id"]
|
||||
[(str "select * from fruit order by " (index))]
|
||||
{:builder-fn rs/as-unqualified-arrays})]
|
||||
(is (every? vector? rs))
|
||||
(is (= 5 (count rs)))
|
||||
|
|
@ -157,11 +161,13 @@
|
|||
(is (= (column :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))))))
|
||||
(let [n (max (.indexOf ^java.util.List (first rs) :name) 1)]
|
||||
(is (every? string? (map #(nth % n) (rest rs)))))))
|
||||
(when-not (xtdb?) ; XTDB does not support this yet
|
||||
(testing "execute! with :max-rows / :maxRows"
|
||||
(let [rs (jdbc/execute!
|
||||
ds-opts
|
||||
["select * from fruit order by id"]
|
||||
[(str "select * from fruit order by " (index))]
|
||||
{:max-rows 2})]
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
|
|
@ -170,19 +176,19 @@
|
|||
(is (= 2 ((column :FRUIT/ID) (last rs)))))
|
||||
(let [rs (jdbc/execute!
|
||||
ds-opts
|
||||
["select * from fruit order by id"]
|
||||
[(str "select * from fruit order by " (index))]
|
||||
{:statement {:maxRows 2}})]
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
(is (= 2 (count rs)))
|
||||
(is (= 1 ((column :FRUIT/ID) (first rs))))
|
||||
(is (= 2 ((column :FRUIT/ID) (last rs)))))))
|
||||
(is (= 2 ((column :FRUIT/ID) (last rs))))))))
|
||||
(testing "prepare"
|
||||
;; default options do not flow over get-connection
|
||||
(let [rs (with-open [con (jdbc/get-connection (ds))
|
||||
ps (jdbc/prepare
|
||||
con
|
||||
["select * from fruit order by id"]
|
||||
[(str "select * from fruit order by " (index))]
|
||||
(default-options))]
|
||||
(jdbc/execute! ps))]
|
||||
(is (every? map? rs))
|
||||
|
|
@ -194,7 +200,7 @@
|
|||
(let [rs (with-open [con (jdbc/get-connection (ds))
|
||||
ps (jdbc/prepare
|
||||
con
|
||||
["select * from fruit where id = ?"]
|
||||
[(str "select * from fruit where " (index) " = ?")]
|
||||
(default-options))]
|
||||
(jdbc/execute! (prep/set-parameters ps [4]) nil {}))]
|
||||
(is (every? map? rs))
|
||||
|
|
@ -205,7 +211,7 @@
|
|||
;; default options do not flow over get-connection
|
||||
(let [rs (with-open [con (jdbc/get-connection (ds))]
|
||||
(jdbc/execute! (prep/statement con (default-options))
|
||||
["select * from fruit order by id"]))]
|
||||
[(str "select * from fruit order by " (index))]))]
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
(is (= 4 (count rs)))
|
||||
|
|
@ -214,11 +220,12 @@
|
|||
;; default options do not flow over get-connection
|
||||
(let [rs (with-open [con (jdbc/get-connection (ds))]
|
||||
(jdbc/execute! (prep/statement con (default-options))
|
||||
["select * from fruit where id = 4"]))]
|
||||
[(str "select * from fruit where " (index) " = 4")]))]
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
(is (= 1 (count rs)))
|
||||
(is (= 4 ((column :FRUIT/ID) (first rs))))))
|
||||
(when-not (xtdb?)
|
||||
(testing "transact"
|
||||
(is (= [{:next.jdbc/update-count 1}]
|
||||
(jdbc/transact (ds)
|
||||
|
|
@ -354,11 +361,11 @@ VALUES ('Pear', 'green', 49, 47)
|
|||
(.rollback t save-point)
|
||||
result))))
|
||||
(is (= 4 (count (jdbc/execute! con ["select * from fruit"]))))
|
||||
(is (= ac (.getAutoCommit con)))))))
|
||||
(is (= ac (.getAutoCommit con))))))))
|
||||
|
||||
(deftest issue-146
|
||||
;; 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
|
||||
;; we'd need to build the jdbcUrl with encryption turned off:
|
||||
(and (mssql?) (not (jtds?))))
|
||||
|
|
@ -495,6 +502,7 @@ VALUES ('Pear', 'green', 49, 47)
|
|||
"\n\t" (ex-message t)))))
|
||||
|
||||
(deftest bool-tests
|
||||
(when-not (xtdb?)
|
||||
(doseq [[n b] [["zero" 0] ["one" 1] ["false" false] ["true" true]]
|
||||
:let [v-bit (if (number? b) b (if b 1 0))
|
||||
v-bool (if (number? b) (pos? b) b)]]
|
||||
|
|
@ -548,9 +556,10 @@ VALUES ('Pear', 'green', 49, 47)
|
|||
[]
|
||||
(jdbc/plan (ds) ["select * from btest"]))]
|
||||
(is (every? boolean? (map :is_it data)))
|
||||
(is (every? boolean? (map :twiddle data)))))
|
||||
(is (every? boolean? (map :twiddle data))))))
|
||||
|
||||
(deftest execute-batch-tests
|
||||
(when-not (xtdb?)
|
||||
(testing "simple batch insert"
|
||||
(is (= [1 1 1 1 1 1 1 1 1 13]
|
||||
(jdbc/with-transaction [t (ds) {:rollback-only true}]
|
||||
|
|
@ -649,9 +658,10 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
;; Derby and SQLite only return one generated key per batch so there
|
||||
;; are only three keys, plus the overall count here:
|
||||
(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
|
||||
(when-not (xtdb?)
|
||||
(testing "simple batch insert"
|
||||
(is (= [1 1 1 1 1 1 1 1 1 13]
|
||||
(try
|
||||
|
|
@ -669,7 +679,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
{})]
|
||||
(conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
||||
(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"])))))
|
||||
(testing "batch with-options"
|
||||
(is (= [1 1 1 1 1 1 1 1 1 13]
|
||||
|
|
@ -688,7 +698,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
{})]
|
||||
(conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
||||
(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"])))))
|
||||
(testing "batch with-logging"
|
||||
(is (= [1 1 1 1 1 1 1 1 1 13]
|
||||
|
|
@ -707,7 +717,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
{})]
|
||||
(conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
||||
(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"])))))
|
||||
(testing "small batch insert"
|
||||
(is (= [1 1 1 1 1 1 1 1 1 13]
|
||||
|
|
@ -726,7 +736,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
{:batch-size 3})]
|
||||
(conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
||||
(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"])))))
|
||||
(testing "big batch insert"
|
||||
(is (= [1 1 1 1 1 1 1 1 1 13]
|
||||
|
|
@ -745,7 +755,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
{:batch-size 8})]
|
||||
(conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
||||
(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"])))))
|
||||
(testing "large batch insert"
|
||||
(when-not (or (jtds?) (sqlite?))
|
||||
|
|
@ -766,7 +776,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
:large true})]
|
||||
(conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
||||
(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"]))))))
|
||||
(testing "return generated keys"
|
||||
(when-not (or (mssql?) (sqlite?))
|
||||
|
|
@ -790,26 +800,30 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
:return-generated-keys true})]
|
||||
(conj result (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
||||
(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 (every? map? (butlast results)))
|
||||
;; Derby and SQLite only return one generated key per batch so there
|
||||
;; are only three keys, plus the overall count here:
|
||||
(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
|
||||
(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))
|
||||
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 "from a Connection"
|
||||
(let [result
|
||||
(with-open [con (jdbc/get-connection (ds))]
|
||||
(r/foldcat
|
||||
(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)))))]
|
||||
(is (= 1000 (count result)))
|
||||
(is (= "Fruit-1" (first result)))
|
||||
|
|
@ -821,7 +835,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
(try
|
||||
(r/fold n r/cat r/append!
|
||||
(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))))
|
||||
(catch java.util.concurrent.RejectedExecutionException _
|
||||
[]))]
|
||||
|
|
@ -832,7 +846,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
(let [result
|
||||
(with-open [con (jdbc/get-connection (ds))
|
||||
stmt (jdbc/prepare con
|
||||
["select * from fruit order by id"]
|
||||
[(str "select * from fruit order by " (index))]
|
||||
(default-options))]
|
||||
(r/foldcat
|
||||
(r/map (column :FRUIT/NAME)
|
||||
|
|
@ -846,7 +860,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
stmt (prep/statement con (default-options))]
|
||||
(r/foldcat
|
||||
(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)))))]
|
||||
(is (= 1000 (count result)))
|
||||
(is (= "Fruit-1" (first result)))
|
||||
|
|
@ -854,7 +868,7 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
|
||||
(deftest connection-tests
|
||||
(testing "datasource via jdbcUrl"
|
||||
(when-not (postgres?)
|
||||
(when-not (or (postgres?) (xtdb?))
|
||||
(let [[url etc] (#'c/spec->url+etc (db))
|
||||
ds (jdbc/get-datasource (assoc etc :jdbcUrl url))]
|
||||
(cond (derby?) (is (= {:create true} etc))
|
||||
|
|
@ -937,11 +951,11 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
|
|||
(let [s (pr-str (into [] (take 3) (jdbc/plan (ds) ["select * from fruit"]
|
||||
(default-options))))]
|
||||
(is (or (re-find #"missing `map` or `reduce`" s)
|
||||
(re-find #"(?i)^\[#:fruit\{.*:id.*\}\]$" s))))
|
||||
(is (every? #(re-find #"(?i)^#:fruit\{.*:id.*\}$" %)
|
||||
(re-find #"(?i)^\[(#:fruit)?\{.*:_?id.*\}\]$" s))))
|
||||
(is (every? #(re-find #"(?i)^(#:fruit)?\{.*:_?id.*\}$" %)
|
||||
(into [] (map str) (jdbc/plan (ds) ["select * from fruit"]
|
||||
(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"]
|
||||
(default-options)))))
|
||||
(is (thrown? IllegalArgumentException
|
||||
|
|
|
|||
Loading…
Reference in a new issue