spacetimedb_expr/
expr.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use std::sync::Arc;

use spacetimedb_lib::{AlgebraicType, AlgebraicValue};
use spacetimedb_primitives::TableId;
use spacetimedb_schema::schema::TableSchema;
use spacetimedb_sql_parser::ast::{BinOp, LogOp};

/// A projection is the root of any relation expression
#[derive(Debug)]
pub enum Project {
    None(RelExpr),
    Relvar(RelExpr, Box<str>),
    Fields(RelExpr, Vec<(Box<str>, FieldProject)>),
}

impl Project {
    /// What is the [TableId] for this projection?
    pub fn table_id(&self) -> Option<TableId> {
        match self {
            Self::Fields(..) => None,
            Self::Relvar(input, var) => input.table_id(Some(var.as_ref())),
            Self::None(input) => input.table_id(None),
        }
    }
}

/// A logical relational expression
#[derive(Debug)]
pub enum RelExpr {
    /// A relvar or table reference
    RelVar(Arc<TableSchema>, Box<str>),
    /// A logical select for filter
    Select(Box<RelExpr>, Expr),
    /// A left deep binary cross product
    LeftDeepJoin(LeftDeepJoin),
    /// A left deep binary equi-join
    EqJoin(LeftDeepJoin, FieldProject, FieldProject),
}

impl RelExpr {
    /// The number of fields this expression returns
    pub fn nfields(&self) -> usize {
        match self {
            Self::RelVar(..) => 1,
            Self::LeftDeepJoin(join) | Self::EqJoin(join, ..) => join.lhs.nfields() + 1,
            Self::Select(input, _) => input.nfields(),
        }
    }

    /// Does this expression return this field?
    pub fn has_field(&self, field: &str) -> bool {
        match self {
            Self::RelVar(_, name) => name.as_ref() == field,
            Self::LeftDeepJoin(join) | Self::EqJoin(join, ..) => {
                join.var.as_ref() == field || join.lhs.has_field(field)
            }
            Self::Select(input, _) => input.has_field(field),
        }
    }

    /// What is the [TableId] for this expression or relvar?
    pub fn table_id(&self, var: Option<&str>) -> Option<TableId> {
        match (self, var) {
            (Self::RelVar(schema, _), None) => Some(schema.table_id),
            (Self::RelVar(schema, name), Some(var)) if name.as_ref() == var => Some(schema.table_id),
            (Self::RelVar(schema, _), Some(_)) => Some(schema.table_id),
            (Self::Select(input, _), _) => input.table_id(var),
            (Self::LeftDeepJoin(..) | Self::EqJoin(..), None) => None,
            (Self::LeftDeepJoin(join) | Self::EqJoin(join, ..), Some(name)) => {
                if join.var.as_ref() == name {
                    Some(join.rhs.table_id)
                } else {
                    join.lhs.table_id(var)
                }
            }
        }
    }
}

/// A left deep binary cross product
#[derive(Debug)]
pub struct LeftDeepJoin {
    /// The lhs is recursive
    pub lhs: Box<RelExpr>,
    /// The rhs is a relvar
    pub rhs: Arc<TableSchema>,
    /// The rhs relvar name
    pub var: Box<str>,
}

/// A typed scalar expression
#[derive(Debug)]
pub enum Expr {
    /// A binary expression
    BinOp(BinOp, Box<Expr>, Box<Expr>),
    /// A binary logic expression
    LogOp(LogOp, Box<Expr>, Box<Expr>),
    /// A typed literal expression
    Value(AlgebraicValue, AlgebraicType),
    /// A field projection
    Field(FieldProject),
}

impl Expr {
    /// A literal boolean value
    pub const fn bool(v: bool) -> Self {
        Self::Value(AlgebraicValue::Bool(v), AlgebraicType::Bool)
    }

    /// A literal string value
    pub const fn str(v: Box<str>) -> Self {
        Self::Value(AlgebraicValue::String(v), AlgebraicType::String)
    }

    /// The [AlgebraicType] of this scalar expression
    pub fn ty(&self) -> &AlgebraicType {
        match self {
            Self::BinOp(..) | Self::LogOp(..) => &AlgebraicType::Bool,
            Self::Value(_, ty) | Self::Field(FieldProject { ty, .. }) => ty,
        }
    }
}

/// A typed qualified field projection
#[derive(Debug)]
pub struct FieldProject {
    pub table: Box<str>,
    pub field: usize,
    pub ty: AlgebraicType,
}