1use super::types::*;
4use crate::api_request::{ApiRequest, Mutation, Payload, PreferResolution, QualifiedIdentifier};
5use crate::error::{Error, Result};
6use crate::schema_cache::Table;
7use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Debug, Serialize, Deserialize)]
11pub enum MutatePlan {
12 Insert {
14 target: QualifiedIdentifier,
16 columns: Vec<CoercibleField>,
18 body: Option<bytes::Bytes>,
20 on_conflict: Option<(PreferResolution, Vec<String>)>,
22 where_clauses: Vec<CoercibleLogicTree>,
24 returning: Vec<String>,
26 pk_cols: Vec<String>,
28 apply_defaults: bool,
30 },
31 Update {
33 target: QualifiedIdentifier,
35 columns: Vec<CoercibleField>,
37 body: Option<bytes::Bytes>,
39 where_clauses: Vec<CoercibleLogicTree>,
41 returning: Vec<String>,
43 apply_defaults: bool,
45 },
46 Delete {
48 target: QualifiedIdentifier,
50 where_clauses: Vec<CoercibleLogicTree>,
52 returning: Vec<String>,
54 },
55}
56
57impl MutatePlan {
58 pub fn from_request(request: &ApiRequest, table: &Table, mutation: &Mutation) -> Result<Self> {
60 let qi = table.qualified_identifier();
61
62 match mutation {
63 Mutation::Create => Self::create_insert(request, table, qi),
64 Mutation::Update => Self::create_update(request, table, qi),
65 Mutation::Delete => Self::create_delete(request, table, qi),
66 Mutation::SingleUpsert => Self::create_upsert(request, table, qi),
67 }
68 }
69
70 fn create_insert(request: &ApiRequest, table: &Table, qi: QualifiedIdentifier) -> Result<Self> {
72 let columns = get_payload_columns(request, table)?;
73 let body = get_body_bytes(request)?;
74 let returning = get_returning_columns(request, table);
75 let apply_defaults =
76 request.preferences.missing == crate::api_request::PreferMissing::ApplyDefaults;
77
78 let on_conflict = request.query_params.on_conflict.as_ref().map(|cols| {
79 let resolution = request
80 .preferences
81 .resolution
82 .clone()
83 .unwrap_or(PreferResolution::MergeDuplicates);
84 (resolution, cols.clone())
85 });
86
87 Ok(Self::Insert {
88 target: qi,
89 columns,
90 body,
91 on_conflict,
92 where_clauses: vec![],
93 returning,
94 pk_cols: table.pk_cols.clone(),
95 apply_defaults,
96 })
97 }
98
99 fn create_update(request: &ApiRequest, table: &Table, qi: QualifiedIdentifier) -> Result<Self> {
101 let columns = get_payload_columns(request, table)?;
102 let body = get_body_bytes(request)?;
103 let where_clauses = build_mutation_where(request, table)?;
104 let returning = get_returning_columns(request, table);
105 let apply_defaults =
106 request.preferences.missing == crate::api_request::PreferMissing::ApplyDefaults;
107
108 Ok(Self::Update {
109 target: qi,
110 columns,
111 body,
112 where_clauses,
113 returning,
114 apply_defaults,
115 })
116 }
117
118 fn create_delete(request: &ApiRequest, table: &Table, qi: QualifiedIdentifier) -> Result<Self> {
120 let where_clauses = build_mutation_where(request, table)?;
121 let returning = get_returning_columns(request, table);
122
123 Ok(Self::Delete {
124 target: qi,
125 where_clauses,
126 returning,
127 })
128 }
129
130 fn create_upsert(request: &ApiRequest, table: &Table, qi: QualifiedIdentifier) -> Result<Self> {
132 let columns = get_payload_columns(request, table)?;
133 let body = get_body_bytes(request)?;
134 let returning = get_returning_columns(request, table);
135
136 let on_conflict = Some((PreferResolution::MergeDuplicates, table.pk_cols.clone()));
138
139 Ok(Self::Insert {
140 target: qi,
141 columns,
142 body,
143 on_conflict,
144 where_clauses: vec![],
145 returning,
146 pk_cols: table.pk_cols.clone(),
147 apply_defaults: true,
148 })
149 }
150
151 pub fn target(&self) -> &QualifiedIdentifier {
153 match self {
154 Self::Insert { target, .. } => target,
155 Self::Update { target, .. } => target,
156 Self::Delete { target, .. } => target,
157 }
158 }
159
160 pub fn has_body(&self) -> bool {
162 match self {
163 Self::Insert { body, .. } => body.is_some(),
164 Self::Update { body, .. } => body.is_some(),
165 Self::Delete { .. } => false,
166 }
167 }
168}
169
170fn get_payload_columns(request: &ApiRequest, table: &Table) -> Result<Vec<CoercibleField>> {
172 let keys = match &request.payload {
173 Some(Payload::ProcessedJson { keys, .. }) => keys,
174 Some(Payload::ProcessedUrlEncoded { keys, .. }) => keys,
175 _ => return Ok(vec![]),
176 };
177
178 let mut columns = Vec::new();
179
180 for key in keys {
181 let column = table
182 .get_column(key)
183 .ok_or_else(|| Error::UnknownColumn(key.clone()))?;
184
185 columns.push(CoercibleField::simple(key, &column.data_type));
186 }
187
188 Ok(columns)
189}
190
191fn get_body_bytes(request: &ApiRequest) -> Result<Option<bytes::Bytes>> {
193 match &request.payload {
194 Some(Payload::ProcessedJson { raw, .. }) => Ok(Some(raw.clone())),
195 Some(Payload::RawJson(raw)) => Ok(Some(raw.clone())),
196 Some(Payload::RawPayload(raw)) => Ok(Some(raw.clone())),
197 Some(Payload::ProcessedUrlEncoded { data, .. }) => {
198 let json = serde_json::to_vec(
200 &data
201 .iter()
202 .cloned()
203 .collect::<std::collections::HashMap<_, _>>(),
204 )
205 .map_err(|e| Error::InvalidBody(e.to_string()))?;
206 Ok(Some(bytes::Bytes::from(json)))
207 }
208 None => Ok(None),
209 }
210}
211
212fn get_returning_columns(request: &ApiRequest, table: &Table) -> Vec<String> {
214 if request.preferences.representation.needs_body() {
215 table.column_names().map(|s| s.to_string()).collect()
216 } else {
217 table.pk_cols.clone()
219 }
220}
221
222fn build_mutation_where(request: &ApiRequest, table: &Table) -> Result<Vec<CoercibleLogicTree>> {
224 let type_resolver = |name: &str| -> String {
225 table
226 .get_column(name)
227 .map(|c| c.data_type.clone())
228 .unwrap_or_else(|| "text".to_string())
229 };
230
231 let mut clauses = Vec::new();
232
233 for filter in &request.query_params.filters_root {
234 let pg_type = type_resolver(&filter.field.name);
235 clauses.push(CoercibleLogicTree::Stmt(CoercibleFilter::from_filter(
236 filter, &pg_type,
237 )));
238 }
239
240 Ok(clauses)
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn test_mutate_plan_target() {
249 let qi = QualifiedIdentifier::new("public", "users");
250 let plan = MutatePlan::Delete {
251 target: qi.clone(),
252 where_clauses: vec![],
253 returning: vec!["id".into()],
254 };
255
256 assert_eq!(plan.target().name, "users");
257 }
258
259 #[test]
260 fn test_mutate_plan_has_body() {
261 let qi = QualifiedIdentifier::new("public", "users");
262
263 let insert = MutatePlan::Insert {
264 target: qi.clone(),
265 columns: vec![],
266 body: Some(bytes::Bytes::from("{}".as_bytes())),
267 on_conflict: None,
268 where_clauses: vec![],
269 returning: vec![],
270 pk_cols: vec![],
271 apply_defaults: true,
272 };
273 assert!(insert.has_body());
274
275 let delete = MutatePlan::Delete {
276 target: qi,
277 where_clauses: vec![],
278 returning: vec![],
279 };
280 assert!(!delete.has_body());
281 }
282}