1use std::io;
5use std::io::Write;
6use std::sync::Arc;
7use std::sync::atomic::AtomicU64;
8use std::sync::atomic::Ordering;
9
10use futures::FutureExt;
11use futures::StreamExt;
12use futures::TryStreamExt;
13use futures::future::Fuse;
14use futures::future::LocalBoxFuture;
15use futures::future::ready;
16use futures::pin_mut;
17use futures::select;
18use itertools::Itertools;
19use vortex_array::ArrayContext;
20use vortex_array::ArrayRef;
21use vortex_array::dtype::DType;
22use vortex_array::dtype::FieldPath;
23use vortex_array::expr::stats::Stat;
24use vortex_array::iter::ArrayIterator;
25use vortex_array::iter::ArrayIteratorExt;
26use vortex_array::session::ArraySessionExt;
27use vortex_array::stats::PRUNING_STATS;
28use vortex_array::stream::ArrayStream;
29use vortex_array::stream::ArrayStreamAdapter;
30use vortex_array::stream::ArrayStreamExt;
31use vortex_array::stream::SendableArrayStream;
32use vortex_buffer::ByteBuffer;
33use vortex_error::VortexError;
34use vortex_error::VortexExpect;
35use vortex_error::VortexResult;
36use vortex_error::vortex_bail;
37use vortex_error::vortex_err;
38use vortex_io::IoBuf;
39use vortex_io::VortexWrite;
40use vortex_io::kanal_ext::KanalExt;
41use vortex_io::runtime::BlockingRuntime;
42use vortex_io::session::RuntimeSessionExt;
43use vortex_layout::LayoutStrategy;
44use vortex_layout::layouts::file_stats::accumulate_stats;
45use vortex_layout::sequence::SequenceId;
46use vortex_layout::sequence::SequentialStreamAdapter;
47use vortex_layout::sequence::SequentialStreamExt;
48use vortex_session::SessionExt;
49use vortex_session::VortexSession;
50use vortex_session::registry::ReadContext;
51use vortex_utils::aliases::hash_map::HashMap;
52
53use crate::ALLOWED_ENCODINGS;
54use crate::Footer;
55use crate::MAGIC_BYTES;
56use crate::WriteStrategyBuilder;
57use crate::counting::CountingVortexWrite;
58use crate::footer::FileStatistics;
59use crate::footer::MAX_METADATA_KEY_BYTES;
60use crate::footer::MAX_METADATA_SEGMENTS;
61use crate::segments::writer::BufferedSegmentSink;
62
63pub struct VortexWriteOptions {
72 session: VortexSession,
73 strategy: Arc<dyn LayoutStrategy>,
74 exclude_dtype: bool,
75 max_variable_length_statistics_size: usize,
76 file_statistics: Vec<Stat>,
77 metadata: HashMap<String, ByteBuffer>,
78}
79
80pub trait WriteOptionsSessionExt: SessionExt {
82 fn write_options(&self) -> VortexWriteOptions {
84 let session = self.session();
85 VortexWriteOptions {
86 strategy: WriteStrategyBuilder::default().build(),
87 session,
88 exclude_dtype: false,
89 file_statistics: PRUNING_STATS.to_vec(),
90 max_variable_length_statistics_size: 64,
91 metadata: HashMap::default(),
92 }
93 }
94}
95impl<S: SessionExt> WriteOptionsSessionExt for S {}
96
97impl VortexWriteOptions {
98 pub fn new(session: VortexSession) -> Self {
100 VortexWriteOptions {
101 strategy: WriteStrategyBuilder::default().build(),
102 session,
103 exclude_dtype: false,
104 file_statistics: PRUNING_STATS.to_vec(),
105 max_variable_length_statistics_size: 64,
106 metadata: HashMap::default(),
107 }
108 }
109
110 pub fn with_strategy(mut self, strategy: Arc<dyn LayoutStrategy>) -> Self {
116 self.strategy = strategy;
117 self
118 }
119
120 pub fn exclude_dtype(mut self) -> Self {
124 self.exclude_dtype = true;
125 self
126 }
127
128 pub fn with_file_statistics(mut self, file_statistics: Vec<Stat>) -> Self {
132 self.file_statistics = file_statistics;
133 self
134 }
135
136 pub fn with_metadata_segment(
139 mut self,
140 key: impl Into<String>,
141 metadata: impl Into<ByteBuffer>,
142 ) -> Self {
143 let key = key.into();
144 let metadata = metadata.into();
145 self.metadata.insert(key, metadata);
146 self
147 }
148
149 pub fn with_metadata_segments<I, K, B>(mut self, metadata: I) -> Self
153 where
154 I: IntoIterator<Item = (K, B)>,
155 K: Into<String>,
156 B: Into<ByteBuffer>,
157 {
158 for (key, metadata) in metadata {
159 self = self.with_metadata_segment(key, metadata);
160 }
161 self
162 }
163}
164
165impl VortexWriteOptions {
166 pub fn blocking<B: BlockingRuntime>(self, runtime: &B) -> BlockingWrite<'_, B> {
171 BlockingWrite {
172 options: self,
173 runtime,
174 }
175 }
176
177 pub async fn write<W: VortexWrite + Unpin, S: ArrayStream + Send + 'static>(
182 self,
183 write: W,
184 stream: S,
185 ) -> VortexResult<WriteSummary> {
186 self.write_internal(write, ArrayStreamExt::boxed(stream))
187 .await
188 }
189
190 async fn write_internal<W: VortexWrite + Unpin>(
191 self,
192 mut write: W,
193 stream: SendableArrayStream,
194 ) -> VortexResult<WriteSummary> {
195 validate_metadata_segments(&self.metadata)?;
196
197 let ctx = ArrayContext::new(ALLOWED_ENCODINGS.iter().cloned().sorted().collect())
203 .with_valid_ids(self.session.arrays().registry().ids());
205 let dtype = stream.dtype().clone();
206
207 let (mut ptr, eof) = SequenceId::root().split();
208
209 let stream = SequentialStreamAdapter::new(
210 dtype.clone(),
211 stream
212 .try_filter(|chunk| ready(!chunk.is_empty()))
213 .map(move |result| result.map(|chunk| (ptr.advance(), chunk))),
214 )
215 .sendable();
216 let (file_stats, stream) = accumulate_stats(
217 stream,
218 self.file_statistics.clone().into(),
219 self.max_variable_length_statistics_size,
220 &self.session,
221 );
222
223 write.write_all(ByteBuffer::copy_from(MAGIC_BYTES)).await?;
225 let mut position = MAGIC_BYTES.len() as u64;
226
227 let (send, recv) = kanal::bounded_async(1);
229
230 let segments = Arc::new(BufferedSegmentSink::new(send, position));
231
232 let ctx2 = ctx.clone();
235 let session = self.session.clone();
236 let layout_fut = self.session.handle().spawn_nested(move |h| async move {
237 let session = session.with_handle(h);
238 let layout = self
239 .strategy
240 .write_stream(
241 ctx2,
242 Arc::<BufferedSegmentSink>::clone(&segments),
243 stream,
244 eof,
245 &session,
246 )
247 .await?;
248 Ok::<_, VortexError>((layout, segments.segment_specs()))
249 });
250
251 let recv_stream = recv.into_stream();
253 pin_mut!(recv_stream);
254 while let Some(buffer) = recv_stream.next().await {
255 if buffer.is_empty() {
256 continue;
257 }
258 position += buffer.len() as u64;
259 write.write_all(buffer).await?;
260 }
261
262 let (layout, segment_specs) = layout_fut.await?;
263
264 let statistics = if self.file_statistics.is_empty() {
266 None
267 } else {
268 Some(FileStatistics::new_with_dtype(
269 file_stats.stats_sets().into(),
270 &dtype,
271 ))
272 };
273 let mut footer = Footer::new(
274 Arc::clone(&layout),
275 segment_specs,
276 statistics,
277 ReadContext::new(ctx.to_ids()),
278 );
279
280 let (footer_buffers, metadata, approx_byte_size) = footer
282 .clone()
283 .into_serializer()
284 .with_metadata_segments(self.metadata)
285 .with_offset(position)
286 .with_exclude_dtype(self.exclude_dtype)
287 .serialize_with_metadata()?;
288 footer = footer
289 .with_metadata_segments(metadata)
290 .with_approx_byte_size(approx_byte_size);
291
292 for buffer in footer_buffers {
293 position += buffer.len() as u64;
294 write.write_all(buffer).await?;
295 }
296
297 write.flush().await?;
298
299 Ok(WriteSummary {
300 footer,
301 size: position,
302 })
303 }
304
305 pub fn writer<'w, W: VortexWrite + Unpin + 'w>(self, write: W, dtype: DType) -> Writer<'w> {
310 let (arrays_send, arrays_recv) = kanal::bounded_async(1);
312
313 let arrays =
314 ArrayStreamExt::boxed(ArrayStreamAdapter::new(dtype, arrays_recv.into_stream()));
315
316 let write = CountingVortexWrite::new(write);
317 let bytes_written = write.counter();
318 let strategy = Arc::clone(&self.strategy);
319 let future = self.write(write, arrays).boxed_local().fuse();
320
321 Writer {
322 arrays: Some(arrays_send),
323 future,
324 bytes_written,
325 strategy,
326 }
327 }
328}
329
330fn validate_metadata_segments(metadata: &HashMap<String, ByteBuffer>) -> VortexResult<()> {
331 if metadata.len() > MAX_METADATA_SEGMENTS {
332 vortex_bail!(
333 "Vortex files may contain at most {} metadata segments; got {} metadata segments. Metadata keys must be non-empty and at most {} bytes",
334 MAX_METADATA_SEGMENTS,
335 metadata.len(),
336 MAX_METADATA_KEY_BYTES
337 );
338 }
339
340 for key in metadata.keys() {
341 if key.is_empty() {
342 vortex_bail!(
343 "Vortex metadata keys must be non-empty and at most {} bytes; files may contain at most {} metadata segments",
344 MAX_METADATA_KEY_BYTES,
345 MAX_METADATA_SEGMENTS
346 );
347 }
348
349 let key_bytes = key.len();
350 if key_bytes > MAX_METADATA_KEY_BYTES {
351 vortex_bail!(
352 "Vortex metadata key {key:?} is {key_bytes} bytes, but keys must be at most {} bytes; files may contain at most {} metadata segments",
353 MAX_METADATA_KEY_BYTES,
354 MAX_METADATA_SEGMENTS
355 );
356 }
357 }
358
359 Ok(())
360}
361
362pub struct Writer<'w> {
364 arrays: Option<kanal::AsyncSender<VortexResult<ArrayRef>>>,
366 future: Fuse<LocalBoxFuture<'w, VortexResult<WriteSummary>>>,
368 bytes_written: Arc<AtomicU64>,
370 strategy: Arc<dyn LayoutStrategy>,
372}
373
374impl Writer<'_> {
375 pub async fn push(&mut self, chunk: ArrayRef) -> VortexResult<()> {
377 let arrays = self.arrays.clone().vortex_expect("missing arrays sender");
378 let send_fut = async move { arrays.send(Ok(chunk)).await }.fuse();
379 pin_mut!(send_fut);
380
381 select! {
384 result = send_fut => {
385 if result.is_err() {
387 return Err(self.handle_failed_task().await);
388 }
389 },
390 result = &mut self.future => {
391 match result {
395 Ok(_) => vortex_bail!("Internal error: writer future completed early"),
396 Err(e) => return Err(e),
397 }
398 }
399 }
400
401 Ok(())
402 }
403
404 pub async fn push_stream(&mut self, mut stream: SendableArrayStream) -> VortexResult<()> {
409 let arrays = self.arrays.clone().vortex_expect("missing arrays sender");
410 let stream_fut = async move {
411 while let Some(chunk) = stream.next().await {
412 arrays.send(chunk).await?;
413 }
414 Ok::<_, kanal::SendError>(())
415 }
416 .fuse();
417 pin_mut!(stream_fut);
418
419 select! {
422 result = stream_fut => {
423 if let Err(_send_err) = result {
424 return Err(self.handle_failed_task().await);
426 }
427 }
428
429 result = &mut self.future => {
430 match result {
434 Ok(_) => vortex_bail!("Internal error: writer future completed early"),
435 Err(e) => return Err(e),
436 }
437 }
438 }
439
440 Ok(())
441 }
442
443 pub fn bytes_written(&self) -> u64 {
445 self.bytes_written.load(Ordering::Relaxed)
446 }
447
448 pub fn buffered_bytes(&self) -> u64 {
450 self.strategy.buffered_bytes()
451 }
452
453 pub async fn finish(mut self) -> VortexResult<WriteSummary> {
456 drop(self.arrays.take());
458
459 self.future.await
461 }
462
463 async fn handle_failed_task(&mut self) -> VortexError {
465 match (&mut self.future).await {
466 Ok(_) => vortex_err!(
467 "Internal error: writer task completed successfully but write future finished early"
468 ),
469 Err(e) => e,
470 }
471 }
472}
473
474pub struct BlockingWrite<'rt, B: BlockingRuntime> {
476 options: VortexWriteOptions,
477 runtime: &'rt B,
478}
479
480impl<'rt, B: BlockingRuntime> BlockingWrite<'rt, B> {
481 pub fn write<W: Write + Unpin>(
486 self,
487 write: W,
488 iter: impl ArrayIterator + Send + 'static,
489 ) -> VortexResult<WriteSummary> {
490 self.runtime.block_on(async move {
491 self.options
492 .write(BlockingWriteAdapter(write), iter.into_array_stream())
493 .await
494 })
495 }
496
497 pub fn writer<'w, W: Write + Unpin + 'w>(
499 self,
500 write: W,
501 dtype: DType,
502 ) -> BlockingWriter<'rt, 'w, B> {
503 BlockingWriter {
504 writer: self.options.writer(BlockingWriteAdapter(write), dtype),
505 runtime: self.runtime,
506 }
507 }
508}
509
510pub struct BlockingWriter<'rt, 'w, B: BlockingRuntime> {
512 runtime: &'rt B,
513 writer: Writer<'w>,
514}
515
516impl<B: BlockingRuntime> BlockingWriter<'_, '_, B> {
517 pub fn push(&mut self, chunk: ArrayRef) -> VortexResult<()> {
519 self.runtime.block_on(self.writer.push(chunk))
520 }
521
522 pub fn bytes_written(&self) -> u64 {
524 self.writer.bytes_written()
525 }
526
527 pub fn buffered_bytes(&self) -> u64 {
529 self.writer.buffered_bytes()
530 }
531
532 pub fn finish(self) -> VortexResult<WriteSummary> {
534 self.runtime.block_on(self.writer.finish())
535 }
536}
537
538struct BlockingWriteAdapter<W>(W);
540
541impl<W: Write + Unpin> VortexWrite for BlockingWriteAdapter<W> {
542 async fn write_all<B: IoBuf>(&mut self, buffer: B) -> io::Result<B> {
543 self.0.write_all(buffer.as_slice())?;
544 Ok(buffer)
545 }
546
547 fn flush(&mut self) -> impl Future<Output = io::Result<()>> {
548 ready(self.0.flush())
549 }
550
551 fn shutdown(&mut self) -> impl Future<Output = io::Result<()>> {
552 ready(Ok(()))
553 }
554}
555
556pub struct WriteSummary {
558 footer: Footer,
559 size: u64,
560 }
562
563impl WriteSummary {
564 pub fn footer(&self) -> &Footer {
566 &self.footer
567 }
568
569 pub fn size(&self) -> u64 {
571 self.size
572 }
573
574 pub fn row_count(&self) -> u64 {
576 self.footer.row_count()
577 }
578
579 pub fn compressed_column_sizes(&self) -> VortexResult<Vec<u64>> {
589 let sizes = self.footer.compressed_field_sizes()?;
590 let Some(fields) = self.footer.dtype().as_struct_fields_opt() else {
591 return Ok(vec![sizes.total()]);
592 };
593 Ok(fields
594 .names()
595 .iter()
596 .map(|name| {
597 sizes
598 .get(&FieldPath::from_name(name.clone()))
599 .unwrap_or_default()
600 })
601 .collect())
602 }
603}