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
40impl<T> From<Col<T, bool>> for BoolExpr<T> {
41    fn from(col: Col<T, bool>) -> Self {
42        col.eq(true)
43    }
44}
45
46impl<T> From<bool> for BoolExpr<T> {
47    fn from(value: bool) -> Self {
48        if value {
49            BoolExpr::Eq(
50                Operand::Literal(LiteralValue("TRUE".to_string())),
51                Operand::Literal(LiteralValue("TRUE".to_string())),
52            )
53        } else {
54            BoolExpr::Eq(
55                Operand::Literal(LiteralValue("FALSE".to_string())),
56                Operand::Literal(LiteralValue("TRUE".to_string())),
57            )
58        }
59    }
60}
61
62/// Trait for types that can be used as the right-hand side of a comparison with a column of type V
63/// in table T.
64///
65/// This trait is implemented for Col<T, V> and various literal types.
66pub trait RHS<T, V> {
67    fn to_expr(self) -> Operand<T>;
68}
69
70impl<T, V> RHS<T, V> for Col<T, V> {
71    fn to_expr(self) -> Operand<T> {
72        Operand::Column(self.col)
73    }
74}
75
76fn format_bool_expr<T>(v: &Operand<T>) -> String {
77    match v {
78        Operand::Column(col) => col.fmt(),
79        Operand::Literal(lit) => lit.0.clone(),
80    }
81}
82
83pub fn format_expr<T>(expr: &BoolExpr<T>) -> String {
84    match expr {
85        BoolExpr::Eq(l, r) => format!("({} = {})", format_bool_expr(l), format_bool_expr(r)),
86        BoolExpr::Ne(l, r) => format!("({} <> {})", format_bool_expr(l), format_bool_expr(r)),
87        BoolExpr::Gt(l, r) => format!("({} > {})", format_bool_expr(l), format_bool_expr(r)),
88        BoolExpr::Lt(l, r) => format!("({} < {})", format_bool_expr(l), format_bool_expr(r)),
89        BoolExpr::Gte(l, r) => format!("({} >= {})", format_bool_expr(l), format_bool_expr(r)),
90        BoolExpr::Lte(l, r) => format!("({} <= {})", format_bool_expr(l), format_bool_expr(r)),
91        BoolExpr::And(a, b) => format!("({} AND {})", format_expr(a), format_expr(b)),
92        BoolExpr::Or(a, b) => format!("({} OR {})", format_expr(a), format_expr(b)),
93        BoolExpr::Not(inner) => format!("(NOT {})", format_expr(inner)),
94    }
95}
96
97#[derive(Clone, Debug)]
98pub struct LiteralValue(String);
99
100impl LiteralValue {
101    pub fn new(s: String) -> Self {
102        Self(s)
103    }
104}
105
106macro_rules! impl_rhs {
107    ($ty:ty, $formatter:expr) => {
108        impl<T> RHS<T, $ty> for $ty {
109            fn to_expr(self) -> Operand<T> {
110                Operand::Literal(LiteralValue($formatter(self)))
111            }
112        }
113    };
114}
115
116impl_rhs!(String, |v: String| format!("'{}'", v.replace('\'', "''")));
117impl_rhs!(&str, |v: &str| format!("'{}'", v.replace('\'', "''")));
118
119impl_rhs!(i8, |v: i8| v.to_string());
120impl_rhs!(i16, |v: i16| v.to_string());
121impl_rhs!(i32, |v: i32| v.to_string());
122impl_rhs!(i64, |v: i64| v.to_string());
123impl_rhs!(i128, |v: i128| v.to_string());
124
125impl_rhs!(u8, |v: u8| v.to_string());
126impl_rhs!(u16, |v: u16| v.to_string());
127impl_rhs!(u32, |v: u32| v.to_string());
128impl_rhs!(u64, |v: u64| v.to_string());
129impl_rhs!(u128, |v: u128| v.to_string());
130impl_rhs!(usize, |v: usize| v.to_string());
131
132impl_rhs!(u256, |v: u256| v.to_string());
133impl_rhs!(i256, |v: i256| v.to_string());
134
135impl_rhs!(f32, |v: f32| (v as f64).to_string());
136impl_rhs!(f64, |v: f64| v.to_string());
137
138impl_rhs!(bool, |b: bool| if b { "TRUE".into() } else { "FALSE".into() });
139
140impl_rhs!(Identity, |id: Identity| format!("0x{}", id.to_hex()));
141impl_rhs!(ConnectionId, |id: ConnectionId| format!("0x{}", id.to_hex()));
142impl_rhs!(Timestamp, |ts: Timestamp| format!("'{}'", ts));
143
144impl_rhs!(Vec<u8>, |b: Vec<u8>| {
145    let hex: String = b.iter().map(|x| format!("{:02x}", x)).collect();
146    format!("0x{}", hex)
147});