-
Notifications
You must be signed in to change notification settings - Fork 183
Expand file tree
/
Copy pathstat.rs
More file actions
148 lines (129 loc) · 4.92 KB
/
Copy pathstat.rs
File metadata and controls
148 lines (129 loc) · 4.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors
//! Scalar function implementation for aggregate-backed stat expressions.
use std::fmt::Display;
use std::fmt::Formatter;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use crate::ArrayRef;
use crate::ExecutionCtx;
use crate::IntoArray;
use crate::aggregate_fn::AggregateFnRef;
use crate::arrays::ConstantArray;
use crate::dtype::DType;
use crate::expr::Expression;
use crate::expr::stats::StatsProvider;
use crate::scalar::Scalar;
use crate::scalar_fn::Arity;
use crate::scalar_fn::ChildName;
use crate::scalar_fn::ExecutionArgs;
use crate::scalar_fn::ScalarFnId;
use crate::scalar_fn::ScalarFnVTable;
use crate::stats::legacy::legacy_stat_for_aggregate;
/// Options for the `stat` scalar function.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct StatOptions {
aggregate_fn: AggregateFnRef,
}
impl StatOptions {
/// Creates options for the provided aggregate statistic.
pub fn new(aggregate_fn: AggregateFnRef) -> Self {
Self { aggregate_fn }
}
/// Returns the aggregate function backing this statistic lookup.
pub fn aggregate_fn(&self) -> &AggregateFnRef {
&self.aggregate_fn
}
}
impl Display for StatOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.aggregate_fn, f)
}
}
/// Scalar function that broadcasts a stored aggregate statistic 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
/// filters drop entire stretches at once. For example, `value < 10` is prunable as
/// `stat(value, max) < 10` (rows where the bound is false are guaranteed false) or
/// `stat(value, min) >= 10` (rows where it is true are guaranteed true) — the zone-map /
/// min-max-index pattern, expressed as an ordinary expression so the existing scalar
/// machinery can rewrite, fold, and execute it.
///
/// The result is row-aligned with the input, at whatever granularity the input carries the
/// stat at: e.g. a flat array yields a single broadcast `ConstantArray`; a chunked array
/// 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.
#[derive(Clone)]
pub struct StatFn;
impl ScalarFnVTable for StatFn {
type Options = StatOptions;
fn id(&self) -> ScalarFnId {
ScalarFnId::new("vortex.stat")
}
fn arity(&self, _options: &Self::Options) -> Arity {
Arity::Exact(1)
}
fn child_name(&self, _options: &Self::Options, child_idx: usize) -> ChildName {
match child_idx {
0 => ChildName::from("input"),
_ => unreachable!("Invalid child index {} for Stat expression", child_idx),
}
}
fn fmt_sql(
&self,
options: &Self::Options,
expr: &Expression,
f: &mut Formatter<'_>,
) -> std::fmt::Result {
write!(f, "stat(")?;
expr.child(0).fmt_sql(f)?;
write!(f, ", {})", options.aggregate_fn())
}
fn return_dtype(&self, options: &Self::Options, arg_dtypes: &[DType]) -> VortexResult<DType> {
stat_dtype(options.aggregate_fn(), &arg_dtypes[0])
}
fn execute(
&self,
options: &Self::Options,
args: &dyn ExecutionArgs,
_ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef> {
let input = args.get(0)?;
let dtype = stat_dtype(options.aggregate_fn(), input.dtype())?;
stat_array(&input, options.aggregate_fn(), dtype, args.row_count())
}
}
fn stat_dtype(aggregate_fn: &AggregateFnRef, input_dtype: &DType) -> VortexResult<DType> {
let Some(dtype) = aggregate_fn.return_dtype(input_dtype) else {
vortex_bail!(
"Aggregate function {} does not support input dtype {}",
aggregate_fn,
input_dtype
);
};
Ok(dtype.as_nullable())
}
fn stat_array(
array: &ArrayRef,
aggregate_fn: &AggregateFnRef,
dtype: DType,
len: usize,
) -> VortexResult<ArrayRef> {
let value = legacy_stat_for_aggregate(aggregate_fn)
.and_then(|stat| {
array
.statistics()
.with_typed_stats_set(|stats| stats.get(stat))
})
// We don't mind whether the stat is approxed or not, since these are row-wise bounds
.map(|stat| stat.into_inner())
.and_then(Scalar::into_value);
let scalar = Scalar::try_new(dtype, value)?;
Ok(ConstantArray::new(scalar, len).into_array())
}