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::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 self.bytes
58 .try_reserve_exact(next_len - self.bytes.len())
59 .map_err(std::io::Error::other)?;
60 if self.bytes.capacity() < next_len || self.bytes.capacity() > self.limit {
61 return Err(std::io::Error::new(
62 std::io::ErrorKind::OutOfMemory,
63 "serialized allocation exceeds its configured bound",
64 ));
65 }
66 }
67 self.bytes.extend_from_slice(buf);
68 debug_assert!(self.bytes.capacity() <= self.limit);
69 Ok(buf.len())
70 }
71
72 fn flush(&mut self) -> std::io::Result<()> {
73 Ok(())
74 }
75}
76
77pub fn serialize_batch_stream(batch: &RecordBatch) -> Result<Vec<u8>, arrow_schema::ArrowError> {
83 let mut buf = Vec::new();
84 {
85 let mut writer = StreamWriter::try_new(&mut buf, &batch.schema())?;
86 writer.write(batch)?;
87 writer.finish()?;
88 }
89 Ok(buf)
90}
91
92pub fn deserialize_batch_stream(bytes: &[u8]) -> Result<RecordBatch, arrow_schema::ArrowError> {
98 let cursor = std::io::Cursor::new(bytes);
99 let mut reader = StreamReader::try_new(cursor, None)?;
100 reader.next().ok_or_else(|| {
101 arrow_schema::ArrowError::IpcError("no record batch in IPC stream".to_string())
102 })?
103}
104
105#[cfg(test)]
107pub(crate) struct BatchStreamEncoder {
108 writer: StreamWriter<Vec<u8>>,
109}
110
111#[cfg(test)]
112impl BatchStreamEncoder {
113 pub(crate) fn new(schema: &Schema) -> Result<Self, ArrowError> {
118 Ok(Self {
119 writer: StreamWriter::try_new(Vec::new(), schema)?,
120 })
121 }
122
123 pub(crate) fn encode(&mut self, batch: &RecordBatch) -> Result<Vec<u8>, ArrowError> {
129 self.writer.write(batch)?;
130 Ok(std::mem::take(self.writer.get_mut()))
131 }
132
133 pub(crate) fn finish(&mut self) -> Result<Vec<u8>, ArrowError> {
140 self.writer.finish()?;
141 Ok(std::mem::take(self.writer.get_mut()))
142 }
143}
144
145#[derive(Debug, Default)]
148pub struct BatchStreamDecoder {
149 decoder: StreamDecoder,
150}
151
152impl BatchStreamDecoder {
153 #[must_use]
155 pub fn new() -> Self {
156 Self::default()
157 }
158
159 pub fn decode_chunk(&mut self, bytes: Vec<u8>) -> Result<Vec<RecordBatch>, ArrowError> {
166 let mut buffer = Buffer::from_vec(bytes);
167 let mut batches = Vec::new();
168 while !buffer.is_empty() {
170 if let Some(batch) = self.decoder.decode(&mut buffer)? {
171 batches.push(batch);
172 }
173 }
174 Ok(batches)
175 }
176
177 #[cfg(feature = "cluster")]
181 pub(crate) fn ensure_message_boundary(&mut self) -> Result<(), ArrowError> {
182 self.decoder.finish()
183 }
184}
185
186pub fn serialize_batches_stream_bounded<'a, I>(
193 schema: &Schema,
194 batches: I,
195 max_bytes: usize,
196) -> Result<Vec<u8>, arrow_schema::ArrowError>
197where
198 I: IntoIterator<Item = &'a RecordBatch>,
199{
200 serialize_batches_stream_bounded_with_capacity(schema, batches, max_bytes, 0)
201}
202
203fn serialize_batches_stream_bounded_with_capacity<'a, I>(
204 schema: &Schema,
205 batches: I,
206 max_bytes: usize,
207 initial_capacity: usize,
208) -> Result<Vec<u8>, arrow_schema::ArrowError>
209where
210 I: IntoIterator<Item = &'a RecordBatch>,
211{
212 let mut bounded = BoundedBytesWriter::with_capacity(max_bytes, initial_capacity);
213 {
214 let mut writer = StreamWriter::try_new(&mut bounded, schema)?;
215 for batch in batches {
216 writer.write(batch)?;
217 }
218 writer.finish()?;
219 }
220 Ok(bounded.bytes)
221}
222
223#[cfg(feature = "cluster")]
224pub(crate) fn serialize_batch_stream_bounded(
226 batch: &RecordBatch,
227 max_bytes: usize,
228 initial_capacity: usize,
229) -> Result<Vec<u8>, arrow_schema::ArrowError> {
230 serialize_batches_stream_bounded_with_capacity(
231 batch.schema().as_ref(),
232 std::iter::once(batch),
233 max_bytes,
234 initial_capacity,
235 )
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use arrow_array::Int32Array;
242 use arrow_schema::{DataType, Field};
243 use std::io::Write as _;
244 use std::sync::Arc;
245
246 fn batch(values: &[i32]) -> RecordBatch {
247 let schema = Arc::new(Schema::new(vec![Field::new("n", DataType::Int32, false)]));
248 RecordBatch::try_new(schema, vec![Arc::new(Int32Array::from(values.to_vec()))]).unwrap()
249 }
250
251 #[test]
255 fn stream_encoder_emits_schema_once() {
256 let schema = Arc::new(Schema::new(vec![Field::new("n", DataType::Int32, false)]));
257 let mut encoder = BatchStreamEncoder::new(&schema).unwrap();
258
259 let first = encoder.encode(&batch(&[1, 2, 3])).unwrap();
260 let second = encoder.encode(&batch(&[4, 5, 6])).unwrap();
261
262 assert!(first.len() > second.len());
265
266 let standalone = serialize_batch_stream(&batch(&[4, 5, 6])).unwrap();
269 assert!(second.len() < standalone.len());
270 }
271
272 #[test]
275 fn stream_encode_decode_roundtrip() {
276 let schema = Arc::new(Schema::new(vec![Field::new("n", DataType::Int32, false)]));
277 let mut encoder = BatchStreamEncoder::new(&schema).unwrap();
278 let inputs = [batch(&[1, 2]), batch(&[3, 4, 5]), batch(&[])];
281 let mut chunks: Vec<Vec<u8>> = inputs.iter().map(|b| encoder.encode(b).unwrap()).collect();
282 chunks.push(encoder.finish().unwrap());
283
284 let mut decoder = BatchStreamDecoder::new();
285 let mut out = Vec::new();
286 for chunk in chunks {
287 out.extend(decoder.decode_chunk(chunk).unwrap());
288 }
289
290 assert_eq!(out, inputs);
291 }
292
293 #[test]
294 fn bounded_writer_never_grows_past_its_limit() {
295 let mut writer = BoundedBytesWriter::with_capacity(8, 4);
296 writer.write_all(&[1, 2, 3]).unwrap();
297 writer.write_all(&[4, 5, 6, 7, 8]).unwrap();
298 assert_eq!(writer.bytes, vec![1, 2, 3, 4, 5, 6, 7, 8]);
299 assert!(writer.bytes.capacity() <= 8);
300
301 let error = writer.write_all(&[9]).unwrap_err();
302 assert_eq!(error.kind(), std::io::ErrorKind::OutOfMemory);
303 assert_eq!(writer.bytes.len(), 8);
304 assert!(writer.bytes.capacity() <= 8);
305 }
306
307 #[test]
308 fn bounded_batch_stream_round_trips_multiple_batches_and_fails_at_the_bound() {
309 let inputs = [batch(&[1, 2]), batch(&[3, 4, 5])];
310 let schema = inputs[0].schema();
311 let encoded =
312 serialize_batches_stream_bounded(schema.as_ref(), inputs.iter(), usize::MAX).unwrap();
313 let decoded = StreamReader::try_new(std::io::Cursor::new(&encoded), None)
314 .unwrap()
315 .collect::<Result<Vec<_>, _>>()
316 .unwrap();
317 assert_eq!(decoded, inputs);
318
319 let error =
320 serialize_batches_stream_bounded(schema.as_ref(), inputs.iter(), encoded.len() - 1)
321 .unwrap_err();
322 assert!(error.to_string().contains("configured bound"));
323
324 let tiny_error =
325 serialize_batches_stream_bounded(schema.as_ref(), inputs.iter(), 1).unwrap_err();
326 assert!(tiny_error.to_string().contains("configured bound"));
327 }
328}