laminar_core/serialization/
mod.rs1pub mod jsonb_tags;
8
9use arrow::buffer::Buffer;
10use arrow_array::RecordBatch;
11use arrow_ipc::reader::{StreamDecoder, StreamReader};
12use arrow_ipc::writer::{IpcWriteOptions, StreamWriter};
13use arrow_schema::{ArrowError, Schema};
14
15pub struct BoundedBytesWriter {
18 bytes: Vec<u8>,
19 limit: usize,
20}
21
22impl BoundedBytesWriter {
23 #[must_use]
25 pub fn new(limit: usize) -> Self {
26 Self::with_capacity(limit, 0)
27 }
28
29 fn with_capacity(limit: usize, initial_capacity: usize) -> Self {
30 Self {
31 bytes: Vec::with_capacity(initial_capacity.min(limit)),
32 limit,
33 }
34 }
35
36 #[must_use]
38 pub fn into_vec(self) -> Vec<u8> {
39 self.bytes
40 }
41}
42
43impl std::io::Write for BoundedBytesWriter {
44 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
45 let next_len = self
46 .bytes
47 .len()
48 .checked_add(buf.len())
49 .filter(|next_len| *next_len <= self.limit)
50 .ok_or_else(|| {
51 std::io::Error::new(
52 std::io::ErrorKind::OutOfMemory,
53 "serialized payload exceeds its configured bound",
54 )
55 })?;
56 if next_len > self.bytes.capacity() {
57 let target_capacity = self
58 .bytes
59 .capacity()
60 .saturating_mul(2)
61 .max(next_len)
62 .min(self.limit);
63 self.bytes
64 .try_reserve_exact(target_capacity - self.bytes.len())
65 .map_err(std::io::Error::other)?;
66 if self.bytes.capacity() < next_len || self.bytes.capacity() > self.limit {
67 return Err(std::io::Error::new(
68 std::io::ErrorKind::OutOfMemory,
69 "serialized allocation exceeds its configured bound",
70 ));
71 }
72 }
73 self.bytes.extend_from_slice(buf);
74 debug_assert!(self.bytes.capacity() <= self.limit);
75 Ok(buf.len())
76 }
77
78 fn flush(&mut self) -> std::io::Result<()> {
79 Ok(())
80 }
81}
82
83pub fn serialize_batch_stream(batch: &RecordBatch) -> Result<Vec<u8>, arrow_schema::ArrowError> {
89 let mut buf = Vec::new();
90 {
91 let mut writer = StreamWriter::try_new(&mut buf, &batch.schema())?;
92 writer.write(batch)?;
93 writer.finish()?;
94 }
95 Ok(buf)
96}
97
98pub fn deserialize_batch_stream(bytes: &[u8]) -> Result<RecordBatch, arrow_schema::ArrowError> {
104 let cursor = std::io::Cursor::new(bytes);
105 let mut reader = StreamReader::try_new(cursor, None)?;
106 reader.next().ok_or_else(|| {
107 arrow_schema::ArrowError::IpcError("no record batch in IPC stream".to_string())
108 })?
109}
110
111#[cfg(test)]
113pub(crate) struct BatchStreamEncoder {
114 writer: StreamWriter<Vec<u8>>,
115}
116
117#[cfg(test)]
118impl BatchStreamEncoder {
119 pub(crate) fn new(schema: &Schema) -> Result<Self, ArrowError> {
124 Ok(Self {
125 writer: StreamWriter::try_new(Vec::new(), schema)?,
126 })
127 }
128
129 pub(crate) fn encode(&mut self, batch: &RecordBatch) -> Result<Vec<u8>, ArrowError> {
135 self.writer.write(batch)?;
136 Ok(std::mem::take(self.writer.get_mut()))
137 }
138
139 pub(crate) fn finish(&mut self) -> Result<Vec<u8>, ArrowError> {
146 self.writer.finish()?;
147 Ok(std::mem::take(self.writer.get_mut()))
148 }
149}
150
151#[derive(Debug, Default)]
154pub struct BatchStreamDecoder {
155 decoder: StreamDecoder,
156}
157
158impl BatchStreamDecoder {
159 #[must_use]
161 pub fn new() -> Self {
162 Self::default()
163 }
164
165 pub fn decode_chunk(&mut self, bytes: Vec<u8>) -> Result<Vec<RecordBatch>, ArrowError> {
172 let mut buffer = Buffer::from_vec(bytes);
173 let mut batches = Vec::new();
174 while !buffer.is_empty() {
176 if let Some(batch) = self.decoder.decode(&mut buffer)? {
177 batches.push(batch);
178 }
179 }
180 Ok(batches)
181 }
182
183 #[cfg(feature = "cluster")]
187 pub(crate) fn ensure_message_boundary(&mut self) -> Result<(), ArrowError> {
188 self.decoder.finish()
189 }
190}
191
192pub fn serialize_batches_stream_bounded<'a, I>(
199 schema: &Schema,
200 batches: I,
201 max_bytes: usize,
202) -> Result<Vec<u8>, arrow_schema::ArrowError>
203where
204 I: IntoIterator<Item = &'a RecordBatch>,
205{
206 serialize_batches_stream_bounded_with_options(
207 schema,
208 batches,
209 max_bytes,
210 0,
211 IpcWriteOptions::default(),
212 )
213}
214
215pub fn serialize_batches_stream_lz4_bounded<'a, I>(
223 schema: &Schema,
224 batches: I,
225 max_bytes: usize,
226) -> Result<Vec<u8>, arrow_schema::ArrowError>
227where
228 I: IntoIterator<Item = &'a RecordBatch>,
229{
230 let options = IpcWriteOptions::default()
231 .try_with_compression(Some(arrow_ipc::CompressionType::LZ4_FRAME))?;
232 serialize_batches_stream_bounded_with_options(schema, batches, max_bytes, 0, options)
233}
234
235fn serialize_batches_stream_bounded_with_options<'a, I>(
236 schema: &Schema,
237 batches: I,
238 max_bytes: usize,
239 initial_capacity: usize,
240 options: IpcWriteOptions,
241) -> Result<Vec<u8>, arrow_schema::ArrowError>
242where
243 I: IntoIterator<Item = &'a RecordBatch>,
244{
245 let mut bounded = BoundedBytesWriter::with_capacity(max_bytes, initial_capacity);
246 {
247 let mut writer = StreamWriter::try_new_with_options(&mut bounded, schema, options)?;
248 for batch in batches {
249 writer.write(batch)?;
250 }
251 writer.finish()?;
252 }
253 Ok(bounded.bytes)
254}
255
256#[cfg(feature = "cluster")]
257pub(crate) fn serialize_batch_stream_bounded(
259 batch: &RecordBatch,
260 max_bytes: usize,
261 initial_capacity: usize,
262) -> Result<Vec<u8>, arrow_schema::ArrowError> {
263 serialize_batches_stream_bounded_with_options(
264 batch.schema().as_ref(),
265 std::iter::once(batch),
266 max_bytes,
267 initial_capacity,
268 IpcWriteOptions::default(),
269 )
270}
271
272#[cfg(test)]
273mod tests;