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;
51
52use crate::ALLOWED_ENCODINGS;
53use crate::Footer;
54use crate::MAGIC_BYTES;
55use crate::WriteStrategyBuilder;
56use crate::counting::CountingVortexWrite;
57use crate::footer::FileStatistics;
58use crate::segments::writer::BufferedSegmentSink;
59
60pub struct VortexWriteOptions {
69 session: VortexSession,
70 strategy: Arc<dyn LayoutStrategy>,
71 exclude_dtype: bool,
72 max_variable_length_statistics_size: usize,
73 file_statistics: Vec<Stat>,
74}
75
76pub trait WriteOptionsSessionExt: SessionExt {
78 fn write_options(&self) -> VortexWriteOptions {
80 let session = self.session();
81 VortexWriteOptions {
82 strategy: WriteStrategyBuilder::default().build(),
83 session,
84 exclude_dtype: false,
85 file_statistics: PRUNING_STATS.to_vec(),
86 max_variable_length_statistics_size: 64,
87 }
88 }
89}
90impl<S: SessionExt> WriteOptionsSessionExt for S {}
91
92impl VortexWriteOptions {
93 pub fn new(session: VortexSession) -> Self {
95 VortexWriteOptions {
96 strategy: WriteStrategyBuilder::default().build(),
97 session,
98 exclude_dtype: false,
99 file_statistics: PRUNING_STATS.to_vec(),
100 max_variable_length_statistics_size: 64,
101 }
102 }
103
104 pub fn with_strategy(mut self, strategy: Arc<dyn LayoutStrategy>) -> Self {
110 self.strategy = strategy;
111 self
112 }
113
114 pub fn exclude_dtype(mut self) -> Self {
118 self.exclude_dtype = true;
119 self
120 }
121
122 pub fn with_file_statistics(mut self, file_statistics: Vec<Stat>) -> Self {
126 self.file_statistics = file_statistics;
127 self
128 }
129}
130
131impl VortexWriteOptions {
132 pub fn blocking<B: BlockingRuntime>(self, runtime: &B) -> BlockingWrite<'_, B> {
137 BlockingWrite {
138 options: self,
139 runtime,
140 }
141 }
142
143 pub async fn write<W: VortexWrite + Unpin, S: ArrayStream + Send + 'static>(
148 self,
149 write: W,
150 stream: S,
151 ) -> VortexResult<WriteSummary> {
152 self.write_internal(write, ArrayStreamExt::boxed(stream))
153 .await
154 }
155
156 async fn write_internal<W: VortexWrite + Unpin>(
157 self,
158 mut write: W,
159 stream: SendableArrayStream,
160 ) -> VortexResult<WriteSummary> {
161 let ctx = ArrayContext::new(ALLOWED_ENCODINGS.iter().cloned().sorted().collect())
167 .with_registry(self.session.arrays().registry().clone());
169 let dtype = stream.dtype().clone();
170
171 let (mut ptr, eof) = SequenceId::root().split();
172
173 let stream = SequentialStreamAdapter::new(
174 dtype.clone(),
175 stream
176 .try_filter(|chunk| ready(!chunk.is_empty()))
177 .map(move |result| result.map(|chunk| (ptr.advance(), chunk))),
178 )
179 .sendable();
180 let (file_stats, stream) = accumulate_stats(
181 stream,
182 self.file_statistics.clone().into(),
183 self.max_variable_length_statistics_size,
184 &self.session,
185 );
186
187 write.write_all(ByteBuffer::copy_from(MAGIC_BYTES)).await?;
189 let mut position = MAGIC_BYTES.len() as u64;
190
191 let (send, recv) = kanal::bounded_async(1);
193
194 let segments = Arc::new(BufferedSegmentSink::new(send, position));
195
196 let ctx2 = ctx.clone();
199 let session = self.session.clone();
200 let layout_fut = self.session.handle().spawn_nested(move |h| async move {
201 let session = session.with_handle(h);
202 let layout = self
203 .strategy
204 .write_stream(
205 ctx2,
206 Arc::<BufferedSegmentSink>::clone(&segments),
207 stream,
208 eof,
209 &session,
210 )
211 .await?;
212 Ok::<_, VortexError>((layout, segments.segment_specs()))
213 });
214
215 let recv_stream = recv.into_stream();
217 pin_mut!(recv_stream);
218 while let Some(buffer) = recv_stream.next().await {
219 if buffer.is_empty() {
220 continue;
221 }
222 position += buffer.len() as u64;
223 write.write_all(buffer).await?;
224 }
225
226 let (layout, segment_specs) = layout_fut.await?;
227
228 let mut footer = Footer::new(
230 Arc::clone(&layout),
231 segment_specs,
232 if self.file_statistics.is_empty() {
233 None
234 } else {
235 Some(FileStatistics::new_with_dtype(
236 file_stats.stats_sets().into(),
237 &dtype,
238 ))
239 },
240 ReadContext::new(ctx.to_ids()),
241 );
242
243 let footer_buffers = footer
245 .clone()
246 .into_serializer()
247 .with_offset(position)
248 .with_exclude_dtype(self.exclude_dtype)
249 .serialize()?;
250
251 footer = footer.with_approx_byte_size(footer_buffers.iter().map(|b| b.len()).sum());
254
255 for buffer in footer_buffers {
256 position += buffer.len() as u64;
257 write.write_all(buffer).await?;
258 }
259
260 write.flush().await?;
261
262 Ok(WriteSummary {
263 footer,
264 size: position,
265 })
266 }
267
268 pub fn writer<'w, W: VortexWrite + Unpin + 'w>(self, write: W, dtype: DType) -> Writer<'w> {
273 let (arrays_send, arrays_recv) = kanal::bounded_async(1);
275
276 let arrays =
277 ArrayStreamExt::boxed(ArrayStreamAdapter::new(dtype, arrays_recv.into_stream()));
278
279 let write = CountingVortexWrite::new(write);
280 let bytes_written = write.counter();
281 let strategy = Arc::clone(&self.strategy);
282 let future = self.write(write, arrays).boxed_local().fuse();
283
284 Writer {
285 arrays: Some(arrays_send),
286 future,
287 bytes_written,
288 strategy,
289 }
290 }
291}
292
293pub struct Writer<'w> {
295 arrays: Option<kanal::AsyncSender<VortexResult<ArrayRef>>>,
297 future: Fuse<LocalBoxFuture<'w, VortexResult<WriteSummary>>>,
299 bytes_written: Arc<AtomicU64>,
301 strategy: Arc<dyn LayoutStrategy>,
303}
304
305impl Writer<'_> {
306 pub async fn push(&mut self, chunk: ArrayRef) -> VortexResult<()> {
308 let arrays = self.arrays.clone().vortex_expect("missing arrays sender");
309 let send_fut = async move { arrays.send(Ok(chunk)).await }.fuse();
310 pin_mut!(send_fut);
311
312 select! {
315 result = send_fut => {
316 if result.is_err() {
318 return Err(self.handle_failed_task().await);
319 }
320 },
321 result = &mut self.future => {
322 match result {
326 Ok(_) => vortex_bail!("Internal error: writer future completed early"),
327 Err(e) => return Err(e),
328 }
329 }
330 }
331
332 Ok(())
333 }
334
335 pub async fn push_stream(&mut self, mut stream: SendableArrayStream) -> VortexResult<()> {
340 let arrays = self.arrays.clone().vortex_expect("missing arrays sender");
341 let stream_fut = async move {
342 while let Some(chunk) = stream.next().await {
343 arrays.send(chunk).await?;
344 }
345 Ok::<_, kanal::SendError>(())
346 }
347 .fuse();
348 pin_mut!(stream_fut);
349
350 select! {
353 result = stream_fut => {
354 if let Err(_send_err) = result {
355 return Err(self.handle_failed_task().await);
357 }
358 }
359
360 result = &mut self.future => {
361 match result {
365 Ok(_) => vortex_bail!("Internal error: writer future completed early"),
366 Err(e) => return Err(e),
367 }
368 }
369 }
370
371 Ok(())
372 }
373
374 pub fn bytes_written(&self) -> u64 {
376 self.bytes_written.load(Ordering::Relaxed)
377 }
378
379 pub fn buffered_bytes(&self) -> u64 {
381 self.strategy.buffered_bytes()
382 }
383
384 pub async fn finish(mut self) -> VortexResult<WriteSummary> {
387 drop(self.arrays.take());
389
390 self.future.await
392 }
393
394 async fn handle_failed_task(&mut self) -> VortexError {
396 match (&mut self.future).await {
397 Ok(_) => vortex_err!(
398 "Internal error: writer task completed successfully but write future finished early"
399 ),
400 Err(e) => e,
401 }
402 }
403}
404
405pub struct BlockingWrite<'rt, B: BlockingRuntime> {
407 options: VortexWriteOptions,
408 runtime: &'rt B,
409}
410
411impl<'rt, B: BlockingRuntime> BlockingWrite<'rt, B> {
412 pub fn write<W: Write + Unpin>(
417 self,
418 write: W,
419 iter: impl ArrayIterator + Send + 'static,
420 ) -> VortexResult<WriteSummary> {
421 self.runtime.block_on(async move {
422 self.options
423 .write(BlockingWriteAdapter(write), iter.into_array_stream())
424 .await
425 })
426 }
427
428 pub fn writer<'w, W: Write + Unpin + 'w>(
430 self,
431 write: W,
432 dtype: DType,
433 ) -> BlockingWriter<'rt, 'w, B> {
434 BlockingWriter {
435 writer: self.options.writer(BlockingWriteAdapter(write), dtype),
436 runtime: self.runtime,
437 }
438 }
439}
440
441pub struct BlockingWriter<'rt, 'w, B: BlockingRuntime> {
443 runtime: &'rt B,
444 writer: Writer<'w>,
445}
446
447impl<B: BlockingRuntime> BlockingWriter<'_, '_, B> {
448 pub fn push(&mut self, chunk: ArrayRef) -> VortexResult<()> {
450 self.runtime.block_on(self.writer.push(chunk))
451 }
452
453 pub fn bytes_written(&self) -> u64 {
455 self.writer.bytes_written()
456 }
457
458 pub fn buffered_bytes(&self) -> u64 {
460 self.writer.buffered_bytes()
461 }
462
463 pub fn finish(self) -> VortexResult<WriteSummary> {
465 self.runtime.block_on(self.writer.finish())
466 }
467}
468
469struct BlockingWriteAdapter<W>(W);
471
472impl<W: Write + Unpin> VortexWrite for BlockingWriteAdapter<W> {
473 async fn write_all<B: IoBuf>(&mut self, buffer: B) -> io::Result<B> {
474 self.0.write_all(buffer.as_slice())?;
475 Ok(buffer)
476 }
477
478 fn flush(&mut self) -> impl Future<Output = io::Result<()>> {
479 ready(self.0.flush())
480 }
481
482 fn shutdown(&mut self) -> impl Future<Output = io::Result<()>> {
483 ready(Ok(()))
484 }
485}
486
487pub struct WriteSummary {
489 footer: Footer,
490 size: u64,
491 }
493
494impl WriteSummary {
495 pub fn footer(&self) -> &Footer {
497 &self.footer
498 }
499
500 pub fn size(&self) -> u64 {
502 self.size
503 }
504
505 pub fn row_count(&self) -> u64 {
507 self.footer.row_count()
508 }
509
510 pub fn compressed_column_sizes(&self) -> VortexResult<Vec<u64>> {
520 let sizes = self.footer.compressed_field_sizes()?;
521 let Some(fields) = self.footer.dtype().as_struct_fields_opt() else {
522 return Ok(vec![sizes.total()]);
523 };
524 Ok(fields
525 .names()
526 .iter()
527 .map(|name| {
528 sizes
529 .get(&FieldPath::from_name(name.clone()))
530 .unwrap_or_default()
531 })
532 .collect())
533 }
534}