use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::hash::Hash;
use std::hash::Hasher;
use std::sync::Arc;
use itertools::Itertools;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_ensure;
use vortex_session::VortexSession;
use crate::dtype::DType;
use crate::expr::Expression;
use crate::expr::display::DisplayTreeExpr;
use crate::expr::scope::Scope;
use crate::expr::traversal::TraversalOrder;
use crate::expr::traversal::pre_order_visit_down;
use crate::scalar_fn::ScalarFnRef;
use crate::scalar_fn::ScalarFnVTable;
use crate::stats::rewrite::StatsRewriteCtx;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum BoundExpression {
Scalar {
dtype: DType,
scalar_fn: ScalarFnRef,
children: Arc<Vec<BoundExpression>>,
},
Root {
dtype: DType,
},
}
#[derive(Clone, Debug)]
pub struct ExactBoundExpr(pub BoundExpression);
impl PartialEq for ExactBoundExpr {
fn eq(&self, other: &Self) -> bool {
match (&self.0, &other.0) {
(
BoundExpression::Root { dtype: lhs_dtype },
BoundExpression::Root { dtype: rhs_dtype },
) => lhs_dtype == rhs_dtype,
(
BoundExpression::Scalar {
dtype: lhs_dtype,
scalar_fn: lhs_fn,
children: lhs_children,
},
BoundExpression::Scalar {
dtype: rhs_dtype,
scalar_fn: rhs_fn,
children: rhs_children,
},
) => {
lhs_fn == rhs_fn
&& Arc::ptr_eq(lhs_children, rhs_children)
&& lhs_dtype == rhs_dtype
}
_ => false,
}
}
}
impl Eq for ExactBoundExpr {}
impl Hash for ExactBoundExpr {
fn hash<H: Hasher>(&self, state: &mut H) {
match &self.0 {
BoundExpression::Root { .. } => state.write_u8(0),
BoundExpression::Scalar {
scalar_fn,
children,
..
} => {
state.write_u8(1);
scalar_fn.hash(state);
Arc::as_ptr(children).hash(state);
}
}
}
}
impl BoundExpression {
pub fn new_root(dtype: DType) -> Self {
Self::Root { dtype }
}
pub fn try_new(
scalar_fn: ScalarFnRef,
children: impl IntoIterator<Item = BoundExpression>,
) -> VortexResult<Self> {
Self::try_new_vec(scalar_fn, children.into_iter().collect())
}
fn try_new_vec(scalar_fn: ScalarFnRef, children: Vec<BoundExpression>) -> VortexResult<Self> {
vortex_ensure!(
scalar_fn.signature().arity().matches(children.len()),
"Expression arity mismatch: expected {} children but got {}",
scalar_fn.signature().arity(),
children.len()
);
let arg_dtypes = children
.iter()
.map(|child| child.dtype().clone())
.collect_vec();
let dtype = scalar_fn.return_dtype(&arg_dtypes)?;
Ok(Self::Scalar {
dtype,
scalar_fn,
children: children.into(),
})
}
pub fn with_children(
self,
children: impl IntoIterator<Item = BoundExpression>,
) -> VortexResult<Self> {
let children = Vec::from_iter(children);
let BoundExpression::Scalar { scalar_fn, .. } = &self else {
vortex_ensure!(
children.is_empty(),
"Root expression cannot have {} children",
children.len()
);
return Ok(self);
};
Self::try_new_vec(scalar_fn.clone(), children)
}
pub fn dtype(&self) -> &DType {
match self {
Self::Scalar { dtype, .. } | Self::Root { dtype } => dtype,
}
}
pub fn children(&self) -> &[BoundExpression] {
match self {
Self::Scalar { children, .. } => children.as_slice(),
Self::Root { .. } => &[],
}
}
pub fn child(&self, index: usize) -> &BoundExpression {
&self.children()[index]
}
pub fn as_scalar(&self) -> Option<&ScalarFnRef> {
match self {
Self::Scalar { scalar_fn, .. } => Some(scalar_fn),
Self::Root { .. } => None,
}
}
pub fn is<V: ScalarFnVTable>(&self) -> bool {
self.as_scalar().is_some_and(ScalarFnRef::is::<V>)
}
pub fn contains<V: ScalarFnVTable>(&self) -> VortexResult<bool> {
let mut contains = false;
pre_order_visit_down(self, |node| {
if node.is::<V>() {
contains = true;
return Ok(TraversalOrder::Stop);
}
Ok(TraversalOrder::Continue)
})?;
Ok(contains)
}
pub fn as_opt<V: ScalarFnVTable>(&self) -> Option<&V::Options> {
self.as_scalar().and_then(ScalarFnRef::as_opt::<V>)
}
pub fn as_<V: ScalarFnVTable>(&self) -> &V::Options {
self.as_opt::<V>()
.vortex_expect("Bound expression options type mismatch")
}
pub fn is_root(&self) -> bool {
matches!(self, Self::Root { .. })
}
pub fn is_root_bound_to(&self, dtype: &DType) -> bool {
let mut is_bound_to = true;
pre_order_visit_down(self, |node| {
if node.is_root() && node.dtype() != dtype {
is_bound_to = false;
return Ok(TraversalOrder::Stop);
}
Ok(TraversalOrder::Continue)
})
.vortex_expect("bound expression traversal cannot not fail");
is_bound_to
}
pub fn falsify(&self, session: &VortexSession) -> VortexResult<Option<BoundExpression>> {
StatsRewriteCtx::new(session).falsify(self)
}
pub fn satisfy(&self, session: &VortexSession) -> VortexResult<Option<BoundExpression>> {
StatsRewriteCtx::new(session).satisfy(self)
}
pub fn display_tree(&self) -> impl Display {
DisplayTreeExpr(self)
}
}
impl Display for BoundExpression {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Scalar { scalar_fn, .. } => scalar_fn.fmt_sql(self, f),
Self::Root { .. } => f.write_str("$"),
}
}
}
impl Expression {
pub fn bind(&self, dtype: &DType) -> VortexResult<BoundExpression> {
self.bind_scope(&Scope::new(dtype.clone()))
}
pub fn bind_scope(&self, scope: &Scope) -> VortexResult<BoundExpression> {
if self.is_root() {
return Ok(BoundExpression::new_root(scope.root().clone()));
}
let children: Vec<_> = self
.children()
.iter()
.map(|child| child.bind_scope(scope))
.try_collect()?;
let scalar_fn = self
.as_scalar()
.vortex_expect("root was handled above, so this is a scalar node");
BoundExpression::try_new(scalar_fn.clone(), children)
}
}
impl Drop for BoundExpression {
fn drop(&mut self) {
let Self::Scalar { children, .. } = self else {
return;
};
let Some(children) = Arc::get_mut(children) else {
return;
};
let mut to_drop = std::mem::take(children);
while let Some(mut child) = to_drop.pop() {
if let BoundExpression::Scalar { children, .. } = &mut child
&& let Some(grandchildren) = Arc::get_mut(children)
{
to_drop.append(grandchildren);
}
}
}
}
#[cfg(test)]
mod tests {
use vortex_error::VortexResult;
use super::*;
use crate::dtype::Nullability;
use crate::dtype::PType;
use crate::expr::col;
use crate::expr::eq;
use crate::expr::lit;
use crate::expr::root;
use crate::expr::test_harness::struct_dtype;
use crate::scalar_fn::fns::literal::Literal;
fn scope() -> Scope {
Scope::new(struct_dtype())
}
#[test]
fn root_binds_to_the_scope() -> VortexResult<()> {
let bound = root().bind_scope(&scope())?;
assert!(bound.is_root());
assert_eq!(bound.dtype(), &struct_dtype());
assert_eq!(bound, BoundExpression::new_root(struct_dtype()));
Ok(())
}
#[test]
fn every_node_carries_its_dtype() -> VortexResult<()> {
let expr = eq(col("a"), lit(1_i32));
let bound = expr.bind_scope(&scope())?;
assert_eq!(bound.dtype(), &DType::Bool(Nullability::NonNullable));
let lhs = &bound.children()[0];
assert_eq!(
lhs.dtype(),
&DType::Primitive(PType::I32, Nullability::NonNullable)
);
assert_eq!(lhs.children()[0].dtype(), &struct_dtype());
Ok(())
}
#[test]
fn bind_agrees_with_return_dtype() -> VortexResult<()> {
for expr in [root(), col("a"), eq(col("a"), lit(1_i32)), lit(true)] {
assert_eq!(
expr.bind(&struct_dtype())?.dtype(),
&expr.return_dtype(&struct_dtype())?,
"disagreement for {expr}"
);
}
Ok(())
}
#[test]
fn contains_scalar_function() -> VortexResult<()> {
let bound = eq(col("a"), lit(1_i32)).bind_scope(&scope())?;
assert!(bound.contains::<Literal>()?);
assert!(!root().bind_scope(&scope())?.contains::<Literal>()?);
Ok(())
}
#[test]
fn bound_to_checks_every_root() -> VortexResult<()> {
let dtype = struct_dtype();
let bound = eq(col("a"), col("a")).bind(&dtype)?;
assert!(bound.is_root_bound_to(&dtype));
assert!(!bound.is_root_bound_to(&DType::Bool(Nullability::NonNullable)));
assert!(
lit(true)
.bind(&dtype)?
.is_root_bound_to(&DType::Bool(Nullability::NonNullable))
);
Ok(())
}
#[test]
fn bound_display_matches_unbound() -> VortexResult<()> {
for expr in [root(), col("a"), eq(col("a"), lit(1_i32)), lit(true)] {
let bound = expr.bind_scope(&scope())?;
assert_eq!(bound.to_string(), expr.to_string());
assert_eq!(
bound.display_tree().to_string(),
expr.display_tree().to_string()
);
}
Ok(())
}
#[test]
fn clone_shares_children() -> VortexResult<()> {
let bound = eq(col("a"), lit(1_i32)).bind_scope(&scope())?;
let cloned = bound.clone();
let (
BoundExpression::Scalar { children: a, .. },
BoundExpression::Scalar { children: b, .. },
) = (&bound, &cloned)
else {
unreachable!("eq is a scalar node")
};
assert!(Arc::ptr_eq(a, b));
Ok(())
}
#[test]
fn repeated_subtree_is_bound_per_occurrence() -> VortexResult<()> {
let shared = col("a");
let bound = eq(shared.clone(), shared).bind_scope(&scope())?;
let children = bound.children();
assert_eq!(children[0].dtype(), children[1].dtype());
Ok(())
}
#[test]
fn structural_and_exact_equality_are_distinct() -> VortexResult<()> {
let expr = eq(col("a"), lit(1_i32));
let bound = expr.bind_scope(&scope())?;
let independently_bound = expr.bind_scope(&scope())?;
assert_eq!(bound, independently_bound);
assert_eq!(ExactBoundExpr(bound.clone()), ExactBoundExpr(bound.clone()));
assert_ne!(ExactBoundExpr(bound), ExactBoundExpr(independently_bound));
Ok(())
}
#[test]
fn binding_reports_a_type_error() {
let expr = eq(col("a"), lit("nope"));
assert!(expr.bind_scope(&scope()).is_err());
}
}