use std::marker::PhantomData;
use crate::attribute::TypeBridgeAttribute;
use crate::expr::{Agg, Expr, SortDir};
pub struct FieldRef<A: TypeBridgeAttribute> {
attr_name: &'static str,
_marker: PhantomData<A>,
}
impl<A: TypeBridgeAttribute> FieldRef<A> {
pub const fn new(attr_name: &'static str) -> Self {
Self {
attr_name,
_marker: PhantomData,
}
}
pub const fn attr_name(&self) -> &'static str {
self.attr_name
}
pub fn eq(&self, value: A) -> Expr {
Expr::Eq {
attr: self.attr_name.to_string(),
value: value.to_value(),
}
}
pub fn neq(&self, value: A) -> Expr {
Expr::Neq {
attr: self.attr_name.to_string(),
value: value.to_value(),
}
}
pub fn gt(&self, value: A) -> Expr {
Expr::Gt {
attr: self.attr_name.to_string(),
value: value.to_value(),
}
}
pub fn gte(&self, value: A) -> Expr {
Expr::Gte {
attr: self.attr_name.to_string(),
value: value.to_value(),
}
}
pub fn lt(&self, value: A) -> Expr {
Expr::Lt {
attr: self.attr_name.to_string(),
value: value.to_value(),
}
}
pub fn lte(&self, value: A) -> Expr {
Expr::Lte {
attr: self.attr_name.to_string(),
value: value.to_value(),
}
}
pub fn contains(&self, substring: impl Into<String>) -> Expr {
Expr::Contains {
attr: self.attr_name.to_string(),
substring: substring.into(),
}
}
pub fn like(&self, pattern: impl Into<String>) -> Expr {
Expr::Like {
attr: self.attr_name.to_string(),
pattern: pattern.into(),
}
}
pub fn asc(&self) -> (String, SortDir) {
(self.attr_name.to_string(), SortDir::Asc)
}
pub fn desc(&self) -> (String, SortDir) {
(self.attr_name.to_string(), SortDir::Desc)
}
pub fn sum(&self) -> Agg {
Agg::Sum(self.attr_name.to_string())
}
pub fn min(&self) -> Agg {
Agg::Min(self.attr_name.to_string())
}
pub fn max(&self) -> Agg {
Agg::Max(self.attr_name.to_string())
}
pub fn mean(&self) -> Agg {
Agg::Mean(self.attr_name.to_string())
}
pub fn median(&self) -> Agg {
Agg::Median(self.attr_name.to_string())
}
pub fn in_range(&self, low: A, high: A) -> Expr {
Expr::And(vec![self.gte(low), self.lte(high)])
}
pub fn startswith(&self, prefix: impl Into<String>) -> Expr {
Expr::startswith(self.attr_name, prefix)
}
pub fn endswith(&self, suffix: impl Into<String>) -> Expr {
Expr::endswith(self.attr_name, suffix)
}
}
pub struct RolePlayerFieldRef<A: TypeBridgeAttribute> {
role_name: &'static str,
attr_name: &'static str,
_marker: PhantomData<A>,
}
impl<A: TypeBridgeAttribute> RolePlayerFieldRef<A> {
pub const fn new(role_name: &'static str, attr_name: &'static str) -> Self {
Self {
role_name,
attr_name,
_marker: PhantomData,
}
}
fn wrap(&self, inner: Expr) -> Expr {
Expr::RolePlayer {
role: self.role_name.to_string(),
inner: Box::new(inner),
}
}
pub fn eq(&self, value: A) -> Expr {
self.wrap(Expr::Eq {
attr: self.attr_name.to_string(),
value: value.to_value(),
})
}
pub fn neq(&self, value: A) -> Expr {
self.wrap(Expr::Neq {
attr: self.attr_name.to_string(),
value: value.to_value(),
})
}
pub fn gt(&self, value: A) -> Expr {
self.wrap(Expr::Gt {
attr: self.attr_name.to_string(),
value: value.to_value(),
})
}
pub fn gte(&self, value: A) -> Expr {
self.wrap(Expr::Gte {
attr: self.attr_name.to_string(),
value: value.to_value(),
})
}
pub fn lt(&self, value: A) -> Expr {
self.wrap(Expr::Lt {
attr: self.attr_name.to_string(),
value: value.to_value(),
})
}
pub fn lte(&self, value: A) -> Expr {
self.wrap(Expr::Lte {
attr: self.attr_name.to_string(),
value: value.to_value(),
})
}
pub fn contains(&self, substring: impl Into<String>) -> Expr {
self.wrap(Expr::Contains {
attr: self.attr_name.to_string(),
substring: substring.into(),
})
}
pub fn like(&self, pattern: impl Into<String>) -> Expr {
self.wrap(Expr::Like {
attr: self.attr_name.to_string(),
pattern: pattern.into(),
})
}
pub fn in_range(&self, low: A, high: A) -> Expr {
self.wrap(Expr::And(vec![
Expr::Gte {
attr: self.attr_name.to_string(),
value: low.to_value(),
},
Expr::Lte {
attr: self.attr_name.to_string(),
value: high.to_value(),
},
]))
}
pub fn startswith(&self, prefix: impl Into<String>) -> Expr {
self.wrap(Expr::startswith(self.attr_name, prefix))
}
pub fn endswith(&self, suffix: impl Into<String>) -> Expr {
self.wrap(Expr::endswith(self.attr_name, suffix))
}
}
pub struct RoleRef {
role_name: &'static str,
}
impl RoleRef {
pub const fn new(role_name: &'static str) -> Self {
Self { role_name }
}
pub fn attr<A: TypeBridgeAttribute>(&self, attr_name: &'static str) -> RolePlayerFieldRef<A> {
RolePlayerFieldRef::new(self.role_name, attr_name)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::define_attribute;
define_attribute!(TestName, "name", "string");
define_attribute!(TestAge, "age", "long");
#[test]
fn field_ref_eq() {
let field: FieldRef<TestAge> = FieldRef::new("age");
let expr = field.eq(TestAge(30));
match expr {
Expr::Eq { attr, value } => {
assert_eq!(attr, "age");
assert_eq!(value, crate::value::AttributeValue::Long(30));
}
_ => panic!("expected Eq"),
}
}
#[test]
fn field_ref_comparisons() {
let field: FieldRef<TestAge> = FieldRef::new("age");
assert!(matches!(field.gt(TestAge(1)), Expr::Gt { .. }));
assert!(matches!(field.lt(TestAge(1)), Expr::Lt { .. }));
assert!(matches!(field.gte(TestAge(1)), Expr::Gte { .. }));
assert!(matches!(field.lte(TestAge(1)), Expr::Lte { .. }));
assert!(matches!(field.neq(TestAge(1)), Expr::Neq { .. }));
}
#[test]
fn field_ref_string_ops() {
let field: FieldRef<TestName> = FieldRef::new("name");
assert!(matches!(field.contains("Ali"), Expr::Contains { .. }));
assert!(matches!(field.like("^A.*"), Expr::Like { .. }));
}
#[test]
fn field_ref_sort() {
let field: FieldRef<TestAge> = FieldRef::new("age");
let (attr, dir) = field.asc();
assert_eq!(attr, "age");
assert_eq!(dir, SortDir::Asc);
let (_, dir) = field.desc();
assert_eq!(dir, SortDir::Desc);
}
#[test]
fn field_ref_in_range() {
let field: FieldRef<TestAge> = FieldRef::new("age");
let expr = field.in_range(TestAge(20), TestAge(30));
match expr {
Expr::And(children) => {
assert_eq!(children.len(), 2);
assert!(matches!(&children[0], Expr::Gte { attr, .. } if attr == "age"));
assert!(matches!(&children[1], Expr::Lte { attr, .. } if attr == "age"));
}
_ => panic!("expected And"),
}
}
#[test]
fn field_ref_startswith() {
let field: FieldRef<TestName> = FieldRef::new("name");
let expr = field.startswith("Ali");
match expr {
Expr::Like { attr, pattern } => {
assert_eq!(attr, "name");
assert_eq!(pattern, "^Ali.*");
}
_ => panic!("expected Like"),
}
}
#[test]
fn field_ref_endswith() {
let field: FieldRef<TestName> = FieldRef::new("name");
let expr = field.endswith("ice");
match expr {
Expr::Like { attr, pattern } => {
assert_eq!(attr, "name");
assert_eq!(pattern, ".*ice$");
}
_ => panic!("expected Like"),
}
}
#[test]
fn role_player_field_ref_eq() {
let rpf: RolePlayerFieldRef<TestAge> = RolePlayerFieldRef::new("employee", "age");
let expr = rpf.eq(TestAge(30));
match expr {
Expr::RolePlayer { role, inner } => {
assert_eq!(role, "employee");
assert!(matches!(*inner, Expr::Eq { ref attr, .. } if attr == "age"));
}
_ => panic!("expected RolePlayer"),
}
}
#[test]
fn role_player_field_ref_comparisons() {
let rpf: RolePlayerFieldRef<TestAge> = RolePlayerFieldRef::new("employee", "age");
assert!(matches!(rpf.gt(TestAge(1)), Expr::RolePlayer { .. }));
assert!(matches!(rpf.lt(TestAge(1)), Expr::RolePlayer { .. }));
assert!(matches!(rpf.gte(TestAge(1)), Expr::RolePlayer { .. }));
assert!(matches!(rpf.lte(TestAge(1)), Expr::RolePlayer { .. }));
assert!(matches!(rpf.neq(TestAge(1)), Expr::RolePlayer { .. }));
}
#[test]
fn role_player_field_ref_in_range() {
let rpf: RolePlayerFieldRef<TestAge> = RolePlayerFieldRef::new("employee", "age");
let expr = rpf.in_range(TestAge(20), TestAge(30));
match expr {
Expr::RolePlayer { role, inner } => {
assert_eq!(role, "employee");
assert!(matches!(*inner, Expr::And(_)));
}
_ => panic!("expected RolePlayer"),
}
}
#[test]
fn role_ref_attr() {
let role = RoleRef::new("employee");
let field: RolePlayerFieldRef<TestAge> = role.attr("age");
let expr = field.gte(TestAge(18));
match expr {
Expr::RolePlayer { role, inner } => {
assert_eq!(role, "employee");
assert!(matches!(*inner, Expr::Gte { ref attr, .. } if attr == "age"));
}
_ => panic!("expected RolePlayer"),
}
}
#[test]
fn field_ref_aggregations() {
let field: FieldRef<TestAge> = FieldRef::new("age");
assert!(matches!(field.sum(), Agg::Sum(ref a) if a == "age"));
assert!(matches!(field.min(), Agg::Min(ref a) if a == "age"));
assert!(matches!(field.max(), Agg::Max(ref a) if a == "age"));
assert!(matches!(field.mean(), Agg::Mean(ref a) if a == "age"));
assert!(matches!(field.median(), Agg::Median(ref a) if a == "age"));
}
}