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.push(SqlFragment::raw(escape_ident(column)));
61 self
62 }
63
64 pub fn returning_all(mut self) -> Self {
66 self.returning.push(SqlFragment::raw("*"));
67 self
68 }
69
70 pub fn build(self) -> SqlFragment {
72 let mut result = SqlFragment::new();
73
74 result.push("DELETE FROM ");
75
76 if let Some(table) = self.table {
77 result.append(table);
78 }
79
80 if !self.using.is_empty() {
82 result.push(" USING ");
83 for (i, table) in self.using.into_iter().enumerate() {
84 if i > 0 {
85 result.push(", ");
86 }
87 result.append(table);
88 }
89 }
90
91 if !self.where_clauses.is_empty() {
93 result.push(" WHERE ");
94 for (i, clause) in self.where_clauses.into_iter().enumerate() {
95 if i > 0 {
96 result.push(" AND ");
97 }
98 result.append(clause);
99 }
100 }
101
102 if !self.returning.is_empty() {
104 result.push(" RETURNING ");
105 for (i, ret) in self.returning.into_iter().enumerate() {
106 if i > 0 {
107 result.push(", ");
108 }
109 result.append(ret);
110 }
111 }
112
113 result
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_simple_delete() {
123 let qi = QualifiedIdentifier::new("public", "users");
124 let sql = DeleteBuilder::new()
125 .from_table(&qi)
126 .where_expr(Expr::eq("id", 1i64))
127 .build();
128
129 assert!(sql.sql().contains("DELETE FROM"));
130 assert!(sql.sql().contains("WHERE"));
131 assert_eq!(sql.params().len(), 1);
132 }
133
134 #[test]
135 fn test_delete_all() {
136 let qi = QualifiedIdentifier::unqualified("logs");
137 let sql = DeleteBuilder::new().from_table(&qi).build();
138
139 assert_eq!(sql.sql(), "DELETE FROM \"logs\"");
140 assert!(sql.params().is_empty());
141 }
142
143 #[test]
144 fn test_delete_returning() {
145 let qi = QualifiedIdentifier::unqualified("users");
146 let sql = DeleteBuilder::new()
147 .from_table(&qi)
148 .where_expr(Expr::is_not_null("deleted_at"))
149 .returning("id")
150 .returning("email")
151 .build();
152
153 assert!(sql.sql().contains("RETURNING"));
154 }
155
156 #[test]
157 fn test_delete_with_using() {
158 let qi = QualifiedIdentifier::unqualified("orders");
159 let sql = DeleteBuilder::new()
160 .from_table(&qi)
161 .using("users")
162 .where_raw(SqlFragment::raw(
163 "\"orders\".\"user_id\" = \"users\".\"id\" AND \"users\".\"deleted\" = true",
164 ))
165 .build();
166
167 assert!(sql.sql().contains("USING"));
168 }
169}