|
1 | 1 | // SPDX-License-Identifier: Apache-2.0 |
2 | 2 | // SPDX-FileCopyrightText: Copyright the Vortex contributors |
3 | 3 |
|
| 4 | +use std::collections::BTreeMap; |
4 | 5 | use std::io; |
5 | 6 | use std::io::Write; |
| 7 | +use std::pin::Pin; |
6 | 8 | use std::sync::Arc; |
7 | 9 | use std::sync::atomic::AtomicU64; |
8 | 10 | use std::sync::atomic::Ordering; |
| 11 | +use std::task::Context; |
| 12 | +use std::task::Poll; |
9 | 13 |
|
10 | 14 | use futures::FutureExt; |
| 15 | +use futures::Stream; |
11 | 16 | use futures::StreamExt; |
12 | 17 | use futures::TryStreamExt; |
13 | 18 | use futures::future::Fuse; |
@@ -153,6 +158,25 @@ impl VortexWriteOptions { |
153 | 158 | .await |
154 | 159 | } |
155 | 160 |
|
| 161 | + /// Write a stream of pairs (index, chunk) to a Vortex file, sorting |
| 162 | + /// chunks by index before write. |
| 163 | + /// "index" must be a contiguous gap-free number starting at 0. |
| 164 | + /// |
| 165 | + /// Errors on duplicate or already written indices. |
| 166 | + pub async fn write_ordered<W, S>( |
| 167 | + self, |
| 168 | + write: W, |
| 169 | + dtype: DType, |
| 170 | + stream: S, |
| 171 | + ) -> VortexResult<WriteSummary> |
| 172 | + where |
| 173 | + W: VortexWrite + Unpin, |
| 174 | + S: Stream<Item = VortexResult<(u64, ArrayRef)>> + Unpin + Send + 'static, |
| 175 | + { |
| 176 | + self.write(write, OrderedArrayStream::new(dtype, stream)) |
| 177 | + .await |
| 178 | + } |
| 179 | + |
156 | 180 | async fn write_internal<W: VortexWrite + Unpin>( |
157 | 181 | self, |
158 | 182 | mut write: W, |
@@ -290,6 +314,69 @@ impl VortexWriteOptions { |
290 | 314 | } |
291 | 315 | } |
292 | 316 |
|
| 317 | +/// Reorder a stream of pairs (index, chunk) into strictly increasing "index". |
| 318 | +/// "index" must be contiguous and gap-free starting from 0. |
| 319 | +struct OrderedArrayStream<S> { |
| 320 | + dtype: DType, |
| 321 | + next: u64, |
| 322 | + pending: BTreeMap<u64, ArrayRef>, |
| 323 | + inner: S, |
| 324 | + done: bool, |
| 325 | +} |
| 326 | + |
| 327 | +impl<S> OrderedArrayStream<S> { |
| 328 | + fn new(dtype: DType, inner: S) -> Self { |
| 329 | + Self { |
| 330 | + dtype, |
| 331 | + next: 0, |
| 332 | + pending: BTreeMap::new(), |
| 333 | + inner, |
| 334 | + done: false, |
| 335 | + } |
| 336 | + } |
| 337 | +} |
| 338 | + |
| 339 | +impl<S> ArrayStream for OrderedArrayStream<S> |
| 340 | +where |
| 341 | + S: Stream<Item = VortexResult<(u64, ArrayRef)>> + Unpin, |
| 342 | +{ |
| 343 | + fn dtype(&self) -> &DType { |
| 344 | + &self.dtype |
| 345 | + } |
| 346 | +} |
| 347 | + |
| 348 | +impl<S> Stream for OrderedArrayStream<S> |
| 349 | +where |
| 350 | + S: Stream<Item = VortexResult<(u64, ArrayRef)>> + Unpin, |
| 351 | +{ |
| 352 | + type Item = VortexResult<ArrayRef>; |
| 353 | + |
| 354 | + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { |
| 355 | + let this = self.get_mut(); |
| 356 | + loop { |
| 357 | + if let Some(array) = this.pending.remove(&this.next) { |
| 358 | + this.next += 1; |
| 359 | + return Poll::Ready(Some(Ok(array))); |
| 360 | + } |
| 361 | + if this.done { |
| 362 | + return Poll::Ready(None); |
| 363 | + } |
| 364 | + match futures::ready!(Pin::new(&mut this.inner).poll_next(cx)) { |
| 365 | + Some(Ok((index, array))) => { |
| 366 | + if index < this.next || this.pending.contains_key(&index) { |
| 367 | + return Poll::Ready(Some(Err(vortex_err!( |
| 368 | + "Duplicate or already present index {index}" |
| 369 | + )))); |
| 370 | + } |
| 371 | + this.pending.insert(index, array); |
| 372 | + } |
| 373 | + Some(Err(e)) => return Poll::Ready(Some(Err(e))), |
| 374 | + None => this.done = true, |
| 375 | + } |
| 376 | + } |
| 377 | + } |
| 378 | +} |
| 379 | + |
293 | 380 | /// An async API for writing Vortex files. |
294 | 381 | pub struct Writer<'w> { |
295 | 382 | // The input channel for sending arrays to the writer. |
|
0 commit comments