Skip to main content

vortex_file/footer/
file_statistics.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! This module defines the file statistics component of the Vortex file footer.
5//!
6//! File statistics provide metadata about the data in the file, such as min/max values,
7//! null counts, and other statistical information that can be used for query optimization
8//! and data exploration.
9use std::sync::Arc;
10
11use flatbuffers::FlatBufferBuilder;
12use flatbuffers::WIPOffset;
13use itertools::Itertools;
14use vortex_array::dtype::DType;
15use vortex_array::stats::StatsSet;
16use vortex_error::VortexResult;
17use vortex_error::vortex_ensure_eq;
18use vortex_flatbuffers::FlatBufferRoot;
19use vortex_flatbuffers::WriteFlatBuffer;
20use vortex_flatbuffers::footer as fb;
21use vortex_session::VortexSession;
22
23/// Contains statistical information about the data in a Vortex file.
24///
25/// This struct wraps an array of `StatsSet` objects, each containing statistics
26/// for a field or column in the file. These statistics can be used for query
27/// optimization and data exploration.
28#[derive(Clone, Debug)]
29pub struct FileStatistics {
30    /// An array of statistics sets, one for each field or column in the file.
31    stats: Arc<[StatsSet]>,
32    /// An array of `DType`s, one for each field or column in the file.
33    dtypes: Arc<[DType]>,
34}
35
36impl FileStatistics {
37    /// Creates a new [`FileStatistics`] from the given statistics and data types.
38    ///
39    /// # Panics
40    ///
41    /// Panics if `stats` and `dtypes` have different lengths.
42    pub fn new(stats: Arc<[StatsSet]>, dtypes: Arc<[DType]>) -> Self {
43        assert_eq!(
44            stats.len(),
45            dtypes.len(),
46            "stats and dtypes must have the same length"
47        );
48
49        Self { stats, dtypes }
50    }
51
52    /// Creates a new [`FileStatistics`] from the given statistics and file dtype.
53    ///
54    /// If the [`DType`] of the file is a [`DType::Struct`], then there must be the same number of
55    /// stats as struct fields. Otherwise, there must be only 1 statistic.
56    ///
57    /// # Panics
58    ///
59    /// Panics if the number of stats doesn't match the expected number based on the dtype.
60    pub fn new_with_dtype(stats: Arc<[StatsSet]>, file_dtype: &DType) -> Self {
61        if let DType::Struct(struct_fields, _) = file_dtype {
62            assert_eq!(
63                stats.len(),
64                struct_fields.nfields(),
65                "stats length must match number of struct fields"
66            );
67
68            let dtypes = struct_fields.fields().collect();
69
70            Self { stats, dtypes }
71        } else {
72            assert_eq!(
73                stats.len(),
74                1,
75                "non-struct dtype must have exactly 1 statistic"
76            );
77
78            Self {
79                stats,
80                dtypes: Arc::new([file_dtype.clone()]),
81            }
82        }
83    }
84
85    /// Creates [`FileStatistics`] from a flatbuffers [`fb::FileStatistics<'a>`].
86    ///
87    /// If the [`DType`] of the file is a [`DType::Struct`], then there must be the same number of
88    /// file stats in the flatbuffer. Otherwise, there must be only 1 statistic.
89    pub fn from_flatbuffer<'a>(
90        fb: &fb::FileStatistics<'a>,
91        file_dtype: &DType,
92        session: &VortexSession,
93    ) -> VortexResult<Self> {
94        let field_stats = fb.field_stats().unwrap_or_default();
95
96        if let DType::Struct(struct_fields, _) = file_dtype {
97            vortex_ensure_eq!(field_stats.len(), struct_fields.nfields());
98
99            let stats_sets: Arc<[StatsSet]> = field_stats
100                .into_iter()
101                .zip(struct_fields.fields())
102                .map(|(array_stat, field_dtype)| {
103                    StatsSet::from_flatbuffer(&array_stat, &field_dtype, session)
104                })
105                .try_collect()?;
106
107            let dtypes = struct_fields.fields().collect();
108
109            Ok(Self {
110                stats: stats_sets,
111                dtypes,
112            })
113        } else {
114            vortex_ensure_eq!(field_stats.len(), 1);
115
116            let array_stat = field_stats.get(0);
117            let stats_set = StatsSet::from_flatbuffer(&array_stat, file_dtype, session)?;
118
119            Ok(Self {
120                stats: Arc::new([stats_set]),
121                dtypes: Arc::new([file_dtype.clone()]),
122            })
123        }
124    }
125
126    /// Returns a reference to the statistics sets.
127    pub fn stats_sets(&self) -> &Arc<[StatsSet]> {
128        &self.stats
129    }
130
131    /// Returns a reference to the data types.
132    pub fn dtypes(&self) -> &Arc<[DType]> {
133        &self.dtypes
134    }
135
136    /// Returns the statistics and data type for a specific field.
137    ///
138    /// # Panics
139    ///
140    /// Panics if `field_idx` is out of bounds.
141    pub fn get(&self, field_idx: usize) -> (&StatsSet, &DType) {
142        (&self.stats[field_idx], &self.dtypes[field_idx])
143    }
144}
145
146impl<'a> IntoIterator for &'a FileStatistics {
147    type Item = (&'a StatsSet, &'a DType);
148    type IntoIter = std::iter::Zip<std::slice::Iter<'a, StatsSet>, std::slice::Iter<'a, DType>>;
149
150    fn into_iter(self) -> Self::IntoIter {
151        self.stats.iter().zip(self.dtypes.iter())
152    }
153}
154
155impl FlatBufferRoot for FileStatistics {}
156
157impl WriteFlatBuffer for FileStatistics {
158    type Target<'a> = fb::FileStatistics<'a>;
159
160    fn write_flatbuffer<'fb>(
161        &self,
162        fbb: &mut FlatBufferBuilder<'fb>,
163    ) -> VortexResult<WIPOffset<Self::Target<'fb>>> {
164        let field_stats = self
165            .stats_sets()
166            .iter()
167            .map(|s| s.write_flatbuffer(fbb))
168            .collect::<VortexResult<Vec<_>>>()?;
169        let field_stats = fbb.create_vector(field_stats.as_slice());
170
171        Ok(fb::FileStatistics::create(
172            fbb,
173            &fb::FileStatisticsArgs {
174                field_stats: Some(field_stats),
175            },
176        ))
177    }
178}