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 {
51 session: VortexSession,
53 segment_cache: Option<Arc<dyn SegmentCache>>,
55 initial_read_size: usize,
57 file_size: Option<u64>,
59 dtype: Option<DType>,
61 footer: Option<Footer>,
63 initial_read_segments: RwLock<HashMap<SegmentId, ByteBuffer>>,
65 metrics_registry: Option<Arc<dyn MetricsRegistry>>,
67 labels: Vec<Label>,
69 cache_layout_reader: bool,
71}
72
73pub trait OpenOptionsSessionExt:
75 ArraySessionExt + LayoutSessionExt + RuntimeSessionExt + MemorySessionExt
76{
77 fn open_options(&self) -> VortexOpenOptions {
79 VortexOpenOptions {
80 session: self.session(),
81 segment_cache: None,
82 initial_read_size: INITIAL_READ_SIZE,
83 file_size: None,
84 dtype: None,
85 footer: None,
86 initial_read_segments: Default::default(),
87 metrics_registry: None,
88 labels: Vec::default(),
89 cache_layout_reader: false,
90 }
91 }
92}
93impl<S: ArraySessionExt + LayoutSessionExt + RuntimeSessionExt + MemorySessionExt>
94 OpenOptionsSessionExt for S
95{
96}
97
98impl VortexOpenOptions {
99 pub fn with_initial_read_size(mut self, initial_read_size: usize) -> Self {
105 self.initial_read_size = initial_read_size;
106 self
107 }
108
109 pub fn with_layout_reader_cache(mut self) -> Self {
114 self.cache_layout_reader = true;
115 self
116 }
117
118 pub fn with_segment_cache(mut self, segment_cache: Arc<dyn SegmentCache>) -> Self {
123 self.segment_cache = Some(segment_cache);
124 self
125 }
126
127 pub fn with_file_size(mut self, file_size: u64) -> Self {
132 self.file_size = Some(file_size);
133 self
134 }
135
136 pub fn with_some_file_size(mut self, file_size: Option<u64>) -> Self {
141 self.file_size = file_size;
142 self
143 }
144
145 pub fn with_dtype(mut self, dtype: DType) -> Self {
151 self.dtype = Some(dtype);
152 self
153 }
154
155 pub fn with_footer(mut self, footer: Footer) -> Self {
160 self.dtype = Some(footer.layout().dtype().clone());
161 self.footer = Some(footer);
162 self
163 }
164
165 pub fn with_metrics_registry(mut self, metrics: Arc<dyn MetricsRegistry>) -> Self {
167 self.metrics_registry = Some(metrics);
168 self
169 }
170
171 pub fn with_labels(mut self, labels: Vec<Label>) -> Self {
173 self.labels.extend(labels);
174 self
175 }
176
177 pub async fn open(self, source: Arc<dyn VortexReadAt>) -> VortexResult<VortexFile> {
184 self.open_read(source).await
185 }
186
187 #[cfg(not(target_arch = "wasm32"))]
189 pub async fn open_path(self, path: impl AsRef<std::path::Path>) -> VortexResult<VortexFile> {
190 use vortex_io::std_file::FileReadAt;
191 let handle = self.session.handle();
192 let allocator = self.session.allocator();
193 let source = Arc::new(FileReadAt::open_with_allocator(path, handle, allocator)?);
194 self.open(source).await
195 }
196
197 pub fn open_buffer<B: Into<ByteBuffer>>(self, buffer: B) -> VortexResult<VortexFile> {
204 let buffer: ByteBuffer = buffer.into();
205
206 if self.segment_cache.is_some() {
207 tracing::warn!("segment cache is ignored for in-memory `open_buffer`");
208 }
209 if self.metrics_registry.is_some() {
210 tracing::warn!("metrics registry is ignored for in-memory `open_buffer`");
211 }
212
213 let cache_layout_reader = self.cache_layout_reader;
214 let mut opts = self.with_initial_read_size(0);
215
216 let footer = match opts.footer.take() {
217 Some(footer) => footer,
218 None => block_on(opts.read_footer(&buffer))?,
219 };
220
221 let segment_source = Arc::new(BufferSegmentSource::new(
222 buffer,
223 Arc::clone(footer.segment_map()),
224 ));
225 let file = VortexFile::new(footer, segment_source, opts.session);
226 Ok(if cache_layout_reader {
227 file.with_caching()
228 } else {
229 file
230 })
231 }
232
233 pub async fn open_read<R: VortexReadAt + Clone>(self, reader: R) -> VortexResult<VortexFile> {
237 let segment_cache = self
238 .segment_cache
239 .clone()
240 .unwrap_or_else(|| Arc::new(NoOpSegmentCache));
241
242 let metrics_registry = self
243 .metrics_registry
244 .clone()
245 .unwrap_or_else(|| Arc::new(DefaultMetricsRegistry::default()));
246
247 let footer = if let Some(footer) = self.footer {
248 footer
249 } else {
250 self.read_footer(&reader).await?
251 };
252
253 let segment_cache = Arc::new(InstrumentedSegmentCache::new(
254 InitialReadSegmentCache {
255 initial: self.initial_read_segments,
256 fallback: segment_cache,
257 },
258 metrics_registry.as_ref(),
259 self.labels.clone(),
260 ));
261
262 let metrics = RequestMetrics::new(metrics_registry.as_ref(), self.labels);
263
264 let segment_source = Arc::new(SharedSegmentSource::new(FileSegmentSource::open(
266 Arc::clone(footer.segment_map()),
267 reader,
268 self.session.handle(),
269 metrics,
270 )));
271
272 let segment_source = Arc::new(SegmentCacheSourceAdapter::new(
274 segment_cache,
275 segment_source,
276 ));
277
278 let file = VortexFile::new(footer, segment_source, self.session.clone());
279 Ok(if self.cache_layout_reader {
280 file.with_caching()
281 } else {
282 file
283 })
284 }
285
286 async fn read_footer(&self, read: &dyn VortexReadAt) -> VortexResult<Footer> {
287 let file_size = match self.file_size {
289 None => read.size().await?,
290 Some(file_size) => file_size,
291 };
292 let mut initial_read_size = self
293 .initial_read_size
294 .max(MAX_POSTSCRIPT_SIZE as usize + EOF_SIZE);
296 if let Ok(file_size) = usize::try_from(file_size) {
297 initial_read_size = initial_read_size.min(file_size);
298 }
299
300 let initial_offset = file_size - initial_read_size as u64;
301 let initial_read: ByteBuffer = read
302 .read_at(initial_offset, initial_read_size, Alignment::none())
303 .await?
304 .try_into_host()?
305 .await?;
306
307 let mut deserializer = Footer::deserializer(initial_read, self.session.clone())
308 .with_size(file_size)
309 .with_some_dtype(self.dtype.clone());
310
311 let footer = loop {
312 match deserializer.deserialize()? {
313 DeserializeStep::NeedMoreData { offset, len } => {
314 let more_data = read
315 .read_at(offset, len, Alignment::none())
316 .await?
317 .try_into_host()?
318 .await?;
319 deserializer.prefix_data(more_data);
320 }
321 DeserializeStep::NeedFileSize => unreachable!("We passed file_size above"),
322 DeserializeStep::Done(footer) => break Ok::<_, VortexError>(footer),
323 }
324 }?;
325
326 let initial_offset = file_size - (deserializer.buffer().len() as u64);
329 self.populate_initial_segments(initial_offset, deserializer.buffer(), &footer);
330
331 Ok(footer)
332 }
333
334 fn populate_initial_segments(
336 &self,
337 initial_offset: u64,
338 initial_read: &ByteBuffer,
339 footer: &Footer,
340 ) {
341 let first_idx = footer
342 .segment_map()
343 .partition_point(|segment| segment.offset < initial_offset);
344
345 let mut initial_read_segments = self.initial_read_segments.write();
346
347 for idx in first_idx..footer.segment_map().len() {
348 let segment = &footer.segment_map()[idx];
349 let segment_id =
350 SegmentId::from(u32::try_from(idx).vortex_expect("Invalid segment ID"));
351 let offset =
352 usize::try_from(segment.offset - initial_offset).vortex_expect("Invalid offset");
353 let buffer = initial_read
354 .slice(offset..offset + (segment.length as usize))
355 .aligned(segment.alignment);
356 initial_read_segments.insert(segment_id, buffer);
357 }
358 }
359}
360
361#[cfg(feature = "object_store")]
362impl VortexOpenOptions {
363 pub async fn open_object_store(
365 self,
366 object_store: &Arc<dyn object_store::ObjectStore>,
367 path: &str,
368 ) -> VortexResult<VortexFile> {
369 use vortex_io::object_store::ObjectStoreReadAt;
370
371 let handle = self.session.handle();
372 let allocator = self.session.allocator();
373 let source = Arc::new(ObjectStoreReadAt::new_with_allocator(
374 Arc::clone(object_store),
375 path.into(),
376 handle,
377 allocator,
378 ));
379 self.open(source).await
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use std::sync::atomic::AtomicUsize;
386 use std::sync::atomic::Ordering;
387
388 use futures::future::BoxFuture;
389 use vortex_array::IntoArray;
390 use vortex_array::buffer::BufferHandle;
391 use vortex_array::memory::DefaultHostAllocator;
392 use vortex_array::memory::HostAllocator;
393 use vortex_array::memory::MemorySessionExt;
394 use vortex_array::memory::WritableHostBuffer;
395 use vortex_buffer::Alignment;
396 use vortex_buffer::Buffer;
397 use vortex_buffer::ByteBuffer;
398 use vortex_buffer::ByteBufferMut;
399 use vortex_io::session::RuntimeSession;
400 use vortex_layout::session::LayoutSession;
401
402 use super::*;
403 use crate::WriteOptionsSessionExt;
404
405 #[derive(Clone)]
406 struct CountingRead<R> {
408 inner: R,
409 total_read: Arc<AtomicUsize>,
410 first_read_len: Arc<AtomicUsize>,
411 }
412
413 impl<R: VortexReadAt + Clone> VortexReadAt for CountingRead<R> {
414 fn size(&self) -> BoxFuture<'static, VortexResult<u64>> {
415 self.inner.size()
416 }
417
418 fn read_at(
419 &self,
420 offset: u64,
421 length: usize,
422 alignment: Alignment,
423 ) -> BoxFuture<'static, VortexResult<BufferHandle>> {
424 self.total_read.fetch_add(length, Ordering::Relaxed);
425 let _ = self.first_read_len.compare_exchange(
426 0,
427 length,
428 Ordering::Relaxed,
429 Ordering::Relaxed,
430 );
431 self.inner.read_at(offset, length, alignment)
432 }
433
434 fn concurrency(&self) -> usize {
435 self.inner.concurrency()
436 }
437 }
438
439 #[derive(Debug)]
440 struct CountingAllocator {
441 allocations: Arc<AtomicUsize>,
442 }
443
444 impl HostAllocator for CountingAllocator {
445 fn allocate(&self, len: usize, alignment: Alignment) -> VortexResult<WritableHostBuffer> {
446 self.allocations.fetch_add(1, Ordering::Relaxed);
447 DefaultHostAllocator.allocate(len, alignment)
448 }
449 }
450
451 #[tokio::test]
452 async fn test_initial_read_size() {
453 let session = vortex_array::array_session()
454 .with::<LayoutSession>()
455 .with::<RuntimeSession>();
456
457 crate::register_default_encodings(&session);
458
459 let mut buf = ByteBufferMut::empty();
461
462 let mut state = 0x9E37_79B9u32;
466 let array = Buffer::from(
467 (0i32..1_500_000)
468 .map(|_| {
469 state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
470 state as i32
471 })
472 .collect::<Vec<i32>>(),
473 )
474 .into_array();
475
476 session
477 .write_options()
478 .write(&mut buf, array.to_array_stream())
479 .await
480 .unwrap();
481
482 let buffer = ByteBuffer::from(buf);
483 assert!(
484 buffer.len() > 1024 * 1024,
485 "Buffer length is only {} bytes",
486 buffer.len()
487 );
488
489 let total_read = Arc::new(AtomicUsize::new(0));
490 let first_read_len = Arc::new(AtomicUsize::new(0));
491 let reader = CountingRead {
492 inner: buffer,
493 total_read: Arc::clone(&total_read),
494 first_read_len: Arc::clone(&first_read_len),
495 };
496
497 let _file = session.open_options().open_read(reader).await.unwrap();
499
500 let first = first_read_len.load(Ordering::Relaxed);
502 assert_eq!(
503 first,
504 MAX_POSTSCRIPT_SIZE as usize + EOF_SIZE,
505 "Read exactly the postscript size"
506 );
507 let read = total_read.load(Ordering::Relaxed);
508 assert!(read < 1024 * 1024, "Read {} bytes, expected < 1MB", read);
509 }
510
511 #[cfg(not(target_arch = "wasm32"))]
512 #[tokio::test]
513 async fn test_open_path_uses_memory_session_allocator() {
514 let session = vortex_array::array_session()
515 .with::<LayoutSession>()
516 .with::<RuntimeSession>();
517
518 crate::register_default_encodings(&session);
519
520 let mut buf = ByteBufferMut::empty();
521 let array = Buffer::from((0i32..16_384).collect::<Vec<i32>>()).into_array();
522 session
523 .write_options()
524 .write(&mut buf, array.to_array_stream())
525 .await
526 .unwrap();
527
528 let file_path = std::env::temp_dir().join(format!(
529 "vortex-open-memory-session-{}.vx",
530 std::process::id()
531 ));
532 std::fs::write(&file_path, ByteBuffer::from(buf).as_slice()).unwrap();
533
534 let allocations = Arc::new(AtomicUsize::new(0));
535 let session = session.with_allocator(Arc::new(CountingAllocator {
536 allocations: Arc::clone(&allocations),
537 }));
538
539 let _file = session.open_options().open_path(&file_path).await.unwrap();
540 std::fs::remove_file(&file_path).unwrap();
541
542 assert!(
543 allocations.load(Ordering::Relaxed) > 0,
544 "expected at least one host allocation from MemorySession"
545 );
546 }
547}