Skip to main content

sqlrite/sql/parser/
select.rs

1use sqlparser::ast::{
2    Expr, LimitClause, OrderByKind, Query, Select, SelectItem, SetExpr, Statement, TableFactor,
3    TableWithJoins,
4};
5
6use crate::error::{Result, SQLRiteError};
7
8/// What columns to project from a SELECT.
9#[derive(Debug, Clone, PartialEq)]
10pub enum Projection {
11    /// `SELECT *` — every column in the table, in declaration order.
12    All,
13    /// `SELECT a, b, c` — explicit list.
14    Columns(Vec<String>),
15}
16
17/// An ORDER BY clause restricted to a single column, ascending by default.
18#[derive(Debug, Clone)]
19pub struct OrderByClause {
20    pub column: String,
21    pub ascending: bool,
22}
23
24/// A parsed, simplified SELECT query.
25#[derive(Debug, Clone)]
26pub struct SelectQuery {
27    pub table_name: String,
28    pub projection: Projection,
29    /// Raw sqlparser WHERE expression, evaluated by the executor at run time.
30    pub selection: Option<Expr>,
31    pub order_by: Option<OrderByClause>,
32    pub limit: Option<usize>,
33}
34
35impl SelectQuery {
36    pub fn new(statement: &Statement) -> Result<Self> {
37        let Statement::Query(query) = statement else {
38            return Err(SQLRiteError::Internal(
39                "Error parsing SELECT: expected a Query statement".to_string(),
40            ));
41        };
42
43        let Query {
44            body,
45            order_by,
46            limit_clause,
47            ..
48        } = query.as_ref();
49
50        let SetExpr::Select(select) = body.as_ref() else {
51            return Err(SQLRiteError::NotImplemented(
52                "Only simple SELECT queries are supported (no UNION / VALUES / CTEs yet)"
53                    .to_string(),
54            ));
55        };
56        let Select {
57            projection,
58            from,
59            selection,
60            distinct,
61            group_by,
62            having,
63            ..
64        } = select.as_ref();
65
66        if distinct.is_some() {
67            return Err(SQLRiteError::NotImplemented(
68                "SELECT DISTINCT is not supported yet".to_string(),
69            ));
70        }
71        if having.is_some() {
72            return Err(SQLRiteError::NotImplemented(
73                "HAVING is not supported yet".to_string(),
74            ));
75        }
76        // GroupByExpr::Expressions(v, _) with an empty v is the "no GROUP BY" shape.
77        if let sqlparser::ast::GroupByExpr::Expressions(exprs, _) = group_by {
78            if !exprs.is_empty() {
79                return Err(SQLRiteError::NotImplemented(
80                    "GROUP BY is not supported yet".to_string(),
81                ));
82            }
83        } else {
84            return Err(SQLRiteError::NotImplemented(
85                "GROUP BY ALL is not supported".to_string(),
86            ));
87        }
88
89        let table_name = extract_single_table_name(from)?;
90        let projection = parse_projection(projection)?;
91        let order_by = parse_order_by(order_by.as_ref())?;
92        let limit = parse_limit(limit_clause.as_ref())?;
93
94        Ok(SelectQuery {
95            table_name,
96            projection,
97            selection: selection.clone(),
98            order_by,
99            limit,
100        })
101    }
102}
103
104fn extract_single_table_name(from: &[TableWithJoins]) -> Result<String> {
105    if from.len() != 1 {
106        return Err(SQLRiteError::NotImplemented(
107            "SELECT from multiple tables (joins / comma-joins) is not supported yet".to_string(),
108        ));
109    }
110    let twj = &from[0];
111    if !twj.joins.is_empty() {
112        return Err(SQLRiteError::NotImplemented(
113            "JOIN is not supported yet".to_string(),
114        ));
115    }
116    match &twj.relation {
117        TableFactor::Table { name, .. } => Ok(name.to_string()),
118        _ => Err(SQLRiteError::NotImplemented(
119            "Only SELECT from a plain table is supported".to_string(),
120        )),
121    }
122}
123
124fn parse_projection(items: &[SelectItem]) -> Result<Projection> {
125    // Special-case `SELECT *`.
126    if items.len() == 1 {
127        if let SelectItem::Wildcard(_) = &items[0] {
128            return Ok(Projection::All);
129        }
130    }
131    let mut cols = Vec::with_capacity(items.len());
132    for item in items {
133        match item {
134            SelectItem::UnnamedExpr(Expr::Identifier(ident)) => cols.push(ident.value.clone()),
135            SelectItem::UnnamedExpr(Expr::CompoundIdentifier(parts)) => {
136                if let Some(last) = parts.last() {
137                    cols.push(last.value.clone());
138                } else {
139                    return Err(SQLRiteError::Internal(
140                        "empty qualified column reference".to_string(),
141                    ));
142                }
143            }
144            SelectItem::Wildcard(_) | SelectItem::QualifiedWildcard(_, _) => {
145                return Err(SQLRiteError::NotImplemented(
146                    "Wildcard mixed with other columns is not supported".to_string(),
147                ));
148            }
149            SelectItem::ExprWithAlias { .. } | SelectItem::UnnamedExpr(_) => {
150                return Err(SQLRiteError::NotImplemented(
151                    "Only bare column references are supported in the projection list".to_string(),
152                ));
153            }
154        }
155    }
156    Ok(Projection::Columns(cols))
157}
158
159fn parse_order_by(order_by: Option<&sqlparser::ast::OrderBy>) -> Result<Option<OrderByClause>> {
160    let Some(ob) = order_by else {
161        return Ok(None);
162    };
163    let exprs = match &ob.kind {
164        OrderByKind::Expressions(v) => v,
165        OrderByKind::All(_) => {
166            return Err(SQLRiteError::NotImplemented(
167                "ORDER BY ALL is not supported".to_string(),
168            ));
169        }
170    };
171    if exprs.len() != 1 {
172        return Err(SQLRiteError::NotImplemented(
173            "ORDER BY must have exactly one column for now".to_string(),
174        ));
175    }
176    let obe = &exprs[0];
177    let column = match &obe.expr {
178        Expr::Identifier(ident) => ident.value.clone(),
179        Expr::CompoundIdentifier(parts) => {
180            parts.last().map(|i| i.value.clone()).unwrap_or_default()
181        }
182        _ => {
183            return Err(SQLRiteError::NotImplemented(
184                "ORDER BY only supports a bare column name for now".to_string(),
185            ));
186        }
187    };
188    // `asc == None` is the dialect default (ASC).
189    let ascending = obe.options.asc.unwrap_or(true);
190    Ok(Some(OrderByClause { column, ascending }))
191}
192
193fn parse_limit(limit: Option<&LimitClause>) -> Result<Option<usize>> {
194    let Some(lc) = limit else {
195        return Ok(None);
196    };
197    let limit_expr = match lc {
198        LimitClause::LimitOffset { limit, offset, .. } => {
199            if offset.is_some() {
200                return Err(SQLRiteError::NotImplemented(
201                    "OFFSET is not supported yet".to_string(),
202                ));
203            }
204            limit.as_ref()
205        }
206        LimitClause::OffsetCommaLimit { .. } => {
207            return Err(SQLRiteError::NotImplemented(
208                "`LIMIT <offset>, <limit>` syntax is not supported yet".to_string(),
209            ));
210        }
211    };
212    let Some(expr) = limit_expr else {
213        return Ok(None);
214    };
215    let n = eval_const_usize(expr)?;
216    Ok(Some(n))
217}
218
219fn eval_const_usize(expr: &Expr) -> Result<usize> {
220    match expr {
221        Expr::Value(v) => match &v.value {
222            sqlparser::ast::Value::Number(n, _) => n.parse::<usize>().map_err(|e| {
223                SQLRiteError::Internal(format!("LIMIT must be a non-negative integer: {e}"))
224            }),
225            _ => Err(SQLRiteError::Internal(
226                "LIMIT must be an integer literal".to_string(),
227            )),
228        },
229        _ => Err(SQLRiteError::NotImplemented(
230            "LIMIT expression must be a literal number".to_string(),
231        )),
232    }
233}