spacetimedb_sql_parser/parser/
sub.rs1use sqlparser::{
57 ast::{GroupByExpr, Query, Select, SetExpr, Statement},
58 dialect::PostgreSqlDialect,
59 parser::Parser,
60};
61
62use crate::ast::sub::SqlSelect;
63
64use super::{
65 errors::{SqlUnsupported, SubscriptionUnsupported},
66 parse_expr_opt, parse_projection, RelParser, SqlParseResult,
67};
68
69pub fn parse_subscription(sql: &str) -> SqlParseResult<SqlSelect> {
71 let mut stmts = Parser::parse_sql(&PostgreSqlDialect {}, sql)?;
72 match stmts.len() {
73 0 => Err(SqlUnsupported::Empty.into()),
74 1 => parse_statement(stmts.swap_remove(0))
75 .map(|ast| ast.qualify_vars())
76 .and_then(|ast| ast.find_unqualified_vars()),
77 _ => Err(SqlUnsupported::MultiStatement.into()),
78 }
79}
80
81fn parse_statement(stmt: Statement) -> SqlParseResult<SqlSelect> {
83 match stmt {
84 Statement::Query(query) => SubParser::parse_query(*query),
85 _ => Err(SubscriptionUnsupported::Dml.into()),
86 }
87}
88
89struct SubParser;
90
91impl RelParser for SubParser {
92 type Ast = SqlSelect;
93
94 fn parse_query(query: Query) -> SqlParseResult<Self::Ast> {
95 match query {
96 Query {
97 with: None,
98 body,
99 order_by,
100 limit: None,
101 offset: None,
102 fetch: None,
103 locks,
104 } if order_by.is_empty() && locks.is_empty() => parse_set_op(*body),
105 _ => Err(SubscriptionUnsupported::feature(query).into()),
106 }
107 }
108}
109
110fn parse_set_op(expr: SetExpr) -> SqlParseResult<SqlSelect> {
112 match expr {
113 SetExpr::Select(select) => parse_select(*select).map(SqlSelect::qualify_vars),
114 _ => Err(SqlUnsupported::SetOp(Box::new(expr)).into()),
115 }
116}
117
118fn parse_select(select: Select) -> SqlParseResult<SqlSelect> {
120 match select {
121 Select {
122 distinct: None,
123 top: None,
124 projection,
125 into: None,
126 from,
127 lateral_views,
128 selection,
129 group_by: GroupByExpr::Expressions(exprs),
130 cluster_by,
131 distribute_by,
132 sort_by,
133 having: None,
134 named_window,
135 qualify: None,
136 } if lateral_views.is_empty()
137 && exprs.is_empty()
138 && cluster_by.is_empty()
139 && distribute_by.is_empty()
140 && sort_by.is_empty()
141 && named_window.is_empty() =>
142 {
143 Ok(SqlSelect {
144 from: SubParser::parse_from(from)?,
145 filter: parse_expr_opt(selection)?,
146 project: parse_projection(projection)?,
147 })
148 }
149 _ => Err(SubscriptionUnsupported::Select(select).into()),
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use crate::parser::sub::parse_subscription;
156
157 #[test]
158 fn unsupported() {
159 for sql in [
160 "delete from t",
161 " ",
162 "",
163 "select distinct a from t",
164 "select * from (select * from t) join (select * from s) on a = b",
165 ] {
166 assert!(parse_subscription(sql).is_err());
167 }
168 }
169
170 #[test]
171 fn supported() {
172 for sql in [
173 "select * from t",
174 "select * from t where a = 1",
175 "select * from t where a <> 1",
176 "select * from t where a = 1 or a = 2",
177 "select t.* from t join s",
178 "select t.* from t join s on t.c = s.d",
179 "select a.* from t as a join s as b on a.c = b.d",
180 "select * from t where x = :sender",
181 ] {
182 assert!(parse_subscription(sql).is_ok());
183 }
184 }
185}