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