Skip to content

Commit 9471f12

Browse files
committed
WIP: New-style parameters - function generation complete.
1 parent 7d8a7cd commit 9471f12

4 files changed

Lines changed: 99 additions & 66 deletions

File tree

src/yesql/generate.clj

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
(ns yesql.generate
22
(:require [clojure.java.jdbc :as jdbc]
33
[clojure.core.typed :as t :refer [ann HMap tc-ignore Any IFn]]
4+
[clojure.string :refer [join]]
45
[yesql.util :refer [distinct-except create-root-var]]
56
[yesql.types :refer [map->Query]]
6-
[yesql.statement-parser :refer [split-at-parameters reassemble-query]])
7+
[yesql.statement-parser :refer [expected-parameter-list rewrite-query-for-jdbc]])
78
(:import [yesql.types Query]))
89

910
;; (ann replace-question-mark-with-gensym
@@ -59,52 +60,49 @@
5960
query-options]
6061
(assert name "Query name is mandatory.")
6162
(assert statement "Query statement is mandatory.")
62-
(let [split-query (split-at-parameters statement)
63-
{:keys [query-args display-args function-args]} (split-query->args split-query)
64-
jdbc-fn (cond
63+
(let [jdbc-fn (cond
6564
(= (take-last 2 name) [\< \!]) insert-handler
6665
(= (last name) \!) execute-handler
6766
:else jdbc/query)
67+
required-args (expected-parameter-list statement)
68+
global-connection (:connection query-options)
6869
real-fn (fn [args call-options]
69-
(let [connection (:connection (merge query-options
70-
call-options))]
70+
(let [connection (or (:connection call-options)
71+
global-connection)]
7172
(assert connection
72-
(format "No database connection supplied to function '%s',\nCheck the docs, and supply {:connection ...} as an option to the function call, or globally to the defquery declaration."
73+
(format (join "\n"
74+
["No database connection supplied to function '%s',"
75+
"Check the docs, and supply {:connection ...} as an option to the function call, or globally to the defquery declaration."])
7376
name))
74-
(jdbc-fn (:connection (merge query-options
75-
call-options))
76-
(reassemble-query split-query args)))) ]
77-
(with-meta
78-
;;; TODO The next step is to get the query args generated.
79-
#_(cond
80-
(and (:connection query-options)
81-
(empty? expected-args))
82-
anonymous-version
83-
84-
(:connection query-options)
85-
single-arg-version
86-
87-
:else double-arg-version)
88-
(fn foo
89-
([]
90-
(foo {}))
91-
([args]
92-
(foo args {}))
93-
([args call-options]
94-
(real-fn args call-options)))
77+
(jdbc-fn connection
78+
(rewrite-query-for-jdbc statement args))))
79+
[display-args generated-function] (let [named-args (if-let [as-vec (seq (mapv (comp symbol clojure.core/name)
80+
required-args))]
81+
{:keys as-vec}
82+
{})
83+
global-args {:keys ['connection]}]
84+
(if global-connection
85+
(if (empty? required-args)
86+
[(list []
87+
[named-args global-args])
88+
(fn foo
89+
([] (foo {} {}))
90+
([args call-options] (real-fn args call-options)))]
91+
[(list [named-args]
92+
[named-args global-args])
93+
(fn foo
94+
([args] (foo args {}))
95+
([args call-options] (real-fn args call-options)))])
96+
[(list [named-args global-args])
97+
(fn foo
98+
([args call-options] (real-fn args call-options)))]))]
99+
(with-meta generated-function
95100
(merge {:name name
96-
:arglists (list [display-args]
97-
[display-args {:connection '...}])
101+
:arglists display-args
98102
::source (str statement)}
99103
(when docstring
100104
{:doc docstring})))))
101105

102-
(generate-query-fn (yesql.types/map->Query {:name "fetch"
103-
:statement "SELECT * FROM users WHERE user_id = ?"})
104-
{:dofault-db {:subprotocol "derby"
105-
:subname (gensym "memory:")
106-
:create true}})
107-
108106
(defprotocol FunctionGenerator
109107
(generate-fn [this options]))
110108

src/yesql/statement_parser.clj

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
(ns yesql.statement-parser
22
(:require [clojure.java.io :as io]
3+
[clojure.set :as set]
4+
[clojure.string :refer [join]]
35
[instaparse.core :as instaparse]
46
[yesql.util :refer [str-non-nil]]
57
[yesql.instaparse-util :refer [process-instaparse-result]]))
@@ -34,22 +36,41 @@
3436
"?"
3537
(clojure.string/join "," (repeat (count args) "?"))))
3638

37-
(defn reassemble-query
38-
[split-query initial-args]
39-
(let [expected-keys (set (map keyword (remove (partial = '?)
40-
(filter symbol? split-query))))
39+
(defn- analyse-split-query
40+
[split-query]
41+
{:expected-keys (set (map keyword (remove (partial = '?)
42+
(filter symbol? split-query))))
43+
:expected-positional-count (count (filter (partial = '?)
44+
split-query))})
45+
46+
(defn expected-parameter-list
47+
[statement]
48+
(let [split-query (split-at-parameters statement)
49+
{:keys [expected-keys expected-positional-count]} (analyse-split-query split-query)]
50+
(if (zero? expected-positional-count)
51+
expected-keys
52+
(conj expected-keys :?))))
53+
54+
55+
(defn rewrite-query-for-jdbc
56+
[statement initial-args]
57+
(let [split-query (split-at-parameters statement)
58+
{:keys [expected-keys expected-positional-count]} (analyse-split-query split-query)
4159
actual-keys (set (keys (dissoc initial-args :?)))
42-
expected-positional-count (count (filter (partial = '?)
43-
split-query))
44-
actual-positional-count (count (:? initial-args))]
45-
(assert (= expected-keys actual-keys)
46-
(format "Query argument mismatch.\nExpected keys: %s\nActual keys: %s\n"
60+
actual-positional-count (count (:? initial-args))
61+
missing-keys (set/difference expected-keys actual-keys)]
62+
(assert (empty? missing-keys)
63+
(format "Query argument mismatch.\nExpected keys: %s\nActual keys: %s\nMissing keys: %s"
4764
(str (seq expected-keys))
48-
(str actual-keys)))
65+
(str (seq actual-keys))
66+
(str (seq missing-keys))))
4967
(assert (= expected-positional-count actual-positional-count)
50-
(format "Query argument mismatch.\nExpected %d positional parameters. Got %d.\nSupply positional parameters as {:? [...]}"
68+
(format (join "\n"
69+
["Query argument mismatch."
70+
"Expected %d positional parameters. Got %d."
71+
"Supply positional parameters as {:? [...]}"])
5172
expected-positional-count actual-positional-count))
52-
(let [[final-query final-parameters _]
73+
(let [[final-query final-parameters consumed-args]
5374
(reduce (fn [[query parameters args] token]
5475
(cond
5576
(string? token) [(str query token)
@@ -65,4 +86,4 @@
6586
new-args])))
6687
["" [] initial-args]
6788
split-query)]
68-
(vec (cons final-query final-parameters)))))
89+
(concat [final-query] final-parameters))))

test/yesql/core_test.clj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@
3939
;;; Test Metadata.
4040
(expect {:doc "Just selects the current time.\nNothing fancy."
4141
:arglists (list '[]
42-
'[_ {:keys [connection]}])}
42+
'[{} {:keys [connection]}])}
4343
(in (meta (var current-time-query))))
4444

4545
(expect {:doc "Here's a query with some named and some anonymous parameters.\n(...and some repeats.)"
46-
:arglists (list '[]
47-
'[{:keys [value1 value2 ?]}]
48-
'[{:keys [value1 value2 ?]} {:keys [connection]}])}
46+
:name 'mixed-parameters-query
47+
:arglists (list '[{:keys [? value2 value1]}]
48+
'[{:keys [? value2 value1]} {:keys [connection]}])}
4949
(in (meta (var mixed-parameters-query))))
5050

5151
;; Running a query in a transaction and using the result outside of it should work as expected.

test/yesql/statement_parser_test.clj

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,26 +43,40 @@
4343
"SELECT :value1 + ? + value2 + ? + :value1\nFROM SYSIBM.SYSDUMMY1"
4444
=> ["SELECT " value1 " + " ? " + value2 + " ? " + " value1 "\nFROM SYSIBM.SYSDUMMY1"])
4545

46+
47+
(expect #{}
48+
(expected-parameter-list "SELECT * FROM user"))
49+
50+
(expect #{:?}
51+
(expected-parameter-list "SELECT * FROM user WHERE user_id = ?"))
52+
53+
(expect #{:name}
54+
(expected-parameter-list "SELECT * FROM user WHERE user_id = :name"))
55+
56+
57+
(expect #{:name :country :?}
58+
(expected-parameter-list "SELECT * FROM user WHERE user_id = :name AND country = :country AND age IN (?,?)"))
59+
4660
;;; Testing reassemble-query
4761
(expect ["SELECT age FROM users WHERE country = ?" "gb"]
48-
(reassemble-query (split-at-parameters "SELECT age FROM users WHERE country = :country")
49-
{:country "gb"}))
62+
(rewrite-query-for-jdbc "SELECT age FROM users WHERE country = :country"
63+
{:country "gb"}))
5064

51-
(expect [ "SELECT age FROM users WHERE (country = ? OR country = ?) AND name = ?" "gb" "us" "tom"]
52-
(reassemble-query (split-at-parameters "SELECT age FROM users WHERE (country = ? OR country = ?) AND name = :name")
53-
{:? ["gb" "us"]
54-
:name "tom"}))
65+
(expect ["SELECT age FROM users WHERE (country = ? OR country = ?) AND name = ?" "gb" "us" "tom"]
66+
(rewrite-query-for-jdbc "SELECT age FROM users WHERE (country = ? OR country = ?) AND name = :name"
67+
{:? ["gb" "us"]
68+
:name "tom"}))
5569

5670
;;; Testing reassemble-query IN strings.
5771
(expect ["SELECT age FROM users WHERE country = ? AND name IN (?,?,?)" "gb" "tom" "dick" "harry"]
58-
(reassemble-query (split-at-parameters "SELECT age FROM users WHERE country = :country AND name IN (:names)")
59-
{:country "gb"
60-
:names ["tom" "dick" "harry"]}))
72+
(rewrite-query-for-jdbc "SELECT age FROM users WHERE country = :country AND name IN (:names)"
73+
{:country "gb"
74+
:names ["tom" "dick" "harry"]}))
6175

6276
(expect AssertionError
63-
(reassemble-query (split-at-parameters "SELECT age FROM users WHERE country = :country AND name = :name")
64-
{:country "gb"}))
77+
(rewrite-query-for-jdbc "SELECT age FROM users WHERE country = :country AND name = :name"
78+
{:country "gb"}))
6579

6680
(expect AssertionError
67-
(reassemble-query (split-at-parameters "SELECT age FROM users WHERE country = ? AND name = ?")
68-
{}))
81+
(rewrite-query-for-jdbc "SELECT age FROM users WHERE country = ? AND name = ?"
82+
{}))

0 commit comments

Comments
 (0)