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 {
65 session: VortexSession,
66 strategy: Arc<dyn LayoutStrategy>,
67 exclude_dtype: bool,
68 max_variable_length_statistics_size: usize,
69 file_statistics: Vec<Stat>,
70}
71
72pub trait WriteOptionsSessionExt: SessionExt {
73 fn write_options(&self) -> VortexWriteOptions {
75 let session = self.session();
76 VortexWriteOptions {
77 strategy: WriteStrategyBuilder::default().build(),
78 session,
79 exclude_dtype: false,
80 file_statistics: PRUNING_STATS.to_vec(),
81 max_variable_length_statistics_size: 64,
82 }
83 }
84}
85impl<S: SessionExt> WriteOptionsSessionExt for S {}
86
87impl VortexWriteOptions {
88 pub fn new(session: VortexSession) -> Self {
90 VortexWriteOptions {
91 strategy: WriteStrategyBuilder::default().build(),
92 session,
93 exclude_dtype: false,
94 file_statistics: PRUNING_STATS.to_vec(),
95 max_variable_length_statistics_size: 64,
96 }
97 }
98
99 pub fn with_strategy(mut self, strategy: Arc<dyn LayoutStrategy>) -> Self {
101 self.strategy = strategy;
102 self
103 }
104
105 pub fn exclude_dtype(mut self) -> Self {
109 self.exclude_dtype = true;
110 self
111 }
112
113 pub fn with_file_statistics(mut self, file_statistics: Vec<Stat>) -> Self {
115 self.file_statistics = file_statistics;
116 self
117 }
118}
119
120impl VortexWriteOptions {
121 pub fn blocking<B: BlockingRuntime>(self, runtime: &B) -> BlockingWrite<'_, B> {
123 BlockingWrite {
124 options: self,
125 runtime,
126 }
127 }
128
129 pub async fn write<W: VortexWrite + Unpin, S: ArrayStream + Send + 'static>(
134 self,
135 write: W,
136 stream: S,
137 ) -> VortexResult<WriteSummary> {
138 self.write_internal(write, ArrayStreamExt::boxed(stream))
139 .await
140 }
141
142 async fn write_internal<W: VortexWrite + Unpin>(
143 self,
144 mut write: W,
145 stream: SendableArrayStream,
146 ) -> VortexResult<WriteSummary> {
147 let ctx = ArrayContext::new(ALLOWED_ENCODINGS.iter().cloned().sorted().collect())
153 .with_registry(self.session.arrays().registry().clone());
155 let dtype = stream.dtype().clone();
156
157 let (mut ptr, eof) = SequenceId::root().split();
158
159 let stream = SequentialStreamAdapter::new(
160 dtype.clone(),
161 stream
162 .try_filter(|chunk| ready(!chunk.is_empty()))
163 .map(move |result| result.map(|chunk| (ptr.advance(), chunk))),
164 )
165 .sendable();
166 let (file_stats, stream) = accumulate_stats(
167 stream,
168 self.file_statistics.clone().into(),
169 self.max_variable_length_statistics_size,
170 &self.session,
171 );
172
173 write.write_all(ByteBuffer::copy_from(MAGIC_BYTES)).await?;
175 let mut position = MAGIC_BYTES.len() as u64;
176
177 let (send, recv) = kanal::bounded_async(1);
179
180 let segments = Arc::new(BufferedSegmentSink::new(send, position));
181
182 let ctx2 = ctx.clone();
185 let session = self.session.clone();
186 let layout_fut = self.session.handle().spawn_nested(move |h| async move {
187 let session = session.with_handle(h);
188 let layout = self
189 .strategy
190 .write_stream(
191 ctx2,
192 Arc::<BufferedSegmentSink>::clone(&segments),
193 stream,
194 eof,
195 &session,
196 )
197 .await?;
198 Ok::<_, VortexError>((layout, segments.segment_specs()))
199 });
200
201 let recv_stream = recv.into_stream();
203 pin_mut!(recv_stream);
204 while let Some(buffer) = recv_stream.next().await {
205 if buffer.is_empty() {
206 continue;
207 }
208 position += buffer.len() as u64;
209 write.write_all(buffer).await?;
210 }
211
212 let (layout, segment_specs) = layout_fut.await?;
213
214 let mut footer = Footer::new(
216 Arc::clone(&layout),
217 segment_specs,
218 if self.file_statistics.is_empty() {
219 None
220 } else {
221 Some(FileStatistics::new_with_dtype(
222 file_stats.stats_sets().into(),
223 &dtype,
224 ))
225 },
226 ReadContext::new(ctx.to_ids()),
227 );
228
229 let footer_buffers = footer
231 .clone()
232 .into_serializer()
233 .with_offset(position)
234 .with_exclude_dtype(self.exclude_dtype)
235 .serialize()?;
236
237 footer = footer.with_approx_byte_size(footer_buffers.iter().map(|b| b.len()).sum());
240
241 for buffer in footer_buffers {
242 position += buffer.len() as u64;
243 write.write_all(buffer).await?;
244 }
245
246 write.flush().await?;
247
248 Ok(WriteSummary {
249 footer,
250 size: position,
251 })
252 }
253
254 pub fn writer<'w, W: VortexWrite + Unpin + 'w>(self, write: W, dtype: DType) -> Writer<'w> {
256 let (arrays_send, arrays_recv) = kanal::bounded_async(1);
258
259 let arrays =
260 ArrayStreamExt::boxed(ArrayStreamAdapter::new(dtype, arrays_recv.into_stream()));
261
262 let write = CountingVortexWrite::new(write);
263 let bytes_written = write.counter();
264 let strategy = Arc::clone(&self.strategy);
265 let future = self.write(write, arrays).boxed_local().fuse();
266
267 Writer {
268 arrays: Some(arrays_send),
269 future,
270 bytes_written,
271 strategy,
272 }
273 }
274}
275
276pub struct Writer<'w> {
278 arrays: Option<kanal::AsyncSender<VortexResult<ArrayRef>>>,
280 future: Fuse<LocalBoxFuture<'w, VortexResult<WriteSummary>>>,
282 bytes_written: Arc<AtomicU64>,
284 strategy: Arc<dyn LayoutStrategy>,
286}
287
288impl Writer<'_> {
289 pub async fn push(&mut self, chunk: ArrayRef) -> VortexResult<()> {
291 let arrays = self.arrays.clone().vortex_expect("missing arrays sender");
292 let send_fut = async move { arrays.send(Ok(chunk)).await }.fuse();
293 pin_mut!(send_fut);
294
295 select! {
298 result = send_fut => {
299 if result.is_err() {
301 return Err(self.handle_failed_task().await);
302 }
303 },
304 result = &mut self.future => {
305 match result {
309 Ok(_) => vortex_bail!("Internal error: writer future completed early"),
310 Err(e) => return Err(e),
311 }
312 }
313 }
314
315 Ok(())
316 }
317
318 pub async fn push_stream(&mut self, mut stream: SendableArrayStream) -> VortexResult<()> {
323 let arrays = self.arrays.clone().vortex_expect("missing arrays sender");
324 let stream_fut = async move {
325 while let Some(chunk) = stream.next().await {
326 arrays.send(chunk).await?;
327 }
328 Ok::<_, kanal::SendError>(())
329 }
330 .fuse();
331 pin_mut!(stream_fut);
332
333 select! {
336 result = stream_fut => {
337 if let Err(_send_err) = result {
338 return Err(self.handle_failed_task().await);
340 }
341 }
342
343 result = &mut self.future => {
344 match result {
348 Ok(_) => vortex_bail!("Internal error: writer future completed early"),
349 Err(e) => return Err(e),
350 }
351 }
352 }
353
354 Ok(())
355 }
356
357 pub fn bytes_written(&self) -> u64 {
359 self.bytes_written.load(Ordering::Relaxed)
360 }
361
362 pub fn buffered_bytes(&self) -> u64 {
364 self.strategy.buffered_bytes()
365 }
366
367 pub async fn finish(mut self) -> VortexResult<WriteSummary> {
370 drop(self.arrays.take());
372
373 self.future.await
375 }
376
377 async fn handle_failed_task(&mut self) -> VortexError {
379 match (&mut self.future).await {
380 Ok(_) => vortex_err!(
381 "Internal error: writer task completed successfully but write future finished early"
382 ),
383 Err(e) => e,
384 }
385 }
386}
387
388pub struct BlockingWrite<'rt, B: BlockingRuntime> {
390 options: VortexWriteOptions,
391 runtime: &'rt B,
392}
393
394impl<'rt, B: BlockingRuntime> BlockingWrite<'rt, B> {
395 pub fn write<W: Write + Unpin>(
397 self,
398 write: W,
399 iter: impl ArrayIterator + Send + 'static,
400 ) -> VortexResult<WriteSummary> {
401 self.runtime.block_on(async move {
402 self.options
403 .write(BlockingWriteAdapter(write), iter.into_array_stream())
404 .await
405 })
406 }
407
408 pub fn writer<'w, W: Write + Unpin + 'w>(
409 self,
410 write: W,
411 dtype: DType,
412 ) -> BlockingWriter<'rt, 'w, B> {
413 BlockingWriter {
414 writer: self.options.writer(BlockingWriteAdapter(write), dtype),
415 runtime: self.runtime,
416 }
417 }
418}
419
420pub struct BlockingWriter<'rt, 'w, B: BlockingRuntime> {
422 runtime: &'rt B,
423 writer: Writer<'w>,
424}
425
426impl<B: BlockingRuntime> BlockingWriter<'_, '_, B> {
427 pub fn push(&mut self, chunk: ArrayRef) -> VortexResult<()> {
428 self.runtime.block_on(self.writer.push(chunk))
429 }
430
431 pub fn bytes_written(&self) -> u64 {
432 self.writer.bytes_written()
433 }
434
435 pub fn buffered_bytes(&self) -> u64 {
436 self.writer.buffered_bytes()
437 }
438
439 pub fn finish(self) -> VortexResult<WriteSummary> {
440 self.runtime.block_on(self.writer.finish())
441 }
442}
443
444struct BlockingWriteAdapter<W>(W);
446
447impl<W: Write + Unpin> VortexWrite for BlockingWriteAdapter<W> {
448 async fn write_all<B: IoBuf>(&mut self, buffer: B) -> io::Result<B> {
449 self.0.write_all(buffer.as_slice())?;
450 Ok(buffer)
451 }
452
453 fn flush(&mut self) -> impl Future<Output = io::Result<()>> {
454 ready(self.0.flush())
455 }
456
457 fn shutdown(&mut self) -> impl Future<Output = io::Result<()>> {
458 ready(Ok(()))
459 }
460}
461
462pub struct WriteSummary {
463 footer: Footer,
464 size: u64,
465 }
467
468impl WriteSummary {
469 pub fn footer(&self) -> &Footer {
471 &self.footer
472 }
473
474 pub fn size(&self) -> u64 {
476 self.size
477 }
478
479 pub fn row_count(&self) -> u64 {
481 self.footer.row_count()
482 }
483}