qail_core/transformer/patterns/
select.rs

1//! SELECT pattern implementation
2
3use sqlparser::ast::{SetExpr, Statement};
4
5use crate::transformer::traits::*;
6use crate::transformer::clauses::*;
7
8/// SELECT query pattern
9pub 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}