use spacetimedb_lib::{
sats::{i256, u256},
ConnectionId, Identity, Timestamp,
};
use crate::{Col, ColumnRef};
pub enum Operand<T> {
Column(ColumnRef<T>),
Literal(LiteralValue),
}
pub enum BoolExpr<T> {
Eq(Operand<T>, Operand<T>),
Ne(Operand<T>, Operand<T>),
Gt(Operand<T>, Operand<T>),
Lt(Operand<T>, Operand<T>),
Gte(Operand<T>, Operand<T>),
Lte(Operand<T>, Operand<T>),
And(Box<BoolExpr<T>>, Box<BoolExpr<T>>),
Or(Box<BoolExpr<T>>, Box<BoolExpr<T>>),
Not(Box<BoolExpr<T>>),
}
impl<T> BoolExpr<T> {
pub fn and(self, other: BoolExpr<T>) -> BoolExpr<T> {
BoolExpr::And(Box::new(self), Box::new(other))
}
pub fn or(self, other: BoolExpr<T>) -> BoolExpr<T> {
BoolExpr::Or(Box::new(self), Box::new(other))
}
#[allow(clippy::should_implement_trait)]
pub fn not(self) -> BoolExpr<T> {
BoolExpr::Not(Box::new(self))
}
}
impl<T> From<Col<T, bool>> for BoolExpr<T> {
fn from(col: Col<T, bool>) -> Self {
col.eq(true)
}
}
impl<T> From<bool> for BoolExpr<T> {
fn from(value: bool) -> Self {
if value {
BoolExpr::Eq(
Operand::Literal(LiteralValue("TRUE".to_string())),
Operand::Literal(LiteralValue("TRUE".to_string())),
)
} else {
BoolExpr::Eq(
Operand::Literal(LiteralValue("FALSE".to_string())),
Operand::Literal(LiteralValue("TRUE".to_string())),
)
}
}
}
pub trait RHS<T, V> {
fn to_expr(self) -> Operand<T>;
}
impl<T, V> RHS<T, V> for Col<T, V> {
fn to_expr(self) -> Operand<T> {
Operand::Column(self.col)
}
}
fn format_bool_expr<T>(v: &Operand<T>) -> String {
match v {
Operand::Column(col) => col.fmt(),
Operand::Literal(lit) => lit.0.clone(),
}
}
pub fn format_expr<T>(expr: &BoolExpr<T>) -> String {
match expr {
BoolExpr::Eq(l, r) => format!("({} = {})", format_bool_expr(l), format_bool_expr(r)),
BoolExpr::Ne(l, r) => format!("({} <> {})", format_bool_expr(l), format_bool_expr(r)),
BoolExpr::Gt(l, r) => format!("({} > {})", format_bool_expr(l), format_bool_expr(r)),
BoolExpr::Lt(l, r) => format!("({} < {})", format_bool_expr(l), format_bool_expr(r)),
BoolExpr::Gte(l, r) => format!("({} >= {})", format_bool_expr(l), format_bool_expr(r)),
BoolExpr::Lte(l, r) => format!("({} <= {})", format_bool_expr(l), format_bool_expr(r)),
BoolExpr::And(a, b) => format!("({} AND {})", format_expr(a), format_expr(b)),
BoolExpr::Or(a, b) => format!("({} OR {})", format_expr(a), format_expr(b)),
BoolExpr::Not(inner) => format!("(NOT {})", format_expr(inner)),
}
}
#[derive(Clone, Debug)]
pub struct LiteralValue(String);
impl LiteralValue {
pub fn new(s: String) -> Self {
Self(s)
}
}
macro_rules! impl_rhs {
($ty:ty, $formatter:expr) => {
impl<T> RHS<T, $ty> for $ty {
fn to_expr(self) -> Operand<T> {
Operand::Literal(LiteralValue($formatter(self)))
}
}
};
}
impl_rhs!(String, |v: String| format!("'{}'", v.replace('\'', "''")));
impl_rhs!(&str, |v: &str| format!("'{}'", v.replace('\'', "''")));
impl_rhs!(i8, |v: i8| v.to_string());
impl_rhs!(i16, |v: i16| v.to_string());
impl_rhs!(i32, |v: i32| v.to_string());
impl_rhs!(i64, |v: i64| v.to_string());
impl_rhs!(i128, |v: i128| v.to_string());
impl_rhs!(u8, |v: u8| v.to_string());
impl_rhs!(u16, |v: u16| v.to_string());
impl_rhs!(u32, |v: u32| v.to_string());
impl_rhs!(u64, |v: u64| v.to_string());
impl_rhs!(u128, |v: u128| v.to_string());
impl_rhs!(usize, |v: usize| v.to_string());
impl_rhs!(u256, |v: u256| v.to_string());
impl_rhs!(i256, |v: i256| v.to_string());
impl_rhs!(f32, |v: f32| (v as f64).to_string());
impl_rhs!(f64, |v: f64| v.to_string());
impl_rhs!(bool, |b: bool| if b { "TRUE".into() } else { "FALSE".into() });
impl_rhs!(Identity, |id: Identity| format!("0x{}", id.to_hex()));
impl_rhs!(ConnectionId, |id: ConnectionId| format!("0x{}", id.to_hex()));
impl_rhs!(Timestamp, |ts: Timestamp| format!("'{}'", ts));
impl_rhs!(Vec<u8>, |b: Vec<u8>| {
let hex: String = b.iter().map(|x| format!("{:02x}", x)).collect();
format!("0x{}", hex)
});