1use std::fmt::Write;
12
13use crate::error::FlozError;
14use crate::expr::Expr;
15use crate::value::Value;
16use super::quote_table;
17
18#[derive(Debug, Clone)]
23pub struct UpdateQuery {
24 table: String,
25 sets: Vec<(String, Value)>,
26 where_clause: Option<Expr>,
27 returning: ReturningClause,
28}
29
30#[derive(Debug, Clone)]
31enum ReturningClause {
32 None,
33 All,
34 Cols(Vec<String>),
35}
36
37impl UpdateQuery {
38 pub fn new(table: &str) -> Self {
40 Self {
41 table: table.to_string(),
42 sets: Vec::new(),
43 where_clause: None,
44 returning: ReturningClause::None,
45 }
46 }
47
48 pub fn set(mut self, column: &str, value: impl Into<Value>) -> Self {
50 self.sets.push((column.to_string(), value.into()));
51 self
52 }
53
54 pub fn where_(mut self, expr: Expr) -> Self {
56 self.where_clause = Some(expr);
57 self
58 }
59
60 pub fn returning_all(mut self) -> Self {
62 self.returning = ReturningClause::All;
63 self
64 }
65
66 pub fn returning_cols(mut self, columns: &[&str]) -> Self {
68 self.returning = ReturningClause::Cols(
69 columns.iter().map(|c| c.to_string()).collect(),
70 );
71 self
72 }
73
74 pub fn to_sql(&self) -> Result<(String, Vec<Value>), FlozError> {
78 if self.where_clause.is_none() {
79 return Err(FlozError::UnguardedUpdate);
80 }
81
82 let mut sql = String::with_capacity(128);
83 let mut params = Vec::new();
84 let mut idx = 0usize;
85
86 write!(sql, "UPDATE {} SET ", quote_table(&self.table)).unwrap();
88
89 for (i, (col, val)) in self.sets.iter().enumerate() {
91 if i > 0 { sql.push_str(", "); }
92 idx += 1;
93 write!(sql, "{} = ${}", col, idx).unwrap();
94 params.push(val.clone());
95 }
96
97 if let Some(ref expr) = self.where_clause {
99 sql.push_str(" WHERE ");
100 expr.to_sql(&mut sql, &mut params, &mut idx);
101 }
102
103 match &self.returning {
105 ReturningClause::None => {}
106 ReturningClause::All => sql.push_str(" RETURNING *"),
107 ReturningClause::Cols(cols) => {
108 sql.push_str(" RETURNING ");
109 for (i, col) in cols.iter().enumerate() {
110 if i > 0 { sql.push_str(", "); }
111 sql.push_str(col);
112 }
113 }
114 }
115
116 Ok((sql, params))
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::column::Column;
124
125 fn col_id() -> Column<i32> { Column::new("id", "users") }
126 fn col_age() -> Column<i32> { Column::new("age", "users") }
127
128 #[test]
129 fn update_basic() {
130 let (sql, params) = UpdateQuery::new("users")
131 .set("age", 31i16)
132 .where_(col_id().eq(1))
133 .to_sql()
134 .unwrap();
135 assert_eq!(sql, "UPDATE users SET age = $1 WHERE id = $2");
136 assert_eq!(params, vec![Value::Short(31), Value::Int(1)]);
137 }
138
139 #[test]
140 fn update_multiple_sets() {
141 let (sql, params) = UpdateQuery::new("users")
142 .set("name", "Updated")
143 .set("age", 31i16)
144 .where_(col_id().eq(1))
145 .to_sql()
146 .unwrap();
147 assert_eq!(sql, "UPDATE users SET name = $1, age = $2 WHERE id = $3");
148 assert_eq!(params.len(), 3);
149 }
150
151 #[test]
152 fn update_returning() {
153 let (sql, _) = UpdateQuery::new("users")
154 .set("age", 31i16)
155 .where_(col_id().eq(1))
156 .returning_all()
157 .to_sql()
158 .unwrap();
159 assert!(sql.ends_with("RETURNING *"));
160 }
161
162 #[test]
163 fn update_returning_cols() {
164 let (sql, _) = UpdateQuery::new("users")
165 .set("age", 31i16)
166 .where_(col_id().eq(1))
167 .returning_cols(&["id", "age"])
168 .to_sql()
169 .unwrap();
170 assert!(sql.ends_with("RETURNING id, age"));
171 }
172
173 #[test]
174 fn update_unguarded_error() {
175 let result = UpdateQuery::new("users")
176 .set("age", 31i16)
177 .to_sql();
178 assert!(matches!(result, Err(FlozError::UnguardedUpdate)));
179 }
180
181 #[test]
182 fn update_complex_where() {
183 let (sql, params) = UpdateQuery::new("users")
184 .set("is_active", false)
185 .where_(col_age().lt(18).or(col_age().gt(65)))
186 .to_sql()
187 .unwrap();
188 assert_eq!(
189 sql,
190 "UPDATE users SET is_active = $1 WHERE (age < $2 OR age > $3)"
191 );
192 assert_eq!(params.len(), 3);
193 }
194
195 #[test]
196 fn update_schema_qualified() {
197 let (sql, _) = UpdateQuery::new("auth.users")
198 .set("age", 31i16)
199 .where_(col_id().eq(1))
200 .to_sql()
201 .unwrap();
202 assert!(sql.starts_with("UPDATE \"auth\".\"users\""));
203 }
204}