Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowed choosing the IN-list data structure type #132

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,26 @@ The query will be automatically expanded to `... IN (1001, 1003, 1005)
Just remember that some databases have a limit on the number of values
in an `IN`-list, and Yesql makes no effort to circumvent such limits.

By default, vectors, lists and seqs are automatically expanded as explained
above, but this behaviour can be overridden by passing an extra parameter
to the query creation functions (`defquery`, `defqueries` and `require-sql`):

```clojure
(defqueries "some/where/queryfile.sql"
{:connection db-spec
:in-list-parameter-predicate set?})

(find-users {:id #{1001 1003 1005}
:maxage 18})
```

In this way you can use data structures such as vectors for other purposes,
for example you are free to use `PostgreSQL`'s ARRAYs.
The `:in-list-parameter-predicate` must be a predicate used to identify
the data structure that must be expanded, but keep in mind that the choosen
data structure must respond to the `Seq` interface. A `nil` value selects
the default behaviour.

### Row And Result Processors

Like `clojure.java.jdbc`, Yesql accepts functions to pre-process each
Expand Down
17 changes: 11 additions & 6 deletions src/yesql/generate.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
[yesql.statement-parser :refer [tokenize]])
(:import [yesql.types Query]))

(def in-list-parameter?
(def default-in-list-parameter?
"Check if a type triggers IN-list expansion."
(some-fn list? vector? seq?))

(defn- args-to-placeholders
[args]
[args in-list-parameter?]
(if (in-list-parameter? args)
(clojure.string/join "," (repeat (count args) "?"))
"?"))
Expand All @@ -33,7 +33,10 @@
(conj expected-keys :?))))

(defn rewrite-query-for-jdbc
[tokens initial-args]
([tokens initial-args]
(rewrite-query-for-jdbc tokens initial-args default-in-list-parameter?))

([tokens initial-args in-list-parameter?]
(let [{:keys [expected-keys expected-positional-count]} (analyse-statement-tokens tokens)
actual-keys (set (keys (dissoc initial-args :?)))
actual-positional-count (count (:? initial-args))
Expand All @@ -58,14 +61,14 @@
(symbol? token) (let [[arg new-args] (if (= '? token)
[(first (:? args)) (update-in args [:?] rest)]
[(get args (keyword token)) args])]
[(str query (args-to-placeholders arg))
[(str query (args-to-placeholders arg in-list-parameter?))
(vec (if (in-list-parameter? arg)
(concat parameters arg)
(conj parameters arg)))
new-args])))
["" [] initial-args]
tokens)]
(concat [final-query] final-parameters))))
(concat [final-query] final-parameters)))))

;; Maintainer's note: clojure.java.jdbc.execute! returns a list of
;; rowcounts, because it takes a list of parameter groups. In our
Expand Down Expand Up @@ -109,6 +112,8 @@
required-args (expected-parameter-list statement)
global-connection (:connection query-options)
tokens (tokenize statement)
in-list-p (:in-list-parameter-predicate query-options)
in-list-parameter? (if (nil? in-list-p) default-in-list-parameter? in-list-p)
real-fn (fn [args call-options]
(let [connection (or (:connection call-options)
global-connection)]
Expand All @@ -118,7 +123,7 @@
"Check the docs, and supply {:connection ...} as an option to the function call, or globally to the defquery declaration."])
name))
(jdbc-fn connection
(rewrite-query-for-jdbc tokens args)
(rewrite-query-for-jdbc tokens args in-list-parameter?)
call-options)))
[display-args generated-function] (let [named-args (if-let [as-vec (seq (mapv (comp symbol clojure.core/name)
required-args))]
Expand Down
1 change: 1 addition & 0 deletions test/yesql/acceptance_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,4 @@

(expect SQLSyntaxErrorException
(syntax-error))

31 changes: 31 additions & 0 deletions test/yesql/acceptance_test_custom_in_list.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
(ns yesql.acceptance-test-custom-in-list
(:require [expectations :refer :all]
[clojure.java.jdbc :as jdbc]
[yesql.core :refer :all])
(:import [java.sql SQLException SQLSyntaxErrorException SQLDataException]))

(def derby-db {:subprotocol "derby"
:subname (gensym "memory:")
:create true})

;;; Multiple-query workflow.
(defqueries
"yesql/sample_files/acceptance_test_combined.sql"
{:connection derby-db :in-list-parameter-predicate set?})

;; Create
(expect (create-person-table!))

;; Insert -> Select.
(expect {:1 1M} (insert-person<! {:name "Alice"
:age 20}))
(expect {:1 2M} (insert-person<! {:name "Bob"
:age 25}))
(expect {:1 3M} (insert-person<! {:name "Charlie"
:age 35}))

;;; Select with IN.
(expect 2 (count (find-by-age {:age #{20 35}})))

;; Drop
(expect (drop-person-table!))
2 changes: 1 addition & 1 deletion test/yesql/generate_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

;;; Testing in-list-parmaeter for "IN-list" statements.
(expect [true true true false false]
(map in-list-parameter?
(map default-in-list-parameter?
(list []
(list)
(lazy-seq (cons 1 [2]))
Expand Down