-
Notifications
You must be signed in to change notification settings - Fork 0
/
honeysql.clj
181 lines (159 loc) · 5.09 KB
/
honeysql.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
(ns phoenix.honeysql
(:require [clojure.string :as str]
[potemkin :refer [import-vars]]
[phoenix.db :as db]
[honeysql.format :as fmt]
[honeysql.core :as sql]
[honeysql.helpers :refer [defhelper]
:as h]))
(import-vars
[honeysql.helpers
columns
values
modifiers
select
from
where
join
left-join
right-join
full-join
group
having
order-by
limit
offset
delete-from])
(def as-sql
"Format sqlmap as sql string, return vector of sql string and params.
See also honeysql.core/format."
#'sql/format)
(fmt/register-clause! :on-duplicate-key 225) ;; after :values
(defhelper on-duplicate-key [m args]
(assoc m :on-duplicate-key (if (sequential? args)
(first args)
args)))
(defmethod fmt/format-clause :on-duplicate-key [[op values] sqlmap]
(if (= :ignore values)
"ON DUPLICATE KEY IGNORE"
(str "ON DUPLICATE KEY UPDATE "
(fmt/comma-join (for [[k v] values]
(str (fmt/to-sql k) " = " (fmt/to-sql v)))))))
;; Reset :columns to be no-op
(defmethod fmt/format-clause :columns [[_ fields] sqlmap]
"")
(defn- ref-alias
"Partition a form into ref and alias, assuming [ref alias] form."
[form]
(if (sequential? form)
[(first form) (second form)]
[form nil]))
(defn- format-columns [cols]
(fmt/paren-wrap (fmt/comma-join (map fmt/to-sql cols))))
(defmethod fmt/format-clause :values [[_ values] _]
(if (sequential? (first values))
;; value in vector form
(str "VALUES "
(fmt/comma-join
(for [xs values] (format-columns xs))))
;; value in map form
(let [ks (keys (first values))]
(str "VALUES "
(fmt/comma-join
(for [m values]
(format-columns (map (partial get m) ks))))))))
(defn- format-table-cols
"Format table with (optionally-typed) columns."
[table cols]
(let [[table alias] (if (sequential? table)
table
[table])]
(str (fmt/to-sql table)
(when alias
(str " " (fmt/to-sql alias)))
(when-let [cols (seq cols)]
(str " " (format-columns cols))))))
(defn- annotate-types
"Annotate cols with types, return seq of cols with optional types.
E.g.:
```
=> (annotate-types user [:username :email :referrer])
(:username :email [:referrer \"VARCHAR(64)\"])
```
"
[table cols]
(if-not (db/table? table)
cols
(let [get-type (partial get (db/types* table))]
(map #(if-let [type* (get-type %)]
[% type*]
%)
(seq cols)))))
(fmt/register-clause! :upsert-into 45) ;; before :select
(defmethod fmt/format-clause :upsert-into [[op table] sqlmap]
(let [cols (or (:columns sqlmap)
(when-let [row (first (:values sqlmap))]
(when (map? row)
(keys row))))]
(->> (annotate-types table cols)
(format-table-cols table)
(str "UPSERT INTO "))))
(defhelper upsert-into [m table]
(assoc m :upsert-into (if (sequential? table) (first table) table)))
(defn- split-qual-col
"Split column name into tuple of [qual name], keywordized. Unqualified columns are
grouped under :_."
[term]
(let [[qual col] (str/split (name term) #"\.")]
(if col
[(keyword qual) (keyword col)]
[:_ (keyword qual)])))
(defn- infer-qual-col
"Infer qual(ifier) and column from a column related expr, return
qual col pair."
[expr]
(cond
(string? expr) (split-qual-col expr)
(keyword? expr) (split-qual-col expr)
(and (vector? expr)
(= 2 (count expr)))
(split-qual-col (first expr))
:else nil))
(defn- group-qual-col
"Group qual-col pair by qualifier, keywordized."
[pairs]
(reduce
(fn [m [k v]]
(if v
(update m (keyword k) (fnil conj []) v)
m))
{}
pairs))
(defmethod fmt/format-clause :from [[_ tables] sqlmap]
(let [select-cols (->> (:select sqlmap)
(map infer-qual-col)
(group-qual-col))]
(str "FROM "
(fmt/comma-join
(for [table-ref tables]
(let [[table alias types] (if (sequential? table-ref)
table-ref
[table-ref])
[alias types] (if (map? alias)
[nil alias]
[(keyword alias) types])]
(cond
;; no type to infer about
(keyword? table)
(format-table-cols [table alias] types)
(db/table? table)
(->> select-cols
((juxt :_ (or alias :_null) (db/table* table)))
(reduce into #{})
(select-keys (db/types* table))
;; user specified type is preferred over inferred
(#(merge %2 %1) types)
(format-table-cols [table alias]))
;; subquery etc
:else
(fmt/to-sql table-ref))))))))