1use errors::{SqlParseError, SqlRequired, SqlUnsupported};
2use sqlparser::ast::{
3 BinaryOperator, Expr, Function, FunctionArg, FunctionArgExpr, Ident, Join, JoinConstraint, JoinOperator,
4 ObjectName, Query, SelectItem, TableAlias, TableFactor, TableWithJoins, UnaryOperator, Value,
5 WildcardAdditionalOptions,
6};
7
8use spacetimedb_lib::sats::raw_identifier::RawNamespacedIdentifier;
9
10use crate::ast::{
11 BinOp, LogOp, Parameter, Project, ProjectElem, ProjectExpr, SqlExpr, SqlFrom, SqlIdent, SqlJoin, SqlLiteral,
12};
13
14pub mod errors;
15pub mod recursion;
16pub mod sql;
17pub mod sub;
18
19pub type SqlParseResult<T> = core::result::Result<T, SqlParseError>;
20
21trait RelParser {
25 type Ast;
26
27 fn parse_query(query: Query) -> SqlParseResult<Self::Ast>;
29
30 fn parse_from(mut tables: Vec<TableWithJoins>) -> SqlParseResult<SqlFrom> {
32 if tables.is_empty() {
33 return Err(SqlRequired::From.into());
34 }
35 if tables.len() > 1 {
36 return Err(SqlUnsupported::ImplicitJoins.into());
37 }
38 let TableWithJoins { relation, joins } = tables.swap_remove(0);
39 let (name, alias) = Self::parse_relvar(relation)?;
40 if joins.is_empty() {
41 return Ok(SqlFrom::Expr(name, alias));
42 }
43 Ok(SqlFrom::Join(name, alias, Self::parse_joins(joins)?))
44 }
45
46 fn parse_joins(joins: Vec<Join>) -> SqlParseResult<Vec<SqlJoin>> {
48 joins.into_iter().map(Self::parse_join).collect()
49 }
50
51 fn parse_join(join: Join) -> SqlParseResult<SqlJoin> {
53 let (var, alias) = Self::parse_relvar(join.relation)?;
54 match join.join_operator {
55 JoinOperator::CrossJoin => Ok(SqlJoin { var, alias, on: None }),
56 JoinOperator::Inner(JoinConstraint::None) => Ok(SqlJoin { var, alias, on: None }),
57 JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp {
58 left,
59 op: BinaryOperator::Eq,
60 right,
61 })) if matches!(*left, Expr::Identifier(..) | Expr::CompoundIdentifier(..))
62 && matches!(*right, Expr::Identifier(..) | Expr::CompoundIdentifier(..)) =>
63 {
64 Ok(SqlJoin {
65 var,
66 alias,
67 on: Some(parse_expr(
68 Expr::BinaryOp {
69 left,
70 op: BinaryOperator::Eq,
71 right,
72 },
73 0,
74 )?),
75 })
76 }
77 _ => Err(SqlUnsupported::JoinType.into()),
78 }
79 }
80
81 fn parse_relvar(expr: TableFactor) -> SqlParseResult<(SqlIdent, SqlIdent)> {
83 match expr {
84 TableFactor::Table {
86 name,
87 alias: None,
88 args: None,
89 with_hints,
90 version: None,
91 partitions,
92 } if with_hints.is_empty() && partitions.is_empty() => {
93 let name = parse_ident(name)?;
94 let alias = name.clone();
95 Ok((name, alias))
96 }
97 TableFactor::Table {
99 name,
100 alias: Some(TableAlias { name: alias, columns }),
101 args: None,
102 with_hints,
103 version: None,
104 partitions,
105 } if with_hints.is_empty() && partitions.is_empty() && columns.is_empty() => {
106 Ok((parse_ident(name)?, alias.into()))
107 }
108 _ => Err(SqlUnsupported::From(expr).into()),
109 }
110 }
111}
112
113pub(crate) fn parse_projection(mut items: Vec<SelectItem>) -> SqlParseResult<Project> {
115 if items.len() == 1 {
116 return parse_project_or_agg(items.swap_remove(0));
117 }
118 Ok(Project::Exprs(
119 items
120 .into_iter()
121 .map(parse_project_elem)
122 .collect::<SqlParseResult<_>>()?,
123 ))
124}
125
126pub(crate) fn parse_project_or_agg(item: SelectItem) -> SqlParseResult<Project> {
128 match item {
129 SelectItem::Wildcard(WildcardAdditionalOptions {
130 opt_exclude: None,
131 opt_except: None,
132 opt_rename: None,
133 opt_replace: None,
134 }) => Ok(Project::Star(None)),
135 SelectItem::QualifiedWildcard(
136 table_name,
137 WildcardAdditionalOptions {
138 opt_exclude: None,
139 opt_except: None,
140 opt_rename: None,
141 opt_replace: None,
142 },
143 ) => Ok(Project::Star(Some(parse_ident(table_name)?))),
144 SelectItem::UnnamedExpr(Expr::Function(_)) => Err(SqlUnsupported::AggregateWithoutAlias.into()),
145 SelectItem::ExprWithAlias {
146 expr: Expr::Function(agg_fn),
147 alias,
148 } => parse_agg_fn(agg_fn, alias.into()),
149 SelectItem::UnnamedExpr(_) | SelectItem::ExprWithAlias { .. } => {
150 Ok(Project::Exprs(vec![parse_project_elem(item)?]))
151 }
152 item => Err(SqlUnsupported::Projection(item).into()),
153 }
154}
155
156fn parse_agg_fn(agg_fn: Function, alias: SqlIdent) -> SqlParseResult<Project> {
158 fn is_count(name: &ObjectName) -> bool {
159 name.0.len() == 1
160 && name
161 .0
162 .first()
163 .is_some_and(|Ident { value, .. }| value.to_lowercase() == "count")
164 }
165 match agg_fn {
166 Function {
167 name,
168 args,
169 over: None,
170 distinct: false,
171 special: false,
172 order_by,
173 } if is_count(&name)
174 && order_by.is_empty()
175 && args.len() == 1
176 && args
177 .first()
178 .is_some_and(|arg| matches!(arg, FunctionArg::Unnamed(FunctionArgExpr::Wildcard))) =>
179 {
180 Ok(Project::Count(alias))
181 }
182 agg_fn => Err(SqlUnsupported::Aggregate(agg_fn).into()),
183 }
184}
185
186pub(crate) fn parse_project_elem(item: SelectItem) -> SqlParseResult<ProjectElem> {
188 match item {
189 SelectItem::Wildcard(_) => Err(SqlUnsupported::MixedWildcardProject.into()),
190 SelectItem::QualifiedWildcard(..) => Err(SqlUnsupported::MixedWildcardProject.into()),
191 SelectItem::UnnamedExpr(expr) => match parse_proj(expr)? {
192 ProjectExpr::Var(name) => Ok(ProjectElem(ProjectExpr::Var(name.clone()), name)),
193 ProjectExpr::Field(name, field) => Ok(ProjectElem(ProjectExpr::Field(name, field.clone()), field)),
194 },
195 SelectItem::ExprWithAlias { expr, alias } => Ok(ProjectElem(parse_proj(expr)?, alias.into())),
196 }
197}
198
199pub(crate) fn parse_proj(expr: Expr) -> SqlParseResult<ProjectExpr> {
201 match expr {
202 Expr::Identifier(ident) => Ok(ProjectExpr::Var(ident.into())),
203 Expr::CompoundIdentifier(idents) if idents.len() >= 2 => {
204 let (table, field) = parse_qualified_field(idents)?;
205 Ok(ProjectExpr::Field(table, field))
206 }
207 _ => Err(SqlUnsupported::ProjectionExpr(expr).into()),
208 }
209}
210
211pub(crate) fn parse_qualified_field(mut idents: Vec<Ident>) -> SqlParseResult<(SqlIdent, SqlIdent)> {
215 let field = idents.pop().expect("caller checked idents.len() >= 2").into();
216 let table = parse_parts(idents)?;
217 Ok((table, field))
218}
219
220#[cfg(target_pointer_width = "64")]
224const _: () = assert!(size_of::<Expr>() == 168);
225#[cfg(target_pointer_width = "64")]
226const _: () = assert!(size_of::<SqlParseResult<SqlExpr>>() == 40);
227
228fn parse_expr(expr: Expr, depth: usize) -> SqlParseResult<SqlExpr> {
230 recursion::guard(depth, recursion::MAX_RECURSION_EXPR, "sql-parser::parse_expr")?;
231 match expr {
232 Expr::Nested(expr) => parse_expr(*expr, depth + 1),
233 Expr::Value(Value::Placeholder(param)) if ¶m == ":sender" => Ok(SqlExpr::Param(Parameter::Sender)),
234 Expr::Value(v) => Ok(SqlExpr::Lit(parse_literal(v)?)),
235 Expr::UnaryOp {
236 op: UnaryOperator::Plus,
237 expr,
238 } => Ok(SqlExpr::Lit(parse_signed_literal_expr(
239 UnaryOperator::Plus,
240 *expr,
241 SqlUnsupported::Expr,
242 )?)),
243 Expr::UnaryOp {
244 op: UnaryOperator::Minus,
245 expr,
246 } => Ok(SqlExpr::Lit(parse_signed_literal_expr(
247 UnaryOperator::Minus,
248 *expr,
249 SqlUnsupported::Expr,
250 )?)),
251 Expr::Identifier(ident) => Ok(SqlExpr::Var(ident.into())),
252 Expr::CompoundIdentifier(idents) if idents.len() >= 2 => {
253 let (table, field) = parse_qualified_field(idents)?;
254 Ok(SqlExpr::Field(table, field))
255 }
256 Expr::BinaryOp {
257 left,
258 op: BinaryOperator::And,
259 right,
260 } => {
261 let l = parse_expr(*left, depth + 1)?;
262 let r = parse_expr(*right, depth + 1)?;
263 Ok(SqlExpr::Log(Box::new(l), Box::new(r), LogOp::And))
264 }
265 Expr::BinaryOp {
266 left,
267 op: BinaryOperator::Or,
268 right,
269 } => {
270 let l = parse_expr(*left, depth + 1)?;
271 let r = parse_expr(*right, depth + 1)?;
272 Ok(SqlExpr::Log(Box::new(l), Box::new(r), LogOp::Or))
273 }
274 Expr::BinaryOp { left, op, right } => {
275 let l = parse_expr(*left, depth + 1)?;
276 let r = parse_expr(*right, depth + 1)?;
277 Ok(SqlExpr::Bin(Box::new(l), Box::new(r), parse_binop(op)?))
278 }
279 _ => Err(SqlUnsupported::Expr(expr).into()),
280 }
281}
282
283fn parse_signed_literal_expr(
284 op: UnaryOperator,
285 expr: Expr,
286 unsupported: fn(Expr) -> SqlUnsupported,
287) -> SqlParseResult<SqlLiteral> {
288 match expr {
289 Expr::Value(Value::Number(n, _)) => {
290 let sign = match op {
291 UnaryOperator::Plus => "+",
292 UnaryOperator::Minus => "-",
293 _ => unreachable!("caller only passes unary plus/minus"),
294 };
295 Ok(SqlLiteral::Num(format!("{sign}{n}").into_boxed_str()))
296 }
297 expr => Err(unsupported(Expr::UnaryOp {
298 op,
299 expr: Box::new(expr),
300 })
301 .into()),
302 }
303}
304
305pub(crate) fn parse_literal_expr(expr: Expr, unsupported: fn(Expr) -> SqlUnsupported) -> SqlParseResult<SqlLiteral> {
307 match expr {
308 Expr::Value(value) => parse_literal(value),
309 Expr::UnaryOp {
310 op: UnaryOperator::Plus,
311 expr,
312 } => parse_signed_literal_expr(UnaryOperator::Plus, *expr, unsupported),
313 Expr::UnaryOp {
314 op: UnaryOperator::Minus,
315 expr,
316 } => parse_signed_literal_expr(UnaryOperator::Minus, *expr, unsupported),
317 expr => Err(unsupported(expr).into()),
318 }
319}
320
321pub(crate) fn parse_expr_opt(opt: Option<Expr>) -> SqlParseResult<Option<SqlExpr>> {
323 opt.map(|expr| parse_expr(expr, 0)).transpose()
324}
325
326pub(crate) fn parse_binop(op: BinaryOperator) -> SqlParseResult<BinOp> {
328 match op {
329 BinaryOperator::Eq => Ok(BinOp::Eq),
330 BinaryOperator::NotEq => Ok(BinOp::Ne),
331 BinaryOperator::Lt => Ok(BinOp::Lt),
332 BinaryOperator::LtEq => Ok(BinOp::Lte),
333 BinaryOperator::Gt => Ok(BinOp::Gt),
334 BinaryOperator::GtEq => Ok(BinOp::Gte),
335 _ => Err(SqlUnsupported::BinOp(op).into()),
336 }
337}
338
339pub(crate) fn parse_literal(value: Value) -> SqlParseResult<SqlLiteral> {
341 match value {
342 Value::Boolean(v) => Ok(SqlLiteral::Bool(v)),
343 Value::Number(v, _) => Ok(SqlLiteral::Num(v.into_boxed_str())),
344 Value::SingleQuotedString(s) => Ok(SqlLiteral::Str(s.into_boxed_str())),
345 Value::HexStringLiteral(s) => Ok(SqlLiteral::Hex(s.into_boxed_str())),
346 _ => Err(SqlUnsupported::Literal(value).into()),
347 }
348}
349
350pub(crate) fn parse_ident(ObjectName(parts): ObjectName) -> SqlParseResult<SqlIdent> {
352 parse_parts(parts)
353}
354
355pub(crate) fn parse_parts(mut parts: Vec<Ident>) -> SqlParseResult<SqlIdent> {
363 if parts.len() == 1 {
364 return Ok(parts.swap_remove(0).into());
365 }
366 let joined = parts.iter().map(|p| p.value.as_str()).collect::<Vec<_>>().join(".");
367 Ok(SqlIdent(RawNamespacedIdentifier::new(joined)))
368}