spacetimedb_expr/
expr.rs

1use std::sync::Arc;
2
3use spacetimedb_lib::{query::Delta, AlgebraicType, AlgebraicValue};
4use spacetimedb_primitives::TableId;
5use spacetimedb_schema::schema::TableSchema;
6use spacetimedb_sql_parser::ast::{BinOp, LogOp};
7
8/// A projection is the root of any relational expression.
9/// This type represents a projection that returns relvars.
10///
11/// For example:
12///
13/// ```sql
14/// select * from t
15/// ```
16///
17/// and
18///
19/// ```sql
20/// select t.* from t join s ...
21/// ```
22#[derive(Debug)]
23pub enum ProjectName {
24    None(RelExpr),
25    Some(RelExpr, Box<str>),
26}
27
28impl ProjectName {
29    /// The [TableSchema] of the returned rows.
30    /// Note this expression returns rows from a relvar.
31    /// Hence it this method should never return [None].
32    pub fn return_table(&self) -> Option<&TableSchema> {
33        match self {
34            Self::None(input) => input.return_table(),
35            Self::Some(input, alias) => input.find_table_schema(alias),
36        }
37    }
38
39    /// The [TableId] of the returned rows.
40    /// Note this expression returns rows from a relvar.
41    /// Hence it this method should never return [None].
42    pub fn return_table_id(&self) -> Option<TableId> {
43        match self {
44            Self::None(input) => input.return_table_id(),
45            Self::Some(input, alias) => input.find_table_id(alias),
46        }
47    }
48
49    /// Iterate over the returned column names and types
50    pub fn for_each_return_field(&self, mut f: impl FnMut(&str, &AlgebraicType)) {
51        if let Some(schema) = self.return_table() {
52            for schema in schema.columns() {
53                f(&schema.col_name, &schema.col_type);
54            }
55        }
56    }
57}
58
59/// A projection is the root of any relational expression.
60/// This type represents a projection that returns fields.
61///
62/// For example:
63///
64/// ```sql
65/// select a, b from t
66/// ```
67///
68/// and
69///
70/// ```sql
71/// select t.a as x from t join s ...
72/// ```
73#[derive(Debug)]
74pub enum ProjectList {
75    Name(ProjectName),
76    List(RelExpr, Vec<(Box<str>, FieldProject)>),
77    Limit(Box<ProjectList>, u64),
78    Agg(RelExpr, AggType, Box<str>, AlgebraicType),
79}
80
81#[derive(Debug)]
82pub enum AggType {
83    Count,
84}
85
86impl ProjectList {
87    /// Does this expression project a single relvar?
88    /// If so, we return it's [TableSchema].
89    /// If not, it projects a list of columns, so we return [None].
90    pub fn return_table(&self) -> Option<&TableSchema> {
91        match self {
92            Self::Name(project) => project.return_table(),
93            Self::Limit(input, _) => input.return_table(),
94            Self::List(..) | Self::Agg(..) => None,
95        }
96    }
97
98    /// Does this expression project a single relvar?
99    /// If so, we return it's [TableId].
100    /// If not, it projects a list of columns, so we return [None].
101    pub fn return_table_id(&self) -> Option<TableId> {
102        match self {
103            Self::Name(project) => project.return_table_id(),
104            Self::Limit(input, _) => input.return_table_id(),
105            Self::List(..) | Self::Agg(..) => None,
106        }
107    }
108
109    /// Iterate over the projected column names and types
110    pub fn for_each_return_field(&self, mut f: impl FnMut(&str, &AlgebraicType)) {
111        match self {
112            Self::Name(input) => {
113                input.for_each_return_field(f);
114            }
115            Self::Limit(input, _) => {
116                input.for_each_return_field(f);
117            }
118            Self::List(_, fields) => {
119                for (name, FieldProject { ty, .. }) in fields {
120                    f(name, ty);
121                }
122            }
123            Self::Agg(_, _, name, ty) => f(name, ty),
124        }
125    }
126}
127
128/// A logical relational expression
129#[derive(Debug)]
130pub enum RelExpr {
131    /// A relvar or table reference
132    RelVar(Relvar),
133    /// A logical select for filter
134    Select(Box<RelExpr>, Expr),
135    /// A left deep binary cross product
136    LeftDeepJoin(LeftDeepJoin),
137    /// A left deep binary equi-join
138    EqJoin(LeftDeepJoin, FieldProject, FieldProject),
139}
140
141/// A table reference
142#[derive(Debug)]
143pub struct Relvar {
144    pub schema: Arc<TableSchema>,
145    pub alias: Box<str>,
146    /// Does this relvar represent a delta table?
147    pub delta: Option<Delta>,
148}
149
150impl RelExpr {
151    /// The number of fields this expression returns
152    pub fn nfields(&self) -> usize {
153        match self {
154            Self::RelVar(..) => 1,
155            Self::LeftDeepJoin(join) | Self::EqJoin(join, ..) => join.lhs.nfields() + 1,
156            Self::Select(input, _) => input.nfields(),
157        }
158    }
159
160    /// Does this expression return this field?
161    pub fn has_field(&self, field: &str) -> bool {
162        match self {
163            Self::RelVar(Relvar { alias, .. }) => alias.as_ref() == field,
164            Self::LeftDeepJoin(join) | Self::EqJoin(join, ..) => {
165                join.rhs.alias.as_ref() == field || join.lhs.has_field(field)
166            }
167            Self::Select(input, _) => input.has_field(field),
168        }
169    }
170
171    /// Return the [TableSchema] for a relvar in the expression
172    pub fn find_table_schema(&self, alias: &str) -> Option<&TableSchema> {
173        match self {
174            Self::RelVar(relvar) if relvar.alias.as_ref() == alias => Some(&relvar.schema),
175            Self::Select(input, _) => input.find_table_schema(alias),
176            Self::EqJoin(LeftDeepJoin { rhs, .. }, ..) if rhs.alias.as_ref() == alias => Some(&rhs.schema),
177            Self::EqJoin(LeftDeepJoin { lhs, .. }, ..) => lhs.find_table_schema(alias),
178            Self::LeftDeepJoin(LeftDeepJoin { rhs, .. }) if rhs.alias.as_ref() == alias => Some(&rhs.schema),
179            Self::LeftDeepJoin(LeftDeepJoin { lhs, .. }) => lhs.find_table_schema(alias),
180            _ => None,
181        }
182    }
183
184    /// Return the [TableId] for a relvar in the expression
185    pub fn find_table_id(&self, alias: &str) -> Option<TableId> {
186        self.find_table_schema(alias).map(|schema| schema.table_id)
187    }
188
189    /// Does this expression return a single relvar?
190    /// If so, return it's [TableSchema], otherwise return [None].
191    pub fn return_table(&self) -> Option<&TableSchema> {
192        match self {
193            Self::RelVar(Relvar { schema, .. }) => Some(schema),
194            Self::Select(input, _) => input.return_table(),
195            _ => None,
196        }
197    }
198
199    /// Does this expression return a single relvar?
200    /// If so, return it's [TableId], otherwise return [None].
201    pub fn return_table_id(&self) -> Option<TableId> {
202        self.return_table().map(|schema| schema.table_id)
203    }
204}
205
206/// A left deep binary cross product
207#[derive(Debug)]
208pub struct LeftDeepJoin {
209    /// The lhs is recursive
210    pub lhs: Box<RelExpr>,
211    /// The rhs is a relvar
212    pub rhs: Relvar,
213}
214
215/// A typed scalar expression
216#[derive(Debug)]
217pub enum Expr {
218    /// A binary expression
219    BinOp(BinOp, Box<Expr>, Box<Expr>),
220    /// A binary logic expression
221    LogOp(LogOp, Box<Expr>, Box<Expr>),
222    /// A typed literal expression
223    Value(AlgebraicValue, AlgebraicType),
224    /// A field projection
225    Field(FieldProject),
226}
227
228impl Expr {
229    /// A literal boolean value
230    pub const fn bool(v: bool) -> Self {
231        Self::Value(AlgebraicValue::Bool(v), AlgebraicType::Bool)
232    }
233
234    /// A literal string value
235    pub const fn str(v: Box<str>) -> Self {
236        Self::Value(AlgebraicValue::String(v), AlgebraicType::String)
237    }
238
239    /// The [AlgebraicType] of this scalar expression
240    pub fn ty(&self) -> &AlgebraicType {
241        match self {
242            Self::BinOp(..) | Self::LogOp(..) => &AlgebraicType::Bool,
243            Self::Value(_, ty) | Self::Field(FieldProject { ty, .. }) => ty,
244        }
245    }
246}
247
248/// A typed qualified field projection
249#[derive(Debug)]
250pub struct FieldProject {
251    pub table: Box<str>,
252    pub field: usize,
253    pub ty: AlgebraicType,
254}