Skip to main content

spacetimedb_query_builder/
expr.rs

1use spacetimedb_lib::{
2    sats::{i256, u256},
3    ConnectionId, Identity, Timestamp,
4};
5
6use crate::{Col, ColumnRef};
7
8pub enum Operand<T> {
9    Column(ColumnRef<T>),
10    Literal(LiteralValue),
11}
12
13pub enum BoolExpr<T> {
14    Eq(Operand<T>, Operand<T>),
15    Ne(Operand<T>, Operand<T>),
16    Gt(Operand<T>, Operand<T>),
17    Lt(Operand<T>, Operand<T>),
18    Gte(Operand<T>, Operand<T>),
19    Lte(Operand<T>, Operand<T>),
20    And(Box<BoolExpr<T>>, Box<BoolExpr<T>>),
21    Or(Box<BoolExpr<T>>, Box<BoolExpr<T>>),
22    Not(Box<BoolExpr<T>>),
23}
24
25impl<T> BoolExpr<T> {
26    pub fn and(self, other: BoolExpr<T>) -> BoolExpr<T> {
27        BoolExpr::And(Box::new(self), Box::new(other))
28    }
29
30    pub fn or(self, other: BoolExpr<T>) -> BoolExpr<T> {
31        BoolExpr::Or(Box::new(self), Box::new(other))
32    }
33
34    #[allow(clippy::should_implement_trait)]
35    pub fn not(self) -> BoolExpr<T> {
36        BoolExpr::Not(Box::new(self))
37    }
38}
39
40/// Trait for types that can be used as the right-hand side of a comparison with a column of type V
41/// in table T.
42///
43/// This trait is implemented for Col<T, V> and various literal types.
44pub trait RHS<T, V> {
45    fn to_expr(self) -> Operand<T>;
46}
47
48impl<T, V> RHS<T, V> for Col<T, V> {
49    fn to_expr(self) -> Operand<T> {
50        Operand::Column(self.col)
51    }
52}
53
54fn format_bool_expr<T>(v: &Operand<T>) -> String {
55    match v {
56        Operand::Column(col) => col.fmt(),
57        Operand::Literal(lit) => lit.0.clone(),
58    }
59}
60
61pub fn format_expr<T>(expr: &BoolExpr<T>) -> String {
62    match expr {
63        BoolExpr::Eq(l, r) => format!("({} = {})", format_bool_expr(l), format_bool_expr(r)),
64        BoolExpr::Ne(l, r) => format!("({} <> {})", format_bool_expr(l), format_bool_expr(r)),
65        BoolExpr::Gt(l, r) => format!("({} > {})", format_bool_expr(l), format_bool_expr(r)),
66        BoolExpr::Lt(l, r) => format!("({} < {})", format_bool_expr(l), format_bool_expr(r)),
67        BoolExpr::Gte(l, r) => format!("({} >= {})", format_bool_expr(l), format_bool_expr(r)),
68        BoolExpr::Lte(l, r) => format!("({} <= {})", format_bool_expr(l), format_bool_expr(r)),
69        BoolExpr::And(a, b) => format!("({} AND {})", format_expr(a), format_expr(b)),
70        BoolExpr::Or(a, b) => format!("({} OR {})", format_expr(a), format_expr(b)),
71        BoolExpr::Not(inner) => format!("(NOT {})", format_expr(inner)),
72    }
73}
74
75#[derive(Clone, Debug)]
76pub struct LiteralValue(String);
77
78impl LiteralValue {
79    pub fn new(s: String) -> Self {
80        Self(s)
81    }
82}
83
84macro_rules! impl_rhs {
85    ($ty:ty, $formatter:expr) => {
86        impl<T> RHS<T, $ty> for $ty {
87            fn to_expr(self) -> Operand<T> {
88                Operand::Literal(LiteralValue($formatter(self)))
89            }
90        }
91    };
92}
93
94impl_rhs!(String, |v: String| format!("'{}'", v.replace('\'', "''")));
95impl_rhs!(&str, |v: &str| format!("'{}'", v.replace('\'', "''")));
96
97impl_rhs!(i8, |v: i8| v.to_string());
98impl_rhs!(i16, |v: i16| v.to_string());
99impl_rhs!(i32, |v: i32| v.to_string());
100impl_rhs!(i64, |v: i64| v.to_string());
101impl_rhs!(i128, |v: i128| v.to_string());
102
103impl_rhs!(u8, |v: u8| v.to_string());
104impl_rhs!(u16, |v: u16| v.to_string());
105impl_rhs!(u32, |v: u32| v.to_string());
106impl_rhs!(u64, |v: u64| v.to_string());
107impl_rhs!(u128, |v: u128| v.to_string());
108impl_rhs!(usize, |v: usize| v.to_string());
109
110impl_rhs!(u256, |v: u256| v.to_string());
111impl_rhs!(i256, |v: i256| v.to_string());
112
113impl_rhs!(f32, |v: f32| (v as f64).to_string());
114impl_rhs!(f64, |v: f64| v.to_string());
115
116impl_rhs!(bool, |b: bool| if b { "TRUE".into() } else { "FALSE".into() });
117
118impl_rhs!(Identity, |id: Identity| format!("0x{}", id.to_hex()));
119impl_rhs!(ConnectionId, |id: ConnectionId| format!("0x{}", id.to_hex()));
120impl_rhs!(Timestamp, |ts: Timestamp| format!("'{}'", ts));
121
122impl_rhs!(Vec<u8>, |b: Vec<u8>| {
123    let hex: String = b.iter().map(|x| format!("{:02x}", x)).collect();
124    format!("0x{}", hex)
125});