1use crate::{
4 builder::SqlFragment,
5 expr::Expr,
6 identifier::{escape_ident, from_qi, QualifiedIdentifier},
7};
8
9#[derive(Clone, Debug, Default)]
11pub struct DeleteBuilder {
12 table: Option<SqlFragment>,
13 using: Vec<SqlFragment>,
14 where_clauses: Vec<SqlFragment>,
15 returning: Vec<SqlFragment>,
16}
17
18impl DeleteBuilder {
19 pub fn new() -> Self {
21 Self::default()
22 }
23
24 pub fn from_table(mut self, qi: &QualifiedIdentifier) -> Self {
26 self.table = Some(SqlFragment::raw(from_qi(qi)));
27 self
28 }
29
30 pub fn from_table_as(mut self, qi: &QualifiedIdentifier, alias: &str) -> Self {
32 self.table = Some(SqlFragment::raw(format!(
33 "{} AS {}",
34 from_qi(qi),
35 escape_ident(alias)
36 )));
37 self
38 }
39
40 pub fn using(mut self, table: &str) -> Self {
42 self.using.push(SqlFragment::raw(escape_ident(table)));
43 self
44 }
45
46 pub fn where_expr(mut self, expr: Expr) -> Self {
48 self.where_clauses.push(expr.into_fragment());
49 self
50 }
51
52 pub fn where_raw(mut self, sql: SqlFragment) -> Self {
54 self.where_clauses.push(sql);
55 self
56 }
57
58 pub fn returning(mut self, column: &str) -> Self {
60 self.returning
61 .push(SqlFragment::raw(escape_ident(column)));
62 self
63 }
64
65 pub fn returning_all(mut self) -> Self {
67 self.returning.push(SqlFragment::raw("*"));
68 self
69 }
70
71 pub fn build(self) -> SqlFragment {
73 let mut result = SqlFragment::new();
74
75 result.push("DELETE FROM ");
76
77 if let Some(table) = self.table {
78 result.append(table);
79 }
80
81 if !self.using.is_empty() {
83 result.push(" USING ");
84 for (i, table) in self.using.into_iter().enumerate() {
85 if i > 0 {
86 result.push(", ");
87 }
88 result.append(table);
89 }
90 }
91
92 if !self.where_clauses.is_empty() {
94 result.push(" WHERE ");
95 for (i, clause) in self.where_clauses.into_iter().enumerate() {
96 if i > 0 {
97 result.push(" AND ");
98 }
99 result.append(clause);
100 }
101 }
102
103 if !self.returning.is_empty() {
105 result.push(" RETURNING ");
106 for (i, ret) in self.returning.into_iter().enumerate() {
107 if i > 0 {
108 result.push(", ");
109 }
110 result.append(ret);
111 }
112 }
113
114 result
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_simple_delete() {
124 let qi = QualifiedIdentifier::new("public", "users");
125 let sql = DeleteBuilder::new()
126 .from_table(&qi)
127 .where_expr(Expr::eq("id", 1i64))
128 .build();
129
130 assert!(sql.sql().contains("DELETE FROM"));
131 assert!(sql.sql().contains("WHERE"));
132 assert_eq!(sql.params().len(), 1);
133 }
134
135 #[test]
136 fn test_delete_all() {
137 let qi = QualifiedIdentifier::unqualified("logs");
138 let sql = DeleteBuilder::new().from_table(&qi).build();
139
140 assert_eq!(sql.sql(), "DELETE FROM \"logs\"");
141 assert!(sql.params().is_empty());
142 }
143
144 #[test]
145 fn test_delete_returning() {
146 let qi = QualifiedIdentifier::unqualified("users");
147 let sql = DeleteBuilder::new()
148 .from_table(&qi)
149 .where_expr(Expr::is_not_null("deleted_at"))
150 .returning("id")
151 .returning("email")
152 .build();
153
154 assert!(sql.sql().contains("RETURNING"));
155 }
156
157 #[test]
158 fn test_delete_with_using() {
159 let qi = QualifiedIdentifier::unqualified("orders");
160 let sql = DeleteBuilder::new()
161 .from_table(&qi)
162 .using("users")
163 .where_raw(SqlFragment::raw(
164 "\"orders\".\"user_id\" = \"users\".\"id\" AND \"users\".\"deleted\" = true",
165 ))
166 .build();
167
168 assert!(sql.sql().contains("USING"));
169 }
170}