#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Column(String),
Literal(Scalar),
BinaryOp {
left: Box<Expr>,
op: Operator,
right: Box<Expr>,
},
UnaryOp { op: UnaryOperator, expr: Box<Expr> },
Agg { func: AggFunc, expr: Box<Expr> },
Function {
input: Box<Expr>,
function: ExprFunction,
},
Alias { expr: Box<Expr>, name: String },
Wildcard,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExprFunction {
String(StringFunction),
Datetime(DatetimeFunction),
List(ListFunction),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StringFunction {
ToLowercase,
ToUppercase,
Contains { pattern: String },
Replace {
pattern: String,
replacement: String,
},
StripChars { chars: Option<String> },
Split { separator: String },
LenChars,
Extract {
pattern: String,
capture_group: usize,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DatetimeFunction {
Year,
Month,
Day,
Weekday,
ToString,
ConvertTimeZone {
from_offset: String,
to_offset: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListFunction {
Join {
separator: String,
null_value: Option<String>,
},
Len,
Contains { value: String },
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Operator {
Add,
Sub,
Mul,
Div,
Eq,
Neq,
Gt,
Lt,
Ge,
Le,
And,
Or,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum UnaryOperator {
Not,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AggFunc {
Sum,
Mean,
Count,
Min,
Max,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Scalar {
Null,
Boolean(bool),
Int64(i64),
Float64(f64),
Utf8(String),
}
impl From<()> for Scalar {
fn from(_: ()) -> Self {
Scalar::Null
}
}
impl From<bool> for Scalar {
fn from(v: bool) -> Self {
Scalar::Boolean(v)
}
}
impl From<i64> for Scalar {
fn from(v: i64) -> Self {
Scalar::Int64(v)
}
}
impl From<f64> for Scalar {
fn from(v: f64) -> Self {
Scalar::Float64(v)
}
}
impl From<String> for Scalar {
fn from(v: String) -> Self {
Scalar::Utf8(v)
}
}
impl From<&str> for Scalar {
fn from(v: &str) -> Self {
Scalar::Utf8(v.to_string())
}
}
impl Expr {
pub fn alias(self, name: impl Into<String>) -> Expr {
Expr::Alias {
expr: Box::new(self),
name: name.into(),
}
}
#[allow(clippy::should_implement_trait)]
pub fn add(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::Add,
right: Box::new(rhs),
}
}
#[allow(clippy::should_implement_trait)]
pub fn sub(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::Sub,
right: Box::new(rhs),
}
}
#[allow(clippy::should_implement_trait)]
pub fn mul(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::Mul,
right: Box::new(rhs),
}
}
#[allow(clippy::should_implement_trait)]
pub fn div(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::Div,
right: Box::new(rhs),
}
}
pub fn eq(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::Eq,
right: Box::new(rhs),
}
}
pub fn neq(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::Neq,
right: Box::new(rhs),
}
}
pub fn gt(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::Gt,
right: Box::new(rhs),
}
}
pub fn lt(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::Lt,
right: Box::new(rhs),
}
}
pub fn ge(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::Ge,
right: Box::new(rhs),
}
}
pub fn le(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::Le,
right: Box::new(rhs),
}
}
pub fn and_(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::And,
right: Box::new(rhs),
}
}
pub fn or_(self, rhs: Expr) -> Expr {
Expr::BinaryOp {
left: Box::new(self),
op: Operator::Or,
right: Box::new(rhs),
}
}
pub fn not_(self) -> Expr {
Expr::UnaryOp {
op: UnaryOperator::Not,
expr: Box::new(self),
}
}
pub fn sum(self) -> Expr {
Expr::Agg {
func: AggFunc::Sum,
expr: Box::new(self),
}
}
pub fn mean(self) -> Expr {
Expr::Agg {
func: AggFunc::Mean,
expr: Box::new(self),
}
}
pub fn count(self) -> Expr {
Expr::Agg {
func: AggFunc::Count,
expr: Box::new(self),
}
}
pub fn min(self) -> Expr {
Expr::Agg {
func: AggFunc::Min,
expr: Box::new(self),
}
}
pub fn max(self) -> Expr {
Expr::Agg {
func: AggFunc::Max,
expr: Box::new(self),
}
}
pub fn str(self) -> StringExpr {
StringExpr { input: self }
}
pub fn dt(self) -> DatetimeExpr {
DatetimeExpr { input: self }
}
pub fn list(self) -> ListExpr {
ListExpr { input: self }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct StringExpr {
input: Expr,
}
impl StringExpr {
fn function(self, function: StringFunction) -> Expr {
Expr::Function {
input: Box::new(self.input),
function: ExprFunction::String(function),
}
}
pub fn to_lowercase(self) -> Expr {
self.function(StringFunction::ToLowercase)
}
pub fn to_uppercase(self) -> Expr {
self.function(StringFunction::ToUppercase)
}
pub fn contains(self, pattern: impl Into<String>) -> Expr {
self.function(StringFunction::Contains {
pattern: pattern.into(),
})
}
pub fn replace(self, pattern: impl Into<String>, replacement: impl Into<String>) -> Expr {
self.function(StringFunction::Replace {
pattern: pattern.into(),
replacement: replacement.into(),
})
}
pub fn strip_chars(self, chars: Option<impl Into<String>>) -> Expr {
self.function(StringFunction::StripChars {
chars: chars.map(Into::into),
})
}
pub fn split(self, separator: impl Into<String>) -> Expr {
self.function(StringFunction::Split {
separator: separator.into(),
})
}
pub fn len_chars(self) -> Expr {
self.function(StringFunction::LenChars)
}
pub fn extract(self, pattern: impl Into<String>, capture_group: usize) -> Expr {
self.function(StringFunction::Extract {
pattern: pattern.into(),
capture_group,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DatetimeExpr {
input: Expr,
}
impl DatetimeExpr {
fn function(self, function: DatetimeFunction) -> Expr {
Expr::Function {
input: Box::new(self.input),
function: ExprFunction::Datetime(function),
}
}
pub fn year(self) -> Expr {
self.function(DatetimeFunction::Year)
}
pub fn month(self) -> Expr {
self.function(DatetimeFunction::Month)
}
pub fn day(self) -> Expr {
self.function(DatetimeFunction::Day)
}
pub fn weekday(self) -> Expr {
self.function(DatetimeFunction::Weekday)
}
#[allow(clippy::inherent_to_string)]
pub fn to_string(self) -> Expr {
self.function(DatetimeFunction::ToString)
}
pub fn convert_time_zone(
self,
from_offset: impl Into<String>,
to_offset: impl Into<String>,
) -> Expr {
self.function(DatetimeFunction::ConvertTimeZone {
from_offset: from_offset.into(),
to_offset: to_offset.into(),
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ListExpr {
input: Expr,
}
impl ListExpr {
fn function(self, function: ListFunction) -> Expr {
Expr::Function {
input: Box::new(self.input),
function: ExprFunction::List(function),
}
}
pub fn join(self, separator: impl Into<String>, null_value: Option<impl Into<String>>) -> Expr {
self.function(ListFunction::Join {
separator: separator.into(),
null_value: null_value.map(Into::into),
})
}
pub fn len(self) -> Expr {
self.function(ListFunction::Len)
}
pub fn contains(self, value: impl Into<String>) -> Expr {
self.function(ListFunction::Contains {
value: value.into(),
})
}
}
#[cfg(test)]
mod tests {
use super::{AggFunc, Expr, ExprFunction, Operator, Scalar, StringFunction, UnaryOperator};
use crate::expr::{col, lit};
#[test]
fn builder_and_chaining_works() {
let expr = col("a").add(lit(1_i64)).alias("b");
assert_eq!(
expr,
Expr::Alias {
expr: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Column("a".to_string())),
op: Operator::Add,
right: Box::new(Expr::Literal(Scalar::Int64(1))),
}),
name: "b".to_string(),
}
);
}
#[test]
fn logical_and_agg_works() {
let expr = col("x")
.gt(lit(1_i64))
.and_(col("y").lt(lit(10_i64)).not_())
.alias("p");
assert!(matches!(
expr,
Expr::Alias {
expr: _,
name
} if name == "p"
));
let agg = col("v").sum();
assert_eq!(
agg,
Expr::Agg {
func: AggFunc::Sum,
expr: Box::new(Expr::Column("v".to_string()))
}
);
let u = Expr::Column("a".to_string()).not_();
assert_eq!(
u,
Expr::UnaryOp {
op: UnaryOperator::Not,
expr: Box::new(Expr::Column("a".to_string()))
}
);
}
#[test]
fn namespace_builders_create_function_exprs() {
let expr = col("name").str().to_lowercase().alias("name_lower");
assert_eq!(
expr,
Expr::Alias {
expr: Box::new(Expr::Function {
input: Box::new(Expr::Column("name".to_string())),
function: ExprFunction::String(StringFunction::ToLowercase),
}),
name: "name_lower".to_string(),
}
);
}
}