qail_core/transformer/patterns/
select.rs1use sqlparser::ast::{SetExpr, Statement};
4
5use crate::transformer::traits::*;
6use crate::transformer::clauses::*;
7
8pub struct SelectPattern;
10
11impl SqlPattern for SelectPattern {
12 fn id(&self) -> &'static str {
13 "select"
14 }
15
16 fn priority(&self) -> u32 {
17 100
18 }
19
20 fn matches(&self, stmt: &Statement, _ctx: &MatchContext) -> bool {
21 matches!(stmt, Statement::Query(q) if matches!(q.body.as_ref(), SetExpr::Select(_)))
22 }
23
24 fn extract(&self, stmt: &Statement, _ctx: &MatchContext) -> Result<PatternData, ExtractError> {
25 let Statement::Query(query) = stmt else {
26 return Err(ExtractError {
27 message: "Expected Query statement".to_string(),
28 });
29 };
30
31 let SetExpr::Select(select) = query.body.as_ref() else {
32 return Err(ExtractError {
33 message: "Expected SELECT".to_string(),
34 });
35 };
36
37 let table = extract_table(select)?;
38 let columns = extract_columns(select);
39 let filter = select
40 .selection
41 .as_ref()
42 .map(extract_filter)
43 .transpose()?;
44
45 let order_by = query.order_by.as_ref().and_then(|ob| {
46 match &ob.kind {
47 sqlparser::ast::OrderByKind::All(_) => None,
48 sqlparser::ast::OrderByKind::Expressions(exprs) => {
49 if exprs.is_empty() {
50 None
51 } else {
52 Some(extract_order_by(exprs))
53 }
54 }
55 }
56 });
57
58 let limit = query.limit_clause.as_ref().and_then(|lc| {
59 match lc {
60 sqlparser::ast::LimitClause::LimitOffset { limit, .. } => {
61 limit.as_ref().and_then(extract_limit)
62 }
63 _ => None,
64 }
65 });
66
67 Ok(PatternData::Select {
68 table,
69 columns,
70 filter,
71 order_by,
72 limit,
73 joins: Vec::new(),
74 })
75 }
76
77 fn transform(&self, data: &PatternData, ctx: &TransformContext) -> Result<String, TransformError> {
78 let PatternData::Select {
79 table,
80 columns,
81 filter,
82 order_by,
83 limit,
84 ..
85 } = data
86 else {
87 return Err(TransformError {
88 message: "Expected Select data".to_string(),
89 });
90 };
91
92 let mut lines = Vec::new();
93
94 if ctx.include_imports {
95 lines.push("use qail_core::ast::{Qail, Operator, Order};".to_string());
96 lines.push(String::new());
97 }
98
99 let mut chain = format!("let cmd = Qail::get(\"{}\")", table);
100
101 if !columns.is_empty() && columns[0] != "*" {
102 let cols: Vec<String> = columns.iter().map(|c| format!("\"{}\"", c)).collect();
103 chain.push_str(&format!("\n .columns([{}])", cols.join(", ")));
104 }
105
106 if let Some(f) = filter {
107 let value = match &f.value {
108 ValueData::Param(n) => {
109 ctx.binds
110 .get(*n - 1)
111 .cloned()
112 .unwrap_or_else(|| format!("param_{}", n))
113 }
114 ValueData::Literal(s) => s.clone(),
115 ValueData::Column(c) => format!("\"{}\"", c),
116 ValueData::Null => "None".to_string(),
117 };
118 chain.push_str(&format!(
119 "\n .filter(\"{}\", {}, {})",
120 f.column, f.operator, value
121 ));
122 }
123
124 if let Some(orders) = order_by {
125 for o in orders {
126 let dir = if o.descending { "Order::Desc" } else { "Order::Asc" };
127 chain.push_str(&format!("\n .order_by(\"{}\", {})", o.column, dir));
128 }
129 }
130
131 if let Some(l) = limit {
132 chain.push_str(&format!("\n .limit({})", l));
133 }
134
135 chain.push(';');
136 lines.push(chain);
137
138 lines.push(String::new());
139 let default_row_type = format!("{}Row", to_pascal_case(table));
140 let row_type = ctx.return_type.as_deref().unwrap_or(&default_row_type);
141 lines.push(format!(
142 "let rows: Vec<{}> = driver.query_as(&cmd).await?;",
143 row_type
144 ));
145
146 Ok(lines.join("\n"))
147 }
148}
149
150fn to_pascal_case(s: &str) -> String {
151 s.split('_')
152 .map(|part| {
153 let mut chars = part.chars();
154 match chars.next() {
155 Some(c) => c.to_uppercase().chain(chars).collect::<String>(),
156 None => String::new(),
157 }
158 })
159 .collect()
160}