(&mut self, batch: &RecordBatch)
| 620 | #[async_trait] |
| 621 | impl FormatFileWriter for BlobFormatWriter { |
| 622 | async fn write(&mut self, batch: &RecordBatch) -> crate::Result<()> { |
| 623 | if batch.num_rows() == 0 { |
| 624 | return Ok(()); |
| 625 | } |
| 626 | |
| 627 | let col = batch |
| 628 | .column(0) |
| 629 | .as_any() |
| 630 | .downcast_ref::<arrow_array::BinaryArray>() |
| 631 | .ok_or_else(|| Error::DataInvalid { |
| 632 | message: "BlobFormatWriter expects a single Binary column".to_string(), |
| 633 | source: None, |
| 634 | })?; |
| 635 | |
| 636 | for row_idx in 0..col.len() { |
| 637 | if col.is_null(row_idx) { |
| 638 | self.lengths.push(-1); |
| 639 | continue; |
| 640 | } |
| 641 | |
| 642 | let value = col.value(row_idx); |
| 643 | |
| 644 | if BlobDescriptor::is_blob_descriptor(value) { |
| 645 | let desc = BlobDescriptor::deserialize(value)?; |
| 646 | let payload_len = desc.length() as u64; |
| 647 | let entry_length = (payload_len + BLOB_ENTRY_OVERHEAD) as i64; |
| 648 | self.lengths.push(entry_length); |
| 649 | |
| 650 | let file_io = self.file_io.as_ref().ok_or_else(|| Error::DataInvalid { |
| 651 | message: |
| 652 | "BlobFormatWriter received a BlobDescriptor but has no FileIO to resolve it" |
| 653 | .to_string(), |
| 654 | source: None, |
| 655 | })?; |
| 656 | let input = file_io.new_input(desc.uri())?; |
| 657 | let reader = input.reader().await?; |
| 658 | |
| 659 | let mut hasher = crc32fast::Hasher::new(); |
| 660 | |
| 661 | hasher.update(&BLOB_MAGIC_NUMBER_BYTES); |
| 662 | self.writer |
| 663 | .write(Bytes::copy_from_slice(&BLOB_MAGIC_NUMBER_BYTES)) |
| 664 | .await?; |
| 665 | |
| 666 | // Stream payload in chunks to avoid loading entire blob into memory |
| 667 | let start = desc.offset() as u64; |
| 668 | let end = start + payload_len; |
| 669 | let mut pos = start; |
| 670 | while pos < end { |
| 671 | let chunk_end = (pos + BLOB_WRITE_BUFFER_SIZE).min(end); |
| 672 | let chunk = reader.read(pos..chunk_end).await?; |
| 673 | hasher.update(&chunk); |
| 674 | self.writer.write(chunk).await?; |
| 675 | pos = chunk_end; |
| 676 | } |
| 677 | |
| 678 | let entry_length_bytes = entry_length.to_le_bytes(); |
| 679 | hasher.update(&entry_length_bytes); |
no test coverage detected