1use std::ops::Range;
10use std::sync::Arc;
11use std::sync::OnceLock;
12
13use itertools::Itertools;
14use vortex_array::ArrayRef;
15use vortex_array::dtype::DType;
16use vortex_array::dtype::FieldMask;
17use vortex_array::expr::Expression;
18use vortex_buffer::ByteBuffer;
19use vortex_error::VortexResult;
20use vortex_layout::LayoutReader;
21use vortex_layout::scan::layout::LayoutReaderDataSource;
22use vortex_layout::scan::scan_builder::ScanBuilder;
23use vortex_layout::scan::split_by::SplitBy;
24use vortex_layout::scan::v2::ScanCache;
25use vortex_layout::scan::v2::ScanFile;
26use vortex_layout::scan::v2::SegmentLocation;
27use vortex_layout::segments::SegmentSource;
28use vortex_scan::DataSourceRef;
29use vortex_session::VortexSession;
30use vortex_utils::aliases::hash_map::HashMap;
31
32use crate::FileStatistics;
33use crate::footer::Footer;
34use crate::pruning::can_prune_file_stats;
35use crate::v2::FileStatsLayoutReader;
36
37#[derive(Clone)]
43pub struct VortexFile {
44 footer: Footer,
46 segment_source: Arc<dyn SegmentSource>,
48 session: VortexSession,
50 metadata: Arc<HashMap<String, ByteBuffer>>,
52 layout_reader_cache: Option<OnceLock<Arc<dyn LayoutReader>>>,
54 scan_cache: Option<FileScanCache>,
56}
57
58#[derive(Clone)]
61struct FileScanCache {
62 locations: Arc<[SegmentLocation]>,
63 plans: ScanCache,
64}
65
66fn segment_locations(footer: &Footer) -> Arc<[SegmentLocation]> {
67 footer
68 .segment_specs_with_metadata()
69 .iter()
70 .map(|spec| SegmentLocation {
71 offset: spec.offset,
72 length: spec.length,
73 alignment: spec.alignment,
74 })
75 .collect()
76}
77
78fn layout_reader(
79 segment_source: Arc<dyn SegmentSource>,
80 footer: &Footer,
81 session: &VortexSession,
82) -> VortexResult<Arc<dyn LayoutReader>> {
83 let root_reader = footer
84 .layout()
85 .new_reader("".into(), segment_source, session, &Default::default())?;
87
88 Ok(if let Some(stats) = footer.statistics().cloned() {
89 Arc::new(FileStatsLayoutReader::new(
90 root_reader,
91 stats,
92 session.clone(),
93 ))
94 } else {
95 root_reader
96 })
97}
98
99impl VortexFile {
100 pub fn new(
102 footer: Footer,
103 segment_source: Arc<dyn SegmentSource>,
104 session: VortexSession,
105 ) -> Self {
106 Self {
107 footer,
108 segment_source,
109 session,
110 metadata: Arc::new(HashMap::new()),
111 layout_reader_cache: None,
112 scan_cache: None,
113 }
114 }
115
116 pub(crate) fn with_metadata(mut self, metadata: Arc<HashMap<String, ByteBuffer>>) -> Self {
117 self.metadata = metadata;
118 self
119 }
120
121 pub fn with_caching(self) -> Self {
127 let scan_cache = FileScanCache {
128 locations: segment_locations(&self.footer),
129 plans: ScanCache::default(),
130 };
131 Self {
132 footer: self.footer,
133 segment_source: self.segment_source,
134 session: self.session,
135 metadata: self.metadata,
136 layout_reader_cache: Some(OnceLock::new()),
137 scan_cache: Some(scan_cache),
138 }
139 }
140
141 pub fn footer(&self) -> &Footer {
143 &self.footer
144 }
145
146 pub fn row_count(&self) -> u64 {
148 self.footer.row_count()
149 }
150
151 pub fn dtype(&self) -> &DType {
153 self.footer.dtype()
154 }
155
156 pub fn file_stats(&self) -> Option<&FileStatistics> {
160 self.footer.statistics()
161 }
162
163 pub fn metadata_segments(&self) -> impl Iterator<Item = (&str, &ByteBuffer)> {
167 self.metadata
168 .iter()
169 .map(|(key, metadata)| (key.as_str(), metadata))
170 }
171
172 pub fn metadata_segment(&self, key: &str) -> Option<&ByteBuffer> {
176 self.metadata.get(key)
177 }
178
179 pub fn segment_source(&self) -> Arc<dyn SegmentSource> {
184 Arc::clone(&self.segment_source)
185 }
186
187 pub fn with_segment_source(mut self, segment_source: Arc<dyn SegmentSource>) -> Self {
192 self.segment_source = segment_source;
193 if self.layout_reader_cache.is_some() {
194 self.layout_reader_cache = Some(OnceLock::new());
195 }
196 if let Some(cache) = &mut self.scan_cache {
197 cache.plans = ScanCache::default();
198 }
199 self
200 }
201
202 pub fn session(&self) -> &VortexSession {
204 &self.session
205 }
206
207 pub fn layout_reader(&self) -> VortexResult<Arc<dyn LayoutReader>> {
211 match &self.layout_reader_cache {
212 None => layout_reader(
213 Arc::clone(&self.segment_source),
214 &self.footer,
215 &self.session,
216 ),
217 Some(reader) => {
218 if let Some(val) = reader.get() {
220 Ok(Arc::clone(val))
221 } else {
222 let inner = layout_reader(
223 Arc::clone(&self.segment_source),
224 &self.footer,
225 &self.session,
226 )?;
227 Ok(if let Err(val) = reader.set(Arc::clone(&inner)) {
228 val
229 } else {
230 inner
231 })
232 }
233 }
234 }
235 }
236
237 pub fn data_source(&self) -> VortexResult<DataSourceRef> {
242 let reader = self.layout_reader()?;
243
244 Ok(Arc::new(LayoutReaderDataSource::new(
245 reader,
246 self.session.clone(),
247 )))
248 }
249
250 pub fn scan(&self) -> VortexResult<ScanBuilder<ArrayRef>> {
253 Ok(ScanBuilder::new(
254 self.session.clone(),
255 self.layout_reader()?,
256 ))
257 }
258
259 pub fn scan_file(&self) -> ScanFile {
265 let (locations, cache) = match &self.scan_cache {
266 Some(cache) => (Arc::clone(&cache.locations), Some(cache.plans.clone())),
267 None => (segment_locations(&self.footer), None),
268 };
269 ScanFile {
270 layout: Arc::clone(self.footer.layout()),
271 locations,
272 segments: Arc::clone(&self.segment_source),
273 cache,
274 }
275 }
276
277 pub fn can_prune(&self, filter: &Expression) -> VortexResult<bool> {
283 let Some((stats, fields)) = self
284 .footer
285 .statistics()
286 .zip(self.footer.dtype().as_struct_fields_opt())
287 else {
288 return Ok(false);
289 };
290
291 can_prune_file_stats(
292 &filter.bind(self.footer.dtype())?,
293 self.footer.row_count(),
294 stats,
295 fields,
296 &self.session,
297 )
298 }
299
300 pub fn splits(&self) -> VortexResult<Vec<Range<u64>>> {
304 let reader = self.layout_reader()?;
305 Ok(SplitBy::default()
306 .splits(reader.as_ref(), &(0..reader.row_count()), &[FieldMask::All])?
307 .into_iter()
308 .tuple_windows()
309 .map(|(start, end)| start..end)
310 .collect())
311 }
312}