1use std::fmt::Write;
11
12use crate::error::FlozError;
13use crate::expr::Expr;
14use crate::value::Value;
15use super::quote_table;
16
17#[derive(Debug, Clone)]
22pub struct DeleteQuery {
23 table: String,
24 where_clause: Option<Expr>,
25 all: bool,
26 returning: ReturningClause,
27}
28
29#[derive(Debug, Clone)]
30enum ReturningClause {
31 None,
32 All,
33 Cols(Vec<String>),
34}
35
36impl DeleteQuery {
37 pub fn new(table: &str) -> Self {
39 Self {
40 table: table.to_string(),
41 where_clause: None,
42 all: false,
43 returning: ReturningClause::None,
44 }
45 }
46
47 pub fn where_(mut self, expr: Expr) -> Self {
49 self.where_clause = Some(expr);
50 self
51 }
52
53 pub fn all(mut self) -> Self {
56 self.all = true;
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() && !self.all {
79 return Err(FlozError::UnguardedDelete);
80 }
81
82 let mut sql = String::with_capacity(64);
83 let mut params = Vec::new();
84 let mut idx = 0usize;
85
86 write!(sql, "DELETE FROM {}", quote_table(&self.table)).unwrap();
88
89 if let Some(ref expr) = self.where_clause {
91 sql.push_str(" WHERE ");
92 expr.to_sql(&mut sql, &mut params, &mut idx);
93 }
94
95 match &self.returning {
97 ReturningClause::None => {}
98 ReturningClause::All => sql.push_str(" RETURNING *"),
99 ReturningClause::Cols(cols) => {
100 sql.push_str(" RETURNING ");
101 for (i, col) in cols.iter().enumerate() {
102 if i > 0 { sql.push_str(", "); }
103 sql.push_str(col);
104 }
105 }
106 }
107
108 Ok((sql, params))
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use crate::column::Column;
116
117 fn col_id() -> Column<i32> { Column::new("id", "users") }
118 fn col_age() -> Column<i32> { Column::new("age", "users") }
119
120 #[test]
121 fn delete_with_where() {
122 let (sql, params) = DeleteQuery::new("users")
123 .where_(col_id().eq(1))
124 .to_sql()
125 .unwrap();
126 assert_eq!(sql, "DELETE FROM users WHERE id = $1");
127 assert_eq!(params, vec![Value::Int(1)]);
128 }
129
130 #[test]
131 fn delete_complex_where() {
132 let (sql, params) = DeleteQuery::new("users")
133 .where_(col_age().lt(18).or(col_age().gt(100)))
134 .to_sql()
135 .unwrap();
136 assert_eq!(sql, "DELETE FROM users WHERE (age < $1 OR age > $2)");
137 assert_eq!(params.len(), 2);
138 }
139
140 #[test]
141 fn delete_all() {
142 let (sql, params) = DeleteQuery::new("users")
143 .all()
144 .to_sql()
145 .unwrap();
146 assert_eq!(sql, "DELETE FROM users");
147 assert!(params.is_empty());
148 }
149
150 #[test]
151 fn delete_unguarded_error() {
152 let result = DeleteQuery::new("users").to_sql();
153 assert!(matches!(result, Err(FlozError::UnguardedDelete)));
154 }
155
156 #[test]
157 fn delete_returning() {
158 let (sql, _) = DeleteQuery::new("users")
159 .where_(col_id().eq(1))
160 .returning_all()
161 .to_sql()
162 .unwrap();
163 assert!(sql.ends_with("RETURNING *"));
164 }
165
166 #[test]
167 fn delete_returning_cols() {
168 let (sql, _) = DeleteQuery::new("users")
169 .where_(col_id().eq(1))
170 .returning_cols(&["id", "name"])
171 .to_sql()
172 .unwrap();
173 assert!(sql.ends_with("RETURNING id, name"));
174 }
175
176 #[test]
177 fn delete_schema_qualified() {
178 let (sql, _) = DeleteQuery::new("auth.users")
179 .all()
180 .to_sql()
181 .unwrap();
182 assert_eq!(sql, r#"DELETE FROM "auth"."users""#);
183 }
184}