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 {
198 footer,
199 segment_source,
200 session: opts.session,
201 })
202 }
203
204 pub async fn open_read<R: VortexReadAt + Clone>(self, reader: R) -> VortexResult<VortexFile> {
206 let segment_cache = self
207 .segment_cache
208 .clone()
209 .unwrap_or_else(|| Arc::new(NoOpSegmentCache));
210
211 let metrics_registry = self
212 .metrics_registry
213 .clone()
214 .unwrap_or_else(|| Arc::new(DefaultMetricsRegistry::default()));
215
216 let footer = if let Some(footer) = self.footer {
217 footer
218 } else {
219 self.read_footer(&reader).await?
220 };
221
222 let segment_cache = Arc::new(InstrumentedSegmentCache::new(
223 InitialReadSegmentCache {
224 initial: self.initial_read_segments,
225 fallback: segment_cache,
226 },
227 metrics_registry.as_ref(),
228 self.labels.clone(),
229 ));
230
231 let metrics = RequestMetrics::new(metrics_registry.as_ref(), self.labels);
232
233 let segment_source = Arc::new(SharedSegmentSource::new(FileSegmentSource::open(
235 Arc::clone(footer.segment_map()),
236 reader,
237 self.session.handle(),
238 metrics,
239 )));
240
241 let segment_source = Arc::new(SegmentCacheSourceAdapter::new(
243 segment_cache,
244 segment_source,
245 ));
246
247 Ok(VortexFile {
248 footer,
249 segment_source,
250 session: self.session.clone(),
251 })
252 }
253
254 async fn read_footer(&self, read: &dyn VortexReadAt) -> VortexResult<Footer> {
255 let file_size = match self.file_size {
257 None => read.size().await?,
258 Some(file_size) => file_size,
259 };
260 let mut initial_read_size = self
261 .initial_read_size
262 .max(MAX_POSTSCRIPT_SIZE as usize + EOF_SIZE);
264 if let Ok(file_size) = usize::try_from(file_size) {
265 initial_read_size = initial_read_size.min(file_size);
266 }
267
268 let initial_offset = file_size - initial_read_size as u64;
269 let initial_read: ByteBuffer = read
270 .read_at(initial_offset, initial_read_size, Alignment::none())
271 .await?
272 .try_into_host()?
273 .await?;
274
275 let mut deserializer = Footer::deserializer(initial_read, self.session.clone())
276 .with_size(file_size)
277 .with_some_dtype(self.dtype.clone());
278
279 let footer = loop {
280 match deserializer.deserialize()? {
281 DeserializeStep::NeedMoreData { offset, len } => {
282 let more_data = read
283 .read_at(offset, len, Alignment::none())
284 .await?
285 .try_into_host()?
286 .await?;
287 deserializer.prefix_data(more_data);
288 }
289 DeserializeStep::NeedFileSize => unreachable!("We passed file_size above"),
290 DeserializeStep::Done(footer) => break Ok::<_, VortexError>(footer),
291 }
292 }?;
293
294 let initial_offset = file_size - (deserializer.buffer().len() as u64);
297 self.populate_initial_segments(initial_offset, deserializer.buffer(), &footer);
298
299 Ok(footer)
300 }
301
302 fn populate_initial_segments(
304 &self,
305 initial_offset: u64,
306 initial_read: &ByteBuffer,
307 footer: &Footer,
308 ) {
309 let first_idx = footer
310 .segment_map()
311 .partition_point(|segment| segment.offset < initial_offset);
312
313 let mut initial_read_segments = self.initial_read_segments.write();
314
315 for idx in first_idx..footer.segment_map().len() {
316 let segment = &footer.segment_map()[idx];
317 let segment_id =
318 SegmentId::from(u32::try_from(idx).vortex_expect("Invalid segment ID"));
319 let offset =
320 usize::try_from(segment.offset - initial_offset).vortex_expect("Invalid offset");
321 let buffer = initial_read
322 .slice(offset..offset + (segment.length as usize))
323 .aligned(segment.alignment);
324 initial_read_segments.insert(segment_id, buffer);
325 }
326 }
327}
328
329#[cfg(feature = "object_store")]
330impl VortexOpenOptions {
331 pub async fn open_object_store(
332 self,
333 object_store: &Arc<dyn object_store::ObjectStore>,
334 path: &str,
335 ) -> VortexResult<VortexFile> {
336 use vortex_io::object_store::ObjectStoreReadAt;
337
338 let handle = self.session.handle();
339 let allocator = self.session.allocator();
340 let source = Arc::new(ObjectStoreReadAt::new_with_allocator(
341 Arc::clone(object_store),
342 path.into(),
343 handle,
344 allocator,
345 ));
346 self.open(source).await
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use std::sync::atomic::AtomicUsize;
353 use std::sync::atomic::Ordering;
354
355 use futures::future::BoxFuture;
356 use vortex_array::IntoArray;
357 use vortex_array::buffer::BufferHandle;
358 use vortex_array::dtype::session::DTypeSession;
359 use vortex_array::memory::DefaultHostAllocator;
360 use vortex_array::memory::HostAllocator;
361 use vortex_array::memory::MemorySessionExt;
362 use vortex_array::memory::WritableHostBuffer;
363 use vortex_array::scalar_fn::session::ScalarFnSession;
364 use vortex_array::session::ArraySession;
365 use vortex_buffer::Alignment;
366 use vortex_buffer::Buffer;
367 use vortex_buffer::ByteBuffer;
368 use vortex_buffer::ByteBufferMut;
369 use vortex_io::session::RuntimeSession;
370 use vortex_layout::session::LayoutSession;
371
372 use super::*;
373 use crate::WriteOptionsSessionExt;
374
375 #[derive(Clone)]
376 struct CountingRead<R> {
378 inner: R,
379 total_read: Arc<AtomicUsize>,
380 first_read_len: Arc<AtomicUsize>,
381 }
382
383 impl<R: VortexReadAt + Clone> VortexReadAt for CountingRead<R> {
384 fn size(&self) -> BoxFuture<'static, VortexResult<u64>> {
385 self.inner.size()
386 }
387
388 fn read_at(
389 &self,
390 offset: u64,
391 length: usize,
392 alignment: Alignment,
393 ) -> BoxFuture<'static, VortexResult<BufferHandle>> {
394 self.total_read.fetch_add(length, Ordering::Relaxed);
395 let _ = self.first_read_len.compare_exchange(
396 0,
397 length,
398 Ordering::Relaxed,
399 Ordering::Relaxed,
400 );
401 self.inner.read_at(offset, length, alignment)
402 }
403
404 fn concurrency(&self) -> usize {
405 self.inner.concurrency()
406 }
407 }
408
409 #[derive(Debug)]
410 struct CountingAllocator {
411 allocations: Arc<AtomicUsize>,
412 }
413
414 impl HostAllocator for CountingAllocator {
415 fn allocate(&self, len: usize, alignment: Alignment) -> VortexResult<WritableHostBuffer> {
416 self.allocations.fetch_add(1, Ordering::Relaxed);
417 DefaultHostAllocator.allocate(len, alignment)
418 }
419 }
420
421 #[tokio::test]
422 async fn test_initial_read_size() {
423 let session = VortexSession::empty()
424 .with::<DTypeSession>()
425 .with::<ArraySession>()
426 .with::<LayoutSession>()
427 .with::<ScalarFnSession>()
428 .with::<RuntimeSession>();
429
430 crate::register_default_encodings(&session);
431
432 let mut buf = ByteBufferMut::empty();
434
435 let array = Buffer::from(
437 (0i32..1_500_000)
438 .map(|i| if i % 2 == 0 { i } else { -i })
439 .collect::<Vec<i32>>(),
440 )
441 .into_array();
442
443 session
444 .write_options()
445 .write(&mut buf, array.to_array_stream())
446 .await
447 .unwrap();
448
449 let buffer = ByteBuffer::from(buf);
450 assert!(
451 buffer.len() > 1024 * 1024,
452 "Buffer length is only {} bytes",
453 buffer.len()
454 );
455
456 let total_read = Arc::new(AtomicUsize::new(0));
457 let first_read_len = Arc::new(AtomicUsize::new(0));
458 let reader = CountingRead {
459 inner: buffer,
460 total_read: Arc::clone(&total_read),
461 first_read_len: Arc::clone(&first_read_len),
462 };
463
464 let _file = session.open_options().open_read(reader).await.unwrap();
466
467 let first = first_read_len.load(Ordering::Relaxed);
469 assert_eq!(
470 first,
471 MAX_POSTSCRIPT_SIZE as usize + EOF_SIZE,
472 "Read exactly the postscript size"
473 );
474 let read = total_read.load(Ordering::Relaxed);
475 assert!(read < 1024 * 1024, "Read {} bytes, expected < 1MB", read);
476 }
477
478 #[cfg(not(target_arch = "wasm32"))]
479 #[tokio::test]
480 async fn test_open_path_uses_memory_session_allocator() {
481 let session = VortexSession::empty()
482 .with::<DTypeSession>()
483 .with::<ArraySession>()
484 .with::<LayoutSession>()
485 .with::<ScalarFnSession>()
486 .with::<RuntimeSession>();
487
488 crate::register_default_encodings(&session);
489
490 let mut buf = ByteBufferMut::empty();
491 let array = Buffer::from((0i32..16_384).collect::<Vec<i32>>()).into_array();
492 session
493 .write_options()
494 .write(&mut buf, array.to_array_stream())
495 .await
496 .unwrap();
497
498 let file_path = std::env::temp_dir().join(format!(
499 "vortex-open-memory-session-{}.vx",
500 std::process::id()
501 ));
502 std::fs::write(&file_path, ByteBuffer::from(buf).as_slice()).unwrap();
503
504 let allocations = Arc::new(AtomicUsize::new(0));
505 session
506 .memory_mut()
507 .set_allocator(Arc::new(CountingAllocator {
508 allocations: Arc::clone(&allocations),
509 }));
510
511 let _file = session.open_options().open_path(&file_path).await.unwrap();
512 std::fs::remove_file(&file_path).unwrap();
513
514 assert!(
515 allocations.load(Ordering::Relaxed) > 0,
516 "expected at least one host allocation from MemorySession"
517 );
518 }
519}