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::expr::stats::Stat;
23use vortex_array::iter::ArrayIterator;
24use vortex_array::iter::ArrayIteratorExt;
25use vortex_array::session::ArraySessionExt;
26use vortex_array::stats::PRUNING_STATS;
27use vortex_array::stream::ArrayStream;
28use vortex_array::stream::ArrayStreamAdapter;
29use vortex_array::stream::ArrayStreamExt;
30use vortex_array::stream::SendableArrayStream;
31use vortex_buffer::ByteBuffer;
32use vortex_error::VortexError;
33use vortex_error::VortexExpect;
34use vortex_error::VortexResult;
35use vortex_error::vortex_bail;
36use vortex_error::vortex_err;
37use vortex_io::IoBuf;
38use vortex_io::VortexWrite;
39use vortex_io::kanal_ext::KanalExt;
40use vortex_io::runtime::BlockingRuntime;
41use vortex_io::session::RuntimeSessionExt;
42use vortex_layout::LayoutStrategy;
43use vortex_layout::layouts::file_stats::accumulate_stats;
44use vortex_layout::sequence::SequenceId;
45use vortex_layout::sequence::SequentialStreamAdapter;
46use vortex_layout::sequence::SequentialStreamExt;
47use vortex_session::SessionExt;
48use vortex_session::VortexSession;
49use vortex_session::registry::ReadContext;
50
51use crate::ALLOWED_ENCODINGS;
52use crate::Footer;
53use crate::MAGIC_BYTES;
54use crate::WriteStrategyBuilder;
55use crate::counting::CountingVortexWrite;
56use crate::footer::FileStatistics;
57use crate::segments::writer::BufferedSegmentSink;
58
59pub struct VortexWriteOptions {
68 session: VortexSession,
69 strategy: Arc<dyn LayoutStrategy>,
70 exclude_dtype: bool,
71 max_variable_length_statistics_size: usize,
72 file_statistics: Vec<Stat>,
73}
74
75pub trait WriteOptionsSessionExt: SessionExt {
77 fn write_options(&self) -> VortexWriteOptions {
79 let session = self.session();
80 VortexWriteOptions {
81 strategy: WriteStrategyBuilder::default().build(),
82 session,
83 exclude_dtype: false,
84 file_statistics: PRUNING_STATS.to_vec(),
85 max_variable_length_statistics_size: 64,
86 }
87 }
88}
89impl<S: SessionExt> WriteOptionsSessionExt for S {}
90
91impl VortexWriteOptions {
92 pub fn new(session: VortexSession) -> Self {
94 VortexWriteOptions {
95 strategy: WriteStrategyBuilder::default().build(),
96 session,
97 exclude_dtype: false,
98 file_statistics: PRUNING_STATS.to_vec(),
99 max_variable_length_statistics_size: 64,
100 }
101 }
102
103 pub fn with_strategy(mut self, strategy: Arc<dyn LayoutStrategy>) -> Self {
109 self.strategy = strategy;
110 self
111 }
112
113 pub fn exclude_dtype(mut self) -> Self {
117 self.exclude_dtype = true;
118 self
119 }
120
121 pub fn with_file_statistics(mut self, file_statistics: Vec<Stat>) -> Self {
125 self.file_statistics = file_statistics;
126 self
127 }
128}
129
130impl VortexWriteOptions {
131 pub fn blocking<B: BlockingRuntime>(self, runtime: &B) -> BlockingWrite<'_, B> {
136 BlockingWrite {
137 options: self,
138 runtime,
139 }
140 }
141
142 pub async fn write<W: VortexWrite + Unpin, S: ArrayStream + Send + 'static>(
147 self,
148 write: W,
149 stream: S,
150 ) -> VortexResult<WriteSummary> {
151 self.write_internal(write, ArrayStreamExt::boxed(stream))
152 .await
153 }
154
155 async fn write_internal<W: VortexWrite + Unpin>(
156 self,
157 mut write: W,
158 stream: SendableArrayStream,
159 ) -> VortexResult<WriteSummary> {
160 let ctx = ArrayContext::new(ALLOWED_ENCODINGS.iter().cloned().sorted().collect())
166 .with_registry(self.session.arrays().registry().clone());
168 let dtype = stream.dtype().clone();
169
170 let (mut ptr, eof) = SequenceId::root().split();
171
172 let stream = SequentialStreamAdapter::new(
173 dtype.clone(),
174 stream
175 .try_filter(|chunk| ready(!chunk.is_empty()))
176 .map(move |result| result.map(|chunk| (ptr.advance(), chunk))),
177 )
178 .sendable();
179 let (file_stats, stream) = accumulate_stats(
180 stream,
181 self.file_statistics.clone().into(),
182 self.max_variable_length_statistics_size,
183 &self.session,
184 );
185
186 write.write_all(ByteBuffer::copy_from(MAGIC_BYTES)).await?;
188 let mut position = MAGIC_BYTES.len() as u64;
189
190 let (send, recv) = kanal::bounded_async(1);
192
193 let segments = Arc::new(BufferedSegmentSink::new(send, position));
194
195 let ctx2 = ctx.clone();
198 let session = self.session.clone();
199 let layout_fut = self.session.handle().spawn_nested(move |h| async move {
200 let session = session.with_handle(h);
201 let layout = self
202 .strategy
203 .write_stream(
204 ctx2,
205 Arc::<BufferedSegmentSink>::clone(&segments),
206 stream,
207 eof,
208 &session,
209 )
210 .await?;
211 Ok::<_, VortexError>((layout, segments.segment_specs()))
212 });
213
214 let recv_stream = recv.into_stream();
216 pin_mut!(recv_stream);
217 while let Some(buffer) = recv_stream.next().await {
218 if buffer.is_empty() {
219 continue;
220 }
221 position += buffer.len() as u64;
222 write.write_all(buffer).await?;
223 }
224
225 let (layout, segment_specs) = layout_fut.await?;
226
227 let mut footer = Footer::new(
229 Arc::clone(&layout),
230 segment_specs,
231 if self.file_statistics.is_empty() {
232 None
233 } else {
234 Some(FileStatistics::new_with_dtype(
235 file_stats.stats_sets().into(),
236 &dtype,
237 ))
238 },
239 ReadContext::new(ctx.to_ids()),
240 );
241
242 let footer_buffers = footer
244 .clone()
245 .into_serializer()
246 .with_offset(position)
247 .with_exclude_dtype(self.exclude_dtype)
248 .serialize()?;
249
250 footer = footer.with_approx_byte_size(footer_buffers.iter().map(|b| b.len()).sum());
253
254 for buffer in footer_buffers {
255 position += buffer.len() as u64;
256 write.write_all(buffer).await?;
257 }
258
259 write.flush().await?;
260
261 Ok(WriteSummary {
262 footer,
263 size: position,
264 })
265 }
266
267 pub fn writer<'w, W: VortexWrite + Unpin + 'w>(self, write: W, dtype: DType) -> Writer<'w> {
272 let (arrays_send, arrays_recv) = kanal::bounded_async(1);
274
275 let arrays =
276 ArrayStreamExt::boxed(ArrayStreamAdapter::new(dtype, arrays_recv.into_stream()));
277
278 let write = CountingVortexWrite::new(write);
279 let bytes_written = write.counter();
280 let strategy = Arc::clone(&self.strategy);
281 let future = self.write(write, arrays).boxed_local().fuse();
282
283 Writer {
284 arrays: Some(arrays_send),
285 future,
286 bytes_written,
287 strategy,
288 }
289 }
290}
291
292pub struct Writer<'w> {
294 arrays: Option<kanal::AsyncSender<VortexResult<ArrayRef>>>,
296 future: Fuse<LocalBoxFuture<'w, VortexResult<WriteSummary>>>,
298 bytes_written: Arc<AtomicU64>,
300 strategy: Arc<dyn LayoutStrategy>,
302}
303
304impl Writer<'_> {
305 pub async fn push(&mut self, chunk: ArrayRef) -> VortexResult<()> {
307 let arrays = self.arrays.clone().vortex_expect("missing arrays sender");
308 let send_fut = async move { arrays.send(Ok(chunk)).await }.fuse();
309 pin_mut!(send_fut);
310
311 select! {
314 result = send_fut => {
315 if result.is_err() {
317 return Err(self.handle_failed_task().await);
318 }
319 },
320 result = &mut self.future => {
321 match result {
325 Ok(_) => vortex_bail!("Internal error: writer future completed early"),
326 Err(e) => return Err(e),
327 }
328 }
329 }
330
331 Ok(())
332 }
333
334 pub async fn push_stream(&mut self, mut stream: SendableArrayStream) -> VortexResult<()> {
339 let arrays = self.arrays.clone().vortex_expect("missing arrays sender");
340 let stream_fut = async move {
341 while let Some(chunk) = stream.next().await {
342 arrays.send(chunk).await?;
343 }
344 Ok::<_, kanal::SendError>(())
345 }
346 .fuse();
347 pin_mut!(stream_fut);
348
349 select! {
352 result = stream_fut => {
353 if let Err(_send_err) = result {
354 return Err(self.handle_failed_task().await);
356 }
357 }
358
359 result = &mut self.future => {
360 match result {
364 Ok(_) => vortex_bail!("Internal error: writer future completed early"),
365 Err(e) => return Err(e),
366 }
367 }
368 }
369
370 Ok(())
371 }
372
373 pub fn bytes_written(&self) -> u64 {
375 self.bytes_written.load(Ordering::Relaxed)
376 }
377
378 pub fn buffered_bytes(&self) -> u64 {
380 self.strategy.buffered_bytes()
381 }
382
383 pub async fn finish(mut self) -> VortexResult<WriteSummary> {
386 drop(self.arrays.take());
388
389 self.future.await
391 }
392
393 async fn handle_failed_task(&mut self) -> VortexError {
395 match (&mut self.future).await {
396 Ok(_) => vortex_err!(
397 "Internal error: writer task completed successfully but write future finished early"
398 ),
399 Err(e) => e,
400 }
401 }
402}
403
404pub struct BlockingWrite<'rt, B: BlockingRuntime> {
406 options: VortexWriteOptions,
407 runtime: &'rt B,
408}
409
410impl<'rt, B: BlockingRuntime> BlockingWrite<'rt, B> {
411 pub fn write<W: Write + Unpin>(
416 self,
417 write: W,
418 iter: impl ArrayIterator + Send + 'static,
419 ) -> VortexResult<WriteSummary> {
420 self.runtime.block_on(async move {
421 self.options
422 .write(BlockingWriteAdapter(write), iter.into_array_stream())
423 .await
424 })
425 }
426
427 pub fn writer<'w, W: Write + Unpin + 'w>(
429 self,
430 write: W,
431 dtype: DType,
432 ) -> BlockingWriter<'rt, 'w, B> {
433 BlockingWriter {
434 writer: self.options.writer(BlockingWriteAdapter(write), dtype),
435 runtime: self.runtime,
436 }
437 }
438}
439
440pub struct BlockingWriter<'rt, 'w, B: BlockingRuntime> {
442 runtime: &'rt B,
443 writer: Writer<'w>,
444}
445
446impl<B: BlockingRuntime> BlockingWriter<'_, '_, B> {
447 pub fn push(&mut self, chunk: ArrayRef) -> VortexResult<()> {
449 self.runtime.block_on(self.writer.push(chunk))
450 }
451
452 pub fn bytes_written(&self) -> u64 {
454 self.writer.bytes_written()
455 }
456
457 pub fn buffered_bytes(&self) -> u64 {
459 self.writer.buffered_bytes()
460 }
461
462 pub fn finish(self) -> VortexResult<WriteSummary> {
464 self.runtime.block_on(self.writer.finish())
465 }
466}
467
468struct BlockingWriteAdapter<W>(W);
470
471impl<W: Write + Unpin> VortexWrite for BlockingWriteAdapter<W> {
472 async fn write_all<B: IoBuf>(&mut self, buffer: B) -> io::Result<B> {
473 self.0.write_all(buffer.as_slice())?;
474 Ok(buffer)
475 }
476
477 fn flush(&mut self) -> impl Future<Output = io::Result<()>> {
478 ready(self.0.flush())
479 }
480
481 fn shutdown(&mut self) -> impl Future<Output = io::Result<()>> {
482 ready(Ok(()))
483 }
484}
485
486pub struct WriteSummary {
488 footer: Footer,
489 size: u64,
490 }
492
493impl WriteSummary {
494 pub fn footer(&self) -> &Footer {
496 &self.footer
497 }
498
499 pub fn size(&self) -> u64 {
501 self.size
502 }
503
504 pub fn row_count(&self) -> u64 {
506 self.footer.row_count()
507 }
508}