1use std::ops::Range;
3
4use arrow_array::ArrayRef;
5use bytes::Bytes;
6use futures::stream::BoxStream;
7
8use crate::scandal_backend::AttemptWrite;
9use crate::{Error, Table};
10
11pub type BlobStream = BoxStream<'static, Result<Bytes, Error>>;
13
14pub use crate::scandal::blob::{Blob, BlobPart, BlobStat, BlobUpload};
15
16pub struct BlobPack<'a> {
19 write: &'a AttemptWrite,
20 payloads: Vec<(Bytes, Option<String>)>,
21}
22
23impl<'a> BlobPack<'a> {
24 pub(crate) fn new(write: &'a AttemptWrite) -> Self {
25 Self {
26 write,
27 payloads: Vec::new(),
28 }
29 }
30
31 pub fn push(&mut self, payload: impl Into<Bytes>, media_type: Option<&str>) -> usize {
34 self.payloads
35 .push((payload.into(), media_type.map(str::to_owned)));
36 self.payloads.len() - 1
37 }
38
39 pub async fn finish(self) -> Result<ArrayRef, Error> {
42 self.write.pack_blobs(self.payloads).await
43 }
44}
45
46impl Table {
47 pub async fn write_blob(
53 &self,
54 media_type: &str,
55 max_bytes: u64,
56 input: BlobStream,
57 ) -> Result<Blob, Error> {
58 self.connection
59 .tables()?
60 .write_blob(self.identity(), media_type, max_bytes, input)
61 .await
62 }
63
64 pub async fn stat_blob(&self, blob: &Blob) -> Result<BlobStat, Error> {
66 if blob.table_id != self.identity().resource_id {
67 return Err(Error::InvalidConfiguration(
68 "blob belongs to a different Table".into(),
69 ));
70 }
71 self.connection
72 .tables()?
73 .stat_blob(self.identity(), blob)
74 .await
75 }
76
77 pub async fn read_blob(
80 &self,
81 blob: &Blob,
82 range: Option<Range<u64>>,
83 ) -> Result<BlobStream, Error> {
84 if blob.table_id != self.identity().resource_id {
85 return Err(Error::InvalidConfiguration(
86 "blob belongs to a different Table".into(),
87 ));
88 }
89 if range
90 .as_ref()
91 .is_some_and(|r| r.start >= r.end || r.end > blob.size.0)
92 {
93 return Err(Error::InvalidConfiguration(
94 "blob range is outside its payload".into(),
95 ));
96 }
97 self.connection
98 .tables()?
99 .read_blob(self.identity(), blob, range)
100 .await
101 }
102}