/
insert.clj
154 lines (130 loc) · 6.87 KB
/
insert.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
(ns toucan2.insert
"Implementation of [[insert!]].
The code for building an INSERT query as Honey SQL lives in [[toucan2.map-backend.honeysql2]]"
(:require
[clojure.spec.alpha :as s]
[methodical.core :as m]
[toucan2.log :as log]
[toucan2.pipeline :as pipeline]
[toucan2.query :as query]))
(s/def ::kv-args
(s/+ (s/cat
:k keyword?
:v any?)))
(s/def ::args.rows
(s/alt :nil nil?
:single-row-map map?
:multiple-row-maps (s/spec (s/* map?))
:kv-pairs ::kv-args
:columns-rows (s/cat :columns (s/spec (s/+ keyword?))
:rows (s/spec (s/+ sequential?)))))
(s/def ::args
(s/cat
:connectable ::query/default-args.connectable
:modelable ::query/default-args.modelable
:rows-or-queryable (s/alt :rows ::args.rows
:queryable some?)))
(m/defmethod query/parse-args :toucan.query-type/insert.*
"Default args parsing method for [[toucan2.insert/insert!]]. Uses the spec `:toucan2.insert/args`."
[query-type unparsed-args]
(let [parsed (query/parse-args-with-spec query-type ::args unparsed-args)
[rows-queryable-type rows-queryable] (:rows-or-queryable parsed)
parsed (select-keys parsed [:modelable :columns :connectable])]
(case rows-queryable-type
:queryable
(assoc parsed :queryable rows-queryable)
:rows
(assoc parsed :rows (let [[rows-type x] rows-queryable]
(condp = rows-type
:nil nil
:single-row-map [x]
:multiple-row-maps x
:kv-pairs [(into {} (map (juxt :k :v)) x)]
:columns-rows (let [{:keys [columns rows]} x]
(map (partial zipmap columns)
rows))))))))
(defn- can-skip-insert? [parsed-args resolved-query]
(and (empty? (:rows parsed-args))
;; don't try to optimize out stuff like identity query.
(or (and (map? resolved-query)
(empty? (:rows resolved-query)))
(nil? resolved-query))))
(m/defmethod pipeline/build [#_query-type :toucan.query-type/insert.*
#_model :default
#_resolved-query :default]
"Default INSERT query method. No-ops if there are no `:rows` to insert in either `parsed-args` or `resolved-query`."
[query-type model parsed-args resolved-query]
(let [rows (some (comp not-empty :rows) [parsed-args resolved-query])]
(if (can-skip-insert? parsed-args resolved-query)
(do
(log/debugf :compile "Query has no changes, skipping update")
::pipeline/no-op)
(do
(log/debugf :compile "Inserting %s rows into %s" (if (seq rows) (count rows) "?") model)
(next-method query-type model parsed-args resolved-query)))))
(defn insert!
"Insert a row or rows into the database. Returns the number of rows inserted.
This function is pretty flexible in what it accepts:
Insert a single row with key-value args:
```clj
(t2/insert! :models/venues :name \"Grant & Green\", :category \"bar\")
```
Insert a single row as a map:
```clj
(t2/insert! :models/venues {:name \"Grant & Green\", :category \"bar\"})
```
Insert multiple row maps:
```clj
(t2/insert! :models/venues [{:name \"Grant & Green\", :category \"bar\"}
{:name \"Savoy Tivoli\", :category \"bar\"}])
```
Insert rows with a vector of column names and a vector of value maps:
```clj
(t2/insert! :models/venues [:name :category] [[\"Grant & Green\" \"bar\"]
[\"Savoy Tivoli\" \"bar\"]])
```
As with other Toucan 2 functions, you can optionally pass a connectable if you pass `:conn` as the first arg. Refer to
the `:toucan2.insert/args` spec for the complete syntax.
Named connectables can also be used to define the rows:
```clj
(t2/define-named-query ::named-rows
{:rows [{:name \"Grant & Green\", :category \"bar\"}
{:name \"North Beach Cantina\", :category \"restaurant\"}]})
(t2/insert! :models/venues ::named-rows)
```"
{:arglists '([modelable row-or-rows-or-queryable]
[modelable k v & more]
[modelable columns row-vectors]
[:conn connectable modelable row-or-rows]
[:conn connectable modelable k v & more]
[:conn connectable modelable columns row-vectors])}
[& unparsed-args]
(pipeline/transduce-unparsed-with-default-rf :toucan.query-type/insert.update-count unparsed-args))
(defn insert-returning-pks!
"Like [[insert!]], but returns a vector of the primary keys of the newly inserted rows rather than the number of rows
inserted. The primary keys are determined by [[model/primary-keys]]. For models with a single primary key, this
returns a vector of single values, e.g. `[1 2]` if the primary key is `:id` and you've inserted rows 1 and 2; for
composite primary keys this returns a vector of tuples where each tuple has the value of corresponding primary key as
returned by [[model/primary-keys]], e.g. for composite PK `[:id :name]` you might get `[[1 \"Cam\"] [2 \"Sam\"]]`."
{:arglists '([modelable row-or-rows-or-queryable]
[modelable k v & more]
[modelable columns row-vectors]
[:conn connectable modelable row-or-rows]
[:conn connectable modelable k v & more]
[:conn connectable modelable columns row-vectors])}
[& unparsed-args]
(pipeline/transduce-unparsed-with-default-rf :toucan.query-type/insert.pks unparsed-args))
(defn insert-returning-instances!
"Like [[insert!]], but returns a vector of the primary keys of the newly inserted rows rather than the number of rows
inserted. The primary keys are determined by [[model/primary-keys]]. For models with a single primary key, this
returns a vector of single values, e.g. `[1 2]` if the primary key is `:id` and you've inserted rows 1 and 2; for
composite primary keys this returns a vector of tuples where each tuple has the value of corresponding primary key as
returned by [[model/primary-keys]], e.g. for composite PK `[:id :name]` you might get `[[1 \"Cam\"] [2 \"Sam\"]]`."
{:arglists '([modelable-columns row-or-rows-or-queryable]
[modelable-columns k v & more]
[modelable-columns columns row-vectors]
[:conn connectable modelable-columns row-or-rows]
[:conn connectable modelable-columns k v & more]
[:conn connectable modelable-columns columns row-vectors])}
[& unparsed-args]
(pipeline/transduce-unparsed-with-default-rf :toucan.query-type/insert.instances unparsed-args))