qail_core/transformer/patterns/
delete.rs1use sqlparser::ast::Statement;
4
5use crate::transformer::traits::*;
6use crate::transformer::clauses::*;
7
8pub 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}