Skip to main content

postgrest_parser/ast/
mutation.rs

1use super::{LogicCondition, OrderTerm, ParsedParams, PreferOptions, RpcParams, SelectItem};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// Represents all supported PostgREST operations (GET, POST, PATCH, DELETE, PUT, RPC).
6///
7/// Each variant contains operation-specific parameters and optional `Prefer` header preferences.
8///
9/// # Examples
10///
11/// ```
12/// use postgrest_parser::{parse, Operation};
13///
14/// // Parse a SELECT operation
15/// let op = parse("GET", "users", "id=eq.123", None, None).unwrap();
16/// match op {
17///     Operation::Select(params, prefer) => {
18///         assert!(params.has_filters());
19///         assert!(prefer.is_none());
20///     }
21///     _ => panic!("Expected Select"),
22/// }
23///
24/// // Parse an INSERT operation
25/// let body = r#"{"name": "Alice", "email": "alice@example.com"}"#;
26/// let op = parse("POST", "users", "", Some(body), None).unwrap();
27/// match op {
28///     Operation::Insert(params, _) => {
29///         assert!(!params.values.is_empty());
30///     }
31///     _ => panic!("Expected Insert"),
32/// }
33/// ```
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub enum Operation {
36    /// SELECT query with parsed parameters and optional Prefer header
37    Select(ParsedParams, Option<PreferOptions>),
38    /// INSERT query with values, conflict resolution, and optional Prefer header
39    Insert(InsertParams, Option<PreferOptions>),
40    /// UPDATE query with SET values, filters, and optional Prefer header
41    Update(UpdateParams, Option<PreferOptions>),
42    /// DELETE query with filters and optional Prefer header
43    Delete(DeleteParams, Option<PreferOptions>),
44    /// RPC function call with arguments, filters, and optional Prefer header
45    Rpc(RpcParams, Option<PreferOptions>),
46}
47
48/// A schema-qualified table name resolved from headers or defaulting to `public`.
49///
50/// PostgREST allows specifying schemas via `Content-Profile` and `Accept-Profile` headers.
51/// This struct represents a fully qualified table reference.
52///
53/// # Examples
54///
55/// ```
56/// use postgrest_parser::ResolvedTable;
57///
58/// let table = ResolvedTable::new("auth", "users");
59/// assert_eq!(table.schema, "auth");
60/// assert_eq!(table.name, "users");
61/// assert_eq!(table.qualified_name(), r#""auth"."users""#);
62/// ```
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct ResolvedTable {
65    /// Schema name (e.g., "public", "auth", "api")
66    pub schema: String,
67    /// Table name
68    pub name: String,
69}
70
71impl ResolvedTable {
72    /// Creates a new schema-qualified table reference.
73    pub fn new(schema: impl Into<String>, name: impl Into<String>) -> Self {
74        Self {
75            schema: schema.into(),
76            name: name.into(),
77        }
78    }
79
80    /// Returns the SQL-quoted qualified name: `"schema"."table"`
81    pub fn qualified_name(&self) -> String {
82        format!("\"{}\".\"{}\"", self.schema, self.name)
83    }
84}
85
86/// Parameters for an INSERT operation.
87///
88/// Supports single and bulk inserts, conflict resolution, and returning specific columns.
89///
90/// # Examples
91///
92/// ```
93/// use postgrest_parser::{InsertParams, InsertValues, OnConflict};
94/// use std::collections::HashMap;
95/// use serde_json::json;
96///
97/// // Single insert
98/// let mut values = HashMap::new();
99/// values.insert("name".to_string(), json!("Alice"));
100/// values.insert("email".to_string(), json!("alice@example.com"));
101///
102/// let params = InsertParams::new(InsertValues::Single(values));
103/// assert_eq!(params.values.len(), 1);
104///
105/// // With conflict resolution
106/// let mut values = HashMap::new();
107/// values.insert("email".to_string(), json!("alice@example.com"));
108///
109/// let params = InsertParams::new(InsertValues::Single(values))
110///     .with_on_conflict(OnConflict::do_update(vec!["email".to_string()]));
111/// assert!(params.on_conflict.is_some());
112/// ```
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
114pub struct InsertParams {
115    /// Values to insert (single or bulk)
116    pub values: InsertValues,
117    /// Optional explicit column list (if None, derived from values)
118    pub columns: Option<Vec<String>>,
119    /// Optional conflict resolution strategy
120    pub on_conflict: Option<OnConflict>,
121    /// Optional RETURNING clause columns
122    pub returning: Option<Vec<SelectItem>>,
123}
124
125impl InsertParams {
126    /// Creates new insert parameters with the given values.
127    pub fn new(values: InsertValues) -> Self {
128        Self {
129            values,
130            columns: None,
131            on_conflict: None,
132            returning: None,
133        }
134    }
135
136    /// Specifies explicit column order for the insert.
137    pub fn with_columns(mut self, columns: Vec<String>) -> Self {
138        self.columns = Some(columns);
139        self
140    }
141
142    /// Adds conflict resolution behavior (ON CONFLICT clause).
143    pub fn with_on_conflict(mut self, on_conflict: OnConflict) -> Self {
144        self.on_conflict = Some(on_conflict);
145        self
146    }
147
148    /// Specifies columns to return after insert (RETURNING clause).
149    pub fn with_returning(mut self, returning: Vec<SelectItem>) -> Self {
150        self.returning = Some(returning);
151        self
152    }
153}
154
155/// Insert values - either a single row or multiple rows (bulk insert).
156///
157/// # Examples
158///
159/// ```
160/// use postgrest_parser::InsertValues;
161/// use std::collections::HashMap;
162/// use serde_json::json;
163///
164/// // Single row insert
165/// let mut row = HashMap::new();
166/// row.insert("name".to_string(), json!("Alice"));
167/// let single = InsertValues::Single(row);
168/// assert_eq!(single.len(), 1);
169///
170/// // Bulk insert
171/// let mut row1 = HashMap::new();
172/// row1.insert("name".to_string(), json!("Alice"));
173/// let mut row2 = HashMap::new();
174/// row2.insert("name".to_string(), json!("Bob"));
175/// let bulk = InsertValues::Bulk(vec![row1, row2]);
176/// assert_eq!(bulk.len(), 2);
177/// ```
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
179pub enum InsertValues {
180    /// Single row insert
181    Single(HashMap<String, serde_json::Value>),
182    /// Bulk insert with multiple rows
183    Bulk(Vec<HashMap<String, serde_json::Value>>),
184}
185
186impl InsertValues {
187    /// Returns the number of rows to insert.
188    pub fn len(&self) -> usize {
189        match self {
190            InsertValues::Single(_) => 1,
191            InsertValues::Bulk(rows) => rows.len(),
192        }
193    }
194
195    /// Returns true if there are no values to insert.
196    pub fn is_empty(&self) -> bool {
197        match self {
198            InsertValues::Single(map) => map.is_empty(),
199            InsertValues::Bulk(rows) => rows.is_empty(),
200        }
201    }
202
203    /// Extracts column names from the values (sorted alphabetically).
204    pub fn get_columns(&self) -> Vec<String> {
205        match self {
206            InsertValues::Single(map) => {
207                let mut cols: Vec<String> = map.keys().cloned().collect();
208                cols.sort();
209                cols
210            }
211            InsertValues::Bulk(rows) => {
212                if let Some(first) = rows.first() {
213                    let mut cols: Vec<String> = first.keys().cloned().collect();
214                    cols.sort();
215                    cols
216                } else {
217                    Vec::new()
218                }
219            }
220        }
221    }
222}
223
224/// ON CONFLICT clause for upsert operations.
225///
226/// Supports both basic conflict resolution and advanced features like partial unique indexes
227/// and selective column updates.
228///
229/// # Examples
230///
231/// ```
232/// use postgrest_parser::{OnConflict, ConflictAction};
233///
234/// // Basic upsert: ON CONFLICT (email) DO UPDATE SET ...
235/// let conflict = OnConflict::do_update(vec!["email".to_string()]);
236/// assert_eq!(conflict.action, ConflictAction::DoUpdate);
237///
238/// // Ignore conflicts: ON CONFLICT (id) DO NOTHING
239/// let conflict = OnConflict::do_nothing(vec!["id".to_string()]);
240/// assert_eq!(conflict.action, ConflictAction::DoNothing);
241///
242/// // Partial unique index: ON CONFLICT (email) WHERE deleted_at IS NULL
243/// use postgrest_parser::{parse_filter, LogicCondition};
244/// let filter = parse_filter("deleted_at", "is.null").unwrap();
245/// let conflict = OnConflict::do_update(vec!["email".to_string()])
246///     .with_where_clause(vec![LogicCondition::Filter(filter)]);
247/// assert!(conflict.where_clause.is_some());
248///
249/// // Selective update: ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name
250/// let conflict = OnConflict::do_update(vec!["id".to_string()])
251///     .with_update_columns(vec!["name".to_string()]);
252/// assert_eq!(conflict.update_columns.unwrap().len(), 1);
253/// ```
254#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
255pub struct OnConflict {
256    /// Columns that define the conflict target (unique constraint or index)
257    pub columns: Vec<String>,
258    /// Action to take on conflict: DO NOTHING or DO UPDATE
259    pub action: ConflictAction,
260    /// Optional WHERE clause for partial unique index
261    ///
262    /// Example: `ON CONFLICT (email) WHERE deleted_at IS NULL`
263    pub where_clause: Option<Vec<LogicCondition>>,
264    /// Specific columns to update on conflict (if None, all columns are updated)
265    ///
266    /// Example: `ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name`
267    pub update_columns: Option<Vec<String>>,
268}
269
270impl OnConflict {
271    /// Creates an ON CONFLICT ... DO NOTHING clause.
272    pub fn do_nothing(columns: Vec<String>) -> Self {
273        Self {
274            columns,
275            action: ConflictAction::DoNothing,
276            where_clause: None,
277            update_columns: None,
278        }
279    }
280
281    /// Creates an ON CONFLICT ... DO UPDATE clause.
282    pub fn do_update(columns: Vec<String>) -> Self {
283        Self {
284            columns,
285            action: ConflictAction::DoUpdate,
286            where_clause: None,
287            update_columns: None,
288        }
289    }
290
291    /// Adds a WHERE clause for partial unique index support.
292    pub fn with_where_clause(mut self, where_clause: Vec<LogicCondition>) -> Self {
293        self.where_clause = Some(where_clause);
294        self
295    }
296
297    /// Specifies which columns to update (instead of all columns).
298    pub fn with_update_columns(mut self, update_columns: Vec<String>) -> Self {
299        self.update_columns = Some(update_columns);
300        self
301    }
302}
303
304/// Action to take when a conflict occurs during INSERT.
305#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
306pub enum ConflictAction {
307    /// Do nothing on conflict (ignore the insert)
308    DoNothing,
309    /// Update the existing row with new values
310    DoUpdate,
311}
312
313/// Parameters for an UPDATE operation.
314///
315/// # Safety
316///
317/// Updates without filters are rejected to prevent accidental mass updates.
318/// Use LIMIT with ORDER BY for predictable results.
319///
320/// # Examples
321///
322/// ```
323/// use postgrest_parser::{UpdateParams, parse_filter, LogicCondition};
324/// use std::collections::HashMap;
325/// use serde_json::json;
326///
327/// // Update with filter
328/// let mut set_values = HashMap::new();
329/// set_values.insert("status".to_string(), json!("active"));
330///
331/// let filter = parse_filter("id", "eq.123").unwrap();
332/// let params = UpdateParams::new(set_values)
333///     .with_filters(vec![LogicCondition::Filter(filter)]);
334///
335/// assert!(params.has_filters());
336/// assert_eq!(params.set_values.len(), 1);
337/// ```
338#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
339pub struct UpdateParams {
340    /// Column-value pairs for the SET clause
341    pub set_values: HashMap<String, serde_json::Value>,
342    /// WHERE clause filters (required for safety)
343    pub filters: Vec<LogicCondition>,
344    /// Optional ORDER BY clause (required if using LIMIT)
345    pub order: Vec<OrderTerm>,
346    /// Optional LIMIT clause
347    pub limit: Option<u64>,
348    /// Optional RETURNING clause columns
349    pub returning: Option<Vec<SelectItem>>,
350}
351
352impl UpdateParams {
353    /// Creates new update parameters with the given SET values.
354    pub fn new(set_values: HashMap<String, serde_json::Value>) -> Self {
355        Self {
356            set_values,
357            filters: Vec::new(),
358            order: Vec::new(),
359            limit: None,
360            returning: None,
361        }
362    }
363
364    /// Adds WHERE clause filters.
365    pub fn with_filters(mut self, filters: Vec<LogicCondition>) -> Self {
366        self.filters = filters;
367        self
368    }
369
370    /// Adds ORDER BY clause.
371    pub fn with_order(mut self, order: Vec<OrderTerm>) -> Self {
372        self.order = order;
373        self
374    }
375
376    /// Adds LIMIT clause (requires ORDER BY for safety).
377    pub fn with_limit(mut self, limit: u64) -> Self {
378        self.limit = Some(limit);
379        self
380    }
381
382    /// Specifies columns to return after update (RETURNING clause).
383    pub fn with_returning(mut self, returning: Vec<SelectItem>) -> Self {
384        self.returning = Some(returning);
385        self
386    }
387
388    /// Returns true if filters are present.
389    pub fn has_filters(&self) -> bool {
390        !self.filters.is_empty()
391    }
392
393    /// Returns true if no columns are being updated.
394    pub fn is_set_empty(&self) -> bool {
395        self.set_values.is_empty()
396    }
397}
398
399/// Parameters for a DELETE operation.
400///
401/// # Safety
402///
403/// Deletes without filters are rejected to prevent accidental mass deletions.
404/// Use LIMIT with ORDER BY for predictable results.
405///
406/// # Examples
407///
408/// ```
409/// use postgrest_parser::{DeleteParams, parse_filter, LogicCondition};
410///
411/// // Delete with filter
412/// let filter = parse_filter("status", "eq.inactive").unwrap();
413/// let params = DeleteParams::new()
414///     .with_filters(vec![LogicCondition::Filter(filter)]);
415///
416/// assert!(params.has_filters());
417///
418/// // Delete with LIMIT and ORDER BY
419/// use postgrest_parser::parse_order;
420/// let filter = parse_filter("created_at", "lt.2020-01-01").unwrap();
421/// let order = parse_order("created_at.asc").unwrap();
422/// let params = DeleteParams::new()
423///     .with_filters(vec![LogicCondition::Filter(filter)])
424///     .with_order(order)
425///     .with_limit(100);
426///
427/// assert_eq!(params.limit, Some(100));
428/// ```
429#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
430pub struct DeleteParams {
431    /// WHERE clause filters (required for safety)
432    pub filters: Vec<LogicCondition>,
433    /// Optional ORDER BY clause (required if using LIMIT)
434    pub order: Vec<OrderTerm>,
435    /// Optional LIMIT clause
436    pub limit: Option<u64>,
437    /// Optional RETURNING clause columns (returns deleted rows)
438    pub returning: Option<Vec<SelectItem>>,
439}
440
441impl DeleteParams {
442    /// Creates new delete parameters with no filters.
443    pub fn new() -> Self {
444        Self {
445            filters: Vec::new(),
446            order: Vec::new(),
447            limit: None,
448            returning: None,
449        }
450    }
451
452    /// Adds WHERE clause filters.
453    pub fn with_filters(mut self, filters: Vec<LogicCondition>) -> Self {
454        self.filters = filters;
455        self
456    }
457
458    /// Adds ORDER BY clause.
459    pub fn with_order(mut self, order: Vec<OrderTerm>) -> Self {
460        self.order = order;
461        self
462    }
463
464    /// Adds LIMIT clause (requires ORDER BY for safety).
465    pub fn with_limit(mut self, limit: u64) -> Self {
466        self.limit = Some(limit);
467        self
468    }
469
470    /// Specifies columns to return from deleted rows (RETURNING clause).
471    pub fn with_returning(mut self, returning: Vec<SelectItem>) -> Self {
472        self.returning = Some(returning);
473        self
474    }
475
476    /// Returns true if filters are present.
477    pub fn has_filters(&self) -> bool {
478        !self.filters.is_empty()
479    }
480}
481
482impl Default for DeleteParams {
483    fn default() -> Self {
484        Self::new()
485    }
486}
487
488#[cfg(test)]
489mod tests {
490    use super::*;
491    use serde_json::json;
492
493    #[test]
494    fn test_resolved_table_new() {
495        let table = ResolvedTable::new("public", "users");
496        assert_eq!(table.schema, "public");
497        assert_eq!(table.name, "users");
498    }
499
500    #[test]
501    fn test_resolved_table_qualified_name() {
502        let table = ResolvedTable::new("auth", "users");
503        assert_eq!(table.qualified_name(), "\"auth\".\"users\"");
504    }
505
506    #[test]
507    fn test_insert_params_new() {
508        let mut map = HashMap::new();
509        map.insert("name".to_string(), json!("Alice"));
510        let params = InsertParams::new(InsertValues::Single(map));
511        assert!(params.columns.is_none());
512        assert!(params.on_conflict.is_none());
513        assert!(params.returning.is_none());
514    }
515
516    #[test]
517    fn test_insert_params_with_columns() {
518        let mut map = HashMap::new();
519        map.insert("name".to_string(), json!("Alice"));
520        let params = InsertParams::new(InsertValues::Single(map))
521            .with_columns(vec!["name".to_string(), "email".to_string()]);
522        assert_eq!(params.columns.as_ref().unwrap().len(), 2);
523    }
524
525    #[test]
526    fn test_insert_params_with_on_conflict() {
527        let mut map = HashMap::new();
528        map.insert("email".to_string(), json!("alice@example.com"));
529        let conflict = OnConflict::do_update(vec!["email".to_string()]);
530        let params = InsertParams::new(InsertValues::Single(map)).with_on_conflict(conflict);
531        assert!(params.on_conflict.is_some());
532        assert_eq!(params.on_conflict.unwrap().action, ConflictAction::DoUpdate);
533    }
534
535    #[test]
536    fn test_insert_values_single_len() {
537        let mut map = HashMap::new();
538        map.insert("name".to_string(), json!("Alice"));
539        let values = InsertValues::Single(map);
540        assert_eq!(values.len(), 1);
541        assert!(!values.is_empty());
542    }
543
544    #[test]
545    fn test_insert_values_bulk_len() {
546        let mut map1 = HashMap::new();
547        map1.insert("name".to_string(), json!("Alice"));
548        let mut map2 = HashMap::new();
549        map2.insert("name".to_string(), json!("Bob"));
550        let values = InsertValues::Bulk(vec![map1, map2]);
551        assert_eq!(values.len(), 2);
552        assert!(!values.is_empty());
553    }
554
555    #[test]
556    fn test_insert_values_get_columns() {
557        let mut map = HashMap::new();
558        map.insert("name".to_string(), json!("Alice"));
559        map.insert("age".to_string(), json!(30));
560        let values = InsertValues::Single(map);
561        let columns = values.get_columns();
562        assert_eq!(columns.len(), 2);
563        assert!(columns.contains(&"name".to_string()));
564        assert!(columns.contains(&"age".to_string()));
565    }
566
567    #[test]
568    fn test_on_conflict_do_nothing() {
569        let conflict = OnConflict::do_nothing(vec!["email".to_string()]);
570        assert_eq!(conflict.columns.len(), 1);
571        assert_eq!(conflict.action, ConflictAction::DoNothing);
572    }
573
574    #[test]
575    fn test_on_conflict_do_update() {
576        let conflict = OnConflict::do_update(vec!["email".to_string()]);
577        assert_eq!(conflict.action, ConflictAction::DoUpdate);
578    }
579
580    #[test]
581    fn test_update_params_new() {
582        let mut map = HashMap::new();
583        map.insert("status".to_string(), json!("active"));
584        let params = UpdateParams::new(map);
585        assert_eq!(params.set_values.len(), 1);
586        assert!(params.filters.is_empty());
587        assert!(params.order.is_empty());
588        assert!(params.limit.is_none());
589    }
590
591    #[test]
592    fn test_update_params_with_limit() {
593        let mut map = HashMap::new();
594        map.insert("status".to_string(), json!("active"));
595        let params = UpdateParams::new(map).with_limit(10);
596        assert_eq!(params.limit, Some(10));
597    }
598
599    #[test]
600    fn test_update_params_has_filters() {
601        let mut map = HashMap::new();
602        map.insert("status".to_string(), json!("active"));
603        let params = UpdateParams::new(map);
604        assert!(!params.has_filters());
605    }
606
607    #[test]
608    fn test_update_params_is_set_empty() {
609        let params = UpdateParams::new(HashMap::new());
610        assert!(params.is_set_empty());
611    }
612
613    #[test]
614    fn test_delete_params_new() {
615        let params = DeleteParams::new();
616        assert!(params.filters.is_empty());
617        assert!(params.order.is_empty());
618        assert!(params.limit.is_none());
619        assert!(params.returning.is_none());
620    }
621
622    #[test]
623    fn test_delete_params_with_limit() {
624        let params = DeleteParams::new().with_limit(5);
625        assert_eq!(params.limit, Some(5));
626    }
627
628    #[test]
629    fn test_delete_params_has_filters() {
630        let params = DeleteParams::new();
631        assert!(!params.has_filters());
632    }
633
634    #[test]
635    fn test_delete_params_default() {
636        let params = DeleteParams::default();
637        assert!(params.filters.is_empty());
638    }
639
640    #[test]
641    fn test_resolved_table_serialization() {
642        let table = ResolvedTable::new("public", "users");
643        let json = serde_json::to_string(&table).unwrap();
644        assert!(json.contains("public"));
645        assert!(json.contains("users"));
646    }
647
648    #[test]
649    fn test_insert_params_serialization() {
650        let mut map = HashMap::new();
651        map.insert("name".to_string(), json!("Alice"));
652        let params = InsertParams::new(InsertValues::Single(map));
653        let json = serde_json::to_string(&params).unwrap();
654        assert!(json.contains("Alice"));
655    }
656
657    #[test]
658    fn test_update_params_serialization() {
659        let mut map = HashMap::new();
660        map.insert("status".to_string(), json!("active"));
661        let params = UpdateParams::new(map);
662        let json = serde_json::to_string(&params).unwrap();
663        assert!(json.contains("active"));
664    }
665
666    #[test]
667    fn test_delete_params_serialization() {
668        let params = DeleteParams::new();
669        let json = serde_json::to_string(&params).unwrap();
670        assert!(json.contains("filters"));
671    }
672
673    #[test]
674    fn test_conflict_action_serialization() {
675        let action = ConflictAction::DoNothing;
676        let json = serde_json::to_string(&action).unwrap();
677        assert!(json.contains("DoNothing"));
678    }
679
680    #[test]
681    fn test_insert_values_empty_map() {
682        let values = InsertValues::Single(HashMap::new());
683        assert!(values.is_empty());
684        assert_eq!(values.len(), 1); // Still counts as 1 row, even if empty
685    }
686
687    #[test]
688    fn test_insert_values_empty_bulk() {
689        let values = InsertValues::Bulk(Vec::new());
690        assert!(values.is_empty());
691        assert_eq!(values.len(), 0);
692    }
693
694    #[test]
695    fn test_bulk_insert_get_columns() {
696        let mut map1 = HashMap::new();
697        map1.insert("name".to_string(), json!("Alice"));
698        map1.insert("age".to_string(), json!(30));
699        let mut map2 = HashMap::new();
700        map2.insert("name".to_string(), json!("Bob"));
701        map2.insert("age".to_string(), json!(25));
702        let values = InsertValues::Bulk(vec![map1, map2]);
703        let columns = values.get_columns();
704        assert_eq!(columns.len(), 2);
705    }
706}