spacetimedb_sql_parser/parser/
sub.rsuse sqlparser::{
ast::{GroupByExpr, Query, Select, SetExpr, SetOperator, SetQuantifier, Statement},
dialect::PostgreSqlDialect,
parser::Parser,
};
use crate::ast::sub::{SqlAst, SqlSelect};
use super::{
errors::{SqlUnsupported, SubscriptionUnsupported},
parse_expr_opt, parse_projection, RelParser, SqlParseResult,
};
pub fn parse_subscription(sql: &str) -> SqlParseResult<SqlAst> {
let mut stmts = Parser::parse_sql(&PostgreSqlDialect {}, sql)?;
if stmts.len() > 1 {
return Err(SqlUnsupported::MultiStatement.into());
}
parse_statement(stmts.swap_remove(0))
}
fn parse_statement(stmt: Statement) -> SqlParseResult<SqlAst> {
match stmt {
Statement::Query(query) => SubParser::parse_query(*query),
_ => Err(SubscriptionUnsupported::Dml.into()),
}
}
struct SubParser;
impl RelParser for SubParser {
type Ast = SqlAst;
fn parse_query(query: Query) -> SqlParseResult<Self::Ast> {
match query {
Query {
with: None,
body,
order_by,
limit: None,
offset: None,
fetch: None,
locks,
} if order_by.is_empty() && locks.is_empty() => parse_set_op(*body),
_ => Err(SubscriptionUnsupported::feature(query).into()),
}
}
}
fn parse_set_op(expr: SetExpr) -> SqlParseResult<SqlAst> {
match expr {
SetExpr::Query(query) => SubParser::parse_query(*query),
SetExpr::Select(select) => Ok(SqlAst::Select(parse_select(*select)?)),
SetExpr::SetOperation {
op: SetOperator::Union,
set_quantifier: SetQuantifier::All,
left,
right,
} => Ok(SqlAst::Union(
Box::new(parse_set_op(*left)?),
Box::new(parse_set_op(*right)?),
)),
SetExpr::SetOperation {
op: SetOperator::Except,
set_quantifier: SetQuantifier::All,
left,
right,
} => Ok(SqlAst::Minus(
Box::new(parse_set_op(*left)?),
Box::new(parse_set_op(*right)?),
)),
_ => Err(SqlUnsupported::SetOp(expr).into()),
}
}
fn parse_select(select: Select) -> SqlParseResult<SqlSelect> {
match select {
Select {
distinct: None,
top: None,
projection,
into: None,
from,
lateral_views,
selection,
group_by: GroupByExpr::Expressions(exprs),
cluster_by,
distribute_by,
sort_by,
having: None,
named_window,
qualify: None,
} if lateral_views.is_empty()
&& exprs.is_empty()
&& cluster_by.is_empty()
&& distribute_by.is_empty()
&& sort_by.is_empty()
&& named_window.is_empty() =>
{
Ok(SqlSelect {
from: SubParser::parse_from(from)?,
filter: parse_expr_opt(selection)?,
project: parse_projection(projection)?,
})
}
_ => Err(SubscriptionUnsupported::Select(select).into()),
}
}
#[cfg(test)]
mod tests {
use crate::parser::sub::parse_subscription;
#[test]
fn unsupported() {
for sql in [
"delete from t",
"select distinct a from t",
"select * from (select * from t) join (select * from s) on a = b",
] {
assert!(parse_subscription(sql).is_err());
}
}
#[test]
fn supported() {
for sql in [
"select * from t",
"select * from t where a = 1",
"select * from t where a <> 1",
"select * from t where a = 1 or a = 2",
"select * from t where a = 1 union all select * from t where a = 2",
"select * from (select * from t)",
"select * from (select t.* from t join s)",
"select * from (select t.* from t join s on t.c = s.d)",
"select * from (select a.* from t as a join s as b on a.c = b.d)",
"select * from (select t.* from (select * from t) t join (select * from s) s on s.id = t.id)",
] {
assert!(parse_subscription(sql).is_ok());
}
}
}