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/// Reserved capacities for the `Vec`s maintained by an [`UpdateBuilder`].
34#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
35pub struct UpdateCapacity {
36    /// Expected number of column assignments.
37    pub assignments: usize,
38    /// Expected number of `RETURNING` columns.
39    pub returning: usize,
40}
41
42/// Builder for UPDATE queries.
43#[derive(Debug, Clone)]
44pub struct UpdateBuilder {
45    table: String,
46    assignments: Vec<(ColumnMarker, Value)>,
47    filter: Option<Expr>,
48    returning: Vec<ColumnMarker>,
49}
50
51impl UpdateBuilder {
52    /// Reserve capacity for the builder's internal vectors.
53    #[must_use]
54    pub fn with_capacity(mut self, capacity: UpdateCapacity) -> Self {
55        self.assignments.reserve(capacity.assignments);
56        self.returning.reserve(capacity.returning);
57        self
58    }
59
60    /// Adds a column-value assignment to the SET clause.
61    #[must_use]
62    pub fn set(mut self, column: ColumnMarker, value: Value) -> Self {
63        self.assignments.push((column, value));
64        self
65    }
66
67    /// Sets all column-value assignments in one call.
68    #[must_use]
69    pub fn assignments(mut self, assignments: Vec<(ColumnMarker, Value)>) -> Self {
70        self.assignments = assignments;
71        self
72    }
73
74    /// Adds a WHERE clause filter.
75    #[must_use]
76    pub fn filter(mut self, expr: Expr) -> Self {
77        self.filter = Some(expr);
78        self
79    }
80
81    /// Sets the RETURNING clause columns.
82    #[must_use]
83    pub fn returning(mut self, columns: Vec<ColumnMarker>) -> Self {
84        self.returning = columns;
85        self
86    }
87
88    /// Builds the final UPDATE query.
89    ///
90    /// # Errors
91    ///
92    /// Returns an error if:
93    /// - The table name is empty
94    /// - No assignments are specified
95    pub fn build(self) -> Result<Update> {
96        if self.table.is_empty() {
97            return Err(Error::MissingField("table".to_string()));
98        }
99
100        if self.assignments.is_empty() {
101            return Err(Error::MissingField("assignments".to_string()));
102        }
103
104        Ok(Update {
105            table: self.table,
106            assignments: self.assignments,
107            filter: self.filter,
108            returning: self.returning,
109        })
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_simple_update() {
119        let update = Update::table("users")
120            .set(
121                ColumnMarker::new("users", "email"),
122                Value::String("new@example.com".to_string()),
123            )
124            .build()
125            .unwrap();
126
127        assert_eq!(update.table, "users");
128        assert_eq!(update.assignments.len(), 1);
129        assert_eq!(update.assignments[0].0.name, "email");
130        assert!(update.filter.is_none());
131        assert!(update.returning.is_empty());
132    }
133
134    #[test]
135    fn test_multi_set_update() {
136        let update = Update::table("users")
137            .set(
138                ColumnMarker::new("users", "email"),
139                Value::String("new@example.com".to_string()),
140            )
141            .set(
142                ColumnMarker::new("users", "name"),
143                Value::String("Alice".to_string()),
144            )
145            .build()
146            .unwrap();
147
148        assert_eq!(update.assignments.len(), 2);
149        assert_eq!(update.assignments[0].0.name, "email");
150        assert_eq!(update.assignments[1].0.name, "name");
151    }
152
153    #[test]
154    fn test_update_with_filter() {
155        let update = Update::table("users")
156            .set(
157                ColumnMarker::new("users", "email"),
158                Value::String("new@example.com".to_string()),
159            )
160            .filter(Expr::column("id").eq(Expr::param(Value::I64(1))))
161            .build()
162            .unwrap();
163
164        assert!(update.filter.is_some());
165    }
166
167    #[test]
168    fn test_update_with_returning() {
169        let update = Update::table("users")
170            .set(
171                ColumnMarker::new("users", "email"),
172                Value::String("new@example.com".to_string()),
173            )
174            .returning(vec![
175                ColumnMarker::new("users", "id"),
176                ColumnMarker::new("users", "email"),
177            ])
178            .build()
179            .unwrap();
180
181        assert_eq!(update.returning.len(), 2);
182        assert_eq!(update.returning[0].name, "id");
183        assert_eq!(update.returning[1].name, "email");
184    }
185
186    #[test]
187    fn test_update_with_null() {
188        let update = Update::table("users")
189            .set(ColumnMarker::new("users", "name"), Value::Null)
190            .build()
191            .unwrap();
192
193        assert_eq!(update.assignments[0].1, Value::Null);
194    }
195
196    #[test]
197    fn test_missing_table() {
198        let result = Update::table("")
199            .set(
200                ColumnMarker::new("users", "email"),
201                Value::String("test".to_string()),
202            )
203            .build();
204
205        assert!(result.is_err());
206    }
207
208    #[test]
209    fn test_missing_assignments() {
210        let result = Update::table("users").build();
211
212        assert!(result.is_err());
213    }
214}