Skip to main content

nautilus_core/
update.rs

1//! UPDATE query AST and builder.
2
3use crate::column::ColumnMarker;
4use crate::error::{Error, Result};
5use crate::expr::Expr;
6use crate::value::Value;
7
8/// UPDATE query AST node.
9#[derive(Debug, Clone, PartialEq)]
10pub struct Update {
11    /// Table name.
12    pub table: String,
13    /// Column-value assignments (SET clause).
14    pub assignments: Vec<(ColumnMarker, Value)>,
15    /// WHERE clause.
16    pub filter: Option<Expr>,
17    /// Columns to return (RETURNING clause). Empty = no RETURNING.
18    pub returning: Vec<ColumnMarker>,
19}
20
21impl Update {
22    /// Creates a new UPDATE query builder for the given table.
23    pub fn table(table: impl Into<String>) -> UpdateBuilder {
24        UpdateBuilder {
25            table: table.into(),
26            assignments: Vec::new(),
27            filter: None,
28            returning: Vec::new(),
29        }
30    }
31}
32
33/// Builder for UPDATE queries.
34#[derive(Debug, Clone)]
35pub struct UpdateBuilder {
36    table: String,
37    assignments: Vec<(ColumnMarker, Value)>,
38    filter: Option<Expr>,
39    returning: Vec<ColumnMarker>,
40}
41
42impl UpdateBuilder {
43    /// Adds a column-value assignment to the SET clause.
44    #[must_use]
45    pub fn set(mut self, column: ColumnMarker, value: Value) -> Self {
46        self.assignments.push((column, value));
47        self
48    }
49
50    /// Adds a WHERE clause filter.
51    #[must_use]
52    pub fn filter(mut self, expr: Expr) -> Self {
53        self.filter = Some(expr);
54        self
55    }
56
57    /// Sets the RETURNING clause columns.
58    #[must_use]
59    pub fn returning(mut self, columns: Vec<ColumnMarker>) -> Self {
60        self.returning = columns;
61        self
62    }
63
64    /// Builds the final UPDATE query.
65    ///
66    /// # Errors
67    ///
68    /// Returns an error if:
69    /// - The table name is empty
70    /// - No assignments are specified
71    pub fn build(self) -> Result<Update> {
72        if self.table.is_empty() {
73            return Err(Error::MissingField("table".to_string()));
74        }
75
76        if self.assignments.is_empty() {
77            return Err(Error::MissingField("assignments".to_string()));
78        }
79
80        Ok(Update {
81            table: self.table,
82            assignments: self.assignments,
83            filter: self.filter,
84            returning: self.returning,
85        })
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_simple_update() {
95        let update = Update::table("users")
96            .set(
97                ColumnMarker::new("users", "email"),
98                Value::String("new@example.com".to_string()),
99            )
100            .build()
101            .unwrap();
102
103        assert_eq!(update.table, "users");
104        assert_eq!(update.assignments.len(), 1);
105        assert_eq!(update.assignments[0].0.name, "email");
106        assert!(update.filter.is_none());
107        assert!(update.returning.is_empty());
108    }
109
110    #[test]
111    fn test_multi_set_update() {
112        let update = Update::table("users")
113            .set(
114                ColumnMarker::new("users", "email"),
115                Value::String("new@example.com".to_string()),
116            )
117            .set(
118                ColumnMarker::new("users", "name"),
119                Value::String("Alice".to_string()),
120            )
121            .build()
122            .unwrap();
123
124        assert_eq!(update.assignments.len(), 2);
125        assert_eq!(update.assignments[0].0.name, "email");
126        assert_eq!(update.assignments[1].0.name, "name");
127    }
128
129    #[test]
130    fn test_update_with_filter() {
131        let update = Update::table("users")
132            .set(
133                ColumnMarker::new("users", "email"),
134                Value::String("new@example.com".to_string()),
135            )
136            .filter(Expr::column("id").eq(Expr::param(Value::I64(1))))
137            .build()
138            .unwrap();
139
140        assert!(update.filter.is_some());
141    }
142
143    #[test]
144    fn test_update_with_returning() {
145        let update = Update::table("users")
146            .set(
147                ColumnMarker::new("users", "email"),
148                Value::String("new@example.com".to_string()),
149            )
150            .returning(vec![
151                ColumnMarker::new("users", "id"),
152                ColumnMarker::new("users", "email"),
153            ])
154            .build()
155            .unwrap();
156
157        assert_eq!(update.returning.len(), 2);
158        assert_eq!(update.returning[0].name, "id");
159        assert_eq!(update.returning[1].name, "email");
160    }
161
162    #[test]
163    fn test_update_with_null() {
164        let update = Update::table("users")
165            .set(ColumnMarker::new("users", "name"), Value::Null)
166            .build()
167            .unwrap();
168
169        assert_eq!(update.assignments[0].1, Value::Null);
170    }
171
172    #[test]
173    fn test_missing_table() {
174        let result = Update::table("")
175            .set(
176                ColumnMarker::new("users", "email"),
177                Value::String("test".to_string()),
178            )
179            .build();
180
181        assert!(result.is_err());
182    }
183
184    #[test]
185    fn test_missing_assignments() {
186        let result = Update::table("users").build();
187
188        assert!(result.is_err());
189    }
190}