postrust_sql/
delete.rs

1//! DELETE statement builder.
2
3use crate::{
4    builder::SqlFragment,
5    expr::Expr,
6    identifier::{escape_ident, from_qi, QualifiedIdentifier},
7};
8
9/// Builder for DELETE statements.
10#[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    /// Create a new DELETE builder.
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// Set the target table.
25    pub fn from_table(mut self, qi: &QualifiedIdentifier) -> Self {
26        self.table = Some(SqlFragment::raw(from_qi(qi)));
27        self
28    }
29
30    /// Set the target table with alias.
31    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    /// Add a USING clause for joins.
41    pub fn using(mut self, table: &str) -> Self {
42        self.using.push(SqlFragment::raw(escape_ident(table)));
43        self
44    }
45
46    /// Add a WHERE clause.
47    pub fn where_expr(mut self, expr: Expr) -> Self {
48        self.where_clauses.push(expr.into_fragment());
49        self
50    }
51
52    /// Add a raw WHERE clause.
53    pub fn where_raw(mut self, sql: SqlFragment) -> Self {
54        self.where_clauses.push(sql);
55        self
56    }
57
58    /// Add RETURNING clause.
59    pub fn returning(mut self, column: &str) -> Self {
60        self.returning
61            .push(SqlFragment::raw(escape_ident(column)));
62        self
63    }
64
65    /// Add RETURNING * clause.
66    pub fn returning_all(mut self) -> Self {
67        self.returning.push(SqlFragment::raw("*"));
68        self
69    }
70
71    /// Build the DELETE statement.
72    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        // USING
82        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        // WHERE
93        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        // RETURNING
104        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}