use std::{fmt, ops::Not};
use arrow::datatypes::DataType;
mod column;
mod literal;
pub use self::{
column::{ColumnQualifier, ColumnRef},
literal::LiteralValue,
};
#[doc(hidden)]
pub fn alias_wrapped_column(expr: &Expr) -> Option<&ColumnRef> {
match expr {
Expr::Column(column) => Some(column),
Expr::Alias { expr, .. } => alias_wrapped_column(expr),
_ => None,
}
}
pub(crate) fn representation_cast_preserves_identity(source_type: &DataType, target_type: &DataType) -> bool {
source_type == target_type
|| matches!(
(source_type, target_type),
(DataType::Utf8 | DataType::LargeUtf8 | DataType::Utf8View, DataType::Utf8View)
| (DataType::Utf8View, DataType::Utf8 | DataType::LargeUtf8)
| (DataType::Utf8, DataType::LargeUtf8)
| (DataType::LargeUtf8, DataType::Utf8)
)
}
#[doc(hidden)]
pub fn representation_cast_column(expr: &Expr) -> Option<(&ColumnRef, &DataType)> {
match expr {
Expr::Alias { expr, .. } => representation_cast_column(expr),
Expr::Cast { expr, target_type } => alias_wrapped_column(expr).map(|column| (column, target_type)),
_ => None,
}
}
#[doc(hidden)]
pub fn interval_month_day_nano(expr: &Expr) -> Option<(i32, i32, i64)> {
let Expr::Literal(LiteralValue::IntervalMonthDayNano { months, days, nanoseconds }) = expr else {
return None;
};
Some((*months, *days, *nanoseconds))
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Column(ColumnRef),
Literal(LiteralValue),
Not(Box<Expr>),
IsNull(Box<Expr>),
IsNotNull(Box<Expr>),
Cast {
expr: Box<Expr>,
target_type: DataType,
},
TryCast {
expr: Box<Expr>,
target_type: DataType,
},
Binary {
left: Box<Expr>,
op: BinaryOp,
right: Box<Expr>,
output_type: Option<DataType>,
},
Like {
expr: Box<Expr>,
pattern: String,
escape: Option<String>,
negated: bool,
},
Substring {
expr: Box<Expr>,
start: i32,
length: Option<i32>,
},
Lower(Box<Expr>),
Upper(Box<Expr>),
Trim {
expr: Box<Expr>,
trim_chars: String,
},
LTrim {
expr: Box<Expr>,
trim_chars: String,
},
RTrim {
expr: Box<Expr>,
trim_chars: String,
},
StringContains {
expr: Box<Expr>,
pattern: String,
},
StartsWith {
expr: Box<Expr>,
prefix: String,
},
EndsWith {
expr: Box<Expr>,
suffix: String,
},
StringLength {
expr: Box<Expr>,
unit: StringLengthUnit,
output_type: DataType,
},
RegexpReplace {
expr: Box<Expr>,
pattern: String,
replacement: String,
output_type: DataType,
},
Concat(Vec<Expr>),
Abs(Box<Expr>),
UnaryMath {
op: UnaryMathOp,
expr: Box<Expr>,
output_type: DataType,
},
Round {
expr: Box<Expr>,
decimal_places: i32,
output_type: DataType,
},
InList {
expr: Box<Expr>,
list: Vec<Expr>,
negated: bool,
},
IsDistinctFrom {
left: Box<Expr>,
right: Box<Expr>,
},
IsNotDistinctFrom {
left: Box<Expr>,
right: Box<Expr>,
},
Case {
branches: Vec<CaseWhen>,
else_expr: Box<Expr>,
},
Extract {
field: DatePart,
expr: Box<Expr>,
},
DateTrunc {
field: DatePart,
expr: Box<Expr>,
output_type: DataType,
},
Alias {
expr: Box<Expr>,
alias: String,
},
}
impl Expr {
pub fn children(&self) -> impl Iterator<Item = &Expr> {
let children: Vec<&Expr> = match self {
Self::Column(_) | Self::Literal(_) => Vec::new(),
Self::Not(expr)
| Self::IsNull(expr)
| Self::IsNotNull(expr)
| Self::Cast { expr, .. }
| Self::TryCast { expr, .. }
| Self::Like { expr, .. }
| Self::Substring { expr, .. }
| Self::Lower(expr)
| Self::Upper(expr)
| Self::Trim { expr, .. }
| Self::LTrim { expr, .. }
| Self::RTrim { expr, .. }
| Self::StringContains { expr, .. }
| Self::StartsWith { expr, .. }
| Self::EndsWith { expr, .. }
| Self::StringLength { expr, .. }
| Self::RegexpReplace { expr, .. }
| Self::Abs(expr)
| Self::UnaryMath { expr, .. }
| Self::Round { expr, .. }
| Self::Extract { expr, .. }
| Self::DateTrunc { expr, .. }
| Self::Alias { expr, .. } => vec![expr.as_ref()],
Self::Binary { left, right, .. }
| Self::IsDistinctFrom { left, right }
| Self::IsNotDistinctFrom { left, right } => vec![left.as_ref(), right.as_ref()],
Self::Concat(exprs) => exprs.iter().collect(),
Self::InList { expr, list, .. } => std::iter::once(expr.as_ref()).chain(list).collect(),
Self::Case { branches, else_expr } => branches
.iter()
.flat_map(|branch| [&branch.when, &branch.then])
.chain(std::iter::once(else_expr.as_ref()))
.collect(),
};
children.into_iter()
}
pub fn any(&self, mut predicate: impl FnMut(&Expr) -> bool) -> bool {
let mut pending = vec![self];
while let Some(expr) = pending.pop() {
if predicate(expr) {
return true;
}
pending.extend(expr.children());
}
false
}
pub fn columns(&self) -> impl Iterator<Item = &ColumnRef> {
let mut pending = vec![self];
std::iter::from_fn(move || {
while let Some(expr) = pending.pop() {
if let Self::Column(column) = expr {
return Some(column);
}
let first = pending.len();
pending.extend(expr.children());
pending[first..].reverse();
}
None
})
}
pub fn column(name: impl Into<String>) -> Self {
Self::Column(ColumnRef::unqualified(name))
}
pub fn input_column(input: usize, name: impl Into<String>, display_name: impl Into<String>) -> Self {
Self::Column(ColumnRef::input(input, name, display_name))
}
pub fn literal(value: LiteralValue) -> Self {
Self::Literal(value)
}
pub fn binary(left: Self, op: BinaryOp, right: Self) -> Self {
Self::Binary { left: Box::new(left), op, right: Box::new(right), output_type: None }
}
pub fn binary_with_output_type(left: Self, op: BinaryOp, right: Self, output_type: DataType) -> Self {
Self::Binary { left: Box::new(left), op, right: Box::new(right), output_type: Some(output_type) }
}
pub fn logical_not(expr: Self) -> Self {
Self::Not(Box::new(expr))
}
pub fn is_null(expr: Self) -> Self {
Self::IsNull(Box::new(expr))
}
pub fn is_not_null(expr: Self) -> Self {
Self::IsNotNull(Box::new(expr))
}
pub fn cast(expr: Self, target_type: DataType) -> Self {
Self::Cast { expr: Box::new(expr), target_type }
}
pub fn try_cast(expr: Self, target_type: DataType) -> Self {
Self::TryCast { expr: Box::new(expr), target_type }
}
pub fn like(expr: Self, pattern: impl Into<String>) -> Self {
Self::Like { expr: Box::new(expr), pattern: pattern.into(), escape: None, negated: false }
}
pub fn not_like(expr: Self, pattern: impl Into<String>) -> Self {
Self::Like { expr: Box::new(expr), pattern: pattern.into(), escape: None, negated: true }
}
pub fn like_escape(expr: Self, pattern: impl Into<String>, escape: impl Into<String>) -> Self {
Self::Like { expr: Box::new(expr), pattern: pattern.into(), escape: Some(escape.into()), negated: false }
}
pub fn not_like_escape(expr: Self, pattern: impl Into<String>, escape: impl Into<String>) -> Self {
Self::Like { expr: Box::new(expr), pattern: pattern.into(), escape: Some(escape.into()), negated: true }
}
pub fn substring(expr: Self, start: i32, length: Option<i32>) -> Self {
Self::Substring { expr: Box::new(expr), start, length }
}
pub fn lower(expr: Self) -> Self {
Self::Lower(Box::new(expr))
}
pub fn upper(expr: Self) -> Self {
Self::Upper(Box::new(expr))
}
pub fn trim(expr: Self, trim_chars: impl Into<String>) -> Self {
Self::Trim { expr: Box::new(expr), trim_chars: trim_chars.into() }
}
pub fn ltrim(expr: Self, trim_chars: impl Into<String>) -> Self {
Self::LTrim { expr: Box::new(expr), trim_chars: trim_chars.into() }
}
pub fn rtrim(expr: Self, trim_chars: impl Into<String>) -> Self {
Self::RTrim { expr: Box::new(expr), trim_chars: trim_chars.into() }
}
pub fn string_contains(expr: Self, pattern: impl Into<String>) -> Self {
Self::StringContains { expr: Box::new(expr), pattern: pattern.into() }
}
pub fn starts_with(expr: Self, prefix: impl Into<String>) -> Self {
Self::StartsWith { expr: Box::new(expr), prefix: prefix.into() }
}
pub fn ends_with(expr: Self, suffix: impl Into<String>) -> Self {
Self::EndsWith { expr: Box::new(expr), suffix: suffix.into() }
}
pub fn string_length(expr: Self, output_type: DataType) -> Self {
Self::StringLength { expr: Box::new(expr), unit: StringLengthUnit::Characters, output_type }
}
pub fn octet_length(expr: Self, output_type: DataType) -> Self {
Self::StringLength { expr: Box::new(expr), unit: StringLengthUnit::Bytes, output_type }
}
pub fn regexp_replace(
expr: Self,
pattern: impl Into<String>,
replacement: impl Into<String>,
output_type: DataType,
) -> Self {
Self::RegexpReplace {
expr: Box::new(expr),
pattern: pattern.into(),
replacement: replacement.into(),
output_type,
}
}
pub fn concat(exprs: Vec<Self>) -> Self {
Self::Concat(exprs)
}
pub fn abs(expr: Self) -> Self {
Self::Abs(Box::new(expr))
}
pub fn unary_math(op: UnaryMathOp, expr: Self, output_type: DataType) -> Self {
Self::UnaryMath { op, expr: Box::new(expr), output_type }
}
pub fn round(expr: Self, decimal_places: i32, output_type: DataType) -> Self {
Self::Round { expr: Box::new(expr), decimal_places, output_type }
}
pub fn in_list(expr: Self, list: Vec<Self>) -> Self {
Self::InList { expr: Box::new(expr), list, negated: false }
}
pub fn not_in_list(expr: Self, list: Vec<Self>) -> Self {
Self::InList { expr: Box::new(expr), list, negated: true }
}
pub fn is_distinct_from(left: Self, right: Self) -> Self {
Self::IsDistinctFrom { left: Box::new(left), right: Box::new(right) }
}
pub fn is_not_distinct_from(left: Self, right: Self) -> Self {
Self::IsNotDistinctFrom { left: Box::new(left), right: Box::new(right) }
}
pub fn case(branches: Vec<CaseWhen>, else_expr: Self) -> Self {
Self::Case { branches, else_expr: Box::new(else_expr) }
}
pub fn extract(field: DatePart, expr: Self) -> Self {
Self::Extract { field, expr: Box::new(expr) }
}
pub fn date_trunc(field: DatePart, expr: Self, output_type: DataType) -> Self {
Self::DateTrunc { field, expr: Box::new(expr), output_type }
}
pub fn alias(self, alias: impl Into<String>) -> Self {
Self::Alias { expr: Box::new(self), alias: alias.into() }
}
pub fn contains_regexp_replace(&self) -> bool {
self.any(|expr| matches!(expr, Self::RegexpReplace { .. }))
}
}
impl Not for Expr {
type Output = Self;
fn not(self) -> Self::Output {
Self::Not(Box::new(self))
}
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Column(column) => write!(f, "{column}"),
Self::Literal(value) => write!(f, "{value}"),
Self::Not(expr) => write!(f, "NOT {expr}"),
Self::IsNull(expr) => write!(f, "{expr} IS NULL"),
Self::IsNotNull(expr) => write!(f, "{expr} IS NOT NULL"),
Self::Cast { expr, target_type } => write!(f, "CAST({expr} AS {target_type})"),
Self::TryCast { expr, target_type } => write!(f, "TRY_CAST({expr} AS {target_type})"),
Self::Binary { left, op, right, output_type } => {
write!(f, "({left} {op} {right})")?;
if let Some(output_type) = output_type {
write!(f, "::{output_type}")?;
}
Ok(())
},
Self::Like { expr, pattern, escape, negated } => {
let operator = if *negated { "NOT LIKE" } else { "LIKE" };
write!(f, "{expr} {operator} {pattern:?}")?;
if let Some(escape) = escape {
write!(f, " ESCAPE {escape:?}")?;
}
Ok(())
},
Self::Substring { expr, start, length } => match length {
Some(length) => write!(f, "SUBSTRING({expr} FROM {start} FOR {length})"),
None => write!(f, "SUBSTRING({expr} FROM {start})"),
},
Self::Lower(expr) => write!(f, "LOWER({expr})"),
Self::Upper(expr) => write!(f, "UPPER({expr})"),
Self::Trim { expr, trim_chars } => write!(f, "TRIM({trim_chars:?} FROM {expr})"),
Self::LTrim { expr, trim_chars } => write!(f, "LTRIM({expr}, {trim_chars:?})"),
Self::RTrim { expr, trim_chars } => write!(f, "RTRIM({expr}, {trim_chars:?})"),
Self::StringContains { expr, pattern } => write!(f, "CONTAINS({expr}, {pattern:?})"),
Self::StartsWith { expr, prefix } => write!(f, "STARTS_WITH({expr}, {prefix:?})"),
Self::EndsWith { expr, suffix } => write!(f, "ENDS_WITH({expr}, {suffix:?})"),
Self::StringLength { expr, unit: StringLengthUnit::Characters, output_type } => {
write!(f, "CHARACTER_LENGTH({expr})::{output_type}")
},
Self::StringLength { expr, unit: StringLengthUnit::Bytes, output_type } => {
write!(f, "OCTET_LENGTH({expr})::{output_type}")
},
Self::RegexpReplace { expr, pattern, replacement, output_type } => {
write!(f, "REGEXP_REPLACE({expr}, {pattern:?}, {replacement:?})::{output_type}")
},
Self::Concat(exprs) => {
f.write_str("CONCAT(")?;
for (index, expr) in exprs.iter().enumerate() {
if index > 0 {
f.write_str(", ")?;
}
write!(f, "{expr}")?;
}
f.write_str(")")
},
Self::Abs(expr) => write!(f, "ABS({expr})"),
Self::UnaryMath { op, expr, output_type } => write!(f, "{op}({expr})::{output_type}"),
Self::Round { expr, decimal_places, output_type } => {
write!(f, "ROUND({expr}, {decimal_places})::{output_type}")
},
Self::InList { expr, list, negated } => {
let operator = if *negated { "NOT IN" } else { "IN" };
write!(f, "{expr} {operator} (")?;
for (index, item) in list.iter().enumerate() {
if index > 0 {
f.write_str(", ")?;
}
write!(f, "{item}")?;
}
f.write_str(")")
},
Self::IsDistinctFrom { left, right } => write!(f, "({left} IS DISTINCT FROM {right})"),
Self::IsNotDistinctFrom { left, right } => write!(f, "({left} IS NOT DISTINCT FROM {right})"),
Self::Case { branches, else_expr } => {
f.write_str("CASE")?;
for branch in branches {
write!(f, " WHEN {} THEN {}", branch.when, branch.then)?;
}
write!(f, " ELSE {else_expr} END")
},
Self::Extract { field, expr } => write!(f, "EXTRACT({field} FROM {expr})"),
Self::DateTrunc { field, expr, output_type } => {
write!(f, "DATE_TRUNC({field}, {expr})::{output_type}")
},
Self::Alias { expr, alias } => write!(f, "{expr} AS {alias}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CaseWhen {
pub when: Expr,
pub then: Expr,
}
impl CaseWhen {
pub fn new(when: Expr, then: Expr) -> Self {
Self { when, then }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StringLengthUnit {
Characters,
Bytes,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DatePart {
Year,
Month,
Day,
Hour,
Minute,
Second,
Millisecond,
Microsecond,
Nanosecond,
}
impl DatePart {
pub const EXTRACT_DATE_FIELDS: [Self; 3] = [Self::Year, Self::Month, Self::Day];
pub const EXTRACT_TIME_FIELDS: [Self; 5] =
[Self::Hour, Self::Minute, Self::Second, Self::Millisecond, Self::Microsecond];
pub const EXTRACT_FIELDS: [Self; 8] = [
Self::Year,
Self::Month,
Self::Day,
Self::Hour,
Self::Minute,
Self::Second,
Self::Millisecond,
Self::Microsecond,
];
pub(crate) const POST_JOIN_EXTRACT_FIELDS: [Self; 3] = [Self::Year, Self::Month, Self::Day];
pub const DATE_TRUNC_FIELDS: [Self; 7] =
[Self::Day, Self::Hour, Self::Minute, Self::Second, Self::Millisecond, Self::Microsecond, Self::Nanosecond];
pub fn from_sql_name(name: &str) -> Option<Self> {
[
Self::Year,
Self::Month,
Self::Day,
Self::Hour,
Self::Minute,
Self::Second,
Self::Millisecond,
Self::Microsecond,
Self::Nanosecond,
]
.into_iter()
.find(|field| field.to_string().eq_ignore_ascii_case(name))
}
}
impl fmt::Display for DatePart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Year => "YEAR",
Self::Month => "MONTH",
Self::Day => "DAY",
Self::Hour => "HOUR",
Self::Minute => "MINUTE",
Self::Second => "SECOND",
Self::Millisecond => "MILLISECOND",
Self::Microsecond => "MICROSECOND",
Self::Nanosecond => "NANOSECOND",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UnaryMathOp {
Negate,
Sqrt,
Ln,
Exp,
Floor,
Ceil,
}
impl fmt::Display for UnaryMathOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Negate => "NEGATE",
Self::Sqrt => "SQRT",
Self::Ln => "LN",
Self::Exp => "EXP",
Self::Floor => "FLOOR",
Self::Ceil => "CEIL",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BinaryOp {
Add,
Subtract,
Multiply,
Divide,
Modulo,
BitwiseAnd,
BitwiseOr,
BitwiseXor,
ShiftLeft,
ShiftRight,
Eq,
NotEq,
Lt,
LtEq,
Gt,
GtEq,
And,
Or,
}
impl fmt::Display for BinaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Add => "+",
Self::Subtract => "-",
Self::Multiply => "*",
Self::Divide => "/",
Self::Modulo => "%",
Self::BitwiseAnd => "&",
Self::BitwiseOr => "|",
Self::BitwiseXor => "^",
Self::ShiftLeft => "<<",
Self::ShiftRight => ">>",
Self::Eq => "=",
Self::NotEq => "!=",
Self::Lt => "<",
Self::LtEq => "<=",
Self::Gt => ">",
Self::GtEq => ">=",
Self::And => "AND",
Self::Or => "OR",
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AggregateSpec {
pub group_exprs: Vec<Expr>,
pub aggr_exprs: Vec<AggregateExpr>,
pub grouping_sets: Option<GroupingSetsSpec>,
}
impl AggregateSpec {
pub fn new(group_exprs: Vec<Expr>, aggr_exprs: Vec<AggregateExpr>) -> Self {
Self { group_exprs, aggr_exprs, grouping_sets: None }
}
pub fn with_grouping_sets(mut self, grouping_sets: GroupingSetsSpec) -> Self {
self.grouping_sets = Some(grouping_sets);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GroupingSetsSpec {
pub groups: Vec<Vec<bool>>,
pub grouping_id_alias: String,
}
impl GroupingSetsSpec {
pub fn new(groups: Vec<Vec<bool>>, grouping_id_alias: impl Into<String>) -> Self {
Self { groups, grouping_id_alias: grouping_id_alias.into() }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AggregateExpr {
pub function: AggregateFunction,
pub expr: Expr,
pub alias: String,
pub distinct: bool,
pub filter: Option<Expr>,
}
impl AggregateExpr {
pub fn new(function: AggregateFunction, expr: Expr, alias: impl Into<String>) -> Self {
Self { function, expr, alias: alias.into(), distinct: false, filter: None }
}
pub fn with_distinct(mut self, distinct: bool) -> Self {
self.distinct = distinct;
self
}
pub fn with_filter(mut self, filter: Option<Expr>) -> Self {
self.filter = filter;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AggregateFunction {
Sum,
Min,
Max,
Count,
NUnique,
Avg,
Median,
StddevSamp,
StddevPop,
VarSamp,
VarPop,
}
impl AggregateFunction {
pub const fn is_floating_statistical(self) -> bool {
matches!(self, Self::StddevSamp | Self::StddevPop | Self::VarSamp | Self::VarPop)
}
pub const fn statistical_ddof(self) -> Option<u32> {
match self {
Self::StddevSamp | Self::VarSamp => Some(1),
Self::StddevPop | Self::VarPop => Some(0),
_ => None,
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Sum => "sum",
Self::Min => "min",
Self::Max => "max",
Self::Count => "count",
Self::NUnique => "nunique",
Self::Avg => "avg",
Self::Median => "median",
Self::StddevSamp => "stddev_samp",
Self::StddevPop => "stddev_pop",
Self::VarSamp => "var_samp",
Self::VarPop => "var_pop",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SortSpec {
pub expressions: Vec<SortExpr>,
}
impl SortSpec {
pub fn new(expressions: Vec<SortExpr>) -> Self {
Self { expressions }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SortExpr {
pub expr: Expr,
pub descending: bool,
pub nulls_first: bool,
}
impl SortExpr {
pub fn new(expr: Expr, descending: bool, nulls_first: bool) -> Self {
Self { expr, descending, nulls_first }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LimitSpec {
pub skip: usize,
pub fetch: Option<usize>,
}
impl LimitSpec {
pub fn new(skip: usize, fetch: impl Into<Option<usize>>) -> Self {
Self { skip, fetch: fetch.into() }
}
pub fn fetch(fetch: usize) -> Self {
Self { skip: 0, fetch: Some(fetch) }
}
pub fn offset(skip: usize) -> Self {
Self { skip, fetch: None }
}
}