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 #[serde(default)]
35 reports_inserted: bool,
36 },
37 Update {
39 target: QualifiedIdentifier,
41 columns: Vec<CoercibleField>,
43 body: Option<bytes::Bytes>,
45 where_clauses: Vec<CoercibleLogicTree>,
47 returning: Vec<String>,
49 apply_defaults: bool,
51 },
52 Delete {
54 target: QualifiedIdentifier,
56 where_clauses: Vec<CoercibleLogicTree>,
58 returning: Vec<String>,
60 },
61}
62
63impl MutatePlan {
64 pub fn from_request(
66 request: &ApiRequest,
67 table: &Table,
68 mutation: &Mutation,
69 schema_cache: &crate::schema_cache::SchemaCache,
70 ) -> Result<Self> {
71 let qi = table.qualified_identifier();
72
73 match mutation {
74 Mutation::Create => Self::create_insert(request, table, qi, schema_cache),
75 Mutation::Update => Self::create_update(request, table, qi, schema_cache),
76 Mutation::Delete => Self::create_delete(request, table, qi),
77 Mutation::SingleUpsert => Self::create_upsert(request, table, qi, schema_cache),
78 }
79 }
80
81 fn create_insert(
83 request: &ApiRequest,
84 table: &Table,
85 qi: QualifiedIdentifier,
86 schema_cache: &crate::schema_cache::SchemaCache,
87 ) -> Result<Self> {
88 let columns = get_payload_columns(request, table, schema_cache)?;
89 let body = get_body_bytes(request)?;
90 let returning = get_returning_columns(request, table, true);
91 let apply_defaults = asked_for_defaults(request);
92
93 let on_conflict = request
98 .preferences
99 .resolution
100 .clone()
101 .and_then(|resolution| {
102 let columns = request
103 .query_params
104 .on_conflict
105 .clone()
106 .unwrap_or_else(|| table.pk_cols.clone());
107 match columns.is_empty() {
108 true => None,
109 false => Some((resolution, columns)),
110 }
111 });
112
113 Ok(Self::Insert {
114 target: qi,
115 columns,
116 body,
117 on_conflict,
118 where_clauses: vec![],
119 returning,
120 pk_cols: table.pk_cols.clone(),
121 apply_defaults,
122 reports_inserted: !table.is_view && !table.is_partitioned,
123 })
124 }
125
126 fn create_update(
128 request: &ApiRequest,
129 table: &Table,
130 qi: QualifiedIdentifier,
131 schema_cache: &crate::schema_cache::SchemaCache,
132 ) -> Result<Self> {
133 let columns = get_payload_columns(request, table, schema_cache)?;
134 let body = get_body_bytes(request)?;
135 let where_clauses = build_mutation_where(request, table)?;
136 let returning = get_returning_columns(request, table, false);
137 let apply_defaults = asked_for_defaults(request);
138
139 Ok(Self::Update {
140 target: qi,
141 columns,
142 body,
143 where_clauses,
144 returning,
145 apply_defaults,
146 })
147 }
148
149 fn create_delete(request: &ApiRequest, table: &Table, qi: QualifiedIdentifier) -> Result<Self> {
151 let where_clauses = build_mutation_where(request, table)?;
152 let returning = get_returning_columns(request, table, false);
153
154 Ok(Self::Delete {
155 target: qi,
156 where_clauses,
157 returning,
158 })
159 }
160
161 fn create_upsert(
163 request: &ApiRequest,
164 table: &Table,
165 qi: QualifiedIdentifier,
166 schema_cache: &crate::schema_cache::SchemaCache,
167 ) -> Result<Self> {
168 validate_put(request, table)?;
169
170 let columns = get_payload_columns(request, table, schema_cache)?;
171 let body = get_body_bytes(request)?;
172 let returning = get_returning_columns(request, table, true);
173
174 let on_conflict = Some((PreferResolution::MergeDuplicates, table.pk_cols.clone()));
176
177 let where_clauses = build_mutation_where(request, table)?;
182
183 Ok(Self::Insert {
184 target: qi,
185 columns,
186 body,
187 on_conflict,
188 where_clauses,
189 returning,
190 pk_cols: table.pk_cols.clone(),
191 apply_defaults: asked_for_defaults(request),
192 reports_inserted: !table.is_view && !table.is_partitioned,
193 })
194 }
195
196 pub fn target(&self) -> &QualifiedIdentifier {
198 match self {
199 Self::Insert { target, .. } => target,
200 Self::Update { target, .. } => target,
201 Self::Delete { target, .. } => target,
202 }
203 }
204
205 pub fn has_body(&self) -> bool {
207 match self {
208 Self::Insert { body, .. } => body.is_some(),
209 Self::Update { body, .. } => body.is_some(),
210 Self::Delete { .. } => false,
211 }
212 }
213}
214
215fn get_payload_columns(
217 request: &ApiRequest,
218 table: &Table,
219 schema_cache: &crate::schema_cache::SchemaCache,
220) -> Result<Vec<CoercibleField>> {
221 let keys = match &request.payload {
222 Some(Payload::ProcessedJson { keys, .. }) => keys,
223 Some(Payload::ProcessedUrlEncoded { keys, .. }) => keys,
224 _ => return Ok(vec![]),
225 };
226
227 let names: Vec<&String> = match &request.query_params.columns {
231 Some(columns) => columns.iter().collect(),
232 None => keys.iter().collect(),
233 };
234
235 let mut columns = Vec::new();
236
237 for key in names {
238 let column = table.get_column(key).ok_or_else(|| Error::UnknownColumn {
239 column: key.clone(),
240 relation: table.name.clone(),
241 })?;
242
243 let mut field = CoercibleField::simple(key, &column.nominal_type);
244 field.default = column.default.clone();
245 if let Some(function) = schema_cache.representation("json", column.representation_type()) {
250 field.ir_type = "json".to_string();
251 field.transform = Some(function.to_string());
252 }
253 columns.push(field);
254 }
255
256 Ok(columns)
257}
258
259fn asked_for_defaults(request: &ApiRequest) -> bool {
265 request
266 .preferences
267 .applied
268 .iter()
269 .any(|pref| pref == "missing=default")
270}
271
272fn validate_put(request: &ApiRequest, table: &Table) -> Result<()> {
279 use crate::api_request::{Operation, QuantOperator};
280
281 if request
282 .query_params
283 .ranges
284 .values()
285 .any(|range| range.limit.is_some() || range.offset != 0)
286 {
287 return Err(Error::PutLimitNotAllowed);
288 }
289
290 let mut keyed: std::collections::HashMap<&str, &str> = std::collections::HashMap::new();
291 for filter in &request.query_params.filters_root {
292 let Operation::Quant {
293 op: QuantOperator::Equal,
294 quantifier: None,
295 value,
296 } = &filter.op_expr.operation
297 else {
298 return Err(Error::InvalidPutFilters);
299 };
300 if filter.op_expr.negated {
301 return Err(Error::InvalidPutFilters);
302 }
303 keyed.insert(&filter.field.name, value);
304 }
305
306 if !request.query_params.logic.is_empty() || !request.query_params.filters.is_empty() {
309 return Err(Error::InvalidPutFilters);
310 }
311
312 if table.pk_cols.is_empty()
313 || keyed.len() != table.pk_cols.len()
314 || !table
315 .pk_cols
316 .iter()
317 .all(|key| keyed.contains_key(key.as_str()))
318 {
319 return Err(Error::InvalidPutFilters);
320 }
321
322 Ok(())
327}
328
329fn get_body_bytes(request: &ApiRequest) -> Result<Option<bytes::Bytes>> {
331 match &request.payload {
332 Some(Payload::ProcessedJson { raw, .. }) => Ok(Some(raw.clone())),
333 Some(Payload::RawJson(raw)) => Ok(Some(raw.clone())),
334 Some(Payload::RawPayload(raw)) => Ok(Some(raw.clone())),
335 Some(Payload::ProcessedUrlEncoded { data, .. }) => {
336 let json = serde_json::to_vec(
338 &data
339 .iter()
340 .cloned()
341 .collect::<std::collections::HashMap<_, _>>(),
342 )
343 .map_err(|e| Error::InvalidBody(e.to_string()))?;
344 Ok(Some(bytes::Bytes::from(json)))
345 }
346 None => Ok(None),
347 }
348}
349
350fn get_returning_columns(request: &ApiRequest, table: &Table, creates: bool) -> Vec<String> {
365 use crate::api_request::SelectItem;
366
367 let everything = || -> Vec<String> { table.column_names().map(str::to_string).collect() };
368
369 let mut wanted: Vec<String> = Vec::new();
370
371 if request.preferences.representation.needs_body() {
373 if request.query_params.select.is_empty() {
374 return everything();
375 }
376 for item in &request.query_params.select {
377 match item {
378 SelectItem::Field { field, .. } => {
379 if field.name == "*"
380 || table
381 .get_computed_column(&field.name)
382 .is_some_and(|c| !c.takes_arguments)
383 {
384 return everything();
385 }
386 wanted.push(field.name.clone());
387 }
388 SelectItem::Relation { .. } | SelectItem::SpreadRelation { .. } => {}
391 }
392 }
393 for (path, terms) in &request.query_params.order {
398 if !path.is_empty() {
399 continue;
400 }
401 for term in terms {
402 if let crate::api_request::OrderTerm::Field { field, .. } = term {
403 wanted.push(field.name.clone());
404 }
405 }
406 }
407 }
408
409 if creates {
412 wanted.extend(table.pk_cols.iter().cloned());
413 }
414
415 wanted.retain(|name| table.get_column(name).is_some());
419 wanted.sort();
420 wanted.dedup();
421 wanted
422}
423
424fn build_mutation_where(request: &ApiRequest, table: &Table) -> Result<Vec<CoercibleLogicTree>> {
426 let type_resolver = |name: &str| -> String {
427 table
428 .get_column(name)
429 .map(|c| c.data_type.clone())
430 .unwrap_or_else(|| "text".to_string())
431 };
432
433 let mut clauses = Vec::new();
434
435 for filter in &request.query_params.filters_root {
436 let pg_type = type_resolver(&filter.field.name);
437 clauses.push(CoercibleLogicTree::Stmt(CoercibleFilter::from_filter(
438 filter, &pg_type,
439 )));
440 }
441
442 for (path, tree) in &request.query_params.logic {
447 if path.is_empty() {
448 clauses.push(CoercibleLogicTree::from_logic_tree(tree, type_resolver));
449 }
450 }
451
452 Ok(clauses)
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 #[test]
460 fn test_mutate_plan_target() {
461 let qi = QualifiedIdentifier::new("public", "users");
462 let plan = MutatePlan::Delete {
463 target: qi.clone(),
464 where_clauses: vec![],
465 returning: vec!["id".into()],
466 };
467
468 assert_eq!(plan.target().name, "users");
469 }
470
471 #[test]
472 fn test_mutate_plan_has_body() {
473 let qi = QualifiedIdentifier::new("public", "users");
474
475 let insert = MutatePlan::Insert {
476 target: qi.clone(),
477 columns: vec![],
478 body: Some(bytes::Bytes::from("{}".as_bytes())),
479 on_conflict: None,
480 where_clauses: vec![],
481 returning: vec![],
482 pk_cols: vec![],
483 apply_defaults: true,
484 reports_inserted: false,
485 };
486 assert!(insert.has_body());
487
488 let delete = MutatePlan::Delete {
489 target: qi,
490 where_clauses: vec![],
491 returning: vec![],
492 };
493 assert!(!delete.has_body());
494 }
495}