Skip to content

Commit 0f7315b

Browse files
committed
Make pruning stats pluggable
Default selects based on the dtypes. Modeled in the same way that zone maps configure aggregate partials.
1 parent 3b07ff7 commit 0f7315b

7 files changed

Lines changed: 193 additions & 465 deletions

File tree

‎vortex-array/src/stats/mod.rs‎

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,8 @@ pub use array::*;
3131
pub use session::*;
3232
use vortex_error::VortexExpect;
3333

34-
use crate::aggregate_fn::AggregateFnRef;
3534
use crate::expr::stats::Stat;
3635

37-
/// Statistics that are used for pruning files (i.e., we want to ensure they are computed when compressing/writing).
38-
/// Sum is included for boolean arrays.
39-
pub const PRUNING_STATS: &[Stat] = &[
40-
Stat::Min,
41-
Stat::Max,
42-
Stat::Sum,
43-
Stat::NullCount,
44-
Stat::NaNCount,
45-
];
46-
47-
/// [`PRUNING_STATS`], converted to their aggregate function form.
48-
pub fn pruning_aggregate_fns() -> Vec<AggregateFnRef> {
49-
PRUNING_STATS
50-
.iter()
51-
.filter_map(|stat| stat.aggregate_fn())
52-
.collect()
53-
}
54-
5536
pub fn as_stat_bitset_bytes(stats: &[Stat]) -> Vec<u8> {
5637
let max_stat = u8::from(last::<Stat>().vortex_expect("last stat")) as usize + 1;
5738
// TODO(ngates): use vortex-buffer::BitBuffer

‎vortex-duckdb/src/copy.rs‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,13 +340,25 @@ mod tests {
340340
use vortex::array::IntoArray;
341341
use vortex::array::aggregate_fn::AggregateFnRef;
342342
use vortex::array::arrays::StructArray;
343-
use vortex::array::stats::pruning_aggregate_fns;
344343
use vortex::array::validity::Validity;
345344
use vortex::buffer::ByteBufferMut;
346345
use vortex::buffer::buffer;
347346

348347
use super::*;
349348

349+
fn pruning_aggregate_fns() -> Vec<AggregateFnRef> {
350+
[
351+
Stat::Min,
352+
Stat::Max,
353+
Stat::Sum,
354+
Stat::NullCount,
355+
Stat::NaNCount,
356+
]
357+
.into_iter()
358+
.filter_map(|stat| stat.aggregate_fn())
359+
.collect()
360+
}
361+
350362
/// Writes a one-column file and returns its summary, with `file_statistics` controlling which
351363
/// statistics the footer carries (empty means none at all).
352364
fn write_summary(file_statistics: Vec<AggregateFnRef>) -> WriteSummary {

‎vortex-duckdb/src/file_reader.rs‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,17 +338,31 @@ pub fn footer_get_statistics(footer: &Footer, index: usize) -> Option<ColumnStat
338338
#[cfg(test)]
339339
mod tests {
340340
use vortex::array::IntoArray;
341+
use vortex::array::aggregate_fn::AggregateFnRef;
341342
use vortex::array::arrays::StructArray;
342-
use vortex::array::stats::pruning_aggregate_fns;
343343
use vortex::array::validity::Validity;
344344
use vortex::buffer::ByteBufferMut;
345345
use vortex::buffer::buffer;
346+
use vortex::expr::stats::Stat;
346347
use vortex::file::WriteOptionsSessionExt;
347348

348349
use super::*;
349350
use crate::RUNTIME;
350351
use crate::SESSION;
351352

353+
fn pruning_aggregate_fns() -> Vec<AggregateFnRef> {
354+
[
355+
Stat::Min,
356+
Stat::Max,
357+
Stat::Sum,
358+
Stat::NullCount,
359+
Stat::NaNCount,
360+
]
361+
.into_iter()
362+
.filter_map(|stat| stat.aggregate_fn())
363+
.collect()
364+
}
365+
352366
#[test]
353367
fn footer_get_statistics_resolves_by_name_past_a_nullable_struct_field() {
354368
// Regression test: a nullable top-level struct field ("s") contributes both a `s.b` entry

‎vortex-file/src/pruning.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ impl FileStatsBinder<'_> {
8080
fn stat_ref(&self, field_path: &FieldPath, stat: Stat) -> Option<BoundExpression> {
8181
let (field_stats, field_dtype) = self.file_stats.get_by_path(field_path)?;
8282

83-
let stat_value = field_stats.get(stat).as_exact()?;
83+
let stat_value = field_stats.get(stat).into_inner()?;
8484
let stat_dtype = stat.dtype(field_dtype)?;
8585
let stat_scalar = Scalar::try_new(stat_dtype, Some(stat_value)).ok()?;
8686

‎vortex-file/src/tests.rs‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use rstest::rstest;
1515
use vortex_array::ArrayRef;
1616
use vortex_array::IntoArray;
1717
use vortex_array::VortexSessionExecute;
18+
use vortex_array::aggregate_fn::AggregateFnRef;
1819
use vortex_array::array_session;
1920
use vortex_array::arrays::BoolArray;
2021
use vortex_array::arrays::ChunkedArray;
@@ -64,7 +65,6 @@ use vortex_array::scalar::ScalarValue;
6465
use vortex_array::scalar_fn::ScalarFnVTableExt;
6566
use vortex_array::scalar_fn::fns::pack::Pack;
6667
use vortex_array::scalar_fn::fns::pack::PackOptions;
67-
use vortex_array::stats::pruning_aggregate_fns;
6868
use vortex_array::stream::ArrayStreamAdapter;
6969
use vortex_array::stream::ArrayStreamExt;
7070
use vortex_array::validity::Validity;
@@ -115,6 +115,19 @@ static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
115115
session
116116
});
117117

118+
fn pruning_aggregate_fns() -> Vec<AggregateFnRef> {
119+
[
120+
Stat::Min,
121+
Stat::Max,
122+
Stat::Sum,
123+
Stat::NullCount,
124+
Stat::NaNCount,
125+
]
126+
.into_iter()
127+
.filter_map(|stat| stat.aggregate_fn())
128+
.collect()
129+
}
130+
118131
fn strict_sorted(indices: Buffer<u64>) -> StrictSortedBuffer<u64> {
119132
StrictSortedBuffer::try_new(indices).expect("test indices should be strictly increasing")
120133
}

‎vortex-file/src/writer.rs‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ use vortex_array::dtype::FieldPath;
2525
use vortex_array::iter::ArrayIterator;
2626
use vortex_array::iter::ArrayIteratorExt;
2727
use vortex_array::session::ArraySessionExt;
28-
use vortex_array::stats::pruning_aggregate_fns;
2928
use vortex_array::stream::ArrayStream;
3029
use vortex_array::stream::ArrayStreamAdapter;
3130
use vortex_array::stream::ArrayStreamExt;
@@ -84,7 +83,10 @@ pub struct VortexWriteOptions {
8483
buffered_bytes: BufferedBytesTracker,
8584
exclude_dtype: bool,
8685
max_variable_length_statistics_size: usize,
87-
file_statistics: Vec<AggregateFnRef>,
86+
/// The aggregates to compute for file-level statistics.
87+
///
88+
/// If unset, the writer chooses pruning aggregates from the file dtype.
89+
file_statistics: Option<Vec<AggregateFnRef>>,
8890
write_legacy_statistics: bool,
8991
metadata: HashMap<String, ByteBuffer>,
9092
}
@@ -107,7 +109,7 @@ impl VortexWriteOptions {
107109
buffered_bytes: BufferedBytesTracker::new(),
108110
session,
109111
exclude_dtype: false,
110-
file_statistics: pruning_aggregate_fns(),
112+
file_statistics: None,
111113
write_legacy_statistics: true,
112114
max_variable_length_statistics_size: 64,
113115
metadata: HashMap::default(),
@@ -158,9 +160,10 @@ impl VortexWriteOptions {
158160

159161
/// Configure which statistics to compute at the file level.
160162
///
161-
/// Pass an empty vector to omit file-level statistics.
163+
/// Pass an empty vector to omit file-level statistics. If left unset, the writer chooses
164+
/// pruning aggregates from the file dtype.
162165
pub fn with_file_statistics(mut self, file_statistics: Vec<AggregateFnRef>) -> Self {
163-
self.file_statistics = file_statistics;
166+
self.file_statistics = Some(file_statistics);
164167
self
165168
}
166169

@@ -286,9 +289,12 @@ impl VortexWriteOptions {
286289
.map(move |result| result.map(|chunk| (ptr.advance(), chunk))),
287290
)
288291
.sendable();
292+
// When unset, `accumulate_stats` resolves each leaf's own default from its dtype, the way
293+
// `default_zoned_aggregate_fns` does for zoned layouts (e.g. bounded min/max for
294+
// variable-length columns), rather than a single dtype-blind default applied uniformly.
289295
let (file_stats, stream) = accumulate_stats(
290296
stream,
291-
self.file_statistics.clone().into(),
297+
self.file_statistics.clone().map(Arc::from),
292298
self.max_variable_length_statistics_size,
293299
&self.session,
294300
self.write_legacy_statistics,
@@ -334,7 +340,7 @@ impl VortexWriteOptions {
334340
let (layout, segment_specs) = layout_fut.await?;
335341

336342
// Assemble the Footer object now that we have all the segments.
337-
let statistics = if self.file_statistics.is_empty() {
343+
let statistics = if matches!(&self.file_statistics, Some(stats) if stats.is_empty()) {
338344
None
339345
} else {
340346
Some(FileStatistics::new_with_dtype(

0 commit comments

Comments
 (0)