From c3ba409ca86d2383fe3ba5f68a398f11d62e7129 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Sun, 17 May 2026 17:40:53 +0100 Subject: [PATCH 1/7] Thread scope dtype through stats rewrites Signed-off-by: Nicholas Gates --- vortex-array/public-api.lock | 4 +-- vortex-array/src/expr/expression.rs | 20 ++++++++--- vortex-array/src/stats/rewrite.rs | 52 +++++++++++++++++++++++++---- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/vortex-array/public-api.lock b/vortex-array/public-api.lock index 9609ce9d79d..472035a9c79 100644 --- a/vortex-array/public-api.lock +++ b/vortex-array/public-api.lock @@ -12918,13 +12918,13 @@ pub fn vortex_array::expr::Expression::children(&self) -> &alloc::sync::Arc impl core::fmt::Display -pub fn vortex_array::expr::Expression::falsify(&self, &vortex_session::VortexSession) -> vortex_error::VortexResult> +pub fn vortex_array::expr::Expression::falsify(&self, &vortex_array::dtype::DType, &vortex_session::VortexSession) -> vortex_error::VortexResult> pub fn vortex_array::expr::Expression::fmt_sql(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result pub fn vortex_array::expr::Expression::return_dtype(&self, &vortex_array::dtype::DType) -> vortex_error::VortexResult -pub fn vortex_array::expr::Expression::satisfy(&self, &vortex_session::VortexSession) -> vortex_error::VortexResult> +pub fn vortex_array::expr::Expression::satisfy(&self, &vortex_array::dtype::DType, &vortex_session::VortexSession) -> vortex_error::VortexResult> pub fn vortex_array::expr::Expression::scalar_fn(&self) -> &vortex_array::scalar_fn::ScalarFnRef diff --git a/vortex-array/src/expr/expression.rs b/vortex-array/src/expr/expression.rs index 10b4389f6e8..cc21fb9a9a6 100644 --- a/vortex-array/src/expr/expression.rs +++ b/vortex-array/src/expr/expression.rs @@ -138,18 +138,30 @@ impl Expression { /// Returns an expression that proves this predicate is definitely false from stats. /// + /// `scope` is the dtype of the row this expression evaluates over. + /// /// If the returned expression evaluates to `true` for a stats scope, this expression is /// guaranteed to be false for every row in that scope. `false` and `null` are unknown. - pub fn falsify(&self, session: &VortexSession) -> VortexResult> { - crate::stats::rewrite::StatsRewriteCtx::new(session).falsify(self) + pub fn falsify( + &self, + scope: &DType, + session: &VortexSession, + ) -> VortexResult> { + crate::stats::rewrite::StatsRewriteCtx::new(session, scope).falsify(self) } /// Returns an expression that proves this predicate is definitely true from stats. /// + /// `scope` is the dtype of the row this expression evaluates over. + /// /// If the returned expression evaluates to `true` for a stats scope, this expression is /// guaranteed to be true for every row in that scope. `false` and `null` are unknown. - pub fn satisfy(&self, session: &VortexSession) -> VortexResult> { - crate::stats::rewrite::StatsRewriteCtx::new(session).satisfy(self) + pub fn satisfy( + &self, + scope: &DType, + session: &VortexSession, + ) -> VortexResult> { + crate::stats::rewrite::StatsRewriteCtx::new(session, scope).satisfy(self) } /// Returns an expression representing the zoned statistic for the given stat, if available. diff --git a/vortex-array/src/stats/rewrite.rs b/vortex-array/src/stats/rewrite.rs index 0eacc2d6629..4d829905d6f 100644 --- a/vortex-array/src/stats/rewrite.rs +++ b/vortex-array/src/stats/rewrite.rs @@ -7,8 +7,10 @@ use std::fmt::Debug; use std::sync::Arc; use vortex_error::VortexResult; +use vortex_error::vortex_ensure; use vortex_session::VortexSession; +use crate::dtype::DType; use crate::expr::Expression; use crate::expr::or_collect; use crate::scalar_fn::ScalarFnId; @@ -54,12 +56,13 @@ pub(crate) trait StatsRewriteRule: Debug + Send + Sync + 'static { /// Context passed to stats rewrite rules. pub(crate) struct StatsRewriteCtx<'a> { session: &'a VortexSession, + scope: &'a DType, } impl<'a> StatsRewriteCtx<'a> { /// Create a rewrite context for `session`. - pub(crate) fn new(session: &'a VortexSession) -> Self { - Self { session } + pub(crate) fn new(session: &'a VortexSession, scope: &'a DType) -> Self { + Self { session, scope } } /// Returns the session that owns the rewrite registry. @@ -67,15 +70,31 @@ impl<'a> StatsRewriteCtx<'a> { self.session } + /// Return the dtype of `expr` within this rewrite scope. + pub(crate) fn return_dtype(&self, expr: &Expression) -> VortexResult { + expr.return_dtype(self.scope) + } + /// Rewrite `expr` into a stats-backed falsifier. pub(crate) fn falsify(&self, expr: &Expression) -> VortexResult> { + self.ensure_predicate(expr)?; rewrite(expr, self, StatsRewriteRule::falsify) } /// Rewrite `expr` into a stats-backed satisfier. pub(crate) fn satisfy(&self, expr: &Expression) -> VortexResult> { + self.ensure_predicate(expr)?; rewrite(expr, self, StatsRewriteRule::satisfy) } + + fn ensure_predicate(&self, expr: &Expression) -> VortexResult<()> { + let dtype = self.return_dtype(expr)?; + vortex_ensure!( + matches!(dtype, DType::Bool(_)), + "Stats rewrites require a boolean predicate, got {dtype}", + ); + Ok(()) + } } fn rewrite( @@ -112,6 +131,9 @@ mod tests { use super::StatsRewriteCtx; use super::StatsRewriteRule; + use crate::dtype::DType; + use crate::dtype::Nullability; + use crate::dtype::PType; use crate::expr::Expression; use crate::expr::lit; use crate::expr::or; @@ -152,6 +174,7 @@ mod tests { #[test] fn combines_multiple_falsifiers_with_or() -> VortexResult<()> { let session = VortexSession::empty().with::(); + let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); session.stats_rewrites().register(StaticLiteralRule { falsifier: Some(lit(false)), satisfier: None, @@ -161,13 +184,17 @@ mod tests { satisfier: None, }); - assert_eq!(lit(7).falsify(&session)?, Some(or(lit(false), lit(true)))); + assert_eq!( + lit(true).falsify(&dtype, &session)?, + Some(or(lit(false), lit(true))) + ); Ok(()) } #[test] fn combines_multiple_satisfiers_with_or() -> VortexResult<()> { let session = VortexSession::empty().with::(); + let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); session.stats_rewrites().register(StaticLiteralRule { falsifier: None, satisfier: Some(lit(false)), @@ -177,16 +204,29 @@ mod tests { satisfier: Some(lit(true)), }); - assert_eq!(lit(7).satisfy(&session)?, Some(or(lit(false), lit(true)))); + assert_eq!( + lit(true).satisfy(&dtype, &session)?, + Some(or(lit(false), lit(true))) + ); Ok(()) } #[test] fn unregistered_expression_has_no_rewrite() -> VortexResult<()> { let session = VortexSession::empty().with::(); + let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); - assert_eq!(lit(7).falsify(&session)?, None); - assert_eq!(lit(7).satisfy(&session)?, None); + assert_eq!(lit(true).falsify(&dtype, &session)?, None); + assert_eq!(lit(true).satisfy(&dtype, &session)?, None); Ok(()) } + + #[test] + fn non_predicate_expression_errors() { + let session = VortexSession::empty().with::(); + let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); + + assert!(lit(7).falsify(&dtype, &session).is_err()); + assert!(lit(7).satisfy(&dtype, &session).is_err()); + } } From 5c04e3d7d694abf47e37d6dd596b78a6c36ee671 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Sun, 17 May 2026 17:47:27 +0100 Subject: [PATCH 2/7] Rename stats rewrite session Signed-off-by: Nicholas Gates --- vortex-array/public-api.lock | 32 ++++++++++++------------- vortex-array/src/stats/rewrite.rs | 26 ++++++++++----------- vortex-array/src/stats/session.rs | 39 +++++++++++++++++-------------- vortex/src/lib.rs | 4 ++-- 4 files changed, 52 insertions(+), 49 deletions(-) diff --git a/vortex-array/public-api.lock b/vortex-array/public-api.lock index 472035a9c79..51733789a4b 100644 --- a/vortex-array/public-api.lock +++ b/vortex-array/public-api.lock @@ -20158,21 +20158,21 @@ pub mod vortex_array::stats::flatbuffers pub mod vortex_array::stats::session -pub struct vortex_array::stats::session::StatsRewriteSession +pub struct vortex_array::stats::session::StatsSession -impl core::default::Default for vortex_array::stats::StatsRewriteSession +impl core::default::Default for vortex_array::stats::StatsSession -pub fn vortex_array::stats::StatsRewriteSession::default() -> vortex_array::stats::StatsRewriteSession +pub fn vortex_array::stats::StatsSession::default() -> vortex_array::stats::StatsSession -impl core::fmt::Debug for vortex_array::stats::StatsRewriteSession +impl core::fmt::Debug for vortex_array::stats::StatsSession -pub fn vortex_array::stats::StatsRewriteSession::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +pub fn vortex_array::stats::StatsSession::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result -impl vortex_session::SessionVar for vortex_array::stats::StatsRewriteSession +impl vortex_session::SessionVar for vortex_array::stats::StatsSession -pub fn vortex_array::stats::StatsRewriteSession::as_any(&self) -> &dyn core::any::Any +pub fn vortex_array::stats::StatsSession::as_any(&self) -> &dyn core::any::Any -pub fn vortex_array::stats::StatsRewriteSession::as_any_mut(&mut self) -> &mut dyn core::any::Any +pub fn vortex_array::stats::StatsSession::as_any_mut(&mut self) -> &mut dyn core::any::Any pub struct vortex_array::stats::ArrayStats @@ -20234,21 +20234,21 @@ pub fn vortex_array::stats::MutTypedStatsSetRef<'_, '_>::is_empty(&self) -> bool pub fn vortex_array::stats::MutTypedStatsSetRef<'_, '_>::len(&self) -> usize -pub struct vortex_array::stats::StatsRewriteSession +pub struct vortex_array::stats::StatsSession -impl core::default::Default for vortex_array::stats::StatsRewriteSession +impl core::default::Default for vortex_array::stats::StatsSession -pub fn vortex_array::stats::StatsRewriteSession::default() -> vortex_array::stats::StatsRewriteSession +pub fn vortex_array::stats::StatsSession::default() -> vortex_array::stats::StatsSession -impl core::fmt::Debug for vortex_array::stats::StatsRewriteSession +impl core::fmt::Debug for vortex_array::stats::StatsSession -pub fn vortex_array::stats::StatsRewriteSession::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +pub fn vortex_array::stats::StatsSession::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result -impl vortex_session::SessionVar for vortex_array::stats::StatsRewriteSession +impl vortex_session::SessionVar for vortex_array::stats::StatsSession -pub fn vortex_array::stats::StatsRewriteSession::as_any(&self) -> &dyn core::any::Any +pub fn vortex_array::stats::StatsSession::as_any(&self) -> &dyn core::any::Any -pub fn vortex_array::stats::StatsRewriteSession::as_any_mut(&mut self) -> &mut dyn core::any::Any +pub fn vortex_array::stats::StatsSession::as_any_mut(&mut self) -> &mut dyn core::any::Any pub struct vortex_array::stats::StatsSet diff --git a/vortex-array/src/stats/rewrite.rs b/vortex-array/src/stats/rewrite.rs index 4d829905d6f..98c9c01f894 100644 --- a/vortex-array/src/stats/rewrite.rs +++ b/vortex-array/src/stats/rewrite.rs @@ -14,7 +14,7 @@ use crate::dtype::DType; use crate::expr::Expression; use crate::expr::or_collect; use crate::scalar_fn::ScalarFnId; -use crate::stats::session::StatsRewriteSessionExt; +use crate::stats::session::StatsSessionExt; /// Shared reference to a stats rewrite rule. pub(crate) type StatsRewriteRuleRef = Arc; @@ -108,8 +108,8 @@ fn rewrite( ) -> VortexResult> { let rules = ctx .session() - .stats_rewrites() - .rules_for(expr.scalar_fn().id()); + .stats() + .rewrite_rules_for(expr.scalar_fn().id()); let Some(rules) = rules else { return Ok(None); }; @@ -140,8 +140,8 @@ mod tests { use crate::scalar_fn::ScalarFnId; use crate::scalar_fn::ScalarFnVTable; use crate::scalar_fn::fns::literal::Literal; - use crate::stats::session::StatsRewriteSession; - use crate::stats::session::StatsRewriteSessionExt; + use crate::stats::session::StatsSession; + use crate::stats::session::StatsSessionExt; #[derive(Debug)] struct StaticLiteralRule { @@ -173,13 +173,13 @@ mod tests { #[test] fn combines_multiple_falsifiers_with_or() -> VortexResult<()> { - let session = VortexSession::empty().with::(); + let session = VortexSession::empty().with::(); let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); - session.stats_rewrites().register(StaticLiteralRule { + session.stats().register_rewrite(StaticLiteralRule { falsifier: Some(lit(false)), satisfier: None, }); - session.stats_rewrites().register(StaticLiteralRule { + session.stats().register_rewrite(StaticLiteralRule { falsifier: Some(lit(true)), satisfier: None, }); @@ -193,13 +193,13 @@ mod tests { #[test] fn combines_multiple_satisfiers_with_or() -> VortexResult<()> { - let session = VortexSession::empty().with::(); + let session = VortexSession::empty().with::(); let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); - session.stats_rewrites().register(StaticLiteralRule { + session.stats().register_rewrite(StaticLiteralRule { falsifier: None, satisfier: Some(lit(false)), }); - session.stats_rewrites().register(StaticLiteralRule { + session.stats().register_rewrite(StaticLiteralRule { falsifier: None, satisfier: Some(lit(true)), }); @@ -213,7 +213,7 @@ mod tests { #[test] fn unregistered_expression_has_no_rewrite() -> VortexResult<()> { - let session = VortexSession::empty().with::(); + let session = VortexSession::empty().with::(); let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); assert_eq!(lit(true).falsify(&dtype, &session)?, None); @@ -223,7 +223,7 @@ mod tests { #[test] fn non_predicate_expression_errors() { - let session = VortexSession::empty().with::(); + let session = VortexSession::empty().with::(); let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); assert!(lit(7).falsify(&dtype, &session).is_err()); diff --git a/vortex-array/src/stats/session.rs b/vortex-array/src/stats/session.rs index da9fe9dc786..8b1dc639441 100644 --- a/vortex-array/src/stats/session.rs +++ b/vortex-array/src/stats/session.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Session state for stats rewrite rules. +//! Session state for stats APIs. use std::any::Any; use std::sync::Arc; @@ -18,23 +18,23 @@ use crate::stats::rewrite::StatsRewriteRuleRef; type StatsRewriteRuleSet = Arc<[StatsRewriteRuleRef]>; -/// Session state for stats rewrite rules. +/// Session state for stats APIs. #[derive(Debug, Default)] -pub struct StatsRewriteSession { - rules: RwLock>, +pub struct StatsSession { + rewrite_rules: RwLock>, } -impl StatsRewriteSession { +impl StatsSession { /// Register a stats rewrite rule. #[allow(dead_code)] - pub(crate) fn register(&self, rule: R) { - self.register_ref(Arc::new(rule)); + pub(crate) fn register_rewrite(&self, rule: R) { + self.register_rewrite_ref(Arc::new(rule)); } /// Register a shared stats rewrite rule. #[allow(dead_code)] - pub(crate) fn register_ref(&self, rule: StatsRewriteRuleRef) { - let mut rules = self.rules.write(); + pub(crate) fn register_rewrite_ref(&self, rule: StatsRewriteRuleRef) { + let mut rules = self.rewrite_rules.write(); let rule_id = rule.scalar_fn_id(); let mut updated_rules = rules .get(&rule_id) @@ -45,12 +45,15 @@ impl StatsRewriteSession { } /// Return the rewrite rules registered for `scalar_fn_id`. - pub(crate) fn rules_for(&self, scalar_fn_id: ScalarFnId) -> Option { - self.rules.read().get(&scalar_fn_id).cloned() + pub(crate) fn rewrite_rules_for( + &self, + scalar_fn_id: ScalarFnId, + ) -> Option { + self.rewrite_rules.read().get(&scalar_fn_id).cloned() } } -impl SessionVar for StatsRewriteSession { +impl SessionVar for StatsSession { fn as_any(&self) -> &dyn Any { self } @@ -60,11 +63,11 @@ impl SessionVar for StatsRewriteSession { } } -/// Extension trait for accessing stats rewrite session data. -pub(crate) trait StatsRewriteSessionExt: SessionExt { - /// Returns the stats rewrite rule registry. - fn stats_rewrites(&self) -> Ref<'_, StatsRewriteSession> { - self.get::() +/// Extension trait for accessing stats session data. +pub(crate) trait StatsSessionExt: SessionExt { + /// Returns the stats session state. + fn stats(&self) -> Ref<'_, StatsSession> { + self.get::() } } -impl StatsRewriteSessionExt for S {} +impl StatsSessionExt for S {} diff --git a/vortex/src/lib.rs b/vortex/src/lib.rs index 3da75c68a3b..8668de339cb 100644 --- a/vortex/src/lib.rs +++ b/vortex/src/lib.rs @@ -16,7 +16,7 @@ use vortex_array::optimizer::kernels::ArrayKernels; pub use vortex_array::scalar_fn; use vortex_array::scalar_fn::session::ScalarFnSession; use vortex_array::session::ArraySession; -use vortex_array::stats::session::StatsRewriteSession; +use vortex_array::stats::session::StatsSession; use vortex_io::session::RuntimeSession; use vortex_layout::session::LayoutSession; use vortex_session::VortexSession; @@ -168,7 +168,7 @@ impl VortexSessionDefault for VortexSession { .with::() .with::() .with::() - .with::() + .with::() .with::() .with::() .with::(); From f12fcece3837ae4892ba9c185291feb776eff807 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Thu, 14 May 2026 23:18:43 +0100 Subject: [PATCH 3/7] Add pruning aggregate functions Signed-off-by: Nicholas Gates --- vortex-array/public-api.lock | 752 ++++++++++++++++++ vortex-array/src/aggregate_fn/accumulator.rs | 21 +- .../src/aggregate_fn/fns/all_nan/mod.rs | 184 +++++ .../src/aggregate_fn/fns/all_non_nan/mod.rs | 161 ++++ .../src/aggregate_fn/fns/all_non_null/mod.rs | 139 ++++ .../src/aggregate_fn/fns/all_null/mod.rs | 142 ++++ .../src/aggregate_fn/fns/bounded_max/mod.rs | 291 +++++++ .../src/aggregate_fn/fns/bounded_min/mod.rs | 264 ++++++ vortex-array/src/aggregate_fn/fns/max/mod.rs | 173 ++++ vortex-array/src/aggregate_fn/fns/min/mod.rs | 194 +++++ .../src/aggregate_fn/fns/min_max/mod.rs | 2 +- vortex-array/src/aggregate_fn/fns/mod.rs | 8 + vortex-array/src/aggregate_fn/session.rs | 16 + vortex-array/src/expr/stats/mod.rs | 12 +- vortex-array/src/scalar_fn/fns/stat.rs | 73 +- .../src/scalar_fn/internal/row_count.rs | 6 + vortex-array/src/stats/expr.rs | 204 +++++ vortex-array/src/stats/mod.rs | 4 + 18 files changed, 2627 insertions(+), 19 deletions(-) create mode 100644 vortex-array/src/aggregate_fn/fns/all_nan/mod.rs create mode 100644 vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs create mode 100644 vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs create mode 100644 vortex-array/src/aggregate_fn/fns/all_null/mod.rs create mode 100644 vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs create mode 100644 vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs create mode 100644 vortex-array/src/aggregate_fn/fns/max/mod.rs create mode 100644 vortex-array/src/aggregate_fn/fns/min/mod.rs diff --git a/vortex-array/public-api.lock b/vortex-array/public-api.lock index 51733789a4b..67ac7686457 100644 --- a/vortex-array/public-api.lock +++ b/vortex-array/public-api.lock @@ -174,6 +174,54 @@ pub type vortex_array::aggregate_fn::combined::CombinedOptions = vortex_array pub mod vortex_array::aggregate_fn::fns +pub mod vortex_array::aggregate_fn::fns::all_nan + +pub struct vortex_array::aggregate_fn::fns::all_nan::AllNan + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::all_nan::AllNan + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::clone(&self) -> vortex_array::aggregate_fn::fns::all_nan::AllNan + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::all_nan::AllNan + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::all_nan::AllNan + +pub type vortex_array::aggregate_fn::fns::all_nan::AllNan::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::all_nan::AllNan::Partial = bool + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + pub mod vortex_array::aggregate_fn::fns::all_non_distinct pub struct vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct @@ -226,6 +274,306 @@ pub struct vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinctPart pub fn vortex_array::aggregate_fn::fns::all_non_distinct::all_non_distinct(&vortex_array::ArrayRef, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult +pub mod vortex_array::aggregate_fn::fns::all_non_nan + +pub struct vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::clone(&self) -> vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan + +pub type vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::Partial = bool + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +pub mod vortex_array::aggregate_fn::fns::all_non_null + +pub struct vortex_array::aggregate_fn::fns::all_non_null::AllNonNull + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::all_non_null::AllNonNull + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::clone(&self) -> vortex_array::aggregate_fn::fns::all_non_null::AllNonNull + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::all_non_null::AllNonNull + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::all_non_null::AllNonNull + +pub type vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::Partial = bool + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +pub mod vortex_array::aggregate_fn::fns::all_null + +pub struct vortex_array::aggregate_fn::fns::all_null::AllNull + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::all_null::AllNull + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::clone(&self) -> vortex_array::aggregate_fn::fns::all_null::AllNull + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::all_null::AllNull + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::all_null::AllNull + +pub type vortex_array::aggregate_fn::fns::all_null::AllNull::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::all_null::AllNull::Partial = bool + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +pub mod vortex_array::aggregate_fn::fns::bounded_max + +pub struct vortex_array::aggregate_fn::fns::bounded_max::BoundedMax + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::bounded_max::BoundedMax + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::clone(&self) -> vortex_array::aggregate_fn::fns::bounded_max::BoundedMax + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::bounded_max::BoundedMax + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::bounded_max::BoundedMax + +pub type vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::Options = vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions + +pub type vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::Partial = vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxPartial + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +pub struct vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions + +pub vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions::max_bytes: usize + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions::clone(&self) -> vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions + +impl core::cmp::Eq for vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions + +impl core::cmp::PartialEq for vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions::eq(&self, &vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions) -> bool + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl core::fmt::Display for vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl core::hash::Hash for vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions::hash<__H: core::hash::Hasher>(&self, &mut __H) + +impl core::marker::StructuralPartialEq for vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions + +pub struct vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxPartial + +pub mod vortex_array::aggregate_fn::fns::bounded_min + +pub struct vortex_array::aggregate_fn::fns::bounded_min::BoundedMin + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::bounded_min::BoundedMin + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::clone(&self) -> vortex_array::aggregate_fn::fns::bounded_min::BoundedMin + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::bounded_min::BoundedMin + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::bounded_min::BoundedMin + +pub type vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::Options = vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions + +pub type vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::Partial = vortex_array::aggregate_fn::fns::bounded_min::BoundedMinPartial + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +pub struct vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions + +pub vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions::max_bytes: usize + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions::clone(&self) -> vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions + +impl core::cmp::Eq for vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions + +impl core::cmp::PartialEq for vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions::eq(&self, &vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions) -> bool + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl core::fmt::Display for vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl core::hash::Hash for vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions::hash<__H: core::hash::Hasher>(&self, &mut __H) + +impl core::marker::StructuralPartialEq for vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions + +pub struct vortex_array::aggregate_fn::fns::bounded_min::BoundedMinPartial + pub mod vortex_array::aggregate_fn::fns::count pub struct vortex_array::aggregate_fn::fns::count::Count @@ -538,6 +886,56 @@ pub struct vortex_array::aggregate_fn::fns::last::LastPartial pub fn vortex_array::aggregate_fn::fns::last::last(&vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult +pub mod vortex_array::aggregate_fn::fns::max + +pub struct vortex_array::aggregate_fn::fns::max::Max + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::max::Max + +pub fn vortex_array::aggregate_fn::fns::max::Max::clone(&self) -> vortex_array::aggregate_fn::fns::max::Max + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::max::Max + +pub fn vortex_array::aggregate_fn::fns::max::Max::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::max::Max + +pub type vortex_array::aggregate_fn::fns::max::Max::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::max::Max::Partial = vortex_array::aggregate_fn::fns::max::MaxPartial + +pub fn vortex_array::aggregate_fn::fns::max::Max::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::max::Max::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::max::Max::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::max::Max::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::max::Max::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::max::Max::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::max::Max::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::max::Max::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::max::Max::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +pub struct vortex_array::aggregate_fn::fns::max::MaxPartial + pub mod vortex_array::aggregate_fn::fns::mean pub struct vortex_array::aggregate_fn::fns::mean::Mean @@ -586,6 +984,56 @@ pub fn vortex_array::aggregate_fn::fns::mean::Mean::serialize(&self, &vortex_arr pub fn vortex_array::aggregate_fn::fns::mean::mean(&vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult +pub mod vortex_array::aggregate_fn::fns::min + +pub struct vortex_array::aggregate_fn::fns::min::Min + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::min::Min + +pub fn vortex_array::aggregate_fn::fns::min::Min::clone(&self) -> vortex_array::aggregate_fn::fns::min::Min + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::min::Min + +pub fn vortex_array::aggregate_fn::fns::min::Min::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::min::Min + +pub type vortex_array::aggregate_fn::fns::min::Min::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::min::Min::Partial = vortex_array::aggregate_fn::fns::min::MinPartial + +pub fn vortex_array::aggregate_fn::fns::min::Min::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::min::Min::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::min::Min::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::min::Min::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::min::Min::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::min::Min::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::min::Min::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::min::Min::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::min::Min::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +pub struct vortex_array::aggregate_fn::fns::min::MinPartial + pub mod vortex_array::aggregate_fn::fns::min_max pub struct vortex_array::aggregate_fn::fns::min_max::MinMax @@ -1138,6 +1586,42 @@ pub fn vortex_array::aggregate_fn::AggregateFnVTable::to_scalar(&self, &Self::Pa pub fn vortex_array::aggregate_fn::AggregateFnVTable::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::all_nan::AllNan + +pub type vortex_array::aggregate_fn::fns::all_nan::AllNan::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::all_nan::AllNan::Partial = bool + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct pub type vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::Options = vortex_array::aggregate_fn::EmptyOptions @@ -1174,6 +1658,186 @@ pub fn vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::to_sca pub fn vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan + +pub type vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::Partial = bool + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::all_non_null::AllNonNull + +pub type vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::Partial = bool + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::all_null::AllNull + +pub type vortex_array::aggregate_fn::fns::all_null::AllNull::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::all_null::AllNull::Partial = bool + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::bounded_max::BoundedMax + +pub type vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::Options = vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions + +pub type vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::Partial = vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxPartial + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::bounded_min::BoundedMin + +pub type vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::Options = vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions + +pub type vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::Partial = vortex_array::aggregate_fn::fns::bounded_min::BoundedMinPartial + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::count::Count pub type vortex_array::aggregate_fn::fns::count::Count::Options = vortex_array::aggregate_fn::EmptyOptions @@ -1354,6 +2018,78 @@ pub fn vortex_array::aggregate_fn::fns::last::Last::to_scalar(&self, &Self::Part pub fn vortex_array::aggregate_fn::fns::last::Last::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::max::Max + +pub type vortex_array::aggregate_fn::fns::max::Max::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::max::Max::Partial = vortex_array::aggregate_fn::fns::max::MaxPartial + +pub fn vortex_array::aggregate_fn::fns::max::Max::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::max::Max::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::max::Max::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::max::Max::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::max::Max::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::max::Max::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::max::Max::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::max::Max::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::max::Max::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::max::Max::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::min::Min + +pub type vortex_array::aggregate_fn::fns::min::Min::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::min::Min::Partial = vortex_array::aggregate_fn::fns::min::MinPartial + +pub fn vortex_array::aggregate_fn::fns::min::Min::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::min::Min::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::min::Min::deserialize(&self, &[u8], &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::empty_partial(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::finalize(&self, vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::finalize_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::min::Min::is_saturated(&self, &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::min::Min::partial_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::min::Min::reset(&self, &mut Self::Partial) + +pub fn vortex_array::aggregate_fn::fns::min::Min::return_dtype(&self, &Self::Options, &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::min::Min::serialize(&self, &Self::Options) -> vortex_error::VortexResult>> + +pub fn vortex_array::aggregate_fn::fns::min::Min::to_scalar(&self, &Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::min::Min::try_accumulate(&self, &mut Self::Partial, &vortex_array::ArrayRef, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::min_max::MinMax pub type vortex_array::aggregate_fn::fns::min_max::MinMax::Options = vortex_array::aggregate_fn::EmptyOptions @@ -20144,6 +20880,14 @@ pub fn vortex_array::scalar_fn::fns::stat::StatOptions::hash<__H: core::hash::Ha impl core::marker::StructuralPartialEq for vortex_array::scalar_fn::fns::stat::StatOptions +pub fn vortex_array::stats::expr::all_nan(vortex_array::expr::Expression) -> vortex_array::expr::Expression + +pub fn vortex_array::stats::expr::all_non_nan(vortex_array::expr::Expression) -> vortex_array::expr::Expression + +pub fn vortex_array::stats::expr::all_non_null(vortex_array::expr::Expression) -> vortex_array::expr::Expression + +pub fn vortex_array::stats::expr::all_null(vortex_array::expr::Expression) -> vortex_array::expr::Expression + pub fn vortex_array::stats::expr::min_max(vortex_array::expr::Expression) -> vortex_array::expr::Expression pub fn vortex_array::stats::expr::nan_count(vortex_array::expr::Expression) -> vortex_array::expr::Expression @@ -20418,6 +21162,14 @@ pub fn vortex_array::stats::TypedStatsSetRef<'_, '_>::len(&self) -> usize pub const vortex_array::stats::PRUNING_STATS: &[vortex_array::expr::stats::Stat] +pub fn vortex_array::stats::all_nan(vortex_array::expr::Expression) -> vortex_array::expr::Expression + +pub fn vortex_array::stats::all_non_nan(vortex_array::expr::Expression) -> vortex_array::expr::Expression + +pub fn vortex_array::stats::all_non_null(vortex_array::expr::Expression) -> vortex_array::expr::Expression + +pub fn vortex_array::stats::all_null(vortex_array::expr::Expression) -> vortex_array::expr::Expression + pub fn vortex_array::stats::as_stat_bitset_bytes(&[vortex_array::expr::stats::Stat]) -> alloc::vec::Vec pub fn vortex_array::stats::min_max(vortex_array::expr::Expression) -> vortex_array::expr::Expression diff --git a/vortex-array/src/aggregate_fn/accumulator.rs b/vortex-array/src/aggregate_fn/accumulator.rs index ddc3bd9a86c..b3685d38884 100644 --- a/vortex-array/src/aggregate_fn/accumulator.rs +++ b/vortex-array/src/aggregate_fn/accumulator.rs @@ -124,14 +124,19 @@ impl DynAccumulator for Accumulator { if let Some(stat) = Stat::from_aggregate_fn(&self.aggregate_fn) && let Some(Precision::Exact(partial)) = batch.statistics().get(stat) { - vortex_ensure!( - partial.dtype() == &self.partial_dtype, - "Aggregate {} read legacy stat {} with dtype {}, expected {}", - self.aggregate_fn, - stat, - partial.dtype(), - self.partial_dtype, - ); + let partial = if partial.dtype() == &self.partial_dtype { + partial + } else { + vortex_ensure!( + partial.dtype().eq_ignore_nullability(&self.partial_dtype), + "Aggregate {} read legacy stat {} with dtype {}, expected {}", + self.aggregate_fn, + stat, + partial.dtype(), + self.partial_dtype, + ); + partial.cast(&self.partial_dtype)? + }; self.vtable.combine_partials(&mut self.partial, partial)?; return Ok(()); } diff --git a/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs b/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs new file mode 100644 index 00000000000..50271111877 --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_error::VortexResult; + +use crate::ArrayRef; +use crate::Columnar; +use crate::ExecutionCtx; +use crate::IntoArray; +use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::EmptyOptions; +use crate::aggregate_fn::fns::nan_count::nan_count; +use crate::dtype::DType; +use crate::dtype::Nullability; +use crate::scalar::Scalar; + +/// Compute whether every value in an array is NaN. +/// +/// This is a pruning aggregate, not just a convenience wrapper around +/// [`NanCount`][crate::aggregate_fn::fns::nan_count::NanCount]. Pruning aggregates must prove a +/// row-wise fact for every value in the scope, so their partials remain valid when a stats column is +/// sliced or concatenated alongside the data. [`NanCount`][crate::aggregate_fn::fns::nan_count::NanCount] +/// carries cross-row count information instead, so it is useful as a legacy storage format but not +/// as the pruning expression itself. +#[derive(Clone, Debug)] +pub struct AllNan; + +impl AggregateFnVTable for AllNan { + type Options = EmptyOptions; + type Partial = bool; + + fn id(&self) -> AggregateFnId { + AggregateFnId::new("vortex.all_nan") + } + + fn serialize(&self, _options: &Self::Options) -> VortexResult>> { + Ok(None) + } + + fn return_dtype(&self, _options: &Self::Options, _input_dtype: &DType) -> Option { + Some(DType::Bool(Nullability::NonNullable)) + } + + fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { + self.return_dtype(options, input_dtype) + } + + fn empty_partial( + &self, + _options: &Self::Options, + input_dtype: &DType, + ) -> VortexResult { + Ok(has_nans(input_dtype)) + } + + fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> { + *partial &= bool::try_from(&other)?; + Ok(()) + } + + fn to_scalar(&self, partial: &Self::Partial) -> VortexResult { + Ok(Scalar::bool(*partial, Nullability::NonNullable)) + } + + fn reset(&self, partial: &mut Self::Partial) { + *partial = true; + } + + fn is_saturated(&self, partial: &Self::Partial) -> bool { + !*partial + } + + fn try_accumulate( + &self, + state: &mut Self::Partial, + batch: &ArrayRef, + ctx: &mut ExecutionCtx, + ) -> VortexResult { + if !has_nans(batch.dtype()) { + *state = false; + return Ok(true); + } + + *state &= nan_count(batch, ctx)? == batch.len(); + Ok(true) + } + + fn accumulate( + &self, + partial: &mut Self::Partial, + batch: &Columnar, + ctx: &mut ExecutionCtx, + ) -> VortexResult<()> { + let array = match batch { + Columnar::Constant(c) => c.clone().into_array(), + Columnar::Canonical(c) => c.clone().into_array(), + }; + if !has_nans(array.dtype()) { + *partial = false; + return Ok(()); + } + + *partial &= nan_count(&array, ctx)? == array.len(); + Ok(()) + } + + fn finalize(&self, partials: ArrayRef) -> VortexResult { + Ok(partials) + } + + fn finalize_scalar(&self, partial: &Self::Partial) -> VortexResult { + self.to_scalar(partial) + } +} + +fn has_nans(dtype: &DType) -> bool { + matches!(dtype, DType::Primitive(ptype, _) if ptype.is_float()) +} + +#[cfg(test)] +mod tests { + use vortex_error::VortexResult; + + use crate::IntoArray; + use crate::LEGACY_SESSION; + use crate::VortexSessionExecute; + use crate::aggregate_fn::Accumulator; + use crate::aggregate_fn::DynAccumulator; + use crate::aggregate_fn::EmptyOptions; + use crate::aggregate_fn::fns::all_nan::AllNan; + use crate::arrays::PrimitiveArray; + use crate::dtype::DType; + use crate::dtype::Nullability; + use crate::dtype::PType; + + #[test] + fn all_nan_aggregate_fn() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::F32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNan, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::from_option_iter([Some(f32::NAN), Some(f32::NAN)]).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(bool::try_from(&acc.finish()?)?); + Ok(()) + } + + #[test] + fn all_nan_false_with_non_nan() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::F32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNan, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::from_option_iter([Some(f32::NAN), Some(1.0f32)]).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(!bool::try_from(&acc.finish()?)?); + Ok(()) + } + + #[test] + fn all_nan_false_for_non_float_values() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNan, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::from_option_iter([Some(1i32), None]).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(!bool::try_from(&acc.finish()?)?); + Ok(()) + } + + #[test] + fn all_nan_false_for_empty_non_float_values() -> VortexResult<()> { + let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNan, EmptyOptions, dtype)?; + + assert!(!bool::try_from(&acc.finish()?)?); + Ok(()) + } +} diff --git a/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs b/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs new file mode 100644 index 00000000000..2d33c29abfd --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_error::VortexResult; + +use crate::ArrayRef; +use crate::Columnar; +use crate::ExecutionCtx; +use crate::IntoArray; +use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::EmptyOptions; +use crate::aggregate_fn::fns::nan_count::nan_count; +use crate::dtype::DType; +use crate::dtype::Nullability; +use crate::scalar::Scalar; + +/// Compute whether every value in an array is not NaN. +/// +/// This is a pruning aggregate, not just a convenience wrapper around +/// [`NanCount`][crate::aggregate_fn::fns::nan_count::NanCount]. Pruning aggregates must prove a +/// row-wise fact for every value in the scope, so their partials remain valid when a stats column is +/// sliced or concatenated alongside the data. [`NanCount`][crate::aggregate_fn::fns::nan_count::NanCount] +/// carries cross-row count information instead, so it is useful as a legacy storage format but not +/// as the pruning expression itself. +#[derive(Clone, Debug)] +pub struct AllNonNan; + +impl AggregateFnVTable for AllNonNan { + type Options = EmptyOptions; + type Partial = bool; + + fn id(&self) -> AggregateFnId { + AggregateFnId::new("vortex.all_non_nan") + } + + fn serialize(&self, _options: &Self::Options) -> VortexResult>> { + Ok(None) + } + + fn return_dtype(&self, _options: &Self::Options, _input_dtype: &DType) -> Option { + Some(DType::Bool(Nullability::NonNullable)) + } + + fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { + self.return_dtype(options, input_dtype) + } + + fn empty_partial( + &self, + _options: &Self::Options, + _input_dtype: &DType, + ) -> VortexResult { + Ok(true) + } + + fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> { + *partial &= bool::try_from(&other)?; + Ok(()) + } + + fn to_scalar(&self, partial: &Self::Partial) -> VortexResult { + Ok(Scalar::bool(*partial, Nullability::NonNullable)) + } + + fn reset(&self, partial: &mut Self::Partial) { + *partial = true; + } + + fn is_saturated(&self, partial: &Self::Partial) -> bool { + !*partial + } + + fn try_accumulate( + &self, + state: &mut Self::Partial, + batch: &ArrayRef, + ctx: &mut ExecutionCtx, + ) -> VortexResult { + *state &= nan_count(batch, ctx)? == 0; + Ok(true) + } + + fn accumulate( + &self, + partial: &mut Self::Partial, + batch: &Columnar, + ctx: &mut ExecutionCtx, + ) -> VortexResult<()> { + let array = match batch { + Columnar::Constant(c) => c.clone().into_array(), + Columnar::Canonical(c) => c.clone().into_array(), + }; + *partial &= nan_count(&array, ctx)? == 0; + Ok(()) + } + + fn finalize(&self, partials: ArrayRef) -> VortexResult { + Ok(partials) + } + + fn finalize_scalar(&self, partial: &Self::Partial) -> VortexResult { + self.to_scalar(partial) + } +} + +#[cfg(test)] +mod tests { + use vortex_error::VortexResult; + + use crate::IntoArray; + use crate::LEGACY_SESSION; + use crate::VortexSessionExecute; + use crate::aggregate_fn::Accumulator; + use crate::aggregate_fn::DynAccumulator; + use crate::aggregate_fn::EmptyOptions; + use crate::aggregate_fn::fns::all_non_nan::AllNonNan; + use crate::arrays::PrimitiveArray; + use crate::dtype::DType; + use crate::dtype::Nullability; + use crate::dtype::PType; + + #[test] + fn all_non_nan_aggregate_fn() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::F32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNonNan, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::from_option_iter([Some(1.0f32), None, Some(3.0)]).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(bool::try_from(&acc.finish()?)?); + Ok(()) + } + + #[test] + fn all_non_nan_false_with_nan() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::F32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNonNan, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::from_option_iter([Some(1.0f32), Some(f32::NAN)]).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(!bool::try_from(&acc.finish()?)?); + Ok(()) + } + + #[test] + fn all_non_nan_true_for_non_float() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNonNan, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::from_option_iter([Some(1i32), None]).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(bool::try_from(&acc.finish()?)?); + Ok(()) + } +} diff --git a/vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs b/vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs new file mode 100644 index 00000000000..e8d89957dc3 --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_error::VortexResult; + +use crate::ArrayRef; +use crate::Columnar; +use crate::ExecutionCtx; +use crate::IntoArray; +use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::EmptyOptions; +use crate::dtype::DType; +use crate::dtype::Nullability; +use crate::scalar::Scalar; + +/// Compute whether every value in an array is non-null. +#[derive(Clone, Debug)] +pub struct AllNonNull; + +impl AggregateFnVTable for AllNonNull { + type Options = EmptyOptions; + type Partial = bool; + + fn id(&self) -> AggregateFnId { + AggregateFnId::new("vortex.all_non_null") + } + + fn serialize(&self, _options: &Self::Options) -> VortexResult>> { + Ok(None) + } + + fn return_dtype(&self, _options: &Self::Options, _input_dtype: &DType) -> Option { + Some(DType::Bool(Nullability::NonNullable)) + } + + fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { + self.return_dtype(options, input_dtype) + } + + fn empty_partial( + &self, + _options: &Self::Options, + _input_dtype: &DType, + ) -> VortexResult { + Ok(true) + } + + fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> { + *partial &= bool::try_from(&other)?; + Ok(()) + } + + fn to_scalar(&self, partial: &Self::Partial) -> VortexResult { + Ok(Scalar::bool(*partial, Nullability::NonNullable)) + } + + fn reset(&self, partial: &mut Self::Partial) { + *partial = true; + } + + fn is_saturated(&self, partial: &Self::Partial) -> bool { + !*partial + } + + fn try_accumulate( + &self, + state: &mut Self::Partial, + batch: &ArrayRef, + ctx: &mut ExecutionCtx, + ) -> VortexResult { + *state &= batch.invalid_count(ctx)? == 0; + Ok(true) + } + + fn accumulate( + &self, + partial: &mut Self::Partial, + batch: &Columnar, + ctx: &mut ExecutionCtx, + ) -> VortexResult<()> { + *partial &= match batch { + Columnar::Constant(c) => c.is_empty() || !c.scalar().is_null(), + Columnar::Canonical(c) => c.clone().into_array().invalid_count(ctx)? == 0, + }; + Ok(()) + } + + fn finalize(&self, partials: ArrayRef) -> VortexResult { + Ok(partials) + } + + fn finalize_scalar(&self, partial: &Self::Partial) -> VortexResult { + self.to_scalar(partial) + } +} + +#[cfg(test)] +mod tests { + use vortex_error::VortexResult; + + use crate::IntoArray; + use crate::LEGACY_SESSION; + use crate::VortexSessionExecute; + use crate::aggregate_fn::Accumulator; + use crate::aggregate_fn::DynAccumulator; + use crate::aggregate_fn::EmptyOptions; + use crate::aggregate_fn::fns::all_non_null::AllNonNull; + use crate::arrays::PrimitiveArray; + use crate::dtype::DType; + use crate::dtype::Nullability; + use crate::dtype::PType; + + #[test] + fn all_non_null_aggregate_fn() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNonNull, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::from_option_iter([Some(1i32), Some(2), Some(3)]).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(bool::try_from(&acc.finish()?)?); + Ok(()) + } + + #[test] + fn all_non_null_false_with_nulls() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNonNull, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::from_option_iter([Some(1i32), None, Some(3)]).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(!bool::try_from(&acc.finish()?)?); + Ok(()) + } +} diff --git a/vortex-array/src/aggregate_fn/fns/all_null/mod.rs b/vortex-array/src/aggregate_fn/fns/all_null/mod.rs new file mode 100644 index 00000000000..56e620b2a0e --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/all_null/mod.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_error::VortexResult; + +use crate::ArrayRef; +use crate::Columnar; +use crate::ExecutionCtx; +use crate::IntoArray; +use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::EmptyOptions; +use crate::dtype::DType; +use crate::dtype::Nullability; +use crate::scalar::Scalar; + +/// Compute whether every value in an array is null. +#[derive(Clone, Debug)] +pub struct AllNull; + +impl AggregateFnVTable for AllNull { + type Options = EmptyOptions; + type Partial = bool; + + fn id(&self) -> AggregateFnId { + AggregateFnId::new("vortex.all_null") + } + + fn serialize(&self, _options: &Self::Options) -> VortexResult>> { + Ok(None) + } + + fn return_dtype(&self, _options: &Self::Options, _input_dtype: &DType) -> Option { + Some(DType::Bool(Nullability::NonNullable)) + } + + fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { + self.return_dtype(options, input_dtype) + } + + fn empty_partial( + &self, + _options: &Self::Options, + _input_dtype: &DType, + ) -> VortexResult { + Ok(true) + } + + fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> { + *partial &= bool::try_from(&other)?; + Ok(()) + } + + fn to_scalar(&self, partial: &Self::Partial) -> VortexResult { + Ok(Scalar::bool(*partial, Nullability::NonNullable)) + } + + fn reset(&self, partial: &mut Self::Partial) { + *partial = true; + } + + fn is_saturated(&self, partial: &Self::Partial) -> bool { + !*partial + } + + fn try_accumulate( + &self, + state: &mut Self::Partial, + batch: &ArrayRef, + ctx: &mut ExecutionCtx, + ) -> VortexResult { + *state &= batch.invalid_count(ctx)? == batch.len(); + Ok(true) + } + + fn accumulate( + &self, + partial: &mut Self::Partial, + batch: &Columnar, + ctx: &mut ExecutionCtx, + ) -> VortexResult<()> { + *partial &= match batch { + Columnar::Constant(c) => c.is_empty() || c.scalar().is_null(), + Columnar::Canonical(c) => { + let array = c.clone().into_array(); + array.invalid_count(ctx)? == array.len() + } + }; + Ok(()) + } + + fn finalize(&self, partials: ArrayRef) -> VortexResult { + Ok(partials) + } + + fn finalize_scalar(&self, partial: &Self::Partial) -> VortexResult { + self.to_scalar(partial) + } +} + +#[cfg(test)] +mod tests { + use vortex_error::VortexResult; + + use crate::IntoArray; + use crate::LEGACY_SESSION; + use crate::VortexSessionExecute; + use crate::aggregate_fn::Accumulator; + use crate::aggregate_fn::DynAccumulator; + use crate::aggregate_fn::EmptyOptions; + use crate::aggregate_fn::fns::all_null::AllNull; + use crate::arrays::PrimitiveArray; + use crate::dtype::DType; + use crate::dtype::Nullability; + use crate::dtype::PType; + + #[test] + fn all_null_aggregate_fn() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNull, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::from_option_iter::([None, None, None]).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(bool::try_from(&acc.finish()?)?); + Ok(()) + } + + #[test] + fn all_null_false_with_non_nulls() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNull, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::from_option_iter([Some(1i32), None, Some(3)]).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(!bool::try_from(&acc.finish()?)?); + Ok(()) + } +} diff --git a/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs b/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs new file mode 100644 index 00000000000..62d4895b4cc --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use std::fmt::Display; +use std::fmt::Formatter; + +use vortex_buffer::BufferString; +use vortex_buffer::ByteBuffer; +use vortex_error::VortexResult; +use vortex_error::vortex_ensure; +use vortex_session::VortexSession; + +use crate::ArrayRef; +use crate::Columnar; +use crate::ExecutionCtx; +use crate::IntoArray; +use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::EmptyOptions; +use crate::aggregate_fn::fns::min_max::MinMax; +use crate::aggregate_fn::fns::min_max::min_max; +use crate::dtype::DType; +use crate::partial_ord::partial_max; +use crate::scalar::Scalar; +use crate::scalar::ScalarTruncation; +use crate::scalar::upper_bound; + +/// Options for [`BoundedMax`]. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct BoundedMaxOptions { + /// Maximum byte length for UTF8/Binary bounds. + pub max_bytes: usize, +} + +impl Display for BoundedMaxOptions { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.max_bytes) + } +} + +/// Compute a byte-bounded upper bound for the maximum non-null value of a UTF8/Binary array. +#[derive(Clone, Debug)] +pub struct BoundedMax; + +enum BoundedMaxState { + Empty, + Value(Scalar), + Unknown, +} + +/// Partial accumulator state for the bounded maximum aggregate. +pub struct BoundedMaxPartial { + state: BoundedMaxState, + element_dtype: DType, + max_bytes: usize, +} + +impl BoundedMaxPartial { + fn merge(&mut self, max: Scalar) { + if max.is_null() { + self.state = BoundedMaxState::Unknown; + return; + } + + self.state = match std::mem::replace(&mut self.state, BoundedMaxState::Empty) { + BoundedMaxState::Empty => BoundedMaxState::Value(max), + BoundedMaxState::Value(current) => BoundedMaxState::Value( + partial_max(max, current).expect("incomparable bounded max scalars"), + ), + BoundedMaxState::Unknown => BoundedMaxState::Unknown, + }; + } + + fn unknown(&mut self) { + self.state = BoundedMaxState::Unknown; + } +} + +impl AggregateFnVTable for BoundedMax { + type Options = BoundedMaxOptions; + type Partial = BoundedMaxPartial; + + fn id(&self) -> AggregateFnId { + AggregateFnId::new("vortex.bounded_max") + } + + fn serialize(&self, options: &Self::Options) -> VortexResult>> { + let max_bytes = u64::try_from(options.max_bytes)?; + Ok(Some(max_bytes.to_le_bytes().to_vec())) + } + + fn deserialize( + &self, + metadata: &[u8], + _session: &VortexSession, + ) -> VortexResult { + vortex_ensure!( + metadata.len() == size_of::(), + "BoundedMax options expected {} bytes, got {}", + size_of::(), + metadata.len() + ); + let mut bytes = [0u8; size_of::()]; + bytes.copy_from_slice(metadata); + let max_bytes = usize::try_from(u64::from_le_bytes(bytes))?; + vortex_ensure!(max_bytes > 0, "BoundedMax requires max_bytes > 0"); + Ok(BoundedMaxOptions { max_bytes }) + } + + fn return_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { + supported_dtype(options, input_dtype).map(DType::as_nullable) + } + + fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { + self.return_dtype(options, input_dtype) + } + + fn empty_partial( + &self, + options: &Self::Options, + input_dtype: &DType, + ) -> VortexResult { + Ok(BoundedMaxPartial { + state: BoundedMaxState::Empty, + element_dtype: input_dtype.clone(), + max_bytes: options.max_bytes, + }) + } + + fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> { + partial.merge(other); + Ok(()) + } + + fn to_scalar(&self, partial: &Self::Partial) -> VortexResult { + let dtype = partial.element_dtype.as_nullable(); + match &partial.state { + BoundedMaxState::Value(max) => max.cast(&dtype), + BoundedMaxState::Empty | BoundedMaxState::Unknown => Ok(Scalar::null(dtype)), + } + } + + fn reset(&self, partial: &mut Self::Partial) { + partial.state = BoundedMaxState::Empty; + } + + fn is_saturated(&self, partial: &Self::Partial) -> bool { + matches!(partial.state, BoundedMaxState::Unknown) + } + + fn accumulate( + &self, + partial: &mut Self::Partial, + batch: &Columnar, + ctx: &mut ExecutionCtx, + ) -> VortexResult<()> { + let array = match batch { + Columnar::Canonical(canonical) => canonical.clone().into_array(), + Columnar::Constant(constant) => constant.clone().into_array(), + }; + let Some(result) = min_max(&array, ctx)? else { + return Ok(()); + }; + match truncate_max(result.max, partial.max_bytes)? { + Some(bound) => partial.merge(bound), + None => partial.unknown(), + } + Ok(()) + } + + fn finalize(&self, partials: ArrayRef) -> VortexResult { + Ok(partials) + } + + fn finalize_scalar(&self, partial: &Self::Partial) -> VortexResult { + self.to_scalar(partial) + } +} + +fn supported_dtype<'a>(options: &BoundedMaxOptions, input_dtype: &'a DType) -> Option<&'a DType> { + if options.max_bytes == 0 { + return None; + } + + MinMax + .return_dtype(&EmptyOptions, input_dtype) + .map(|_| input_dtype) +} + +#[cfg(test)] +mod tests { + use vortex_buffer::buffer; + use vortex_error::VortexResult; + use vortex_session::VortexSession; + + use crate::IntoArray; + use crate::LEGACY_SESSION; + use crate::VortexSessionExecute; + use crate::aggregate_fn::Accumulator; + use crate::aggregate_fn::AggregateFnVTable; + use crate::aggregate_fn::DynAccumulator; + use crate::aggregate_fn::fns::bounded_max::BoundedMax; + use crate::aggregate_fn::fns::bounded_max::BoundedMaxOptions; + use crate::arrays::PrimitiveArray; + use crate::arrays::VarBinViewArray; + use crate::dtype::Nullability; + use crate::scalar::Scalar; + use crate::validity::Validity; + + #[test] + fn bounded_max_truncates_utf8_to_upper_bound() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let array = VarBinViewArray::from_iter_str(["aardvark", "char🪩"]).into_array(); + let mut acc = Accumulator::try_new( + BoundedMax, + BoundedMaxOptions { max_bytes: 5 }, + array.dtype().clone(), + )?; + + acc.accumulate(&array, &mut ctx)?; + + assert_eq!(acc.finish()?, Scalar::utf8("chas", Nullability::Nullable)); + Ok(()) + } + + #[test] + fn bounded_max_unknown_upper_bound_returns_null() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let array = VarBinViewArray::from_iter_bin([&[255u8, 255, 255][..]]).into_array(); + let mut acc = Accumulator::try_new( + BoundedMax, + BoundedMaxOptions { max_bytes: 2 }, + array.dtype().clone(), + )?; + + acc.accumulate(&array, &mut ctx)?; + + assert_eq!(acc.finish()?, Scalar::null(array.dtype().as_nullable())); + Ok(()) + } + + #[test] + fn bounded_max_keeps_fixed_width_values_exact() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let array = PrimitiveArray::new(buffer![10i32, 20, 5], Validity::NonNullable).into_array(); + let mut acc = Accumulator::try_new( + BoundedMax, + BoundedMaxOptions { max_bytes: 9 }, + array.dtype().clone(), + )?; + + acc.accumulate(&array, &mut ctx)?; + + assert_eq!( + acc.finish()?, + Scalar::primitive(20i32, Nullability::Nullable) + ); + Ok(()) + } + + #[test] + fn bounded_max_options_round_trip() -> VortexResult<()> { + let options = BoundedMaxOptions { max_bytes: 64 }; + let metadata = BoundedMax + .serialize(&options)? + .expect("serializable options"); + let roundtrip = BoundedMax.deserialize(&metadata, &VortexSession::empty())?; + + assert_eq!(roundtrip, options); + Ok(()) + } +} + +fn truncate_max(value: Scalar, max_bytes: usize) -> VortexResult> { + let nullability = value.dtype().nullability(); + match value.dtype() { + DType::Utf8(_) => { + Ok( + upper_bound(BufferString::from_scalar(value)?, max_bytes, nullability) + .map(|(bound, _)| bound), + ) + } + DType::Binary(_) => { + Ok( + upper_bound(ByteBuffer::from_scalar(value)?, max_bytes, nullability) + .map(|(bound, _)| bound), + ) + } + _ => Ok(Some(value)), + } +} diff --git a/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs b/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs new file mode 100644 index 00000000000..99043fff5c3 --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use std::fmt::Display; +use std::fmt::Formatter; + +use vortex_buffer::BufferString; +use vortex_buffer::ByteBuffer; +use vortex_error::VortexResult; +use vortex_error::vortex_ensure; +use vortex_session::VortexSession; + +use crate::ArrayRef; +use crate::Columnar; +use crate::ExecutionCtx; +use crate::IntoArray; +use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::EmptyOptions; +use crate::aggregate_fn::fns::min_max::MinMax; +use crate::aggregate_fn::fns::min_max::min_max; +use crate::dtype::DType; +use crate::partial_ord::partial_min; +use crate::scalar::Scalar; +use crate::scalar::ScalarTruncation; +use crate::scalar::lower_bound; + +/// Options for [`BoundedMin`]. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct BoundedMinOptions { + /// Maximum byte length for UTF8/Binary bounds. + pub max_bytes: usize, +} + +impl Display for BoundedMinOptions { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.max_bytes) + } +} + +/// Compute a byte-bounded lower bound for the minimum non-null value of a UTF8/Binary array. +#[derive(Clone, Debug)] +pub struct BoundedMin; + +/// Partial accumulator state for the bounded minimum aggregate. +pub struct BoundedMinPartial { + min: Option, + element_dtype: DType, + max_bytes: usize, +} + +impl BoundedMinPartial { + fn merge(&mut self, min: Scalar) { + if min.is_null() { + return; + } + + self.min = Some(match self.min.take() { + Some(current) => partial_min(min, current).expect("incomparable bounded min scalars"), + None => min, + }); + } +} + +impl AggregateFnVTable for BoundedMin { + type Options = BoundedMinOptions; + type Partial = BoundedMinPartial; + + fn id(&self) -> AggregateFnId { + AggregateFnId::new("vortex.bounded_min") + } + + fn serialize(&self, options: &Self::Options) -> VortexResult>> { + let max_bytes = u64::try_from(options.max_bytes)?; + Ok(Some(max_bytes.to_le_bytes().to_vec())) + } + + fn deserialize( + &self, + metadata: &[u8], + _session: &VortexSession, + ) -> VortexResult { + vortex_ensure!( + metadata.len() == size_of::(), + "BoundedMin options expected {} bytes, got {}", + size_of::(), + metadata.len() + ); + let mut bytes = [0u8; size_of::()]; + bytes.copy_from_slice(metadata); + let max_bytes = usize::try_from(u64::from_le_bytes(bytes))?; + vortex_ensure!(max_bytes > 0, "BoundedMin requires max_bytes > 0"); + Ok(BoundedMinOptions { max_bytes }) + } + + fn return_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { + supported_dtype(options, input_dtype).map(DType::as_nullable) + } + + fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { + self.return_dtype(options, input_dtype) + } + + fn empty_partial( + &self, + options: &Self::Options, + input_dtype: &DType, + ) -> VortexResult { + Ok(BoundedMinPartial { + min: None, + element_dtype: input_dtype.clone(), + max_bytes: options.max_bytes, + }) + } + + fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> { + partial.merge(other); + Ok(()) + } + + fn to_scalar(&self, partial: &Self::Partial) -> VortexResult { + let dtype = partial.element_dtype.as_nullable(); + match &partial.min { + Some(min) => min.cast(&dtype), + None => Ok(Scalar::null(dtype)), + } + } + + fn reset(&self, partial: &mut Self::Partial) { + partial.min = None; + } + + fn is_saturated(&self, _partial: &Self::Partial) -> bool { + false + } + + fn accumulate( + &self, + partial: &mut Self::Partial, + batch: &Columnar, + ctx: &mut ExecutionCtx, + ) -> VortexResult<()> { + let array = match batch { + Columnar::Canonical(canonical) => canonical.clone().into_array(), + Columnar::Constant(constant) => constant.clone().into_array(), + }; + let Some(result) = min_max(&array, ctx)? else { + return Ok(()); + }; + if let Some(bound) = truncate_min(result.min, partial.max_bytes)? { + partial.merge(bound); + } + Ok(()) + } + + fn finalize(&self, partials: ArrayRef) -> VortexResult { + Ok(partials) + } + + fn finalize_scalar(&self, partial: &Self::Partial) -> VortexResult { + self.to_scalar(partial) + } +} + +fn supported_dtype<'a>(options: &BoundedMinOptions, input_dtype: &'a DType) -> Option<&'a DType> { + if options.max_bytes == 0 { + return None; + } + + MinMax + .return_dtype(&EmptyOptions, input_dtype) + .map(|_| input_dtype) +} + +#[cfg(test)] +mod tests { + use vortex_buffer::buffer; + use vortex_error::VortexResult; + use vortex_session::VortexSession; + + use crate::IntoArray; + use crate::LEGACY_SESSION; + use crate::VortexSessionExecute; + use crate::aggregate_fn::Accumulator; + use crate::aggregate_fn::AggregateFnVTable; + use crate::aggregate_fn::DynAccumulator; + use crate::aggregate_fn::fns::bounded_min::BoundedMin; + use crate::aggregate_fn::fns::bounded_min::BoundedMinOptions; + use crate::arrays::PrimitiveArray; + use crate::arrays::VarBinViewArray; + use crate::dtype::Nullability; + use crate::scalar::Scalar; + use crate::validity::Validity; + + #[test] + fn bounded_min_truncates_utf8_to_lower_bound() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let array = + VarBinViewArray::from_iter_str(["snowman⛄️snowman", "untruncated"]).into_array(); + let mut acc = Accumulator::try_new( + BoundedMin, + BoundedMinOptions { max_bytes: 9 }, + array.dtype().clone(), + )?; + + acc.accumulate(&array, &mut ctx)?; + + assert_eq!( + acc.finish()?, + Scalar::utf8("snowman", Nullability::Nullable) + ); + Ok(()) + } + + #[test] + fn bounded_min_keeps_fixed_width_values_exact() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let array = PrimitiveArray::new(buffer![10i32, 20, 5], Validity::NonNullable).into_array(); + let mut acc = Accumulator::try_new( + BoundedMin, + BoundedMinOptions { max_bytes: 9 }, + array.dtype().clone(), + )?; + + acc.accumulate(&array, &mut ctx)?; + + assert_eq!( + acc.finish()?, + Scalar::primitive(5i32, Nullability::Nullable) + ); + Ok(()) + } + + #[test] + fn bounded_min_options_round_trip() -> VortexResult<()> { + let options = BoundedMinOptions { max_bytes: 64 }; + let metadata = BoundedMin + .serialize(&options)? + .expect("serializable options"); + let roundtrip = BoundedMin.deserialize(&metadata, &VortexSession::empty())?; + + assert_eq!(roundtrip, options); + Ok(()) + } +} + +fn truncate_min(value: Scalar, max_bytes: usize) -> VortexResult> { + let nullability = value.dtype().nullability(); + match value.dtype() { + DType::Utf8(_) => { + Ok( + lower_bound(BufferString::from_scalar(value)?, max_bytes, nullability) + .map(|(bound, _)| bound), + ) + } + DType::Binary(_) => { + Ok( + lower_bound(ByteBuffer::from_scalar(value)?, max_bytes, nullability) + .map(|(bound, _)| bound), + ) + } + _ => Ok(Some(value)), + } +} diff --git a/vortex-array/src/aggregate_fn/fns/max/mod.rs b/vortex-array/src/aggregate_fn/fns/max/mod.rs new file mode 100644 index 00000000000..b56e87b7acc --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/max/mod.rs @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_error::VortexExpect; +use vortex_error::VortexResult; + +use crate::ArrayRef; +use crate::Columnar; +use crate::ExecutionCtx; +use crate::IntoArray; +use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::EmptyOptions; +use crate::aggregate_fn::fns::min_max::MinMax; +use crate::aggregate_fn::fns::min_max::min_max; +use crate::dtype::DType; +use crate::partial_ord::partial_max; +use crate::scalar::Scalar; + +/// Compute the maximum non-null value of an array. +#[derive(Clone, Debug)] +pub struct Max; + +/// Partial accumulator state for the maximum aggregate. +pub struct MaxPartial { + max: Option, + element_dtype: DType, +} + +impl MaxPartial { + fn merge(&mut self, max: Scalar) { + if max.is_null() { + return; + } + + self.max = Some(match self.max.take() { + Some(current) => partial_max(max, current).vortex_expect("incomparable max scalars"), + None => max, + }); + } +} + +impl AggregateFnVTable for Max { + type Options = EmptyOptions; + type Partial = MaxPartial; + + fn id(&self) -> AggregateFnId { + AggregateFnId::new("vortex.max") + } + + fn serialize(&self, _options: &Self::Options) -> VortexResult>> { + Ok(None) + } + + fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option { + MinMax + .return_dtype(&EmptyOptions, input_dtype) + .map(|_| input_dtype.as_nullable()) + } + + fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { + self.return_dtype(options, input_dtype) + } + + fn empty_partial( + &self, + _options: &Self::Options, + input_dtype: &DType, + ) -> VortexResult { + Ok(MaxPartial { + max: None, + element_dtype: input_dtype.clone(), + }) + } + + fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> { + partial.merge(other); + Ok(()) + } + + fn to_scalar(&self, partial: &Self::Partial) -> VortexResult { + let dtype = partial.element_dtype.as_nullable(); + match &partial.max { + Some(max) => max.cast(&dtype), + None => Ok(Scalar::null(dtype)), + } + } + + fn reset(&self, partial: &mut Self::Partial) { + partial.max = None; + } + + fn is_saturated(&self, _partial: &Self::Partial) -> bool { + false + } + + fn accumulate( + &self, + partial: &mut Self::Partial, + batch: &Columnar, + ctx: &mut ExecutionCtx, + ) -> VortexResult<()> { + // Delegate to the existing min_max implementation for now. A dedicated max aggregate + // would avoid computing min when only max is needed. + let array = match batch { + Columnar::Canonical(canonical) => canonical.clone().into_array(), + Columnar::Constant(constant) => constant.clone().into_array(), + }; + if let Some(result) = min_max(&array, ctx)? { + partial.merge(result.max); + } + Ok(()) + } + + fn finalize(&self, partials: ArrayRef) -> VortexResult { + Ok(partials) + } + + fn finalize_scalar(&self, partial: &Self::Partial) -> VortexResult { + self.to_scalar(partial) + } +} + +#[cfg(test)] +mod tests { + use vortex_buffer::buffer; + use vortex_error::VortexResult; + + use crate::IntoArray as _; + use crate::LEGACY_SESSION; + use crate::VortexSessionExecute; + use crate::aggregate_fn::Accumulator; + use crate::aggregate_fn::DynAccumulator; + use crate::aggregate_fn::EmptyOptions; + use crate::aggregate_fn::fns::max::Max; + use crate::arrays::PrimitiveArray; + use crate::dtype::DType; + use crate::dtype::Nullability; + use crate::dtype::PType; + use crate::scalar::Scalar; + use crate::validity::Validity; + + #[test] + fn max_aggregate_fn() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); + let mut acc = Accumulator::try_new(Max, EmptyOptions, dtype)?; + + let batch1 = PrimitiveArray::new(buffer![10i32, 20, 5], Validity::NonNullable).into_array(); + acc.accumulate(&batch1, &mut ctx)?; + + let batch2 = PrimitiveArray::new(buffer![3i32, 25], Validity::NonNullable).into_array(); + acc.accumulate(&batch2, &mut ctx)?; + + assert_eq!( + acc.finish()?, + Scalar::primitive(25i32, Nullability::Nullable) + ); + Ok(()) + } + + #[test] + fn max_empty_group_returns_null() -> VortexResult<()> { + let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); + let mut acc = Accumulator::try_new(Max, EmptyOptions, dtype)?; + + assert_eq!( + acc.finish()?, + Scalar::null(DType::Primitive(PType::I32, Nullability::Nullable)) + ); + Ok(()) + } +} diff --git a/vortex-array/src/aggregate_fn/fns/min/mod.rs b/vortex-array/src/aggregate_fn/fns/min/mod.rs new file mode 100644 index 00000000000..1a9be7101bf --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/min/mod.rs @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_error::VortexExpect; +use vortex_error::VortexResult; + +use crate::ArrayRef; +use crate::Columnar; +use crate::ExecutionCtx; +use crate::IntoArray; +use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::EmptyOptions; +use crate::aggregate_fn::fns::min_max::MinMax; +use crate::aggregate_fn::fns::min_max::min_max; +use crate::dtype::DType; +use crate::partial_ord::partial_min; +use crate::scalar::Scalar; + +/// Compute the minimum non-null value of an array. +#[derive(Clone, Debug)] +pub struct Min; + +/// Partial accumulator state for the minimum aggregate. +pub struct MinPartial { + min: Option, + element_dtype: DType, +} + +impl MinPartial { + fn merge(&mut self, min: Scalar) { + if min.is_null() { + return; + } + + self.min = Some(match self.min.take() { + Some(current) => partial_min(min, current).vortex_expect("incomparable min scalars"), + None => min, + }); + } +} + +impl AggregateFnVTable for Min { + type Options = EmptyOptions; + type Partial = MinPartial; + + fn id(&self) -> AggregateFnId { + AggregateFnId::new("vortex.min") + } + + fn serialize(&self, _options: &Self::Options) -> VortexResult>> { + Ok(None) + } + + fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option { + MinMax + .return_dtype(&EmptyOptions, input_dtype) + .map(|_| input_dtype.as_nullable()) + } + + fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { + self.return_dtype(options, input_dtype) + } + + fn empty_partial( + &self, + _options: &Self::Options, + input_dtype: &DType, + ) -> VortexResult { + Ok(MinPartial { + min: None, + element_dtype: input_dtype.clone(), + }) + } + + fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> { + partial.merge(other); + Ok(()) + } + + fn to_scalar(&self, partial: &Self::Partial) -> VortexResult { + let dtype = partial.element_dtype.as_nullable(); + match &partial.min { + Some(min) => min.cast(&dtype), + None => Ok(Scalar::null(dtype)), + } + } + + fn reset(&self, partial: &mut Self::Partial) { + partial.min = None; + } + + fn is_saturated(&self, _partial: &Self::Partial) -> bool { + false + } + + fn accumulate( + &self, + partial: &mut Self::Partial, + batch: &Columnar, + ctx: &mut ExecutionCtx, + ) -> VortexResult<()> { + // Delegate to the existing min_max implementation for now. A dedicated min aggregate + // would avoid computing max when only min is needed. + let array = match batch { + Columnar::Canonical(canonical) => canonical.clone().into_array(), + Columnar::Constant(constant) => constant.clone().into_array(), + }; + if let Some(result) = min_max(&array, ctx)? { + partial.merge(result.min); + } + Ok(()) + } + + fn finalize(&self, partials: ArrayRef) -> VortexResult { + Ok(partials) + } + + fn finalize_scalar(&self, partial: &Self::Partial) -> VortexResult { + self.to_scalar(partial) + } +} + +#[cfg(test)] +mod tests { + use vortex_buffer::buffer; + use vortex_error::VortexResult; + + use crate::IntoArray as _; + use crate::LEGACY_SESSION; + use crate::VortexSessionExecute; + use crate::aggregate_fn::Accumulator; + use crate::aggregate_fn::DynAccumulator; + use crate::aggregate_fn::EmptyOptions; + use crate::aggregate_fn::fns::min::Min; + use crate::arrays::PrimitiveArray; + use crate::dtype::DType; + use crate::dtype::Nullability; + use crate::dtype::PType; + use crate::expr::stats::Precision; + use crate::expr::stats::Stat; + use crate::scalar::Scalar; + use crate::scalar::ScalarValue; + use crate::validity::Validity; + + #[test] + fn min_aggregate_fn() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); + let mut acc = Accumulator::try_new(Min, EmptyOptions, dtype)?; + + let batch1 = PrimitiveArray::new(buffer![10i32, 20, 5], Validity::NonNullable).into_array(); + acc.accumulate(&batch1, &mut ctx)?; + + let batch2 = PrimitiveArray::new(buffer![3i32, 25], Validity::NonNullable).into_array(); + acc.accumulate(&batch2, &mut ctx)?; + + assert_eq!( + acc.finish()?, + Scalar::primitive(3i32, Nullability::Nullable) + ); + Ok(()) + } + + #[test] + fn min_empty_group_returns_null() -> VortexResult<()> { + let dtype = DType::Primitive(PType::I32, Nullability::NonNullable); + let mut acc = Accumulator::try_new(Min, EmptyOptions, dtype)?; + + assert_eq!( + acc.finish()?, + Scalar::null(DType::Primitive(PType::I32, Nullability::Nullable)) + ); + Ok(()) + } + + #[test] + fn min_casts_nonnullable_legacy_stat_to_nullable_partial() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let batch = PrimitiveArray::new(buffer![10i32, 20], Validity::NonNullable).into_array(); + batch + .statistics() + .set(Stat::Min, Precision::Exact(ScalarValue::from(3i32))); + let mut acc = Accumulator::try_new(Min, EmptyOptions, batch.dtype().clone())?; + + acc.accumulate(&batch, &mut ctx)?; + + assert_eq!( + acc.finish()?, + Scalar::primitive(3i32, Nullability::Nullable) + ); + Ok(()) + } +} diff --git a/vortex-array/src/aggregate_fn/fns/min_max/mod.rs b/vortex-array/src/aggregate_fn/fns/min_max/mod.rs index 786a70e5ffa..03b10e32301 100644 --- a/vortex-array/src/aggregate_fn/fns/min_max/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/min_max/mod.rs @@ -177,7 +177,7 @@ impl AggregateFnVTable for MinMax { } fn serialize(&self, _options: &Self::Options) -> VortexResult>> { - unimplemented!("MinMax is not yet serializable"); + Ok(None) } fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option { diff --git a/vortex-array/src/aggregate_fn/fns/mod.rs b/vortex-array/src/aggregate_fn/fns/mod.rs index a0a54f13f0e..da81e67ecb9 100644 --- a/vortex-array/src/aggregate_fn/fns/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/mod.rs @@ -1,13 +1,21 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +pub mod all_nan; pub mod all_non_distinct; +pub mod all_non_nan; +pub mod all_non_null; +pub mod all_null; +pub mod bounded_max; +pub mod bounded_min; pub mod count; pub mod first; pub mod is_constant; pub mod is_sorted; pub mod last; +pub mod max; pub mod mean; +pub mod min; pub mod min_max; pub mod nan_count; pub mod null_count; diff --git a/vortex-array/src/aggregate_fn/session.rs b/vortex-array/src/aggregate_fn/session.rs index 92eb9c0ed38..d89f9da069d 100644 --- a/vortex-array/src/aggregate_fn/session.rs +++ b/vortex-array/src/aggregate_fn/session.rs @@ -14,11 +14,19 @@ use vortex_utils::aliases::hash_map::HashMap; use crate::aggregate_fn::AggregateFnId; use crate::aggregate_fn::AggregateFnPluginRef; use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::fns::all_nan::AllNan; use crate::aggregate_fn::fns::all_non_distinct::AllNonDistinct; +use crate::aggregate_fn::fns::all_non_nan::AllNonNan; +use crate::aggregate_fn::fns::all_non_null::AllNonNull; +use crate::aggregate_fn::fns::all_null::AllNull; +use crate::aggregate_fn::fns::bounded_max::BoundedMax; +use crate::aggregate_fn::fns::bounded_min::BoundedMin; use crate::aggregate_fn::fns::first::First; use crate::aggregate_fn::fns::is_constant::IsConstant; use crate::aggregate_fn::fns::is_sorted::IsSorted; use crate::aggregate_fn::fns::last::Last; +use crate::aggregate_fn::fns::max::Max; +use crate::aggregate_fn::fns::min::Min; use crate::aggregate_fn::fns::min_max::MinMax; use crate::aggregate_fn::fns::nan_count::NanCount; use crate::aggregate_fn::fns::null_count::NullCount; @@ -69,10 +77,18 @@ impl Default for AggregateFnSession { // Register the built-in aggregate functions this.register(AllNonDistinct); + this.register(AllNonNan); + this.register(AllNonNull); + this.register(AllNan); + this.register(AllNull); + this.register(BoundedMax); + this.register(BoundedMin); this.register(First); this.register(IsConstant); this.register(IsSorted); this.register(Last); + this.register(Max); + this.register(Min); this.register(MinMax); this.register(NanCount); this.register(NullCount); diff --git a/vortex-array/src/expr/stats/mod.rs b/vortex-array/src/expr/stats/mod.rs index 181eea5446b..cd5da7811e9 100644 --- a/vortex-array/src/expr/stats/mod.rs +++ b/vortex-array/src/expr/stats/mod.rs @@ -194,6 +194,8 @@ impl Stat { /// Return the built-in aggregate function corresponding to this statistic, if one exists. pub fn aggregate_fn(&self) -> Option { Some(match self { + Self::Max => aggregate_fn::fns::max::Max.bind(EmptyOptions), + Self::Min => aggregate_fn::fns::min::Min.bind(EmptyOptions), Self::Sum => aggregate_fn::fns::sum::Sum.bind(EmptyOptions), Self::NullCount => aggregate_fn::fns::null_count::NullCount.bind(EmptyOptions), Self::NaNCount => aggregate_fn::fns::nan_count::NanCount.bind(EmptyOptions), @@ -201,9 +203,7 @@ impl Stat { aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes .bind(EmptyOptions) } - Self::IsConstant | Self::IsSorted | Self::IsStrictSorted | Self::Max | Self::Min => { - return None; - } + Self::IsConstant | Self::IsSorted | Self::IsStrictSorted => return None, }) } @@ -218,6 +218,12 @@ impl Stat { if aggregate_fn.is::() { return Some(Self::NullCount); } + if aggregate_fn.is::() { + return Some(Self::Min); + } + if aggregate_fn.is::() { + return Some(Self::Max); + } if aggregate_fn .is::() { diff --git a/vortex-array/src/scalar_fn/fns/stat.rs b/vortex-array/src/scalar_fn/fns/stat.rs index 540d4bcaff6..9895fc21147 100644 --- a/vortex-array/src/scalar_fn/fns/stat.rs +++ b/vortex-array/src/scalar_fn/fns/stat.rs @@ -13,12 +13,19 @@ use crate::ArrayRef; use crate::ExecutionCtx; use crate::IntoArray; use crate::aggregate_fn::AggregateFnRef; +use crate::aggregate_fn::fns::all_nan::AllNan; +use crate::aggregate_fn::fns::all_non_nan::AllNonNan; +use crate::aggregate_fn::fns::all_non_null::AllNonNull; +use crate::aggregate_fn::fns::all_null::AllNull; use crate::arrays::ConstantArray; use crate::dtype::DType; use crate::expr::Expression; +use crate::expr::stats::Precision; use crate::expr::stats::Stat; use crate::expr::stats::StatsProvider; +use crate::expr::stats::StatsProviderExt; use crate::scalar::Scalar; +use crate::scalar::ScalarValue; use crate::scalar_fn::Arity; use crate::scalar_fn::ChildName; use crate::scalar_fn::ExecutionArgs; @@ -49,7 +56,7 @@ impl Display for StatOptions { } } -/// Scalar function that broadcasts a stored aggregate statistic over the input rows. +/// Scalar function that broadcasts a stored aggregate partial over the input rows. /// /// The only current consumer is **row-wise pruning**: substituting `stat(col, agg)` into a /// predicate produces a cheap, row-aligned approximation whose constant runs let downstream @@ -64,10 +71,10 @@ impl Display for StatOptions { /// yields a constant per chunk; a zone-mapped array would yield a run-end-encoded array, /// one run per zone. If the requested stat is not available, the result is a null constant. /// -/// Pruning only makes sense for aggregates that bound individual rows — `min`, `max`, -/// `has_nulls`, bloom filters, etc. Non-idempotent aggregates like `sum`, `count`, `mean`, -/// `null_count`, and `nan_count` still produce a meaningful per-chunk value but do **not** -/// bound any single row. +/// Pruning only makes sense for aggregates that can prove something about every row in the scope +/// — `min`, `max`, `all_null`, `all_non_null`, bloom filters, etc. Non-idempotent aggregates like +/// `sum`, `count`, `mean`, `null_count`, and `nan_count` still produce a meaningful per-chunk +/// value but do **not** bound any single row. #[derive(Clone)] pub struct StatFn; @@ -117,7 +124,7 @@ impl ScalarFnVTable for StatFn { } fn stat_dtype(aggregate_fn: &AggregateFnRef, input_dtype: &DType) -> VortexResult { - let Some(dtype) = aggregate_fn.return_dtype(input_dtype) else { + let Some(dtype) = aggregate_fn.state_dtype(input_dtype) else { vortex_bail!( "Aggregate function {} does not support input dtype {}", aggregate_fn, @@ -133,7 +140,55 @@ fn stat_array( dtype: DType, len: usize, ) -> VortexResult { - let value = if let Some(stat) = Stat::from_aggregate_fn(aggregate_fn) { + let value = if aggregate_fn.is::() { + let len = u64::try_from(len)?; + array + .statistics() + .with_typed_stats_set(|stats| stats.get_as::(Stat::NullCount)) + .and_then(|null_count| match null_count { + Precision::Exact(count) => Some(count == len), + Precision::Inexact(count) => (count < len).then_some(false), + }) + .map(ScalarValue::Bool) + } else if aggregate_fn.is::() { + array + .statistics() + .with_typed_stats_set(|stats| stats.get_as::(Stat::NullCount)) + .and_then(|null_count| match null_count { + Precision::Exact(count) => Some(count == 0), + Precision::Inexact(0) => Some(true), + Precision::Inexact(_) => None, + }) + .map(ScalarValue::Bool) + } else if aggregate_fn.is::() { + let len = u64::try_from(len)?; + if !has_nans(array.dtype()) { + Some(false) + } else { + array + .statistics() + .with_typed_stats_set(|stats| stats.get_as::(Stat::NaNCount)) + .and_then(|nan_count| match nan_count { + Precision::Exact(count) => Some(count == len), + Precision::Inexact(count) => (count < len).then_some(false), + }) + } + .map(ScalarValue::Bool) + } else if aggregate_fn.is::() { + if !has_nans(array.dtype()) { + Some(true) + } else { + array + .statistics() + .with_typed_stats_set(|stats| stats.get_as::(Stat::NaNCount)) + .and_then(|nan_count| match nan_count { + Precision::Exact(count) => Some(count == 0), + Precision::Inexact(0) => Some(true), + Precision::Inexact(_) => None, + }) + } + .map(ScalarValue::Bool) + } else if let Some(stat) = Stat::from_aggregate_fn(aggregate_fn) { array .statistics() .with_typed_stats_set(|stats| stats.get(stat)) @@ -151,3 +206,7 @@ fn stat_array( let scalar = Scalar::try_new(dtype, value)?; Ok(ConstantArray::new(scalar, len).into_array()) } + +fn has_nans(dtype: &DType) -> bool { + matches!(dtype, DType::Primitive(ptype, _) if ptype.is_float()) +} diff --git a/vortex-array/src/scalar_fn/internal/row_count.rs b/vortex-array/src/scalar_fn/internal/row_count.rs index f411626321d..ea349f2ddf8 100644 --- a/vortex-array/src/scalar_fn/internal/row_count.rs +++ b/vortex-array/src/scalar_fn/internal/row_count.rs @@ -24,6 +24,12 @@ use vortex_error::vortex_ensure; /// Zero-argument placeholder for the row count of the current evaluation scope. /// +/// This is a legacy pruning hack for readers that only have a `null_count` +/// stat and need to support `is_not_null` pruning. It is currently substituted +/// by the zoned/file stats pruning paths before execution. New stats rewrites +/// should prefer boolean `all_null` and `all_non_null` aggregates instead of +/// depending on this scope-level placeholder. +/// /// This expression *MUST* be replaced with a concrete array before evaluation. /// Currently, the rewrite only happens in the context of stats pruning. /// diff --git a/vortex-array/src/stats/expr.rs b/vortex-array/src/stats/expr.rs index 479908acb92..ddd1976ff97 100644 --- a/vortex-array/src/stats/expr.rs +++ b/vortex-array/src/stats/expr.rs @@ -6,6 +6,10 @@ use crate::aggregate_fn::AggregateFnRef; use crate::aggregate_fn::AggregateFnVTableExt; use crate::aggregate_fn::EmptyOptions; +use crate::aggregate_fn::fns::all_nan::AllNan; +use crate::aggregate_fn::fns::all_non_nan::AllNonNan; +use crate::aggregate_fn::fns::all_non_null::AllNonNull; +use crate::aggregate_fn::fns::all_null::AllNull; use crate::aggregate_fn::fns::min_max::MinMax; use crate::aggregate_fn::fns::nan_count::NanCount; use crate::aggregate_fn::fns::null_count::NullCount; @@ -38,6 +42,26 @@ pub fn null_count(expr: Expression) -> Expression { stat(expr, NullCount.bind(EmptyOptions)) } +/// Creates `stat(expr, all_null)`, returning a nullable all-null statistic. +pub fn all_null(expr: Expression) -> Expression { + stat(expr, AllNull.bind(EmptyOptions)) +} + +/// Creates `stat(expr, all_nan)`, returning a nullable all-NaN statistic. +pub fn all_nan(expr: Expression) -> Expression { + stat(expr, AllNan.bind(EmptyOptions)) +} + +/// Creates `stat(expr, all_non_null)`, returning a nullable all-non-null statistic. +pub fn all_non_null(expr: Expression) -> Expression { + stat(expr, AllNonNull.bind(EmptyOptions)) +} + +/// Creates `stat(expr, all_non_nan)`, returning a nullable all-non-NaN statistic. +pub fn all_non_nan(expr: Expression) -> Expression { + stat(expr, AllNonNan.bind(EmptyOptions)) +} + /// Creates `stat(expr, nan_count)`, returning a nullable NaN-count statistic. pub fn nan_count(expr: Expression) -> Expression { stat(expr, NanCount.bind(EmptyOptions)) @@ -56,6 +80,8 @@ mod tests { use crate::VortexSessionExecute; use crate::aggregate_fn::AggregateFn; use crate::aggregate_fn::EmptyOptions; + use crate::aggregate_fn::fns::all_non_null::AllNonNull; + use crate::aggregate_fn::fns::all_null::AllNull; use crate::aggregate_fn::fns::sum::Sum; use crate::arrays::Chunked; use crate::arrays::ChunkedArray; @@ -70,6 +96,7 @@ mod tests { use crate::expr::stats::Precision; use crate::expr::stats::Stat; use crate::scalar::Scalar; + use crate::scalar::ScalarValue; use crate::validity::Validity; #[test] @@ -172,4 +199,181 @@ mod tests { Ok(()) } + + #[test] + fn stat_expr_reads_cached_all_null_from_null_count() -> VortexResult<()> { + let array = PrimitiveArray::from_option_iter::([None, None, None]).into_array(); + array + .statistics() + .set(Stat::NullCount, Precision::exact(ScalarValue::from(3u64))); + + let result = array + .apply(&super::all_null(root()))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::bool(true, Nullability::Nullable), 3).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + + #[test] + fn stat_expr_reads_cached_all_null_false_from_inexact_low_null_count() -> VortexResult<()> { + let array = PrimitiveArray::from_option_iter::([None, Some(2), None]).into_array(); + array + .statistics() + .set(Stat::NullCount, Precision::inexact(ScalarValue::from(2u64))); + + let result = array + .apply(&super::all_null(root()))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::bool(false, Nullability::Nullable), 3).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + + #[test] + fn stat_expr_returns_null_for_inexact_full_null_count_as_all_null() -> VortexResult<()> { + let array = PrimitiveArray::from_option_iter::([None, Some(2), None]).into_array(); + array + .statistics() + .set(Stat::NullCount, Precision::inexact(ScalarValue::from(3u64))); + + let result = array + .apply(&stat( + root(), + AggregateFn::new(AllNull, EmptyOptions).erased(), + ))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::null(DType::Bool(Nullability::Nullable)), 3).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + + #[test] + fn stat_expr_reads_cached_all_non_null_from_null_count() -> VortexResult<()> { + let array = buffer![1i32, 2, 3].into_array(); + array + .statistics() + .set(Stat::NullCount, Precision::exact(ScalarValue::from(0u64))); + + let result = array + .apply(&super::all_non_null(root()))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::bool(true, Nullability::Nullable), 3).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + + #[test] + fn stat_expr_reads_cached_all_non_null_true_from_inexact_zero_null_count() -> VortexResult<()> { + let array = buffer![1i32, 2, 3].into_array(); + array + .statistics() + .set(Stat::NullCount, Precision::inexact(ScalarValue::from(0u64))); + + let result = array + .apply(&super::all_non_null(root()))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::bool(true, Nullability::Nullable), 3).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + + #[test] + fn stat_expr_returns_null_for_inexact_nonzero_null_count_as_all_non_null() -> VortexResult<()> { + let array = + PrimitiveArray::from_option_iter([Some(1i32), None, Some(3), None]).into_array(); + array + .statistics() + .set(Stat::NullCount, Precision::inexact(ScalarValue::from(2u64))); + + let result = array + .apply(&stat( + root(), + AggregateFn::new(AllNonNull, EmptyOptions).erased(), + ))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::null(DType::Bool(Nullability::Nullable)), 4).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + + #[test] + fn stat_expr_reads_all_nan_false_for_empty_non_float() -> VortexResult<()> { + let array = PrimitiveArray::empty::(Nullability::NonNullable).into_array(); + + let result = array + .apply(&super::all_nan(root()))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::bool(false, Nullability::Nullable), 0).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + + #[test] + fn stat_expr_reads_cached_min_and_max() -> VortexResult<()> { + let array = buffer![3i32, 1, 2].into_array(); + array + .statistics() + .set(Stat::Min, Precision::exact(ScalarValue::from(1i32))); + array + .statistics() + .set(Stat::Max, Precision::exact(ScalarValue::from(3i32))); + + let min_result = array + .clone() + .apply(&stat( + root(), + Stat::Min + .aggregate_fn() + .vortex_expect("min should have an aggregate function"), + ))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + let expected_min = + ConstantArray::new(Scalar::primitive(1i32, Nullability::Nullable), 3).into_array(); + assert_arrays_eq!(min_result, expected_min); + + let max_result = array + .apply(&stat( + root(), + Stat::Max + .aggregate_fn() + .vortex_expect("max should have an aggregate function"), + ))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + let expected_max = + ConstantArray::new(Scalar::primitive(3i32, Nullability::Nullable), 3).into_array(); + assert_arrays_eq!(max_result, expected_max); + + Ok(()) + } } diff --git a/vortex-array/src/stats/mod.rs b/vortex-array/src/stats/mod.rs index 1d2994d01ff..ceb085e0815 100644 --- a/vortex-array/src/stats/mod.rs +++ b/vortex-array/src/stats/mod.rs @@ -7,6 +7,10 @@ use arrow_buffer::BooleanBufferBuilder; use arrow_buffer::MutableBuffer; use arrow_buffer::bit_iterator::BitIterator; use enum_iterator::last; +pub use expr::all_nan; +pub use expr::all_non_nan; +pub use expr::all_non_null; +pub use expr::all_null; pub use expr::min_max; pub use expr::nan_count; pub use expr::null_count; From d4bdc410bc81bb34bb3046488200dc3e31929514 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Sun, 17 May 2026 16:50:50 +0100 Subject: [PATCH 4/7] Fix NaN pruning aggregate support Signed-off-by: Nicholas Gates --- .../src/aggregate_fn/fns/all_nan/mod.rs | 29 ++++++++----- .../src/aggregate_fn/fns/all_non_nan/mod.rs | 32 +++++++++++--- .../src/aggregate_fn/fns/bounded_max/mod.rs | 41 +++++++++--------- .../src/aggregate_fn/fns/bounded_min/mod.rs | 43 ++++++++++--------- vortex-array/src/stats/expr.rs | 13 +++--- 5 files changed, 93 insertions(+), 65 deletions(-) diff --git a/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs b/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs index 50271111877..c6b41f1ac8f 100644 --- a/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs @@ -38,8 +38,8 @@ impl AggregateFnVTable for AllNan { Ok(None) } - fn return_dtype(&self, _options: &Self::Options, _input_dtype: &DType) -> Option { - Some(DType::Bool(Nullability::NonNullable)) + fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option { + has_nans(input_dtype).then_some(DType::Bool(Nullability::Nullable)) } fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { @@ -49,9 +49,9 @@ impl AggregateFnVTable for AllNan { fn empty_partial( &self, _options: &Self::Options, - input_dtype: &DType, + _input_dtype: &DType, ) -> VortexResult { - Ok(has_nans(input_dtype)) + Ok(true) } fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> { @@ -60,7 +60,7 @@ impl AggregateFnVTable for AllNan { } fn to_scalar(&self, partial: &Self::Partial) -> VortexResult { - Ok(Scalar::bool(*partial, Nullability::NonNullable)) + Ok(Scalar::bool(*partial, Nullability::Nullable)) } fn reset(&self, partial: &mut Self::Partial) { @@ -161,12 +161,19 @@ mod tests { } #[test] - fn all_nan_false_for_non_float_values() -> VortexResult<()> { - let mut ctx = LEGACY_SESSION.create_execution_ctx(); + fn all_nan_unsupported_for_non_float_values() -> VortexResult<()> { let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + assert!(Accumulator::try_new(AllNan, EmptyOptions, dtype).is_err()); + Ok(()) + } + + #[test] + fn all_nan_false_with_null() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::F32, Nullability::Nullable); let mut acc = Accumulator::try_new(AllNan, EmptyOptions, dtype)?; - let batch = PrimitiveArray::from_option_iter([Some(1i32), None]).into_array(); + let batch = PrimitiveArray::from_option_iter([Some(f32::NAN), None]).into_array(); acc.accumulate(&batch, &mut ctx)?; assert!(!bool::try_from(&acc.finish()?)?); @@ -174,11 +181,11 @@ mod tests { } #[test] - fn all_nan_false_for_empty_non_float_values() -> VortexResult<()> { - let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + fn all_nan_true_for_empty_float_values() -> VortexResult<()> { + let dtype = DType::Primitive(PType::F32, Nullability::Nullable); let mut acc = Accumulator::try_new(AllNan, EmptyOptions, dtype)?; - assert!(!bool::try_from(&acc.finish()?)?); + assert!(bool::try_from(&acc.finish()?)?); Ok(()) } } diff --git a/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs b/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs index 2d33c29abfd..aacce0d0c71 100644 --- a/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs @@ -38,8 +38,8 @@ impl AggregateFnVTable for AllNonNan { Ok(None) } - fn return_dtype(&self, _options: &Self::Options, _input_dtype: &DType) -> Option { - Some(DType::Bool(Nullability::NonNullable)) + fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option { + has_nans(input_dtype).then_some(DType::Bool(Nullability::Nullable)) } fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { @@ -60,7 +60,7 @@ impl AggregateFnVTable for AllNonNan { } fn to_scalar(&self, partial: &Self::Partial) -> VortexResult { - Ok(Scalar::bool(*partial, Nullability::NonNullable)) + Ok(Scalar::bool(*partial, Nullability::Nullable)) } fn reset(&self, partial: &mut Self::Partial) { @@ -104,6 +104,10 @@ impl AggregateFnVTable for AllNonNan { } } +fn has_nans(dtype: &DType) -> bool { + matches!(dtype, DType::Primitive(ptype, _) if ptype.is_float()) +} + #[cfg(test)] mod tests { use vortex_error::VortexResult; @@ -147,12 +151,28 @@ mod tests { } #[test] - fn all_non_nan_true_for_non_float() -> VortexResult<()> { - let mut ctx = LEGACY_SESSION.create_execution_ctx(); + fn all_non_nan_unsupported_for_non_float() -> VortexResult<()> { let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + assert!(Accumulator::try_new(AllNonNan, EmptyOptions, dtype).is_err()); + Ok(()) + } + + #[test] + fn all_non_nan_true_for_empty_float() -> VortexResult<()> { + let dtype = DType::Primitive(PType::F32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNonNan, EmptyOptions, dtype)?; + + assert!(bool::try_from(&acc.finish()?)?); + Ok(()) + } + + #[test] + fn all_non_nan_true_with_nulls() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::F32, Nullability::Nullable); let mut acc = Accumulator::try_new(AllNonNan, EmptyOptions, dtype)?; - let batch = PrimitiveArray::from_option_iter([Some(1i32), None]).into_array(); + let batch = PrimitiveArray::from_option_iter([Some(1.0f32), None]).into_array(); acc.accumulate(&batch, &mut ctx)?; assert!(bool::try_from(&acc.finish()?)?); diff --git a/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs b/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs index 62d4895b4cc..0f80a486e26 100644 --- a/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs @@ -6,6 +6,7 @@ use std::fmt::Formatter; use vortex_buffer::BufferString; use vortex_buffer::ByteBuffer; +use vortex_error::VortexExpect; use vortex_error::VortexResult; use vortex_error::vortex_ensure; use vortex_session::VortexSession; @@ -65,7 +66,7 @@ impl BoundedMaxPartial { self.state = match std::mem::replace(&mut self.state, BoundedMaxState::Empty) { BoundedMaxState::Empty => BoundedMaxState::Value(max), BoundedMaxState::Value(current) => BoundedMaxState::Value( - partial_max(max, current).expect("incomparable bounded max scalars"), + partial_max(max, current).vortex_expect("incomparable bounded max scalars"), ), BoundedMaxState::Unknown => BoundedMaxState::Unknown, }; @@ -187,6 +188,25 @@ fn supported_dtype<'a>(options: &BoundedMaxOptions, input_dtype: &'a DType) -> O .map(|_| input_dtype) } +fn truncate_max(value: Scalar, max_bytes: usize) -> VortexResult> { + let nullability = value.dtype().nullability(); + match value.dtype() { + DType::Utf8(_) => { + Ok( + upper_bound(BufferString::from_scalar(value)?, max_bytes, nullability) + .map(|(bound, _)| bound), + ) + } + DType::Binary(_) => { + Ok( + upper_bound(ByteBuffer::from_scalar(value)?, max_bytes, nullability) + .map(|(bound, _)| bound), + ) + } + _ => Ok(Some(value)), + } +} + #[cfg(test)] mod tests { use vortex_buffer::buffer; @@ -270,22 +290,3 @@ mod tests { Ok(()) } } - -fn truncate_max(value: Scalar, max_bytes: usize) -> VortexResult> { - let nullability = value.dtype().nullability(); - match value.dtype() { - DType::Utf8(_) => { - Ok( - upper_bound(BufferString::from_scalar(value)?, max_bytes, nullability) - .map(|(bound, _)| bound), - ) - } - DType::Binary(_) => { - Ok( - upper_bound(ByteBuffer::from_scalar(value)?, max_bytes, nullability) - .map(|(bound, _)| bound), - ) - } - _ => Ok(Some(value)), - } -} diff --git a/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs b/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs index 99043fff5c3..60cce43815c 100644 --- a/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs @@ -6,6 +6,7 @@ use std::fmt::Formatter; use vortex_buffer::BufferString; use vortex_buffer::ByteBuffer; +use vortex_error::VortexExpect; use vortex_error::VortexResult; use vortex_error::vortex_ensure; use vortex_session::VortexSession; @@ -56,7 +57,9 @@ impl BoundedMinPartial { } self.min = Some(match self.min.take() { - Some(current) => partial_min(min, current).expect("incomparable bounded min scalars"), + Some(current) => { + partial_min(min, current).vortex_expect("incomparable bounded min scalars") + } None => min, }); } @@ -172,6 +175,25 @@ fn supported_dtype<'a>(options: &BoundedMinOptions, input_dtype: &'a DType) -> O .map(|_| input_dtype) } +fn truncate_min(value: Scalar, max_bytes: usize) -> VortexResult> { + let nullability = value.dtype().nullability(); + match value.dtype() { + DType::Utf8(_) => { + Ok( + lower_bound(BufferString::from_scalar(value)?, max_bytes, nullability) + .map(|(bound, _)| bound), + ) + } + DType::Binary(_) => { + Ok( + lower_bound(ByteBuffer::from_scalar(value)?, max_bytes, nullability) + .map(|(bound, _)| bound), + ) + } + _ => Ok(Some(value)), + } +} + #[cfg(test)] mod tests { use vortex_buffer::buffer; @@ -243,22 +265,3 @@ mod tests { Ok(()) } } - -fn truncate_min(value: Scalar, max_bytes: usize) -> VortexResult> { - let nullability = value.dtype().nullability(); - match value.dtype() { - DType::Utf8(_) => { - Ok( - lower_bound(BufferString::from_scalar(value)?, max_bytes, nullability) - .map(|(bound, _)| bound), - ) - } - DType::Binary(_) => { - Ok( - lower_bound(ByteBuffer::from_scalar(value)?, max_bytes, nullability) - .map(|(bound, _)| bound), - ) - } - _ => Ok(Some(value)), - } -} diff --git a/vortex-array/src/stats/expr.rs b/vortex-array/src/stats/expr.rs index ddd1976ff97..a35497f3d17 100644 --- a/vortex-array/src/stats/expr.rs +++ b/vortex-array/src/stats/expr.rs @@ -322,18 +322,15 @@ mod tests { } #[test] - fn stat_expr_reads_all_nan_false_for_empty_non_float() -> VortexResult<()> { + fn stat_expr_rejects_all_nan_for_non_float() -> VortexResult<()> { let array = PrimitiveArray::empty::(Nullability::NonNullable).into_array(); + let mut ctx = LEGACY_SESSION.create_execution_ctx(); let result = array - .apply(&super::all_nan(root()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? - .into_array(); - - let expected = - ConstantArray::new(Scalar::bool(false, Nullability::Nullable), 0).into_array(); - assert_arrays_eq!(result, expected); + .apply(&super::all_nan(root())) + .and_then(|array| array.execute::(&mut ctx)); + assert!(result.is_err()); Ok(()) } From 4dec6956e19b3699d9ccddf88e2c35cc1a818494 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Sun, 17 May 2026 17:35:47 +0100 Subject: [PATCH 5/7] Tighten pruning aggregate semantics Signed-off-by: Nicholas Gates --- vortex-array/public-api.lock | 4 +- .../src/aggregate_fn/fns/all_nan/mod.rs | 13 ++- .../aggregate_fn/fns/all_non_distinct/mod.rs | 3 + .../src/aggregate_fn/fns/all_non_nan/mod.rs | 9 +- .../src/aggregate_fn/fns/all_non_null/mod.rs | 15 +++ .../src/aggregate_fn/fns/all_null/mod.rs | 15 +++ .../src/aggregate_fn/fns/bounded_max/mod.rs | 87 ++++++++++++--- .../src/aggregate_fn/fns/bounded_min/mod.rs | 40 ++++--- vortex-array/src/aggregate_fn/fns/max/mod.rs | 21 ++++ .../src/aggregate_fn/fns/nan_count/mod.rs | 4 +- vortex-array/src/scalar_fn/fns/stat.rs | 8 +- vortex-array/src/stats/expr.rs | 102 ++++++++++++++++++ 12 files changed, 271 insertions(+), 50 deletions(-) diff --git a/vortex-array/public-api.lock b/vortex-array/public-api.lock index 67ac7686457..a83f08f129c 100644 --- a/vortex-array/public-api.lock +++ b/vortex-array/public-api.lock @@ -468,7 +468,7 @@ pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::try_accumulate( pub struct vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions -pub vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions::max_bytes: usize +pub vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions::max_bytes: core::num::nonzero::NonZeroUsize impl core::clone::Clone for vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions @@ -546,7 +546,7 @@ pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::try_accumulate( pub struct vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions -pub vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions::max_bytes: usize +pub vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions::max_bytes: core::num::nonzero::NonZeroUsize impl core::clone::Clone for vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions diff --git a/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs b/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs index c6b41f1ac8f..e63a2357b53 100644 --- a/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs @@ -17,6 +17,8 @@ use crate::scalar::Scalar; /// Compute whether every value in an array is NaN. /// +/// Like other `all` aggregates, this is vacuously true for empty input. +/// /// This is a pruning aggregate, not just a convenience wrapper around /// [`NanCount`][crate::aggregate_fn::fns::nan_count::NanCount]. Pruning aggregates must prove a /// row-wise fact for every value in the scope, so their partials remain valid when a stats column is @@ -39,7 +41,8 @@ impl AggregateFnVTable for AllNan { } fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option { - has_nans(input_dtype).then_some(DType::Bool(Nullability::Nullable)) + matches!(input_dtype, DType::Primitive(ptype, _) if ptype.is_float()) + .then_some(DType::Bool(Nullability::Nullable)) } fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { @@ -77,7 +80,7 @@ impl AggregateFnVTable for AllNan { batch: &ArrayRef, ctx: &mut ExecutionCtx, ) -> VortexResult { - if !has_nans(batch.dtype()) { + if !matches!(batch.dtype(), DType::Primitive(ptype, _) if ptype.is_float()) { *state = false; return Ok(true); } @@ -96,7 +99,7 @@ impl AggregateFnVTable for AllNan { Columnar::Constant(c) => c.clone().into_array(), Columnar::Canonical(c) => c.clone().into_array(), }; - if !has_nans(array.dtype()) { + if !matches!(array.dtype(), DType::Primitive(ptype, _) if ptype.is_float()) { *partial = false; return Ok(()); } @@ -114,10 +117,6 @@ impl AggregateFnVTable for AllNan { } } -fn has_nans(dtype: &DType) -> bool { - matches!(dtype, DType::Primitive(ptype, _) if ptype.is_float()) -} - #[cfg(test)] mod tests { use vortex_error::VortexResult; diff --git a/vortex-array/src/aggregate_fn/fns/all_non_distinct/mod.rs b/vortex-array/src/aggregate_fn/fns/all_non_distinct/mod.rs index 10763ba42f3..445f4e17b6f 100644 --- a/vortex-array/src/aggregate_fn/fns/all_non_distinct/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/all_non_distinct/mod.rs @@ -51,6 +51,7 @@ use crate::validity::Validity; /// Returns `true` if and only if: /// - Both arrays have the same dtype and length /// - At every position, both are null or both are non-null with the same value +/// - The arrays are empty, vacuously /// /// This is a fused `bool_all(non_distinct(lhs, rhs))` aggregate that allows early /// termination via accumulator saturation as soon as a mismatch is found. @@ -102,6 +103,8 @@ static NAMES: LazyLock = LazyLock::new(|| FieldNames::from(["lhs", " /// as the first distinct pair is found, the accumulator is saturated and remaining batches /// are skipped. /// +/// Like other `all` aggregates, this is vacuously true for empty input. +/// /// The input is a `Struct{lhs: T, rhs: T}` and the result is `Bool(NonNullable)`. #[derive(Clone, Debug)] pub struct AllNonDistinct; diff --git a/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs b/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs index aacce0d0c71..42ffa947aee 100644 --- a/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs @@ -17,6 +17,8 @@ use crate::scalar::Scalar; /// Compute whether every value in an array is not NaN. /// +/// Like other `all` aggregates, this is vacuously true for empty input. +/// /// This is a pruning aggregate, not just a convenience wrapper around /// [`NanCount`][crate::aggregate_fn::fns::nan_count::NanCount]. Pruning aggregates must prove a /// row-wise fact for every value in the scope, so their partials remain valid when a stats column is @@ -39,7 +41,8 @@ impl AggregateFnVTable for AllNonNan { } fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option { - has_nans(input_dtype).then_some(DType::Bool(Nullability::Nullable)) + matches!(input_dtype, DType::Primitive(ptype, _) if ptype.is_float()) + .then_some(DType::Bool(Nullability::Nullable)) } fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { @@ -104,10 +107,6 @@ impl AggregateFnVTable for AllNonNan { } } -fn has_nans(dtype: &DType) -> bool { - matches!(dtype, DType::Primitive(ptype, _) if ptype.is_float()) -} - #[cfg(test)] mod tests { use vortex_error::VortexResult; diff --git a/vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs b/vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs index e8d89957dc3..4b553ee7489 100644 --- a/vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs @@ -15,6 +15,8 @@ use crate::dtype::Nullability; use crate::scalar::Scalar; /// Compute whether every value in an array is non-null. +/// +/// Like other `all` aggregates, this is vacuously true for empty input. #[derive(Clone, Debug)] pub struct AllNonNull; @@ -136,4 +138,17 @@ mod tests { assert!(!bool::try_from(&acc.finish()?)?); Ok(()) } + + #[test] + fn all_non_null_true_for_empty_input() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNonNull, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::empty::(Nullability::Nullable).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(bool::try_from(&acc.finish()?)?); + Ok(()) + } } diff --git a/vortex-array/src/aggregate_fn/fns/all_null/mod.rs b/vortex-array/src/aggregate_fn/fns/all_null/mod.rs index 56e620b2a0e..3dbbbbf6256 100644 --- a/vortex-array/src/aggregate_fn/fns/all_null/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/all_null/mod.rs @@ -15,6 +15,8 @@ use crate::dtype::Nullability; use crate::scalar::Scalar; /// Compute whether every value in an array is null. +/// +/// Like other `all` aggregates, this is vacuously true for empty input. #[derive(Clone, Debug)] pub struct AllNull; @@ -139,4 +141,17 @@ mod tests { assert!(!bool::try_from(&acc.finish()?)?); Ok(()) } + + #[test] + fn all_null_true_for_empty_input() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let dtype = DType::Primitive(PType::I32, Nullability::Nullable); + let mut acc = Accumulator::try_new(AllNull, EmptyOptions, dtype)?; + + let batch = PrimitiveArray::empty::(Nullability::Nullable).into_array(); + acc.accumulate(&batch, &mut ctx)?; + + assert!(bool::try_from(&acc.finish()?)?); + Ok(()) + } } diff --git a/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs b/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs index 0f80a486e26..9c1ddeb6e8b 100644 --- a/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs @@ -3,6 +3,7 @@ use std::fmt::Display; use std::fmt::Formatter; +use std::num::NonZeroUsize; use vortex_buffer::BufferString; use vortex_buffer::ByteBuffer; @@ -30,12 +31,12 @@ use crate::scalar::upper_bound; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct BoundedMaxOptions { /// Maximum byte length for UTF8/Binary bounds. - pub max_bytes: usize, + pub max_bytes: NonZeroUsize, } impl Display for BoundedMaxOptions { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.max_bytes) + write!(f, "{}", self.max_bytes.get()) } } @@ -53,7 +54,7 @@ enum BoundedMaxState { pub struct BoundedMaxPartial { state: BoundedMaxState, element_dtype: DType, - max_bytes: usize, + max_bytes: NonZeroUsize, } impl BoundedMaxPartial { @@ -86,7 +87,7 @@ impl AggregateFnVTable for BoundedMax { } fn serialize(&self, options: &Self::Options) -> VortexResult>> { - let max_bytes = u64::try_from(options.max_bytes)?; + let max_bytes = u64::try_from(options.max_bytes.get())?; Ok(Some(max_bytes.to_le_bytes().to_vec())) } @@ -105,7 +106,9 @@ impl AggregateFnVTable for BoundedMax { bytes.copy_from_slice(metadata); let max_bytes = usize::try_from(u64::from_le_bytes(bytes))?; vortex_ensure!(max_bytes > 0, "BoundedMax requires max_bytes > 0"); - Ok(BoundedMaxOptions { max_bytes }) + Ok(BoundedMaxOptions { + max_bytes: NonZeroUsize::new(max_bytes).vortex_expect("checked non-zero max_bytes"), + }) } fn return_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { @@ -162,7 +165,7 @@ impl AggregateFnVTable for BoundedMax { let Some(result) = min_max(&array, ctx)? else { return Ok(()); }; - match truncate_max(result.max, partial.max_bytes)? { + match truncate_max(result.max, partial.max_bytes.get())? { Some(bound) => partial.merge(bound), None => partial.unknown(), } @@ -178,11 +181,7 @@ impl AggregateFnVTable for BoundedMax { } } -fn supported_dtype<'a>(options: &BoundedMaxOptions, input_dtype: &'a DType) -> Option<&'a DType> { - if options.max_bytes == 0 { - return None; - } - +fn supported_dtype<'a>(_options: &BoundedMaxOptions, input_dtype: &'a DType) -> Option<&'a DType> { MinMax .return_dtype(&EmptyOptions, input_dtype) .map(|_| input_dtype) @@ -209,7 +208,10 @@ fn truncate_max(value: Scalar, max_bytes: usize) -> VortexResult> #[cfg(test)] mod tests { + use std::num::NonZeroUsize; + use vortex_buffer::buffer; + use vortex_error::VortexExpect; use vortex_error::VortexResult; use vortex_session::VortexSession; @@ -227,13 +229,19 @@ mod tests { use crate::scalar::Scalar; use crate::validity::Validity; + fn max_bytes(value: usize) -> NonZeroUsize { + NonZeroUsize::new(value).vortex_expect("non-zero max_bytes") + } + #[test] fn bounded_max_truncates_utf8_to_upper_bound() -> VortexResult<()> { let mut ctx = LEGACY_SESSION.create_execution_ctx(); let array = VarBinViewArray::from_iter_str(["aardvark", "char🪩"]).into_array(); let mut acc = Accumulator::try_new( BoundedMax, - BoundedMaxOptions { max_bytes: 5 }, + BoundedMaxOptions { + max_bytes: max_bytes(5), + }, array.dtype().clone(), )?; @@ -249,7 +257,9 @@ mod tests { let array = VarBinViewArray::from_iter_bin([&[255u8, 255, 255][..]]).into_array(); let mut acc = Accumulator::try_new( BoundedMax, - BoundedMaxOptions { max_bytes: 2 }, + BoundedMaxOptions { + max_bytes: max_bytes(2), + }, array.dtype().clone(), )?; @@ -259,13 +269,58 @@ mod tests { Ok(()) } + #[test] + fn bounded_max_empty_does_not_poison_later_values() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let empty = VarBinViewArray::from_iter_bin(Vec::<&[u8]>::new()).into_array(); + let values = VarBinViewArray::from_iter_bin([&[1u8][..]]).into_array(); + let mut acc = Accumulator::try_new( + BoundedMax, + BoundedMaxOptions { + max_bytes: max_bytes(2), + }, + empty.dtype().clone(), + )?; + + acc.accumulate(&empty, &mut ctx)?; + acc.accumulate(&values, &mut ctx)?; + + assert_eq!( + acc.finish()?, + Scalar::binary(buffer![1u8], Nullability::Nullable) + ); + Ok(()) + } + + #[test] + fn bounded_max_unknown_poisons_later_values() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let unknown = VarBinViewArray::from_iter_bin([&[255u8, 255, 255][..]]).into_array(); + let values = VarBinViewArray::from_iter_bin([&[1u8][..]]).into_array(); + let mut acc = Accumulator::try_new( + BoundedMax, + BoundedMaxOptions { + max_bytes: max_bytes(2), + }, + unknown.dtype().clone(), + )?; + + acc.accumulate(&unknown, &mut ctx)?; + acc.accumulate(&values, &mut ctx)?; + + assert_eq!(acc.finish()?, Scalar::null(unknown.dtype().as_nullable())); + Ok(()) + } + #[test] fn bounded_max_keeps_fixed_width_values_exact() -> VortexResult<()> { let mut ctx = LEGACY_SESSION.create_execution_ctx(); let array = PrimitiveArray::new(buffer![10i32, 20, 5], Validity::NonNullable).into_array(); let mut acc = Accumulator::try_new( BoundedMax, - BoundedMaxOptions { max_bytes: 9 }, + BoundedMaxOptions { + max_bytes: max_bytes(9), + }, array.dtype().clone(), )?; @@ -280,7 +335,9 @@ mod tests { #[test] fn bounded_max_options_round_trip() -> VortexResult<()> { - let options = BoundedMaxOptions { max_bytes: 64 }; + let options = BoundedMaxOptions { + max_bytes: max_bytes(64), + }; let metadata = BoundedMax .serialize(&options)? .expect("serializable options"); diff --git a/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs b/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs index 60cce43815c..960bfbc9d1e 100644 --- a/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs @@ -3,6 +3,7 @@ use std::fmt::Display; use std::fmt::Formatter; +use std::num::NonZeroUsize; use vortex_buffer::BufferString; use vortex_buffer::ByteBuffer; @@ -30,12 +31,12 @@ use crate::scalar::lower_bound; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct BoundedMinOptions { /// Maximum byte length for UTF8/Binary bounds. - pub max_bytes: usize, + pub max_bytes: NonZeroUsize, } impl Display for BoundedMinOptions { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.max_bytes) + write!(f, "{}", self.max_bytes.get()) } } @@ -47,7 +48,7 @@ pub struct BoundedMin; pub struct BoundedMinPartial { min: Option, element_dtype: DType, - max_bytes: usize, + max_bytes: NonZeroUsize, } impl BoundedMinPartial { @@ -74,7 +75,7 @@ impl AggregateFnVTable for BoundedMin { } fn serialize(&self, options: &Self::Options) -> VortexResult>> { - let max_bytes = u64::try_from(options.max_bytes)?; + let max_bytes = u64::try_from(options.max_bytes.get())?; Ok(Some(max_bytes.to_le_bytes().to_vec())) } @@ -93,7 +94,9 @@ impl AggregateFnVTable for BoundedMin { bytes.copy_from_slice(metadata); let max_bytes = usize::try_from(u64::from_le_bytes(bytes))?; vortex_ensure!(max_bytes > 0, "BoundedMin requires max_bytes > 0"); - Ok(BoundedMinOptions { max_bytes }) + Ok(BoundedMinOptions { + max_bytes: NonZeroUsize::new(max_bytes).vortex_expect("checked non-zero max_bytes"), + }) } fn return_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { @@ -150,7 +153,7 @@ impl AggregateFnVTable for BoundedMin { let Some(result) = min_max(&array, ctx)? else { return Ok(()); }; - if let Some(bound) = truncate_min(result.min, partial.max_bytes)? { + if let Some(bound) = truncate_min(result.min, partial.max_bytes.get())? { partial.merge(bound); } Ok(()) @@ -165,11 +168,7 @@ impl AggregateFnVTable for BoundedMin { } } -fn supported_dtype<'a>(options: &BoundedMinOptions, input_dtype: &'a DType) -> Option<&'a DType> { - if options.max_bytes == 0 { - return None; - } - +fn supported_dtype<'a>(_options: &BoundedMinOptions, input_dtype: &'a DType) -> Option<&'a DType> { MinMax .return_dtype(&EmptyOptions, input_dtype) .map(|_| input_dtype) @@ -196,7 +195,10 @@ fn truncate_min(value: Scalar, max_bytes: usize) -> VortexResult> #[cfg(test)] mod tests { + use std::num::NonZeroUsize; + use vortex_buffer::buffer; + use vortex_error::VortexExpect; use vortex_error::VortexResult; use vortex_session::VortexSession; @@ -214,6 +216,10 @@ mod tests { use crate::scalar::Scalar; use crate::validity::Validity; + fn max_bytes(value: usize) -> NonZeroUsize { + NonZeroUsize::new(value).vortex_expect("non-zero max_bytes") + } + #[test] fn bounded_min_truncates_utf8_to_lower_bound() -> VortexResult<()> { let mut ctx = LEGACY_SESSION.create_execution_ctx(); @@ -221,7 +227,9 @@ mod tests { VarBinViewArray::from_iter_str(["snowman⛄️snowman", "untruncated"]).into_array(); let mut acc = Accumulator::try_new( BoundedMin, - BoundedMinOptions { max_bytes: 9 }, + BoundedMinOptions { + max_bytes: max_bytes(9), + }, array.dtype().clone(), )?; @@ -240,7 +248,9 @@ mod tests { let array = PrimitiveArray::new(buffer![10i32, 20, 5], Validity::NonNullable).into_array(); let mut acc = Accumulator::try_new( BoundedMin, - BoundedMinOptions { max_bytes: 9 }, + BoundedMinOptions { + max_bytes: max_bytes(9), + }, array.dtype().clone(), )?; @@ -255,7 +265,9 @@ mod tests { #[test] fn bounded_min_options_round_trip() -> VortexResult<()> { - let options = BoundedMinOptions { max_bytes: 64 }; + let options = BoundedMinOptions { + max_bytes: max_bytes(64), + }; let metadata = BoundedMin .serialize(&options)? .expect("serializable options"); diff --git a/vortex-array/src/aggregate_fn/fns/max/mod.rs b/vortex-array/src/aggregate_fn/fns/max/mod.rs index b56e87b7acc..5cc10cf3cd7 100644 --- a/vortex-array/src/aggregate_fn/fns/max/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/max/mod.rs @@ -137,7 +137,10 @@ mod tests { use crate::dtype::DType; use crate::dtype::Nullability; use crate::dtype::PType; + use crate::expr::stats::Precision; + use crate::expr::stats::Stat; use crate::scalar::Scalar; + use crate::scalar::ScalarValue; use crate::validity::Validity; #[test] @@ -170,4 +173,22 @@ mod tests { ); Ok(()) } + + #[test] + fn max_casts_nonnullable_legacy_stat_to_nullable_partial() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let batch = PrimitiveArray::new(buffer![10i32, 20], Validity::NonNullable).into_array(); + batch + .statistics() + .set(Stat::Max, Precision::Exact(ScalarValue::from(25i32))); + let mut acc = Accumulator::try_new(Max, EmptyOptions, batch.dtype().clone())?; + + acc.accumulate(&batch, &mut ctx)?; + + assert_eq!( + acc.finish()?, + Scalar::primitive(25i32, Nullability::Nullable) + ); + Ok(()) + } } diff --git a/vortex-array/src/aggregate_fn/fns/nan_count/mod.rs b/vortex-array/src/aggregate_fn/fns/nan_count/mod.rs index 0e229ed7982..72427372609 100644 --- a/vortex-array/src/aggregate_fn/fns/nan_count/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/nan_count/mod.rs @@ -29,6 +29,8 @@ use crate::scalar::ScalarValue; /// Return the number of NaN values in an array. /// +/// Null values are not NaN and are not counted. +/// /// See [`NanCount`] for details. pub fn nan_count(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { // Short-circuit using cached array statistics. @@ -72,7 +74,7 @@ pub fn nan_count(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult() { let len = u64::try_from(len)?; - if !has_nans(array.dtype()) { + if !matches!(array.dtype(), DType::Primitive(ptype, _) if ptype.is_float()) { Some(false) } else { array @@ -175,7 +175,7 @@ fn stat_array( } .map(ScalarValue::Bool) } else if aggregate_fn.is::() { - if !has_nans(array.dtype()) { + if !matches!(array.dtype(), DType::Primitive(ptype, _) if ptype.is_float()) { Some(true) } else { array @@ -206,7 +206,3 @@ fn stat_array( let scalar = Scalar::try_new(dtype, value)?; Ok(ConstantArray::new(scalar, len).into_array()) } - -fn has_nans(dtype: &DType) -> bool { - matches!(dtype, DType::Primitive(ptype, _) if ptype.is_float()) -} diff --git a/vortex-array/src/stats/expr.rs b/vortex-array/src/stats/expr.rs index a35497f3d17..4c686133f5e 100644 --- a/vortex-array/src/stats/expr.rs +++ b/vortex-array/src/stats/expr.rs @@ -334,6 +334,108 @@ mod tests { Ok(()) } + #[test] + fn stat_expr_reads_cached_all_nan_from_nan_count() -> VortexResult<()> { + let array = + PrimitiveArray::from_option_iter([Some(f32::NAN), Some(f32::NAN), Some(f32::NAN)]) + .into_array(); + array + .statistics() + .set(Stat::NaNCount, Precision::exact(ScalarValue::from(3u64))); + + let result = array + .apply(&super::all_nan(root()))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::bool(true, Nullability::Nullable), 3).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + + #[test] + fn stat_expr_reads_cached_all_nan_false_from_inexact_low_nan_count() -> VortexResult<()> { + let array = + PrimitiveArray::from_option_iter([Some(f32::NAN), Some(1.0f32), Some(f32::NAN)]) + .into_array(); + array + .statistics() + .set(Stat::NaNCount, Precision::inexact(ScalarValue::from(2u64))); + + let result = array + .apply(&super::all_nan(root()))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::bool(false, Nullability::Nullable), 3).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + + #[test] + fn stat_expr_returns_null_for_inexact_full_nan_count_as_all_nan() -> VortexResult<()> { + let array = + PrimitiveArray::from_option_iter([Some(f32::NAN), Some(1.0f32), Some(f32::NAN)]) + .into_array(); + array + .statistics() + .set(Stat::NaNCount, Precision::inexact(ScalarValue::from(3u64))); + + let result = array + .apply(&super::all_nan(root()))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::null(DType::Bool(Nullability::Nullable)), 3).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + + #[test] + fn stat_expr_reads_cached_all_non_nan_true_from_inexact_zero_nan_count() -> VortexResult<()> { + let array = buffer![1.0f32, 2.0, 3.0].into_array(); + array + .statistics() + .set(Stat::NaNCount, Precision::inexact(ScalarValue::from(0u64))); + + let result = array + .apply(&super::all_non_nan(root()))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::bool(true, Nullability::Nullable), 3).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + + #[test] + fn stat_expr_returns_null_for_inexact_nonzero_nan_count_as_all_non_nan() -> VortexResult<()> { + let array = PrimitiveArray::from_option_iter([Some(1.0f32), Some(f32::NAN), Some(3.0)]) + .into_array(); + array + .statistics() + .set(Stat::NaNCount, Precision::inexact(ScalarValue::from(1u64))); + + let result = array + .apply(&super::all_non_nan(root()))? + .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .into_array(); + + let expected = + ConstantArray::new(Scalar::null(DType::Bool(Nullability::Nullable)), 3).into_array(); + assert_arrays_eq!(result, expected); + + Ok(()) + } + #[test] fn stat_expr_reads_cached_min_and_max() -> VortexResult<()> { let array = buffer![3i32, 1, 2].into_array(); From e09432ddd968233af502092f46080c5f8294cce0 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Tue, 19 May 2026 16:25:36 -0400 Subject: [PATCH 6/7] Add aggregate satisfaction relation Signed-off-by: Nicholas Gates --- vortex-array/public-api.lock | 132 ++++++++++++++++++ vortex-array/src/aggregate_fn/erased.rs | 6 + .../src/aggregate_fn/fns/bounded_max/mod.rs | 54 +++++++ .../src/aggregate_fn/fns/bounded_min/mod.rs | 54 +++++++ vortex-array/src/aggregate_fn/mod.rs | 3 + vortex-array/src/aggregate_fn/satisfaction.rs | 26 ++++ vortex-array/src/aggregate_fn/typed.rs | 6 + vortex-array/src/aggregate_fn/vtable.rs | 20 +++ 8 files changed, 301 insertions(+) create mode 100644 vortex-array/src/aggregate_fn/satisfaction.rs diff --git a/vortex-array/public-api.lock b/vortex-array/public-api.lock index a83f08f129c..c5184c008cd 100644 --- a/vortex-array/public-api.lock +++ b/vortex-array/public-api.lock @@ -56,6 +56,8 @@ pub type vortex_array::aggregate_fn::combined::Combined::Partial = (alloc::bo pub fn vortex_array::aggregate_fn::combined::Combined::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::combined::Combined::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::combined::Combined::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::combined::Combined::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -194,6 +196,8 @@ pub type vortex_array::aggregate_fn::fns::all_nan::AllNan::Partial = bool pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -242,6 +246,8 @@ pub type vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::Part pub fn vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -294,6 +300,8 @@ pub type vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::Partial = bool pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -342,6 +350,8 @@ pub type vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::Partial = bo pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -390,6 +400,8 @@ pub type vortex_array::aggregate_fn::fns::all_null::AllNull::Partial = bool pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -438,6 +450,8 @@ pub type vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::Partial = vor pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -516,6 +530,8 @@ pub type vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::Partial = vor pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -594,6 +610,8 @@ pub type vortex_array::aggregate_fn::fns::count::Count::Partial = u64 pub fn vortex_array::aggregate_fn::fns::count::Count::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::count::Count::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::count::Count::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::count::Count::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -642,6 +660,8 @@ pub type vortex_array::aggregate_fn::fns::first::First::Partial = vortex_array:: pub fn vortex_array::aggregate_fn::fns::first::First::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::first::First::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::first::First::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::first::First::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -704,6 +724,8 @@ pub type vortex_array::aggregate_fn::fns::is_constant::IsConstant::Partial = vor pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -762,6 +784,8 @@ pub type vortex_array::aggregate_fn::fns::is_sorted::IsSorted::Partial = vortex_ pub fn vortex_array::aggregate_fn::fns::is_sorted::IsSorted::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::is_sorted::IsSorted::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::is_sorted::IsSorted::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::is_sorted::IsSorted::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -854,6 +878,8 @@ pub type vortex_array::aggregate_fn::fns::last::Last::Partial = vortex_array::ag pub fn vortex_array::aggregate_fn::fns::last::Last::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::last::Last::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::last::Last::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::last::Last::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -906,6 +932,8 @@ pub type vortex_array::aggregate_fn::fns::max::Max::Partial = vortex_array::aggr pub fn vortex_array::aggregate_fn::fns::max::Max::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::max::Max::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::max::Max::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::max::Max::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1004,6 +1032,8 @@ pub type vortex_array::aggregate_fn::fns::min::Min::Partial = vortex_array::aggr pub fn vortex_array::aggregate_fn::fns::min::Min::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::min::Min::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::min::Min::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::min::Min::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1054,6 +1084,8 @@ pub type vortex_array::aggregate_fn::fns::min_max::MinMax::Partial = vortex_arra pub fn vortex_array::aggregate_fn::fns::min_max::MinMax::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::min_max::MinMax::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::min_max::MinMax::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::min_max::MinMax::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1134,6 +1166,8 @@ pub type vortex_array::aggregate_fn::fns::nan_count::NanCount::Partial = u64 pub fn vortex_array::aggregate_fn::fns::nan_count::NanCount::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::nan_count::NanCount::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::nan_count::NanCount::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::nan_count::NanCount::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1184,6 +1218,8 @@ pub type vortex_array::aggregate_fn::fns::null_count::NullCount::Partial = u64 pub fn vortex_array::aggregate_fn::fns::null_count::NullCount::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::null_count::NullCount::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::null_count::NullCount::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::null_count::NullCount::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1248,6 +1284,8 @@ pub type vortex_array::aggregate_fn::fns::sum::Sum::Partial = vortex_array::aggr pub fn vortex_array::aggregate_fn::fns::sum::Sum::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::sum::Sum::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::sum::Sum::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::sum::Sum::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1300,6 +1338,8 @@ pub type vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::Uncompress pub fn vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1380,6 +1420,54 @@ pub fn S::aggregate_fns(&self) -> vortex_session::Ref<'_, vortex_array::aggregat pub type vortex_array::aggregate_fn::session::AggregateFnRegistry = vortex_session::registry::Registry +pub enum vortex_array::aggregate_fn::AggregateFnSatisfaction + +pub vortex_array::aggregate_fn::AggregateFnSatisfaction::Approximate + +pub vortex_array::aggregate_fn::AggregateFnSatisfaction::Exact + +pub vortex_array::aggregate_fn::AggregateFnSatisfaction::No + +impl vortex_array::aggregate_fn::AggregateFnSatisfaction + +pub fn vortex_array::aggregate_fn::AggregateFnSatisfaction::is_exact(self) -> bool + +pub fn vortex_array::aggregate_fn::AggregateFnSatisfaction::is_satisfied(self) -> bool + +impl core::clone::Clone for vortex_array::aggregate_fn::AggregateFnSatisfaction + +pub fn vortex_array::aggregate_fn::AggregateFnSatisfaction::clone(&self) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + +impl core::cmp::Eq for vortex_array::aggregate_fn::AggregateFnSatisfaction + +impl core::cmp::Ord for vortex_array::aggregate_fn::AggregateFnSatisfaction + +pub fn vortex_array::aggregate_fn::AggregateFnSatisfaction::cmp(&self, &vortex_array::aggregate_fn::AggregateFnSatisfaction) -> core::cmp::Ordering + +impl core::cmp::PartialEq for vortex_array::aggregate_fn::AggregateFnSatisfaction + +pub fn vortex_array::aggregate_fn::AggregateFnSatisfaction::eq(&self, &vortex_array::aggregate_fn::AggregateFnSatisfaction) -> bool + +impl core::cmp::PartialOrd for vortex_array::aggregate_fn::AggregateFnSatisfaction + +pub fn vortex_array::aggregate_fn::AggregateFnSatisfaction::partial_cmp(&self, &vortex_array::aggregate_fn::AggregateFnSatisfaction) -> core::option::Option + +impl core::default::Default for vortex_array::aggregate_fn::AggregateFnSatisfaction + +pub fn vortex_array::aggregate_fn::AggregateFnSatisfaction::default() -> vortex_array::aggregate_fn::AggregateFnSatisfaction + +impl core::fmt::Debug for vortex_array::aggregate_fn::AggregateFnSatisfaction + +pub fn vortex_array::aggregate_fn::AggregateFnSatisfaction::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl core::hash::Hash for vortex_array::aggregate_fn::AggregateFnSatisfaction + +pub fn vortex_array::aggregate_fn::AggregateFnSatisfaction::hash<__H: core::hash::Hasher>(&self, &mut __H) + +impl core::marker::Copy for vortex_array::aggregate_fn::AggregateFnSatisfaction + +impl core::marker::StructuralPartialEq for vortex_array::aggregate_fn::AggregateFnSatisfaction + pub struct vortex_array::aggregate_fn::Accumulator impl vortex_array::aggregate_fn::Accumulator @@ -1456,6 +1544,8 @@ pub fn vortex_array::aggregate_fn::AggregateFnRef::as_(&self) -> core::option::Option<&::Options> +pub fn vortex_array::aggregate_fn::AggregateFnRef::can_satisfy(&self, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::AggregateFnRef::coerce_args(&self, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::AggregateFnRef::id(&self) -> vortex_array::aggregate_fn::AggregateFnId @@ -1558,6 +1648,8 @@ pub type vortex_array::aggregate_fn::AggregateFnVTable::Partial: 'static + core: pub fn vortex_array::aggregate_fn::AggregateFnVTable::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::AggregateFnVTable::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::AggregateFnVTable::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::AggregateFnVTable::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1594,6 +1686,8 @@ pub type vortex_array::aggregate_fn::fns::all_nan::AllNan::Partial = bool pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::all_nan::AllNan::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1630,6 +1724,8 @@ pub type vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::Part pub fn vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::all_non_distinct::AllNonDistinct::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1666,6 +1762,8 @@ pub type vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::Partial = bool pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1702,6 +1800,8 @@ pub type vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::Partial = bo pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::all_non_null::AllNonNull::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1738,6 +1838,8 @@ pub type vortex_array::aggregate_fn::fns::all_null::AllNull::Partial = bool pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::all_null::AllNull::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1774,6 +1876,8 @@ pub type vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::Partial = vor pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::bounded_max::BoundedMax::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1810,6 +1914,8 @@ pub type vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::Partial = vor pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::bounded_min::BoundedMin::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1846,6 +1952,8 @@ pub type vortex_array::aggregate_fn::fns::count::Count::Partial = u64 pub fn vortex_array::aggregate_fn::fns::count::Count::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::count::Count::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::count::Count::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::count::Count::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1882,6 +1990,8 @@ pub type vortex_array::aggregate_fn::fns::first::First::Partial = vortex_array:: pub fn vortex_array::aggregate_fn::fns::first::First::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::first::First::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::first::First::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::first::First::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1918,6 +2028,8 @@ pub type vortex_array::aggregate_fn::fns::is_constant::IsConstant::Partial = vor pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1954,6 +2066,8 @@ pub type vortex_array::aggregate_fn::fns::is_sorted::IsSorted::Partial = vortex_ pub fn vortex_array::aggregate_fn::fns::is_sorted::IsSorted::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::is_sorted::IsSorted::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::is_sorted::IsSorted::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::is_sorted::IsSorted::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -1990,6 +2104,8 @@ pub type vortex_array::aggregate_fn::fns::last::Last::Partial = vortex_array::ag pub fn vortex_array::aggregate_fn::fns::last::Last::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::last::Last::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::last::Last::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::last::Last::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -2026,6 +2142,8 @@ pub type vortex_array::aggregate_fn::fns::max::Max::Partial = vortex_array::aggr pub fn vortex_array::aggregate_fn::fns::max::Max::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::max::Max::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::max::Max::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::max::Max::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -2062,6 +2180,8 @@ pub type vortex_array::aggregate_fn::fns::min::Min::Partial = vortex_array::aggr pub fn vortex_array::aggregate_fn::fns::min::Min::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::min::Min::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::min::Min::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::min::Min::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -2098,6 +2218,8 @@ pub type vortex_array::aggregate_fn::fns::min_max::MinMax::Partial = vortex_arra pub fn vortex_array::aggregate_fn::fns::min_max::MinMax::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::min_max::MinMax::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::min_max::MinMax::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::min_max::MinMax::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -2134,6 +2256,8 @@ pub type vortex_array::aggregate_fn::fns::nan_count::NanCount::Partial = u64 pub fn vortex_array::aggregate_fn::fns::nan_count::NanCount::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::nan_count::NanCount::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::nan_count::NanCount::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::nan_count::NanCount::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -2170,6 +2294,8 @@ pub type vortex_array::aggregate_fn::fns::null_count::NullCount::Partial = u64 pub fn vortex_array::aggregate_fn::fns::null_count::NullCount::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::null_count::NullCount::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::null_count::NullCount::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::null_count::NullCount::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -2206,6 +2332,8 @@ pub type vortex_array::aggregate_fn::fns::sum::Sum::Partial = vortex_array::aggr pub fn vortex_array::aggregate_fn::fns::sum::Sum::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::sum::Sum::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::sum::Sum::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::sum::Sum::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -2242,6 +2370,8 @@ pub type vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::Uncompress pub fn vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> @@ -2278,6 +2408,8 @@ pub type vortex_array::aggregate_fn::combined::Combined::Partial = (alloc::bo pub fn vortex_array::aggregate_fn::combined::Combined::accumulate(&self, &mut Self::Partial, &vortex_array::Columnar, &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::combined::Combined::can_satisfy(&self, &Self::Options, &vortex_array::aggregate_fn::AggregateFnRef) -> vortex_array::aggregate_fn::AggregateFnSatisfaction + pub fn vortex_array::aggregate_fn::combined::Combined::coerce_args(&self, &Self::Options, &vortex_array::dtype::DType) -> vortex_error::VortexResult pub fn vortex_array::aggregate_fn::combined::Combined::combine_partials(&self, &mut Self::Partial, vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> diff --git a/vortex-array/src/aggregate_fn/erased.rs b/vortex-array/src/aggregate_fn/erased.rs index 750a7c24f77..0cf1f7c464d 100644 --- a/vortex-array/src/aggregate_fn/erased.rs +++ b/vortex-array/src/aggregate_fn/erased.rs @@ -16,6 +16,7 @@ use vortex_utils::debug_with::DebugWith; use crate::aggregate_fn::AccumulatorRef; use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnSatisfaction; use crate::aggregate_fn::AggregateFnVTable; use crate::aggregate_fn::GroupedAccumulatorRef; use crate::aggregate_fn::options::AggregateFnOptions; @@ -74,6 +75,11 @@ impl AggregateFnRef { AggregateFnOptions { inner: &*self.0 } } + /// Return whether this stored aggregate can satisfy `requested`. + pub fn can_satisfy(&self, requested: &AggregateFnRef) -> AggregateFnSatisfaction { + self.0.can_satisfy(requested) + } + /// Coerce the input type for this aggregate function. pub fn coerce_args(&self, input_dtype: &DType) -> VortexResult { self.0.coerce_args(input_dtype) diff --git a/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs b/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs index 9c1ddeb6e8b..743dfa762e5 100644 --- a/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs @@ -17,8 +17,11 @@ use crate::Columnar; use crate::ExecutionCtx; use crate::IntoArray; use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnRef; +use crate::aggregate_fn::AggregateFnSatisfaction; use crate::aggregate_fn::AggregateFnVTable; use crate::aggregate_fn::EmptyOptions; +use crate::aggregate_fn::fns::max::Max; use crate::aggregate_fn::fns::min_max::MinMax; use crate::aggregate_fn::fns::min_max::min_max; use crate::dtype::DType; @@ -115,6 +118,25 @@ impl AggregateFnVTable for BoundedMax { supported_dtype(options, input_dtype).map(DType::as_nullable) } + fn can_satisfy( + &self, + options: &Self::Options, + requested: &AggregateFnRef, + ) -> AggregateFnSatisfaction { + if requested + .as_opt::() + .is_some_and(|other| other == options) + { + return AggregateFnSatisfaction::Exact; + } + + if requested.is::() || requested.is::() { + AggregateFnSatisfaction::Approximate + } else { + AggregateFnSatisfaction::No + } + } + fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { self.return_dtype(options, input_dtype) } @@ -219,10 +241,15 @@ mod tests { use crate::LEGACY_SESSION; use crate::VortexSessionExecute; use crate::aggregate_fn::Accumulator; + use crate::aggregate_fn::AggregateFnSatisfaction; use crate::aggregate_fn::AggregateFnVTable; + use crate::aggregate_fn::AggregateFnVTableExt; use crate::aggregate_fn::DynAccumulator; + use crate::aggregate_fn::EmptyOptions; use crate::aggregate_fn::fns::bounded_max::BoundedMax; use crate::aggregate_fn::fns::bounded_max::BoundedMaxOptions; + use crate::aggregate_fn::fns::max::Max; + use crate::aggregate_fn::fns::min::Min; use crate::arrays::PrimitiveArray; use crate::arrays::VarBinViewArray; use crate::dtype::Nullability; @@ -333,6 +360,33 @@ mod tests { Ok(()) } + #[test] + fn bounded_max_satisfies_max_bounds() { + let stored = BoundedMax.bind(BoundedMaxOptions { + max_bytes: max_bytes(5), + }); + let same = BoundedMax.bind(BoundedMaxOptions { + max_bytes: max_bytes(5), + }); + let other_bounded = BoundedMax.bind(BoundedMaxOptions { + max_bytes: max_bytes(6), + }); + + assert_eq!(stored.can_satisfy(&same), AggregateFnSatisfaction::Exact); + assert_eq!( + stored.can_satisfy(&other_bounded), + AggregateFnSatisfaction::Approximate + ); + assert_eq!( + stored.can_satisfy(&Max.bind(EmptyOptions)), + AggregateFnSatisfaction::Approximate + ); + assert_eq!( + stored.can_satisfy(&Min.bind(EmptyOptions)), + AggregateFnSatisfaction::No + ); + } + #[test] fn bounded_max_options_round_trip() -> VortexResult<()> { let options = BoundedMaxOptions { diff --git a/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs b/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs index 960bfbc9d1e..05434f9ed5b 100644 --- a/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs @@ -17,8 +17,11 @@ use crate::Columnar; use crate::ExecutionCtx; use crate::IntoArray; use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnRef; +use crate::aggregate_fn::AggregateFnSatisfaction; use crate::aggregate_fn::AggregateFnVTable; use crate::aggregate_fn::EmptyOptions; +use crate::aggregate_fn::fns::min::Min; use crate::aggregate_fn::fns::min_max::MinMax; use crate::aggregate_fn::fns::min_max::min_max; use crate::dtype::DType; @@ -103,6 +106,25 @@ impl AggregateFnVTable for BoundedMin { supported_dtype(options, input_dtype).map(DType::as_nullable) } + fn can_satisfy( + &self, + options: &Self::Options, + requested: &AggregateFnRef, + ) -> AggregateFnSatisfaction { + if requested + .as_opt::() + .is_some_and(|other| other == options) + { + return AggregateFnSatisfaction::Exact; + } + + if requested.is::() || requested.is::() { + AggregateFnSatisfaction::Approximate + } else { + AggregateFnSatisfaction::No + } + } + fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option { self.return_dtype(options, input_dtype) } @@ -206,10 +228,15 @@ mod tests { use crate::LEGACY_SESSION; use crate::VortexSessionExecute; use crate::aggregate_fn::Accumulator; + use crate::aggregate_fn::AggregateFnSatisfaction; use crate::aggregate_fn::AggregateFnVTable; + use crate::aggregate_fn::AggregateFnVTableExt; use crate::aggregate_fn::DynAccumulator; + use crate::aggregate_fn::EmptyOptions; use crate::aggregate_fn::fns::bounded_min::BoundedMin; use crate::aggregate_fn::fns::bounded_min::BoundedMinOptions; + use crate::aggregate_fn::fns::max::Max; + use crate::aggregate_fn::fns::min::Min; use crate::arrays::PrimitiveArray; use crate::arrays::VarBinViewArray; use crate::dtype::Nullability; @@ -263,6 +290,33 @@ mod tests { Ok(()) } + #[test] + fn bounded_min_satisfies_min_bounds() { + let stored = BoundedMin.bind(BoundedMinOptions { + max_bytes: max_bytes(5), + }); + let same = BoundedMin.bind(BoundedMinOptions { + max_bytes: max_bytes(5), + }); + let other_bounded = BoundedMin.bind(BoundedMinOptions { + max_bytes: max_bytes(6), + }); + + assert_eq!(stored.can_satisfy(&same), AggregateFnSatisfaction::Exact); + assert_eq!( + stored.can_satisfy(&other_bounded), + AggregateFnSatisfaction::Approximate + ); + assert_eq!( + stored.can_satisfy(&Min.bind(EmptyOptions)), + AggregateFnSatisfaction::Approximate + ); + assert_eq!( + stored.can_satisfy(&Max.bind(EmptyOptions)), + AggregateFnSatisfaction::No + ); + } + #[test] fn bounded_min_options_round_trip() -> VortexResult<()> { let options = BoundedMinOptions { diff --git a/vortex-array/src/aggregate_fn/mod.rs b/vortex-array/src/aggregate_fn/mod.rs index 1bcd2cac030..820001d3a58 100644 --- a/vortex-array/src/aggregate_fn/mod.rs +++ b/vortex-array/src/aggregate_fn/mod.rs @@ -11,6 +11,9 @@ use vortex_session::registry::Id; mod accumulator; pub use accumulator::*; +mod satisfaction; +pub use satisfaction::*; + mod accumulator_grouped; pub use accumulator_grouped::*; diff --git a/vortex-array/src/aggregate_fn/satisfaction.rs b/vortex-array/src/aggregate_fn/satisfaction.rs new file mode 100644 index 00000000000..ffbe346d2d6 --- /dev/null +++ b/vortex-array/src/aggregate_fn/satisfaction.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +/// Whether a stored aggregate function can satisfy a requested aggregate function. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum AggregateFnSatisfaction { + /// The stored aggregate cannot satisfy the requested aggregate. + #[default] + No, + /// The stored aggregate can satisfy the request as an approximate bound. + Approximate, + /// The stored aggregate exactly satisfies the request. + Exact, +} + +impl AggregateFnSatisfaction { + /// Returns whether the stored aggregate can satisfy the requested aggregate. + pub fn is_satisfied(self) -> bool { + !matches!(self, Self::No) + } + + /// Returns whether the stored aggregate exactly satisfies the requested aggregate. + pub fn is_exact(self) -> bool { + matches!(self, Self::Exact) + } +} diff --git a/vortex-array/src/aggregate_fn/typed.rs b/vortex-array/src/aggregate_fn/typed.rs index 3d1d4a8d15a..b399477c0a5 100644 --- a/vortex-array/src/aggregate_fn/typed.rs +++ b/vortex-array/src/aggregate_fn/typed.rs @@ -24,6 +24,7 @@ use crate::aggregate_fn::Accumulator; use crate::aggregate_fn::AccumulatorRef; use crate::aggregate_fn::AggregateFnId; use crate::aggregate_fn::AggregateFnRef; +use crate::aggregate_fn::AggregateFnSatisfaction; use crate::aggregate_fn::AggregateFnVTable; use crate::aggregate_fn::GroupedAccumulator; use crate::aggregate_fn::GroupedAccumulatorRef; @@ -39,6 +40,7 @@ pub(super) trait DynAggregateFn: 'static + Send + Sync + super::sealed::Sealed { fn options_any(&self) -> &dyn Any; fn coerce_args(&self, input_dtype: &DType) -> VortexResult; + fn can_satisfy(&self, requested: &AggregateFnRef) -> AggregateFnSatisfaction; fn return_dtype(&self, input_dtype: &DType) -> Option; fn state_dtype(&self, input_dtype: &DType) -> Option; fn accumulator(&self, input_dtype: &DType) -> VortexResult; @@ -80,6 +82,10 @@ impl DynAggregateFn for AggregateFnInner { V::coerce_args(&self.vtable, &self.options, input_dtype) } + fn can_satisfy(&self, requested: &AggregateFnRef) -> AggregateFnSatisfaction { + V::can_satisfy(&self.vtable, &self.options, requested) + } + fn return_dtype(&self, input_dtype: &DType) -> Option { V::return_dtype(&self.vtable, &self.options, input_dtype) } diff --git a/vortex-array/src/aggregate_fn/vtable.rs b/vortex-array/src/aggregate_fn/vtable.rs index da6fcbc4165..28b91d45166 100644 --- a/vortex-array/src/aggregate_fn/vtable.rs +++ b/vortex-array/src/aggregate_fn/vtable.rs @@ -17,6 +17,7 @@ use crate::ExecutionCtx; use crate::aggregate_fn::AggregateFn; use crate::aggregate_fn::AggregateFnId; use crate::aggregate_fn::AggregateFnRef; +use crate::aggregate_fn::AggregateFnSatisfaction; use crate::dtype::DType; use crate::scalar::Scalar; @@ -66,6 +67,25 @@ pub trait AggregateFnVTable: 'static + Sized + Clone + Send + Sync { Ok(input_dtype.clone()) } + /// Return whether this stored aggregate can satisfy `requested`. + /// + /// The default implementation only treats exactly equal aggregate functions as satisfying the + /// request. Approximate pruning aggregates can override this to expose looser-but-sound bounds. + fn can_satisfy( + &self, + options: &Self::Options, + requested: &AggregateFnRef, + ) -> AggregateFnSatisfaction { + if requested + .as_opt::() + .is_some_and(|other| other == options) + { + AggregateFnSatisfaction::Exact + } else { + AggregateFnSatisfaction::No + } + } + /// The return [`DType`] of the aggregate. /// /// Returns `None` if the aggregate function cannot be applied to the input dtype. From d99cb3aac3c9059d6446ae4ccd583f4c2cc9b11a Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Tue, 19 May 2026 17:19:32 -0400 Subject: [PATCH 7/7] Address comments Signed-off-by: Nicholas Gates --- .../src/aggregate_fn/fns/all_nan/mod.rs | 2 + .../src/aggregate_fn/fns/all_non_nan/mod.rs | 2 + .../src/aggregate_fn/fns/all_non_null/mod.rs | 2 + .../src/aggregate_fn/fns/all_null/mod.rs | 2 + .../src/aggregate_fn/fns/bounded_max/mod.rs | 29 ++++++ .../src/aggregate_fn/fns/bounded_min/mod.rs | 59 +++++++++--- vortex-array/src/scalar_fn/fns/stat.rs | 46 ++++----- vortex-array/src/stats/expr.rs | 93 ++++++++++--------- 8 files changed, 149 insertions(+), 86 deletions(-) diff --git a/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs b/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs index e63a2357b53..c33443dd02b 100644 --- a/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/all_nan/mod.rs @@ -95,6 +95,8 @@ impl AggregateFnVTable for AllNan { batch: &Columnar, ctx: &mut ExecutionCtx, ) -> VortexResult<()> { + // Normal array dispatch is handled by `try_accumulate`, which always short-circuits. + // Keep this fallback in sync for direct Columnar accumulation paths. let array = match batch { Columnar::Constant(c) => c.clone().into_array(), Columnar::Canonical(c) => c.clone().into_array(), diff --git a/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs b/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs index 42ffa947aee..f8d371cb776 100644 --- a/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/all_non_nan/mod.rs @@ -90,6 +90,8 @@ impl AggregateFnVTable for AllNonNan { batch: &Columnar, ctx: &mut ExecutionCtx, ) -> VortexResult<()> { + // Normal array dispatch is handled by `try_accumulate`, which always short-circuits. + // Keep this fallback in sync for direct Columnar accumulation paths. let array = match batch { Columnar::Constant(c) => c.clone().into_array(), Columnar::Canonical(c) => c.clone().into_array(), diff --git a/vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs b/vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs index 4b553ee7489..a297c318fc9 100644 --- a/vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/all_non_null/mod.rs @@ -81,6 +81,8 @@ impl AggregateFnVTable for AllNonNull { batch: &Columnar, ctx: &mut ExecutionCtx, ) -> VortexResult<()> { + // Normal array dispatch is handled by `try_accumulate`, which always short-circuits. + // Keep this fallback in sync for direct Columnar accumulation paths. *partial &= match batch { Columnar::Constant(c) => c.is_empty() || !c.scalar().is_null(), Columnar::Canonical(c) => c.clone().into_array().invalid_count(ctx)? == 0, diff --git a/vortex-array/src/aggregate_fn/fns/all_null/mod.rs b/vortex-array/src/aggregate_fn/fns/all_null/mod.rs index 3dbbbbf6256..5476c7d534b 100644 --- a/vortex-array/src/aggregate_fn/fns/all_null/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/all_null/mod.rs @@ -81,6 +81,8 @@ impl AggregateFnVTable for AllNull { batch: &Columnar, ctx: &mut ExecutionCtx, ) -> VortexResult<()> { + // Normal array dispatch is handled by `try_accumulate`, which always short-circuits. + // Keep this fallback in sync for direct Columnar accumulation paths. *partial &= match batch { Columnar::Constant(c) => c.is_empty() || c.scalar().is_null(), Columnar::Canonical(c) => { diff --git a/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs b/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs index 743dfa762e5..d34d4033ce0 100644 --- a/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/bounded_max/mod.rs @@ -63,6 +63,9 @@ pub struct BoundedMaxPartial { impl BoundedMaxPartial { fn merge(&mut self, max: Scalar) { if max.is_null() { + // Serialized partials encode both empty input and unknown upper bounds as null. + // Treat null as unknown when merging; this may lose a bound from an empty shard, but + // it preserves pruning soundness. self.state = BoundedMaxState::Unknown; return; } @@ -180,6 +183,8 @@ impl AggregateFnVTable for BoundedMax { batch: &Columnar, ctx: &mut ExecutionCtx, ) -> VortexResult<()> { + // Delegate to the existing min_max implementation for now. A dedicated bounded-max + // aggregate would avoid computing min when only max is needed. let array = match batch { Columnar::Canonical(canonical) => canonical.clone().into_array(), Columnar::Constant(constant) => constant.clone().into_array(), @@ -254,12 +259,17 @@ mod tests { use crate::arrays::VarBinViewArray; use crate::dtype::Nullability; use crate::scalar::Scalar; + use crate::session::ArraySession; use crate::validity::Validity; fn max_bytes(value: usize) -> NonZeroUsize { NonZeroUsize::new(value).vortex_expect("non-zero max_bytes") } + fn fresh_session() -> VortexSession { + VortexSession::empty().with::() + } + #[test] fn bounded_max_truncates_utf8_to_upper_bound() -> VortexResult<()> { let mut ctx = LEGACY_SESSION.create_execution_ctx(); @@ -339,6 +349,25 @@ mod tests { Ok(()) } + #[test] + fn bounded_max_null_partial_poisons_existing_bound() -> VortexResult<()> { + let mut ctx = fresh_session().create_execution_ctx(); + let values = VarBinViewArray::from_iter_bin([&[1u8][..]]).into_array(); + let mut acc = Accumulator::try_new( + BoundedMax, + BoundedMaxOptions { + max_bytes: max_bytes(2), + }, + values.dtype().clone(), + )?; + + acc.accumulate(&values, &mut ctx)?; + acc.combine_partials(Scalar::null(values.dtype().as_nullable()))?; + + assert_eq!(acc.finish()?, Scalar::null(values.dtype().as_nullable())); + Ok(()) + } + #[test] fn bounded_max_keeps_fixed_width_values_exact() -> VortexResult<()> { let mut ctx = LEGACY_SESSION.create_execution_ctx(); diff --git a/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs b/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs index 05434f9ed5b..50608f3536c 100644 --- a/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/bounded_min/mod.rs @@ -47,9 +47,14 @@ impl Display for BoundedMinOptions { #[derive(Clone, Debug)] pub struct BoundedMin; +enum BoundedMinState { + Empty, + Value(Scalar), +} + /// Partial accumulator state for the bounded minimum aggregate. pub struct BoundedMinPartial { - min: Option, + state: BoundedMinState, element_dtype: DType, max_bytes: NonZeroUsize, } @@ -60,12 +65,12 @@ impl BoundedMinPartial { return; } - self.min = Some(match self.min.take() { - Some(current) => { - partial_min(min, current).vortex_expect("incomparable bounded min scalars") - } - None => min, - }); + self.state = match std::mem::replace(&mut self.state, BoundedMinState::Empty) { + BoundedMinState::Empty => BoundedMinState::Value(min), + BoundedMinState::Value(current) => BoundedMinState::Value( + partial_min(min, current).vortex_expect("incomparable bounded min scalars"), + ), + }; } } @@ -135,7 +140,7 @@ impl AggregateFnVTable for BoundedMin { input_dtype: &DType, ) -> VortexResult { Ok(BoundedMinPartial { - min: None, + state: BoundedMinState::Empty, element_dtype: input_dtype.clone(), max_bytes: options.max_bytes, }) @@ -148,14 +153,14 @@ impl AggregateFnVTable for BoundedMin { fn to_scalar(&self, partial: &Self::Partial) -> VortexResult { let dtype = partial.element_dtype.as_nullable(); - match &partial.min { - Some(min) => min.cast(&dtype), - None => Ok(Scalar::null(dtype)), + match &partial.state { + BoundedMinState::Empty => Ok(Scalar::null(dtype)), + BoundedMinState::Value(min) => min.cast(&dtype), } } fn reset(&self, partial: &mut Self::Partial) { - partial.min = None; + partial.state = BoundedMinState::Empty; } fn is_saturated(&self, _partial: &Self::Partial) -> bool { @@ -168,6 +173,8 @@ impl AggregateFnVTable for BoundedMin { batch: &Columnar, ctx: &mut ExecutionCtx, ) -> VortexResult<()> { + // Delegate to the existing min_max implementation for now. A dedicated bounded-min + // aggregate would avoid computing max when only min is needed. let array = match batch { Columnar::Canonical(canonical) => canonical.clone().into_array(), Columnar::Constant(constant) => constant.clone().into_array(), @@ -214,7 +221,6 @@ fn truncate_min(value: Scalar, max_bytes: usize) -> VortexResult> _ => Ok(Some(value)), } } - #[cfg(test)] mod tests { use std::num::NonZeroUsize; @@ -241,12 +247,17 @@ mod tests { use crate::arrays::VarBinViewArray; use crate::dtype::Nullability; use crate::scalar::Scalar; + use crate::session::ArraySession; use crate::validity::Validity; fn max_bytes(value: usize) -> NonZeroUsize { NonZeroUsize::new(value).vortex_expect("non-zero max_bytes") } + fn fresh_session() -> VortexSession { + VortexSession::empty().with::() + } + #[test] fn bounded_min_truncates_utf8_to_lower_bound() -> VortexResult<()> { let mut ctx = LEGACY_SESSION.create_execution_ctx(); @@ -290,6 +301,28 @@ mod tests { Ok(()) } + #[test] + fn bounded_min_null_partial_does_not_poison_existing_bound() -> VortexResult<()> { + let mut ctx = fresh_session().create_execution_ctx(); + let values = VarBinViewArray::from_iter_bin([&[1u8][..]]).into_array(); + let mut acc = Accumulator::try_new( + BoundedMin, + BoundedMinOptions { + max_bytes: max_bytes(2), + }, + values.dtype().clone(), + )?; + + acc.accumulate(&values, &mut ctx)?; + acc.combine_partials(Scalar::null(values.dtype().as_nullable()))?; + + assert_eq!( + acc.finish()?, + Scalar::binary(buffer![1u8], Nullability::Nullable) + ); + Ok(()) + } + #[test] fn bounded_min_satisfies_min_bounds() { let stored = BoundedMin.bind(BoundedMinOptions { diff --git a/vortex-array/src/scalar_fn/fns/stat.rs b/vortex-array/src/scalar_fn/fns/stat.rs index 9cf8aa9d2b0..82ea4c6b86c 100644 --- a/vortex-array/src/scalar_fn/fns/stat.rs +++ b/vortex-array/src/scalar_fn/fns/stat.rs @@ -144,7 +144,7 @@ fn stat_array( let len = u64::try_from(len)?; array .statistics() - .with_typed_stats_set(|stats| stats.get_as::(Stat::NullCount)) + .get_as::(Stat::NullCount) .and_then(|null_count| match null_count { Precision::Exact(count) => Some(count == len), Precision::Inexact(count) => (count < len).then_some(false), @@ -153,7 +153,7 @@ fn stat_array( } else if aggregate_fn.is::() { array .statistics() - .with_typed_stats_set(|stats| stats.get_as::(Stat::NullCount)) + .get_as::(Stat::NullCount) .and_then(|null_count| match null_count { Precision::Exact(count) => Some(count == 0), Precision::Inexact(0) => Some(true), @@ -162,32 +162,24 @@ fn stat_array( .map(ScalarValue::Bool) } else if aggregate_fn.is::() { let len = u64::try_from(len)?; - if !matches!(array.dtype(), DType::Primitive(ptype, _) if ptype.is_float()) { - Some(false) - } else { - array - .statistics() - .with_typed_stats_set(|stats| stats.get_as::(Stat::NaNCount)) - .and_then(|nan_count| match nan_count { - Precision::Exact(count) => Some(count == len), - Precision::Inexact(count) => (count < len).then_some(false), - }) - } - .map(ScalarValue::Bool) + array + .statistics() + .get_as::(Stat::NaNCount) + .and_then(|nan_count| match nan_count { + Precision::Exact(count) => Some(count == len), + Precision::Inexact(count) => (count < len).then_some(false), + }) + .map(ScalarValue::Bool) } else if aggregate_fn.is::() { - if !matches!(array.dtype(), DType::Primitive(ptype, _) if ptype.is_float()) { - Some(true) - } else { - array - .statistics() - .with_typed_stats_set(|stats| stats.get_as::(Stat::NaNCount)) - .and_then(|nan_count| match nan_count { - Precision::Exact(count) => Some(count == 0), - Precision::Inexact(0) => Some(true), - Precision::Inexact(_) => None, - }) - } - .map(ScalarValue::Bool) + array + .statistics() + .get_as::(Stat::NaNCount) + .and_then(|nan_count| match nan_count { + Precision::Exact(count) => Some(count == 0), + Precision::Inexact(0) => Some(true), + Precision::Inexact(_) => None, + }) + .map(ScalarValue::Bool) } else if let Some(stat) = Stat::from_aggregate_fn(aggregate_fn) { array .statistics() diff --git a/vortex-array/src/stats/expr.rs b/vortex-array/src/stats/expr.rs index 4c686133f5e..32fd48804de 100644 --- a/vortex-array/src/stats/expr.rs +++ b/vortex-array/src/stats/expr.rs @@ -69,20 +69,23 @@ pub fn nan_count(expr: Expression) -> Expression { #[cfg(test)] mod tests { + use std::sync::LazyLock; + use vortex_buffer::buffer; use vortex_error::VortexExpect; use vortex_error::VortexResult; + use vortex_session::VortexSession; + use super::all_nan; + use super::all_non_nan; + use super::all_non_null; + use super::all_null; + use super::null_count; use super::stat; + use super::sum; use crate::Canonical; use crate::IntoArray; - use crate::LEGACY_SESSION; use crate::VortexSessionExecute; - use crate::aggregate_fn::AggregateFn; - use crate::aggregate_fn::EmptyOptions; - use crate::aggregate_fn::fns::all_non_null::AllNonNull; - use crate::aggregate_fn::fns::all_null::AllNull; - use crate::aggregate_fn::fns::sum::Sum; use crate::arrays::Chunked; use crate::arrays::ChunkedArray; use crate::arrays::ConstantArray; @@ -97,8 +100,12 @@ mod tests { use crate::expr::stats::Stat; use crate::scalar::Scalar; use crate::scalar::ScalarValue; + use crate::session::ArraySession; use crate::validity::Validity; + static SESSION: LazyLock = + LazyLock::new(|| VortexSession::empty().with::()); + #[test] fn stat_expr_reads_cached_sum() -> VortexResult<()> { let array = buffer![1i32, 2, 3].into_array(); @@ -109,8 +116,8 @@ mod tests { ); let result = array - .apply(&stat(root(), AggregateFn::new(Sum, EmptyOptions).erased()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&sum(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -125,8 +132,8 @@ mod tests { let array = buffer![1i32, 2, 3].into_array(); let result = array - .apply(&stat(root(), AggregateFn::new(Sum, EmptyOptions).erased()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&sum(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = ConstantArray::new( @@ -154,7 +161,7 @@ mod tests { )? .into_array(); - let result = chunked.apply(&stat(root(), AggregateFn::new(Sum, EmptyOptions).erased()))?; + let result = chunked.apply(&sum(root()))?; let chunked_result = result .as_opt::() @@ -162,7 +169,7 @@ mod tests { assert_eq!(chunked_result.nchunks(), 2); let result = result - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = PrimitiveArray::new( buffer![3i64, 3, 0, 0, 0], @@ -189,8 +196,8 @@ mod tests { ); let result = array - .apply(&super::null_count(root()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&null_count(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -208,8 +215,8 @@ mod tests { .set(Stat::NullCount, Precision::exact(ScalarValue::from(3u64))); let result = array - .apply(&super::all_null(root()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&all_null(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -227,8 +234,8 @@ mod tests { .set(Stat::NullCount, Precision::inexact(ScalarValue::from(2u64))); let result = array - .apply(&super::all_null(root()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&all_null(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -246,11 +253,8 @@ mod tests { .set(Stat::NullCount, Precision::inexact(ScalarValue::from(3u64))); let result = array - .apply(&stat( - root(), - AggregateFn::new(AllNull, EmptyOptions).erased(), - ))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&all_null(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -268,8 +272,8 @@ mod tests { .set(Stat::NullCount, Precision::exact(ScalarValue::from(0u64))); let result = array - .apply(&super::all_non_null(root()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&all_non_null(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -287,8 +291,8 @@ mod tests { .set(Stat::NullCount, Precision::inexact(ScalarValue::from(0u64))); let result = array - .apply(&super::all_non_null(root()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&all_non_null(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -307,11 +311,8 @@ mod tests { .set(Stat::NullCount, Precision::inexact(ScalarValue::from(2u64))); let result = array - .apply(&stat( - root(), - AggregateFn::new(AllNonNull, EmptyOptions).erased(), - ))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&all_non_null(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -324,10 +325,10 @@ mod tests { #[test] fn stat_expr_rejects_all_nan_for_non_float() -> VortexResult<()> { let array = PrimitiveArray::empty::(Nullability::NonNullable).into_array(); - let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let mut ctx = SESSION.create_execution_ctx(); let result = array - .apply(&super::all_nan(root())) + .apply(&all_nan(root())) .and_then(|array| array.execute::(&mut ctx)); assert!(result.is_err()); @@ -344,8 +345,8 @@ mod tests { .set(Stat::NaNCount, Precision::exact(ScalarValue::from(3u64))); let result = array - .apply(&super::all_nan(root()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&all_nan(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -365,8 +366,8 @@ mod tests { .set(Stat::NaNCount, Precision::inexact(ScalarValue::from(2u64))); let result = array - .apply(&super::all_nan(root()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&all_nan(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -386,8 +387,8 @@ mod tests { .set(Stat::NaNCount, Precision::inexact(ScalarValue::from(3u64))); let result = array - .apply(&super::all_nan(root()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&all_nan(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -405,8 +406,8 @@ mod tests { .set(Stat::NaNCount, Precision::inexact(ScalarValue::from(0u64))); let result = array - .apply(&super::all_non_nan(root()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&all_non_nan(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -425,8 +426,8 @@ mod tests { .set(Stat::NaNCount, Precision::inexact(ScalarValue::from(1u64))); let result = array - .apply(&super::all_non_nan(root()))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .apply(&all_non_nan(root()))? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected = @@ -454,7 +455,7 @@ mod tests { .aggregate_fn() .vortex_expect("min should have an aggregate function"), ))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected_min = ConstantArray::new(Scalar::primitive(1i32, Nullability::Nullable), 3).into_array(); @@ -467,7 +468,7 @@ mod tests { .aggregate_fn() .vortex_expect("max should have an aggregate function"), ))? - .execute::(&mut LEGACY_SESSION.create_execution_ctx())? + .execute::(&mut SESSION.create_execution_ctx())? .into_array(); let expected_max = ConstantArray::new(Scalar::primitive(3i32, Nullability::Nullable), 3).into_array();