Skip to main content

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.push(SqlFragment::raw(escape_ident(column)));
61        self
62    }
63
64    /// Add RETURNING * clause.
65    pub fn returning_all(mut self) -> Self {
66        self.returning.push(SqlFragment::raw("*"));
67        self
68    }
69
70    /// Build the DELETE statement.
71    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        // USING
81        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        // WHERE
92        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        // RETURNING
103        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}