spacetimedb_sql_parser/ast/
mod.rs

1use std::fmt::{Display, Formatter};
2
3use sqlparser::ast::Ident;
4
5pub mod sql;
6pub mod sub;
7
8/// The FROM clause is either a relvar or a JOIN
9#[derive(Debug)]
10pub enum SqlFrom {
11    Expr(SqlIdent, SqlIdent),
12    Join(SqlIdent, SqlIdent, Vec<SqlJoin>),
13}
14
15impl SqlFrom {
16    pub fn has_unqualified_vars(&self) -> bool {
17        match self {
18            Self::Join(_, _, joins) => joins.iter().any(|join| join.has_unqualified_vars()),
19            _ => false,
20        }
21    }
22}
23
24/// An inner join in a FROM clause
25#[derive(Debug)]
26pub struct SqlJoin {
27    pub var: SqlIdent,
28    pub alias: SqlIdent,
29    pub on: Option<SqlExpr>,
30}
31
32impl SqlJoin {
33    pub fn has_unqualified_vars(&self) -> bool {
34        self.on.as_ref().is_some_and(|expr| expr.has_unqualified_vars())
35    }
36}
37
38/// A projection expression in a SELECT clause
39#[derive(Debug)]
40pub struct ProjectElem(pub ProjectExpr, pub SqlIdent);
41
42impl ProjectElem {
43    pub fn qualify_vars(self, with: SqlIdent) -> Self {
44        let Self(expr, alias) = self;
45        Self(expr.qualify_vars(with), alias)
46    }
47}
48
49/// A column projection in a SELECT clause
50#[derive(Debug)]
51pub enum ProjectExpr {
52    Var(SqlIdent),
53    Field(SqlIdent, SqlIdent),
54}
55
56impl From<ProjectExpr> for SqlExpr {
57    fn from(value: ProjectExpr) -> Self {
58        match value {
59            ProjectExpr::Var(name) => Self::Var(name),
60            ProjectExpr::Field(table, field) => Self::Field(table, field),
61        }
62    }
63}
64
65impl ProjectExpr {
66    pub fn qualify_vars(self, with: SqlIdent) -> Self {
67        match self {
68            Self::Var(name) => Self::Field(with, name),
69            Self::Field(_, _) => self,
70        }
71    }
72}
73
74/// A SQL SELECT clause
75#[derive(Debug)]
76pub enum Project {
77    /// SELECT *
78    /// SELECT a.*
79    Star(Option<SqlIdent>),
80    /// SELECT a, b
81    Exprs(Vec<ProjectElem>),
82    /// SELECT COUNT(*)
83    Count(SqlIdent),
84}
85
86impl Project {
87    pub fn qualify_vars(self, with: SqlIdent) -> Self {
88        match self {
89            Self::Star(..) | Self::Count(..) => self,
90            Self::Exprs(elems) => Self::Exprs(elems.into_iter().map(|elem| elem.qualify_vars(with.clone())).collect()),
91        }
92    }
93
94    pub fn has_unqualified_vars(&self) -> bool {
95        match self {
96            Self::Exprs(exprs) => exprs
97                .iter()
98                .any(|ProjectElem(expr, _)| matches!(expr, ProjectExpr::Var(_))),
99            _ => false,
100        }
101    }
102}
103
104/// A scalar SQL expression
105#[derive(Debug)]
106pub enum SqlExpr {
107    /// A constant expression
108    Lit(SqlLiteral),
109    /// Unqualified column ref
110    Var(SqlIdent),
111    /// Qualified column ref
112    Field(SqlIdent, SqlIdent),
113    /// A binary infix expression
114    Bin(Box<SqlExpr>, Box<SqlExpr>, BinOp),
115    /// A binary logic expression
116    Log(Box<SqlExpr>, Box<SqlExpr>, LogOp),
117}
118
119impl SqlExpr {
120    pub fn qualify_vars(self, with: SqlIdent) -> Self {
121        match self {
122            Self::Var(name) => Self::Field(with, name),
123            Self::Lit(..) | Self::Field(..) => self,
124            Self::Bin(a, b, op) => Self::Bin(
125                Box::new(a.qualify_vars(with.clone())),
126                Box::new(b.qualify_vars(with)),
127                op,
128            ),
129            Self::Log(a, b, op) => Self::Log(
130                Box::new(a.qualify_vars(with.clone())),
131                Box::new(b.qualify_vars(with)),
132                op,
133            ),
134        }
135    }
136
137    pub fn has_unqualified_vars(&self) -> bool {
138        match self {
139            Self::Var(_) => true,
140            Self::Bin(a, b, _) | Self::Log(a, b, _) => a.has_unqualified_vars() || b.has_unqualified_vars(),
141            _ => false,
142        }
143    }
144}
145
146/// A SQL identifier or named reference.
147/// Currently case sensitive.
148#[derive(Debug, Clone)]
149pub struct SqlIdent(pub Box<str>);
150
151/// Case insensitivity should be implemented here if at all
152impl From<Ident> for SqlIdent {
153    fn from(Ident { value, .. }: Ident) -> Self {
154        SqlIdent(value.into_boxed_str())
155    }
156}
157
158/// A SQL constant expression
159#[derive(Debug)]
160pub enum SqlLiteral {
161    /// A boolean constant
162    Bool(bool),
163    /// A hex value like 0xFF or x'FF'
164    Hex(Box<str>),
165    /// An integer or float value
166    Num(Box<str>),
167    /// A string value
168    Str(Box<str>),
169}
170
171/// Binary infix operators
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub enum BinOp {
174    Eq,
175    Ne,
176    Lt,
177    Gt,
178    Lte,
179    Gte,
180}
181
182impl Display for BinOp {
183    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
184        match self {
185            Self::Eq => write!(f, "="),
186            Self::Ne => write!(f, "<>"),
187            Self::Lt => write!(f, "<"),
188            Self::Gt => write!(f, ">"),
189            Self::Lte => write!(f, "<="),
190            Self::Gte => write!(f, ">="),
191        }
192    }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum LogOp {
197    And,
198    Or,
199}
200
201impl Display for LogOp {
202    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
203        match self {
204            Self::And => write!(f, "AND"),
205            Self::Or => write!(f, "OR"),
206        }
207    }
208}