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::scalar_fn::fns::root::Root;
use crate::stats::rewrite::StatsRewriteCtx;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct BoundExpression {
kind: BoundKind,
dtype: DType,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum BoundKind {
Scalar {
scalar_fn: ScalarFnRef,
children: Arc<Vec<BoundExpression>>,
},
Root,
}
#[derive(Clone, Debug)]
pub struct ExactBoundExpr(pub BoundExpression);
impl PartialEq for ExactBoundExpr {
fn eq(&self, other: &Self) -> bool {
match (&self.0.kind, &other.0.kind) {
(BoundKind::Root, BoundKind::Root) => self.0.dtype == other.0.dtype,
(
BoundKind::Scalar {
scalar_fn: lhs_fn,
children: lhs_children,
},
BoundKind::Scalar {
scalar_fn: rhs_fn,
children: rhs_children,
},
) => {
lhs_fn == rhs_fn
&& Arc::ptr_eq(lhs_children, rhs_children)
&& self.0.dtype == other.0.dtype
}
_ => false,
}
}
}
impl Eq for ExactBoundExpr {}
impl Hash for ExactBoundExpr {
fn hash<H: Hasher>(&self, state: &mut H) {
match &self.0.kind {
BoundKind::Root => state.write_u8(0),
BoundKind::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 {
kind: BoundKind::Root,
dtype,
}
}
pub fn try_new(
scalar_fn: ScalarFnRef,
children: impl IntoIterator<Item = BoundExpression>,
) -> VortexResult<Self> {
let children = Vec::from_iter(children);
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 {
kind: BoundKind::Scalar {
scalar_fn,
children: children.into(),
},
dtype,
})
}
pub fn with_children(
self,
children: impl IntoIterator<Item = BoundExpression>,
) -> VortexResult<Self> {
let children = Vec::from_iter(children);
let BoundKind::Scalar { scalar_fn, .. } = &self.kind else {
vortex_ensure!(
children.is_empty(),
"Root expression cannot have {} children",
children.len()
);
return Ok(self);
};
Self::try_new(scalar_fn.clone(), children)
}
pub fn dtype(&self) -> &DType {
&self.dtype
}
pub fn kind(&self) -> &BoundKind {
&self.kind
}
pub fn children(&self) -> &[BoundExpression] {
match &self.kind {
BoundKind::Scalar { children, .. } => children.as_slice(),
BoundKind::Root => &[],
}
}
pub fn child(&self, index: usize) -> &BoundExpression {
&self.children()[index]
}
pub fn as_scalar(&self) -> Option<&ScalarFnRef> {
match &self.kind {
BoundKind::Scalar { scalar_fn, .. } => Some(scalar_fn),
BoundKind::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.kind, BoundKind::Root)
}
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.kind() {
BoundKind::Scalar { scalar_fn, .. } => scalar_fn.fmt_sql(self, f),
BoundKind::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()?;
BoundExpression::try_new(self.scalar_fn().clone(), children)
}
}
impl Drop for BoundExpression {
fn drop(&mut self) {
let BoundKind::Scalar { children, .. } = &mut self.kind 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 BoundKind::Scalar { children, .. } = &mut child.kind
&& 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_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 (BoundKind::Scalar { children: a, .. }, BoundKind::Scalar { children: b, .. }) =
(bound.kind(), cloned.kind())
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());
}
}