qail_core/transformer/patterns/
delete.rs

1//! DELETE pattern implementation
2
3use sqlparser::ast::Statement;
4
5use crate::transformer::traits::*;
6use crate::transformer::clauses::*;
7
8/// DELETE query pattern
9pub struct DeletePattern;
10
11impl SqlPattern for DeletePattern {
12    fn id(&self) -> &'static str {
13        "delete"
14    }
15
16    fn priority(&self) -> u32 {
17        100
18    }
19
20    fn matches(&self, stmt: &Statement, _ctx: &MatchContext) -> bool {
21        matches!(stmt, Statement::Delete(_))
22    }
23
24    fn extract(&self, stmt: &Statement, _ctx: &MatchContext) -> Result<PatternData, ExtractError> {
25        let Statement::Delete(delete) = stmt else {
26            return Err(ExtractError {
27                message: "Expected DELETE statement".to_string(),
28            });
29        };
30
31        let table = match &delete.from {
32            sqlparser::ast::FromTable::WithFromKeyword(tables) => {
33                tables.first()
34                    .map(|t| extract_table_from_factor(&t.relation))
35                    .transpose()?
36                    .unwrap_or_else(|| "table".to_string())
37            }
38            sqlparser::ast::FromTable::WithoutKeyword(tables) => {
39                tables.first()
40                    .map(|t| extract_table_from_factor(&t.relation))
41                    .transpose()?
42                    .unwrap_or_else(|| "table".to_string())
43            }
44        };
45
46        let filter = delete
47            .selection
48            .as_ref()
49            .map(extract_filter)
50            .transpose()?;
51
52        let returning = delete.returning.as_ref().map(|items| {
53            items
54                .iter()
55                .map(|item| match item {
56                    sqlparser::ast::SelectItem::UnnamedExpr(e) => e.to_string(),
57                    sqlparser::ast::SelectItem::Wildcard(_) => "*".to_string(),
58                    _ => item.to_string(),
59                })
60                .collect()
61        });
62
63        Ok(PatternData::Delete {
64            table,
65            filter,
66            returning,
67        })
68    }
69
70    fn transform(&self, data: &PatternData, ctx: &TransformContext) -> Result<String, TransformError> {
71        let PatternData::Delete {
72            table,
73            filter,
74            returning,
75        } = data
76        else {
77            return Err(TransformError {
78                message: "Expected Delete data".to_string(),
79            });
80        };
81
82        let mut lines = Vec::new();
83
84        if ctx.include_imports {
85            lines.push("use qail_core::ast::{Qail, Operator};".to_string());
86            lines.push(String::new());
87        }
88
89        let mut chain = format!("let cmd = Qail::del(\"{}\")", table);
90
91        if let Some(f) = filter {
92            let value = match &f.value {
93                ValueData::Param(n) => {
94                    ctx.binds
95                        .get(*n - 1)
96                        .cloned()
97                        .unwrap_or_else(|| format!("param_{}", n))
98                }
99                ValueData::Literal(s) => s.clone(),
100                ValueData::Column(c) => format!("\"{}\"", c),
101                ValueData::Null => "None".to_string(),
102            };
103            chain.push_str(&format!(
104                "\n    .filter(\"{}\", {}, {})",
105                f.column, f.operator, value
106            ));
107        }
108
109        if let Some(ret) = returning {
110            if ret.contains(&"*".to_string()) {
111                chain.push_str("\n    .returning([\"*\"])");
112            } else {
113                let cols: Vec<String> = ret.iter().map(|c| format!("\"{}\"", c)).collect();
114                chain.push_str(&format!("\n    .returning([{}])", cols.join(", ")));
115            }
116        }
117
118        chain.push(';');
119        lines.push(chain);
120
121        lines.push(String::new());
122        if returning.is_some() {
123            let default_row_type = format!("{}Row", to_pascal_case(table));
124            let row_type = ctx.return_type.as_deref().unwrap_or(&default_row_type);
125            lines.push(format!(
126                "let row: {} = driver.query_one(&cmd).await?;",
127                row_type
128            ));
129        } else {
130            lines.push("driver.execute(&cmd).await?;".to_string());
131        }
132
133        Ok(lines.join("\n"))
134    }
135}
136
137fn to_pascal_case(s: &str) -> String {
138    s.split('_')
139        .map(|part| {
140            let mut chars = part.chars();
141            match chars.next() {
142                Some(c) => c.to_uppercase().chain(chars).collect::<String>(),
143                None => String::new(),
144            }
145        })
146        .collect()
147}