From c077107d5bc3e46b105c6ba120d2e60ae7d04a2a Mon Sep 17 00:00:00 2001 From: Matt Katz Date: Thu, 6 Aug 2026 19:48:30 -0700 Subject: [PATCH 1/4] lambdas and variables Signed-off-by: Matt Katz --- vortex-array/src/expr/analysis/fallible.rs | 31 ++- .../src/expr/analysis/immediate_access.rs | 44 +++- .../expr/analysis/referenced_field_paths.rs | 2 +- vortex-array/src/expr/analysis/strict.rs | 7 +- vortex-array/src/expr/bound_expression.rs | 240 ++++++++++++++++-- vortex-array/src/expr/display.rs | 20 +- vortex-array/src/expr/expression.rs | 118 +++++++-- vortex-array/src/expr/exprs.rs | 19 ++ vortex-array/src/expr/lambda.rs | 74 ++++++ vortex-array/src/expr/mod.rs | 14 + vortex-array/src/expr/optimize.rs | 6 +- vortex-array/src/expr/proto.rs | 42 ++- vortex-array/src/expr/scope.rs | 143 ++++++++++- .../src/expr/transform/bound_partition.rs | 9 +- vortex-array/src/expr/traversal/mod.rs | 8 +- vortex-array/src/expr/variable.rs | 96 +++++++ vortex-array/src/expression.rs | 45 ++-- vortex-layout/src/layouts/chunked/reader.rs | 4 +- vortex-layout/src/layouts/list/reader.rs | 2 +- vortex-layout/src/layouts/partitioned.rs | 2 +- vortex-layout/src/scan/filter.rs | 8 +- 21 files changed, 844 insertions(+), 90 deletions(-) create mode 100644 vortex-array/src/expr/lambda.rs create mode 100644 vortex-array/src/expr/variable.rs diff --git a/vortex-array/src/expr/analysis/fallible.rs b/vortex-array/src/expr/analysis/fallible.rs index ff43d51603b..4b39f9c09b5 100644 --- a/vortex-array/src/expr/analysis/fallible.rs +++ b/vortex-array/src/expr/analysis/fallible.rs @@ -10,8 +10,11 @@ pub fn label_is_fallible(expr: &Expression) -> BooleanLabels<'_> { expr, |expr| match expr { Expression::Scalar { scalar_fn, .. } => scalar_fn.signature().is_fallible(), - // The scope itself cannot fail. - Expression::Root => false, + // These add no fallibility of their own. Note this is the *self* label: a lambda's + // body is one of its children, so the folded label at a lambda node is the body's + // fallibility. A higher-order function therefore picks the body up through the + // ordinary fold instead of walking it by hand. + Expression::Root | Expression::Variable(_) | Expression::Lambda(_) => false, }, |acc, &child| acc | child, ) @@ -82,3 +85,27 @@ mod tests { assert_eq!(labels.get(&expr), Some(&false)); } } + +#[cfg(test)] +mod lambda_tests { + use super::*; + use crate::expr::checked_add; + use crate::expr::lambda; + use crate::expr::lit; + use crate::expr::var; + + /// A lambda contributes no fallibility of its own, but its body is one of its children, so the + /// label at the lambda node is the body's. That is what lets a future higher-order function + /// pick the body up through the ordinary fold rather than walking it by hand. + #[test] + fn a_lambdas_label_is_its_bodys_fallibility() { + let fallible = Expression::from(lambda(["x"], checked_add(var("x"), lit(1i32)))); + assert_eq!(label_is_fallible(&fallible).get(&fallible), Some(&true)); + + let infallible = Expression::from(lambda(["x"], var("x"))); + assert_eq!( + label_is_fallible(&infallible).get(&infallible), + Some(&false) + ); + } +} diff --git a/vortex-array/src/expr/analysis/immediate_access.rs b/vortex-array/src/expr/analysis/immediate_access.rs index 6c2e4975a92..8f97c0e862a 100644 --- a/vortex-array/src/expr/analysis/immediate_access.rs +++ b/vortex-array/src/expr/analysis/immediate_access.rs @@ -67,7 +67,13 @@ pub fn make_bound_free_field_annotator( ) -> impl AnnotationFn { move |expr: &BoundExpression| { let Some(scalar_fn) = expr.as_scalar() else { - return scope.names().iter().cloned().collect(); + // Only the scope root reads every field. A variable resolves against a frame, so it + // reads none of them, and saying otherwise would defeat column pruning. + return if expr.is_root() { + scope.names().iter().cloned().collect() + } else { + vec![] + }; }; if let Some(selection) = scalar_fn.as_opt::