1use std::ops::Range;
10use std::sync::Arc;
11use std::sync::OnceLock;
12
13use itertools::Itertools;
14use vortex_array::ArrayRef;
15use vortex_array::Columnar;
16use vortex_array::IntoArray;
17use vortex_array::VortexSessionExecute;
18use vortex_array::arrays::ConstantArray;
19use vortex_array::dtype::DType;
20use vortex_array::dtype::Field;
21use vortex_array::dtype::FieldMask;
22use vortex_array::dtype::FieldPath;
23use vortex_array::dtype::FieldPathSet;
24use vortex_array::expr::Expression;
25use vortex_array::expr::pruning::checked_pruning_expr;
26use vortex_array::scalar_fn::internal::row_count::substitute_row_count;
27use vortex_error::VortexResult;
28use vortex_layout::LayoutReader;
29use vortex_layout::scan::layout::LayoutReaderDataSource;
30use vortex_layout::scan::scan_builder::ScanBuilder;
31use vortex_layout::scan::split_by::SplitBy;
32use vortex_layout::segments::SegmentSource;
33use vortex_scan::DataSourceRef;
34use vortex_session::VortexSession;
35use vortex_utils::aliases::hash_map::HashMap;
36
37use crate::FileStatistics;
38use crate::footer::Footer;
39use crate::pruning::extract_relevant_file_stats_as_struct_row;
40use crate::v2::FileStatsLayoutReader;
41
42#[derive(Clone)]
48pub struct VortexFile {
49 footer: Footer,
51 segment_source: Arc<dyn SegmentSource>,
53 session: VortexSession,
55 layout_reader_cache: Option<OnceLock<Arc<dyn LayoutReader>>>,
57}
58
59fn layout_reader(
60 segment_source: Arc<dyn SegmentSource>,
61 footer: &Footer,
62 session: &VortexSession,
63) -> VortexResult<Arc<dyn LayoutReader>> {
64 let root_reader = footer
65 .layout()
66 .new_reader("".into(), segment_source, session, &Default::default())?;
68
69 Ok(if let Some(stats) = footer.statistics().cloned() {
70 Arc::new(FileStatsLayoutReader::new(
71 root_reader,
72 stats,
73 session.clone(),
74 ))
75 } else {
76 root_reader
77 })
78}
79
80impl VortexFile {
81 pub fn new(
83 footer: Footer,
84 segment_source: Arc<dyn SegmentSource>,
85 session: VortexSession,
86 ) -> Self {
87 Self {
88 footer,
89 segment_source,
90 session,
91 layout_reader_cache: None,
92 }
93 }
94
95 pub fn with_caching(self) -> Self {
97 Self {
98 footer: self.footer,
99 segment_source: self.segment_source,
100 session: self.session,
101 layout_reader_cache: Some(OnceLock::new()),
102 }
103 }
104
105 pub fn footer(&self) -> &Footer {
107 &self.footer
108 }
109
110 pub fn row_count(&self) -> u64 {
112 self.footer.row_count()
113 }
114
115 pub fn dtype(&self) -> &DType {
117 self.footer.dtype()
118 }
119
120 pub fn file_stats(&self) -> Option<&FileStatistics> {
124 self.footer.statistics()
125 }
126
127 pub fn segment_source(&self) -> Arc<dyn SegmentSource> {
132 Arc::clone(&self.segment_source)
133 }
134
135 pub fn session(&self) -> &VortexSession {
137 &self.session
138 }
139
140 pub fn layout_reader(&self) -> VortexResult<Arc<dyn LayoutReader>> {
144 match &self.layout_reader_cache {
145 None => layout_reader(
146 Arc::clone(&self.segment_source),
147 &self.footer,
148 &self.session,
149 ),
150 Some(reader) => {
151 if let Some(val) = reader.get() {
153 Ok(Arc::clone(val))
154 } else {
155 let inner = layout_reader(
156 Arc::clone(&self.segment_source),
157 &self.footer,
158 &self.session,
159 )?;
160 Ok(if let Err(val) = reader.set(Arc::clone(&inner)) {
161 val
162 } else {
163 inner
164 })
165 }
166 }
167 }
168 }
169
170 pub fn data_source(&self) -> VortexResult<DataSourceRef> {
175 let reader = self.layout_reader()?;
176
177 Ok(Arc::new(LayoutReaderDataSource::new(
178 reader,
179 self.session.clone(),
180 )))
181 }
182
183 pub fn scan(&self) -> VortexResult<ScanBuilder<ArrayRef>> {
185 Ok(ScanBuilder::new(
186 self.session.clone(),
187 self.layout_reader()?,
188 ))
189 }
190
191 pub fn can_prune(&self, filter: &Expression) -> VortexResult<bool> {
197 let Some((stats, fields)) = self
198 .footer
199 .statistics()
200 .zip(self.footer.dtype().as_struct_fields_opt())
201 else {
202 return Ok(false);
203 };
204
205 let set = FieldPathSet::from_iter(
206 fields
207 .names()
208 .iter()
209 .zip(stats.stats_sets().iter())
210 .flat_map(|(name, stats)| {
211 stats.iter().map(|(stat, _)| {
212 FieldPath::from_iter([
213 Field::Name(name.clone()),
214 Field::Name(stat.name().into()),
215 ])
216 })
217 }),
218 );
219
220 let Some((predicate, required_stats)) = checked_pruning_expr(filter, &set) else {
221 return Ok(false);
222 };
223
224 let required_file_stats = HashMap::from_iter(
225 required_stats
226 .map()
227 .iter()
228 .map(|(path, stats)| (path.clone(), stats.clone())),
229 );
230
231 let Some(file_stats) = extract_relevant_file_stats_as_struct_row(
232 &required_file_stats,
233 stats.stats_sets(),
234 fields,
235 )?
236 else {
237 return Ok(false);
238 };
239
240 let applied = file_stats.apply(&predicate)?;
243 let row_count_replacement =
244 ConstantArray::new(self.footer.row_count(), applied.len()).into_array();
245 let applied = substitute_row_count(applied, &row_count_replacement)?;
246
247 let mut ctx = self.session.create_execution_ctx();
248 Ok(match applied.execute::<Columnar>(&mut ctx)? {
249 Columnar::Constant(s) => s.scalar().as_bool().value() == Some(true),
250 Columnar::Canonical(c) => {
251 c.into_array()
252 .execute_scalar(0, &mut ctx)?
253 .as_bool()
254 .value()
255 == Some(true)
256 }
257 })
258 }
259
260 pub fn splits(&self) -> VortexResult<Vec<Range<u64>>> {
261 let reader = self.layout_reader()?;
262 Ok(SplitBy::Layout
263 .splits(reader.as_ref(), &(0..reader.row_count()), &[FieldMask::All])?
264 .into_iter()
265 .tuple_windows()
266 .map(|(start, end)| start..end)
267 .collect())
268 }
269}