1use std::sync::Arc;
5
6use futures::executor::block_on;
7use parking_lot::RwLock;
8use vortex_array::dtype::DType;
9use vortex_array::memory::MemorySessionExt;
10use vortex_array::session::ArraySessionExt;
11use vortex_buffer::Alignment;
12use vortex_buffer::ByteBuffer;
13use vortex_error::VortexError;
14use vortex_error::VortexExpect;
15use vortex_error::VortexResult;
16use vortex_io::VortexReadAt;
17use vortex_io::session::RuntimeSessionExt;
18use vortex_layout::segments::InstrumentedSegmentCache;
19use vortex_layout::segments::NoOpSegmentCache;
20use vortex_layout::segments::SegmentCache;
21use vortex_layout::segments::SegmentCacheSourceAdapter;
22use vortex_layout::segments::SegmentId;
23use vortex_layout::segments::SharedSegmentSource;
24use vortex_layout::session::LayoutSessionExt;
25use vortex_metrics::DefaultMetricsRegistry;
26use vortex_metrics::Label;
27use vortex_metrics::MetricsRegistry;
28use vortex_session::VortexSession;
29use vortex_utils::aliases::hash_map::HashMap;
30
31use crate::DeserializeStep;
32use crate::EOF_SIZE;
33use crate::MAX_POSTSCRIPT_SIZE;
34use crate::VortexFile;
35use crate::footer::Footer;
36use crate::segments::BufferSegmentSource;
37use crate::segments::FileSegmentSource;
38use crate::segments::InitialReadSegmentCache;
39use crate::segments::RequestMetrics;
40
41const INITIAL_READ_SIZE: usize = MAX_POSTSCRIPT_SIZE as usize + EOF_SIZE;
42
43pub struct VortexOpenOptions {
45 session: VortexSession,
47 segment_cache: Option<Arc<dyn SegmentCache>>,
49 initial_read_size: usize,
51 file_size: Option<u64>,
53 dtype: Option<DType>,
55 footer: Option<Footer>,
57 initial_read_segments: RwLock<HashMap<SegmentId, ByteBuffer>>,
59 metrics_registry: Option<Arc<dyn MetricsRegistry>>,
61 labels: Vec<Label>,
63 cache_layout_reader: bool,
65}
66
67pub trait OpenOptionsSessionExt:
68 ArraySessionExt + LayoutSessionExt + RuntimeSessionExt + MemorySessionExt
69{
70 fn open_options(&self) -> VortexOpenOptions {
72 VortexOpenOptions {
73 session: self.session(),
74 segment_cache: None,
75 initial_read_size: INITIAL_READ_SIZE,
76 file_size: None,
77 dtype: None,
78 footer: None,
79 initial_read_segments: Default::default(),
80 metrics_registry: None,
81 labels: Vec::default(),
82 cache_layout_reader: false,
83 }
84 }
85}
86impl<S: ArraySessionExt + LayoutSessionExt + RuntimeSessionExt + MemorySessionExt>
87 OpenOptionsSessionExt for S
88{
89}
90
91impl VortexOpenOptions {
92 pub fn with_initial_read_size(mut self, initial_read_size: usize) -> Self {
94 self.initial_read_size = initial_read_size;
95 self
96 }
97
98 pub fn with_layout_reader_cache(mut self) -> Self {
100 self.cache_layout_reader = true;
101 self
102 }
103
104 pub fn with_segment_cache(mut self, segment_cache: Arc<dyn SegmentCache>) -> Self {
106 self.segment_cache = Some(segment_cache);
107 self
108 }
109
110 pub fn with_file_size(mut self, file_size: u64) -> Self {
115 self.file_size = Some(file_size);
116 self
117 }
118
119 pub fn with_some_file_size(mut self, file_size: Option<u64>) -> Self {
124 self.file_size = file_size;
125 self
126 }
127
128 pub fn with_dtype(mut self, dtype: DType) -> Self {
134 self.dtype = Some(dtype);
135 self
136 }
137
138 pub fn with_footer(mut self, footer: Footer) -> Self {
143 self.dtype = Some(footer.layout().dtype().clone());
144 self.footer = Some(footer);
145 self
146 }
147
148 pub fn with_metrics_registry(mut self, metrics: Arc<dyn MetricsRegistry>) -> Self {
150 self.metrics_registry = Some(metrics);
151 self
152 }
153
154 pub fn with_labels(mut self, labels: Vec<Label>) -> Self {
156 self.labels.extend(labels);
157 self
158 }
159
160 pub async fn open(self, source: Arc<dyn VortexReadAt>) -> VortexResult<VortexFile> {
167 self.open_read(source).await
168 }
169
170 #[cfg(not(target_arch = "wasm32"))]
172 pub async fn open_path(self, path: impl AsRef<std::path::Path>) -> VortexResult<VortexFile> {
173 use vortex_io::std_file::FileReadAt;
174 let handle = self.session.handle();
175 let allocator = self.session.allocator();
176 let source = Arc::new(FileReadAt::open_with_allocator(path, handle, allocator)?);
177 self.open(source).await
178 }
179
180 pub fn open_buffer<B: Into<ByteBuffer>>(self, buffer: B) -> VortexResult<VortexFile> {
185 let buffer: ByteBuffer = buffer.into();
186
187 if self.segment_cache.is_some() {
188 tracing::warn!("segment cache is ignored for in-memory `open_buffer`");
189 }
190 if self.metrics_registry.is_some() {
191 tracing::warn!("metrics registry is ignored for in-memory `open_buffer`");
192 }
193
194 let cache_layout_reader = self.cache_layout_reader;
195 let mut opts = self.with_initial_read_size(0);
196
197 let footer = match opts.footer.take() {
198 Some(footer) => footer,
199 None => block_on(opts.read_footer(&buffer))?,
200 };
201
202 let segment_source = Arc::new(BufferSegmentSource::new(
203 buffer,
204 Arc::clone(footer.segment_map()),
205 ));
206 let file = VortexFile::new(footer, segment_source, opts.session);
207 Ok(if cache_layout_reader {
208 file.with_caching()
209 } else {
210 file
211 })
212 }
213
214 pub async fn open_read<R: VortexReadAt + Clone>(self, reader: R) -> VortexResult<VortexFile> {
216 let segment_cache = self
217 .segment_cache
218 .clone()
219 .unwrap_or_else(|| Arc::new(NoOpSegmentCache));
220
221 let metrics_registry = self
222 .metrics_registry
223 .clone()
224 .unwrap_or_else(|| Arc::new(DefaultMetricsRegistry::default()));
225
226 let footer = if let Some(footer) = self.footer {
227 footer
228 } else {
229 self.read_footer(&reader).await?
230 };
231
232 let segment_cache = Arc::new(InstrumentedSegmentCache::new(
233 InitialReadSegmentCache {
234 initial: self.initial_read_segments,
235 fallback: segment_cache,
236 },
237 metrics_registry.as_ref(),
238 self.labels.clone(),
239 ));
240
241 let metrics = RequestMetrics::new(metrics_registry.as_ref(), self.labels);
242
243 let segment_source = Arc::new(SharedSegmentSource::new(FileSegmentSource::open(
245 Arc::clone(footer.segment_map()),
246 reader,
247 self.session.handle(),
248 metrics,
249 )));
250
251 let segment_source = Arc::new(SegmentCacheSourceAdapter::new(
253 segment_cache,
254 segment_source,
255 ));
256
257 let file = VortexFile::new(footer, segment_source, self.session.clone());
258 Ok(if self.cache_layout_reader {
259 file.with_caching()
260 } else {
261 file
262 })
263 }
264
265 async fn read_footer(&self, read: &dyn VortexReadAt) -> VortexResult<Footer> {
266 let file_size = match self.file_size {
268 None => read.size().await?,
269 Some(file_size) => file_size,
270 };
271 let mut initial_read_size = self
272 .initial_read_size
273 .max(MAX_POSTSCRIPT_SIZE as usize + EOF_SIZE);
275 if let Ok(file_size) = usize::try_from(file_size) {
276 initial_read_size = initial_read_size.min(file_size);
277 }
278
279 let initial_offset = file_size - initial_read_size as u64;
280 let initial_read: ByteBuffer = read
281 .read_at(initial_offset, initial_read_size, Alignment::none())
282 .await?
283 .try_into_host()?
284 .await?;
285
286 let mut deserializer = Footer::deserializer(initial_read, self.session.clone())
287 .with_size(file_size)
288 .with_some_dtype(self.dtype.clone());
289
290 let footer = loop {
291 match deserializer.deserialize()? {
292 DeserializeStep::NeedMoreData { offset, len } => {
293 let more_data = read
294 .read_at(offset, len, Alignment::none())
295 .await?
296 .try_into_host()?
297 .await?;
298 deserializer.prefix_data(more_data);
299 }
300 DeserializeStep::NeedFileSize => unreachable!("We passed file_size above"),
301 DeserializeStep::Done(footer) => break Ok::<_, VortexError>(footer),
302 }
303 }?;
304
305 let initial_offset = file_size - (deserializer.buffer().len() as u64);
308 self.populate_initial_segments(initial_offset, deserializer.buffer(), &footer);
309
310 Ok(footer)
311 }
312
313 fn populate_initial_segments(
315 &self,
316 initial_offset: u64,
317 initial_read: &ByteBuffer,
318 footer: &Footer,
319 ) {
320 let first_idx = footer
321 .segment_map()
322 .partition_point(|segment| segment.offset < initial_offset);
323
324 let mut initial_read_segments = self.initial_read_segments.write();
325
326 for idx in first_idx..footer.segment_map().len() {
327 let segment = &footer.segment_map()[idx];
328 let segment_id =
329 SegmentId::from(u32::try_from(idx).vortex_expect("Invalid segment ID"));
330 let offset =
331 usize::try_from(segment.offset - initial_offset).vortex_expect("Invalid offset");
332 let buffer = initial_read
333 .slice(offset..offset + (segment.length as usize))
334 .aligned(segment.alignment);
335 initial_read_segments.insert(segment_id, buffer);
336 }
337 }
338}
339
340#[cfg(feature = "object_store")]
341impl VortexOpenOptions {
342 pub async fn open_object_store(
343 self,
344 object_store: &Arc<dyn object_store::ObjectStore>,
345 path: &str,
346 ) -> VortexResult<VortexFile> {
347 use vortex_io::object_store::ObjectStoreReadAt;
348
349 let handle = self.session.handle();
350 let allocator = self.session.allocator();
351 let source = Arc::new(ObjectStoreReadAt::new_with_allocator(
352 Arc::clone(object_store),
353 path.into(),
354 handle,
355 allocator,
356 ));
357 self.open(source).await
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use std::sync::atomic::AtomicUsize;
364 use std::sync::atomic::Ordering;
365
366 use futures::future::BoxFuture;
367 use vortex_array::IntoArray;
368 use vortex_array::buffer::BufferHandle;
369 use vortex_array::dtype::session::DTypeSession;
370 use vortex_array::memory::DefaultHostAllocator;
371 use vortex_array::memory::HostAllocator;
372 use vortex_array::memory::MemorySessionExt;
373 use vortex_array::memory::WritableHostBuffer;
374 use vortex_array::scalar_fn::session::ScalarFnSession;
375 use vortex_array::session::ArraySession;
376 use vortex_buffer::Alignment;
377 use vortex_buffer::Buffer;
378 use vortex_buffer::ByteBuffer;
379 use vortex_buffer::ByteBufferMut;
380 use vortex_io::session::RuntimeSession;
381 use vortex_layout::session::LayoutSession;
382
383 use super::*;
384 use crate::WriteOptionsSessionExt;
385
386 #[derive(Clone)]
387 struct CountingRead<R> {
389 inner: R,
390 total_read: Arc<AtomicUsize>,
391 first_read_len: Arc<AtomicUsize>,
392 }
393
394 impl<R: VortexReadAt + Clone> VortexReadAt for CountingRead<R> {
395 fn size(&self) -> BoxFuture<'static, VortexResult<u64>> {
396 self.inner.size()
397 }
398
399 fn read_at(
400 &self,
401 offset: u64,
402 length: usize,
403 alignment: Alignment,
404 ) -> BoxFuture<'static, VortexResult<BufferHandle>> {
405 self.total_read.fetch_add(length, Ordering::Relaxed);
406 let _ = self.first_read_len.compare_exchange(
407 0,
408 length,
409 Ordering::Relaxed,
410 Ordering::Relaxed,
411 );
412 self.inner.read_at(offset, length, alignment)
413 }
414
415 fn concurrency(&self) -> usize {
416 self.inner.concurrency()
417 }
418 }
419
420 #[derive(Debug)]
421 struct CountingAllocator {
422 allocations: Arc<AtomicUsize>,
423 }
424
425 impl HostAllocator for CountingAllocator {
426 fn allocate(&self, len: usize, alignment: Alignment) -> VortexResult<WritableHostBuffer> {
427 self.allocations.fetch_add(1, Ordering::Relaxed);
428 DefaultHostAllocator.allocate(len, alignment)
429 }
430 }
431
432 #[tokio::test]
433 async fn test_initial_read_size() {
434 let session = VortexSession::empty()
435 .with::<DTypeSession>()
436 .with::<ArraySession>()
437 .with::<LayoutSession>()
438 .with::<ScalarFnSession>()
439 .with::<RuntimeSession>();
440
441 crate::register_default_encodings(&session);
442
443 let mut buf = ByteBufferMut::empty();
445
446 let array = Buffer::from(
448 (0i32..1_500_000)
449 .map(|i| if i % 2 == 0 { i } else { -i })
450 .collect::<Vec<i32>>(),
451 )
452 .into_array();
453
454 session
455 .write_options()
456 .write(&mut buf, array.to_array_stream())
457 .await
458 .unwrap();
459
460 let buffer = ByteBuffer::from(buf);
461 assert!(
462 buffer.len() > 1024 * 1024,
463 "Buffer length is only {} bytes",
464 buffer.len()
465 );
466
467 let total_read = Arc::new(AtomicUsize::new(0));
468 let first_read_len = Arc::new(AtomicUsize::new(0));
469 let reader = CountingRead {
470 inner: buffer,
471 total_read: Arc::clone(&total_read),
472 first_read_len: Arc::clone(&first_read_len),
473 };
474
475 let _file = session.open_options().open_read(reader).await.unwrap();
477
478 let first = first_read_len.load(Ordering::Relaxed);
480 assert_eq!(
481 first,
482 MAX_POSTSCRIPT_SIZE as usize + EOF_SIZE,
483 "Read exactly the postscript size"
484 );
485 let read = total_read.load(Ordering::Relaxed);
486 assert!(read < 1024 * 1024, "Read {} bytes, expected < 1MB", read);
487 }
488
489 #[cfg(not(target_arch = "wasm32"))]
490 #[tokio::test]
491 async fn test_open_path_uses_memory_session_allocator() {
492 let session = VortexSession::empty()
493 .with::<DTypeSession>()
494 .with::<ArraySession>()
495 .with::<LayoutSession>()
496 .with::<ScalarFnSession>()
497 .with::<RuntimeSession>();
498
499 crate::register_default_encodings(&session);
500
501 let mut buf = ByteBufferMut::empty();
502 let array = Buffer::from((0i32..16_384).collect::<Vec<i32>>()).into_array();
503 session
504 .write_options()
505 .write(&mut buf, array.to_array_stream())
506 .await
507 .unwrap();
508
509 let file_path = std::env::temp_dir().join(format!(
510 "vortex-open-memory-session-{}.vx",
511 std::process::id()
512 ));
513 std::fs::write(&file_path, ByteBuffer::from(buf).as_slice()).unwrap();
514
515 let allocations = Arc::new(AtomicUsize::new(0));
516 session
517 .memory_mut()
518 .set_allocator(Arc::new(CountingAllocator {
519 allocations: Arc::clone(&allocations),
520 }));
521
522 let _file = session.open_options().open_path(&file_path).await.unwrap();
523 std::fs::remove_file(&file_path).unwrap();
524
525 assert!(
526 allocations.load(Ordering::Relaxed) > 0,
527 "expected at least one host allocation from MemorySession"
528 );
529 }
530}