1use std::borrow::Cow;
5use std::fmt::Debug;
6use std::fmt::Formatter;
7use std::iter;
8use std::sync::Arc;
9
10use flatbuffers::FlatBufferBuilder;
11use flatbuffers::Follow;
12use flatbuffers::WIPOffset;
13use flatbuffers::root;
14use vortex_buffer::Alignment;
15use vortex_buffer::ByteBuffer;
16use vortex_error::VortexError;
17use vortex_error::VortexExpect;
18use vortex_error::VortexResult;
19use vortex_error::vortex_bail;
20use vortex_error::vortex_err;
21use vortex_error::vortex_panic;
22use vortex_flatbuffers::FlatBuffer;
23use vortex_flatbuffers::WriteFlatBuffer;
24use vortex_flatbuffers::array as fba;
25use vortex_flatbuffers::array::Compression;
26use vortex_session::VortexSession;
27use vortex_session::registry::ReadContext;
28use vortex_utils::aliases::hash_map::HashMap;
29
30use crate::ArrayContext;
31use crate::ArrayRef;
32use crate::ArraySlots;
33use crate::array::ArrayDeserialization;
34use crate::array::ArrayId;
35use crate::array::new_foreign_array;
36use crate::buffer::BufferHandle;
37use crate::dtype::DType;
38use crate::dtype::TryFromBytes;
39use crate::session::ArraySessionExt;
40use crate::stats::StatsSet;
41
42#[derive(Default, Debug)]
44pub struct SerializeOptions {
45 pub offset: usize,
48 pub include_padding: bool,
50}
51
52impl ArrayRef {
53 pub fn serialize(
64 &self,
65 ctx: &ArrayContext,
66 session: &VortexSession,
67 options: &SerializeOptions,
68 ) -> VortexResult<Vec<ByteBuffer>> {
69 let root = ArrayNodeFlatBuffer::try_new(ctx, session, self)?;
72 let array_buffers = root.array.buffers();
73
74 let mut buffers = vec![];
76 let mut fb_buffers = Vec::with_capacity(buffers.capacity());
77
78 let max_alignment = array_buffers
80 .iter()
81 .map(|buf| buf.alignment())
82 .chain(iter::once(FlatBuffer::alignment()))
83 .max()
84 .unwrap_or_else(FlatBuffer::alignment);
85
86 let zeros = ByteBuffer::zeroed(max_alignment.as_usize());
88
89 buffers.push(ByteBuffer::zeroed_aligned(0, max_alignment));
92
93 let mut pos = options.offset;
95
96 for buffer in array_buffers {
98 let padding = if options.include_padding {
99 let padding = pos.next_multiple_of(buffer.alignment().as_usize()) - pos;
100 if padding > 0 {
101 pos += padding;
102 buffers.push(zeros.slice(0..padding));
103 }
104 padding
105 } else {
106 0
107 };
108
109 fb_buffers.push(fba::Buffer::new(
110 u16::try_from(padding).vortex_expect("padding fits into u16"),
111 buffer.alignment().exponent(),
112 Compression::None,
113 u32::try_from(buffer.len())
114 .map_err(|_| vortex_err!("All buffers must fit into u32 for serialization"))?,
115 ));
116
117 pos += buffer.len();
118 buffers.push(buffer.aligned(Alignment::none()));
119 }
120
121 let mut fbb = FlatBufferBuilder::new();
123
124 let fb_root = root.try_write_flatbuffer(&mut fbb)?;
125
126 let fb_buffers = fbb.create_vector(&fb_buffers);
127 let fb_array = fba::Array::create(
128 &mut fbb,
129 &fba::ArrayArgs {
130 root: Some(fb_root),
131 buffers: Some(fb_buffers),
132 },
133 );
134 fbb.finish_minimal(fb_array);
135 let (fb_vec, fb_start) = fbb.collapse();
136 let fb_end = fb_vec.len();
137 let fb_buffer = ByteBuffer::from(fb_vec).slice(fb_start..fb_end);
138 let fb_length = fb_buffer.len();
139
140 if options.include_padding {
141 let padding = pos.next_multiple_of(FlatBuffer::alignment().as_usize()) - pos;
142 if padding > 0 {
143 buffers.push(zeros.slice(0..padding));
144 }
145 }
146 buffers.push(fb_buffer);
147
148 buffers.push(ByteBuffer::from(
150 u32::try_from(fb_length)
151 .map_err(|_| vortex_err!("Array metadata flatbuffer must fit into u32 for serialization. Array encoding tree is too large."))?
152 .to_le_bytes()
153 .to_vec(),
154 ));
155
156 Ok(buffers)
157 }
158}
159
160#[derive(Clone, Debug)]
161struct ArraySerializationTree {
162 source: ArrayRef,
163 serialized_id: ArrayId,
164 metadata: Vec<u8>,
165 buffers: Vec<ByteBuffer>,
166 children: Vec<ArraySerializationTree>,
167}
168
169impl ArraySerializationTree {
170 fn try_new(session: &VortexSession, source: &ArrayRef) -> VortexResult<Self> {
171 let Some(serialization) = session.array_serialize(source)? else {
172 vortex_bail!(
173 "Array {} does not support serialization",
174 source.encoding_id()
175 );
176 };
177 let children = serialization
178 .children
179 .iter()
180 .map(|child| Self::try_new(session, child))
181 .collect::<VortexResult<Vec<_>>>()?;
182
183 Ok(Self {
184 source: source.clone(),
185 serialized_id: serialization.serialized_id,
186 metadata: serialization.metadata,
187 buffers: serialization.buffers,
188 children,
189 })
190 }
191
192 fn nbuffers_recursive(&self) -> usize {
193 self.buffers.len()
194 + self
195 .children
196 .iter()
197 .map(Self::nbuffers_recursive)
198 .sum::<usize>()
199 }
200
201 fn buffers(&self) -> Vec<ByteBuffer> {
202 let mut buffers = Vec::with_capacity(self.nbuffers_recursive());
203 self.append_buffers(&mut buffers);
204 buffers
205 }
206
207 fn append_buffers(&self, buffers: &mut Vec<ByteBuffer>) {
208 buffers.extend(self.buffers.iter().cloned());
209 for child in &self.children {
210 child.append_buffers(buffers);
211 }
212 }
213}
214
215pub struct ArrayNodeFlatBuffer<'a> {
217 ctx: &'a ArrayContext,
218 array: ArraySerializationTree,
219}
220
221impl<'a> ArrayNodeFlatBuffer<'a> {
222 pub fn try_new(
223 ctx: &'a ArrayContext,
224 session: &'a VortexSession,
225 array: &ArrayRef,
226 ) -> VortexResult<Self> {
227 let array = ArraySerializationTree::try_new(session, array)?;
228 let n_buffers_recursive = array.nbuffers_recursive();
229 if n_buffers_recursive > u16::MAX as usize {
230 vortex_bail!(
231 "Array and all descendent arrays can have at most u16::MAX buffers: {}",
232 n_buffers_recursive
233 );
234 };
235 Ok(Self { ctx, array })
236 }
237
238 pub fn try_write_flatbuffer<'fb>(
239 &self,
240 fbb: &mut FlatBufferBuilder<'fb>,
241 ) -> VortexResult<WIPOffset<fba::ArrayNode<'fb>>> {
242 self.try_write_node(fbb, &self.array, 0)
243 }
244
245 fn try_write_node<'fb>(
246 &self,
247 fbb: &mut FlatBufferBuilder<'fb>,
248 array: &ArraySerializationTree,
249 buffer_idx: u16,
250 ) -> VortexResult<WIPOffset<fba::ArrayNode<'fb>>> {
251 let encoding_idx = self.ctx.intern(&array.serialized_id).ok_or_else(|| {
252 vortex_err!(
253 "Serialized array ID {} not permitted by ctx",
254 array.serialized_id
255 )
256 })?;
257
258 let metadata = Some(fbb.create_vector(array.metadata.as_slice()));
259
260 let nbuffers = u16::try_from(array.buffers.len())
262 .map_err(|_| vortex_err!("Array can have at most u16::MAX buffers"))?;
263 let mut child_buffer_idx = buffer_idx + nbuffers;
264
265 let children = array
266 .children
267 .iter()
268 .map(|child| {
269 let msg = self.try_write_node(fbb, child, child_buffer_idx)?;
271
272 child_buffer_idx = u16::try_from(child.nbuffers_recursive())
273 .ok()
274 .and_then(|nbuffers| nbuffers.checked_add(child_buffer_idx))
275 .ok_or_else(|| vortex_err!("Too many buffers (u16) for Array"))?;
276
277 Ok(msg)
278 })
279 .collect::<VortexResult<Vec<_>>>()?;
280 let children = Some(fbb.create_vector(&children));
281
282 let buffers = Some(fbb.create_vector_from_iter((0..nbuffers).map(|i| i + buffer_idx)));
283 let stats = Some(array.source.statistics().write_flatbuffer(fbb)?);
284
285 Ok(fba::ArrayNode::create(
286 fbb,
287 &fba::ArrayNodeArgs {
288 encoding: encoding_idx,
289 metadata,
290 children,
291 buffers,
292 stats,
293 },
294 ))
295 }
296}
297
298pub trait ArrayChildren {
301 fn get(&self, index: usize, dtype: &DType, len: usize) -> VortexResult<ArrayRef>;
303
304 fn len(&self) -> usize;
306
307 fn is_empty(&self) -> bool {
309 self.len() == 0
310 }
311}
312
313impl<T: AsRef<[ArrayRef]>> ArrayChildren for T {
314 fn get(&self, index: usize, dtype: &DType, len: usize) -> VortexResult<ArrayRef> {
315 let array = self.as_ref()[index].clone();
316 assert_eq!(array.len(), len);
317 assert_eq!(array.dtype(), dtype);
318 Ok(array)
319 }
320
321 fn len(&self) -> usize {
322 self.as_ref().len()
323 }
324}
325
326#[derive(Clone)]
333pub struct SerializedArray {
334 flatbuffer: FlatBuffer,
336 flatbuffer_loc: usize,
338 buffers: Arc<[BufferHandle]>,
339}
340
341impl Debug for SerializedArray {
342 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
343 f.debug_struct("SerializedArray")
344 .field("encoding_id", &self.encoding_id())
345 .field("children", &(0..self.nchildren()).map(|i| self.child(i)))
346 .field(
347 "buffers",
348 &(0..self.nbuffers()).map(|i| self.buffer(i).ok()),
349 )
350 .field("metadata", &self.metadata())
351 .finish()
352 }
353}
354
355impl SerializedArray {
356 pub fn decode(
358 &self,
359 dtype: &DType,
360 len: usize,
361 ctx: &ReadContext,
362 session: &VortexSession,
363 ) -> VortexResult<ArrayRef> {
364 let encoding_idx = self.flatbuffer().encoding();
365 let encoding_id = ctx
366 .resolve(encoding_idx)
367 .ok_or_else(|| vortex_err!("Unknown encoding index: {}", encoding_idx))?;
368 let Some(plugin) = session.arrays().registry().get(&encoding_id) else {
369 if session.allows_unknown() {
370 return self.decode_foreign(encoding_id, dtype, len, ctx);
371 }
372 vortex_bail!("Unknown encoding: {}", encoding_id);
373 };
374
375 let children = SerializedArrayChildren {
376 ser: self,
377 ctx,
378 session,
379 };
380
381 let buffers = self.collect_buffers()?;
382
383 let decoded = plugin.deserialize(
384 ArrayDeserialization::new(
385 encoding_id,
386 dtype,
387 len,
388 self.metadata(),
389 &buffers,
390 &children,
391 ),
392 session,
393 )?;
394
395 assert_eq!(
396 decoded.len(),
397 len,
398 "Array decoded from {} has incorrect length {}, expected {}",
399 encoding_id,
400 decoded.len(),
401 len
402 );
403 assert_eq!(
404 decoded.dtype(),
405 dtype,
406 "Array decoded from {} has incorrect dtype {}, expected {}",
407 encoding_id,
408 decoded.dtype(),
409 dtype,
410 );
411
412 assert!(
413 plugin.is_supported_encoding(&decoded.encoding_id()),
414 "Array decoded from {} has incorrect encoding {}",
415 encoding_id,
416 decoded.encoding_id(),
417 );
418
419 if let Some(stats) = self.flatbuffer().stats() {
421 decoded
422 .statistics()
423 .set_iter(StatsSet::from_flatbuffer(&stats, dtype, session)?.into_iter());
424 }
425
426 Ok(decoded)
427 }
428
429 fn decode_foreign(
430 &self,
431 encoding_id: ArrayId,
432 dtype: &DType,
433 len: usize,
434 ctx: &ReadContext,
435 ) -> VortexResult<ArrayRef> {
436 let children = (0..self.nchildren())
437 .map(|idx| {
438 let child = self.child(idx);
439 let child_encoding_idx = child.flatbuffer().encoding();
440 let child_encoding_id = ctx
441 .resolve(child_encoding_idx)
442 .ok_or_else(|| vortex_err!("Unknown encoding index: {}", child_encoding_idx))?;
443 child
444 .decode_foreign(child_encoding_id, dtype, len, ctx)
445 .map(Some)
446 })
447 .collect::<VortexResult<ArraySlots>>()?;
448
449 new_foreign_array(
450 encoding_id,
451 dtype.clone(),
452 len,
453 self.metadata().to_vec(),
454 self.collect_buffers()?.into_owned(),
455 children,
456 )
457 }
458
459 pub fn encoding_id(&self) -> u16 {
461 self.flatbuffer().encoding()
462 }
463
464 pub fn metadata(&self) -> &[u8] {
466 self.flatbuffer()
467 .metadata()
468 .map(|metadata| metadata.bytes())
469 .unwrap_or(&[])
470 }
471
472 pub fn nchildren(&self) -> usize {
474 self.flatbuffer()
475 .children()
476 .map_or(0, |children| children.len())
477 }
478
479 pub fn child(&self, idx: usize) -> SerializedArray {
481 let children = self
482 .flatbuffer()
483 .children()
484 .vortex_expect("Expected array to have children");
485 if idx >= children.len() {
486 vortex_panic!(
487 "Invalid child index {} for array with {} children",
488 idx,
489 children.len()
490 );
491 }
492 self.with_root(children.get(idx))
493 }
494
495 pub fn nbuffers(&self) -> usize {
497 self.flatbuffer()
498 .buffers()
499 .map_or(0, |buffers| buffers.len())
500 }
501
502 pub fn buffer(&self, idx: usize) -> VortexResult<BufferHandle> {
504 let buffer_idx = self
505 .flatbuffer()
506 .buffers()
507 .ok_or_else(|| vortex_err!("Array has no buffers"))?
508 .get(idx);
509 self.buffers
510 .get(buffer_idx as usize)
511 .cloned()
512 .ok_or_else(|| {
513 vortex_err!(
514 "Invalid buffer index {} for array with {} buffers",
515 buffer_idx,
516 self.nbuffers()
517 )
518 })
519 }
520
521 fn collect_buffers(&self) -> VortexResult<Cow<'_, [BufferHandle]>> {
526 let Some(fb_buffers) = self.flatbuffer().buffers() else {
527 return Ok(Cow::Borrowed(&[]));
528 };
529 let count = fb_buffers.len();
530 if count == 0 {
531 return Ok(Cow::Borrowed(&[]));
532 }
533 let start = fb_buffers.get(0) as usize;
534 let contiguous = fb_buffers
535 .iter()
536 .enumerate()
537 .all(|(i, idx)| idx as usize == start + i);
538 if contiguous {
539 self.buffers.get(start..start + count).map_or_else(
540 || {
541 vortex_bail!(
542 "buffer indices {}..{} out of range for {} buffers",
543 start,
544 start + count,
545 self.buffers.len()
546 )
547 },
548 |slice| Ok(Cow::Borrowed(slice)),
549 )
550 } else {
551 (0..count)
552 .map(|idx| self.buffer(idx))
553 .collect::<VortexResult<Vec<_>>>()
554 .map(Cow::Owned)
555 }
556 }
557
558 pub fn buffer_lengths(&self) -> Vec<usize> {
564 let fb_array = root::<fba::Array>(self.flatbuffer.as_ref())
565 .vortex_expect("SerializedArray flatbuffer must be a valid Array");
566 fb_array
567 .buffers()
568 .map(|buffers| buffers.iter().map(|b| b.length() as usize).collect())
569 .unwrap_or_default()
570 }
571
572 fn validate_array_tree(array_tree: impl Into<ByteBuffer>) -> VortexResult<(FlatBuffer, usize)> {
574 let fb_buffer = FlatBuffer::align_from(array_tree.into());
575 let fb_array = root::<fba::Array>(fb_buffer.as_ref())?;
576 let fb_root = fb_array
577 .root()
578 .ok_or_else(|| vortex_err!("Array must have a root node"))?;
579 let flatbuffer_loc = fb_root._tab.loc();
580 Ok((fb_buffer, flatbuffer_loc))
581 }
582
583 pub fn from_flatbuffer_with_buffers(
590 array_tree: impl Into<ByteBuffer>,
591 buffers: Vec<BufferHandle>,
592 ) -> VortexResult<Self> {
593 let (flatbuffer, flatbuffer_loc) = Self::validate_array_tree(array_tree)?;
594 Ok(SerializedArray {
595 flatbuffer,
596 flatbuffer_loc,
597 buffers: buffers.into(),
598 })
599 }
600
601 pub fn from_array_tree(array_tree: impl Into<ByteBuffer>) -> VortexResult<Self> {
610 let (flatbuffer, flatbuffer_loc) = Self::validate_array_tree(array_tree)?;
611 Ok(SerializedArray {
612 flatbuffer,
613 flatbuffer_loc,
614 buffers: Arc::new([]),
615 })
616 }
617
618 fn flatbuffer(&self) -> fba::ArrayNode<'_> {
620 unsafe { fba::ArrayNode::follow(self.flatbuffer.as_ref(), self.flatbuffer_loc) }
621 }
622
623 fn with_root(&self, root: fba::ArrayNode) -> Self {
626 let mut this = self.clone();
627 this.flatbuffer_loc = root._tab.loc();
628 this
629 }
630
631 pub fn from_flatbuffer_and_segment(
637 array_tree: ByteBuffer,
638 segment: BufferHandle,
639 ) -> VortexResult<Self> {
640 Self::from_flatbuffer_and_segment_with_overrides(array_tree, segment, &HashMap::new())
642 }
643
644 pub fn from_flatbuffer_and_segment_with_overrides(
651 array_tree: ByteBuffer,
652 segment: BufferHandle,
653 buffer_overrides: &HashMap<u32, ByteBuffer>,
654 ) -> VortexResult<Self> {
655 let segment = segment.ensure_aligned(Alignment::none())?;
658
659 let (fb_buffer, flatbuffer_loc) = Self::validate_array_tree(array_tree)?;
662 let fb_array = unsafe { fba::root_as_array_unchecked(fb_buffer.as_ref()) };
664
665 let mut offset = 0usize;
666 let buffers = fb_array
667 .buffers()
668 .unwrap_or_default()
669 .iter()
670 .enumerate()
671 .map(|(idx, fb_buf)| {
672 let idx = u32::try_from(idx).vortex_expect("buffer count must fit in u32");
673
674 let buffer_len = fb_buf.length() as usize;
678 let start = offset
679 .checked_add(fb_buf.padding() as usize)
680 .ok_or_else(|| {
681 vortex_err!("Buffer {idx} offset overflows when adding its padding")
682 })?;
683 let end = start.checked_add(buffer_len).ok_or_else(|| {
684 vortex_err!("Buffer {idx} offset overflows when adding its length")
685 })?;
686
687 let alignment =
690 Alignment::try_from_untrusted_exponent(fb_buf.alignment_exponent())?;
691 let handle = if let Some(host_data) = buffer_overrides.get(&idx) {
692 BufferHandle::new_host(host_data.clone()).ensure_aligned(alignment)?
693 } else {
694 if end > segment.len() {
697 vortex_bail!(
698 "Buffer {idx} at offset {start} with length {buffer_len} is out of \
699 bounds of the {}-byte segment",
700 segment.len(),
701 );
702 }
703 segment.slice(start..end).ensure_aligned(alignment)?
704 };
705
706 offset = end;
707 Ok(handle)
708 })
709 .collect::<VortexResult<Arc<[_]>>>()?;
710
711 Ok(SerializedArray {
712 flatbuffer: fb_buffer,
713 flatbuffer_loc,
714 buffers,
715 })
716 }
717}
718
719struct SerializedArrayChildren<'a> {
720 ser: &'a SerializedArray,
721 ctx: &'a ReadContext,
722 session: &'a VortexSession,
723}
724
725impl ArrayChildren for SerializedArrayChildren<'_> {
726 fn get(&self, index: usize, dtype: &DType, len: usize) -> VortexResult<ArrayRef> {
727 self.ser
728 .child(index)
729 .decode(dtype, len, self.ctx, self.session)
730 }
731
732 fn len(&self) -> usize {
733 self.ser.nchildren()
734 }
735}
736
737impl TryFrom<ByteBuffer> for SerializedArray {
738 type Error = VortexError;
739
740 fn try_from(value: ByteBuffer) -> Result<Self, Self::Error> {
741 if value.len() < 4 {
743 vortex_bail!("SerializedArray buffer is too short");
744 }
745
746 let value = value.aligned(Alignment::none());
748
749 let fb_length = u32::try_from_le_bytes(&value.as_slice()[value.len() - 4..])? as usize;
750 if value.len() < 4 + fb_length {
751 vortex_bail!("SerializedArray buffer is too short for flatbuffer");
752 }
753
754 let fb_offset = value.len() - 4 - fb_length;
755 let array_tree = value.slice(fb_offset..fb_offset + fb_length);
756 let segment = BufferHandle::new_host(value.slice(0..fb_offset));
757
758 Self::from_flatbuffer_and_segment(array_tree, segment)
759 }
760}
761
762impl TryFrom<BufferHandle> for SerializedArray {
763 type Error = VortexError;
764
765 fn try_from(value: BufferHandle) -> Result<Self, Self::Error> {
766 Self::try_from(value.try_to_host_sync()?)
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 use std::sync::atomic::AtomicUsize;
773 use std::sync::atomic::Ordering;
774
775 use vortex_buffer::ByteBufferMut;
776 use vortex_error::vortex_ensure;
777 use vortex_session::registry::CachedId;
778
779 use super::*;
780 use crate::Array;
781 use crate::ArrayPlugin;
782 use crate::ArraySerialization;
783 use crate::ArrayVTable;
784 use crate::IntoArray;
785 use crate::array_session;
786 use crate::arrays::Primitive;
787 use crate::arrays::PrimitiveArray;
788
789 static SERIALIZER_CALLS: AtomicUsize = AtomicUsize::new(0);
790
791 fn old_primitive_id() -> ArrayId {
792 ArrayVTable::id(&Primitive)
793 }
794
795 fn new_primitive_id() -> ArrayId {
796 static ID: CachedId = CachedId::new("vortex.test.primitive_v2");
797 *ID
798 }
799
800 #[derive(Debug)]
801 struct VersionedPrimitivePlugin;
802
803 impl ArrayPlugin for VersionedPrimitivePlugin {
804 fn id(&self) -> ArrayId {
805 old_primitive_id()
806 }
807
808 fn serialized_ids(&self) -> Vec<ArrayId> {
809 vec![old_primitive_id(), new_primitive_id()]
810 }
811
812 fn serialize(
813 &self,
814 array: &ArrayRef,
815 _session: &VortexSession,
816 ) -> VortexResult<Option<ArraySerialization>> {
817 vortex_ensure!(
818 array.encoding_id() == self.id(),
819 "versioned primitive serializer received {}",
820 array.encoding_id(),
821 );
822
823 let serialized_id = if array.len() <= 4 {
824 old_primitive_id()
825 } else {
826 new_primitive_id()
827 };
828
829 Ok(Some(ArraySerialization::from_array(
830 serialized_id,
831 array,
832 vec![],
833 )))
834 }
835
836 fn deserialize(
837 &self,
838 parts: ArrayDeserialization<'_>,
839 session: &VortexSession,
840 ) -> VortexResult<ArrayRef> {
841 vortex_ensure!(
842 parts.serialized_id == old_primitive_id()
843 || parts.serialized_id == new_primitive_id(),
844 "versioned primitive deserializer does not recognize {}",
845 parts.serialized_id,
846 );
847 vortex_ensure!(
848 parts.serialized_id != old_primitive_id() || parts.len <= 4,
849 "old primitive wire ID cannot represent length {}",
850 parts.len,
851 );
852 Ok(Array::<Primitive>::try_from_parts(ArrayVTable::deserialize(
853 &Primitive,
854 parts.dtype,
855 parts.len,
856 parts.metadata,
857 parts.buffers,
858 parts.children,
859 session,
860 )?)?
861 .into_array())
862 }
863 }
864
865 #[derive(Debug)]
866 struct CountingVersionedPrimitivePlugin;
867
868 impl ArrayPlugin for CountingVersionedPrimitivePlugin {
869 fn id(&self) -> ArrayId {
870 VersionedPrimitivePlugin.id()
871 }
872
873 fn serialized_ids(&self) -> Vec<ArrayId> {
874 VersionedPrimitivePlugin.serialized_ids()
875 }
876
877 fn serialize(
878 &self,
879 array: &ArrayRef,
880 session: &VortexSession,
881 ) -> VortexResult<Option<ArraySerialization>> {
882 SERIALIZER_CALLS.fetch_add(1, Ordering::Relaxed);
883 VersionedPrimitivePlugin.serialize(array, session)
884 }
885
886 fn deserialize(
887 &self,
888 parts: ArrayDeserialization<'_>,
889 session: &VortexSession,
890 ) -> VortexResult<ArrayRef> {
891 VersionedPrimitivePlugin.deserialize(parts, session)
892 }
893 }
894
895 fn versioned_primitive_session() -> VortexSession {
896 let session = array_session();
897 session.arrays().register(VersionedPrimitivePlugin);
898 session
899 }
900
901 fn restricted_context(ids: &[ArrayId]) -> ArrayContext {
902 ArrayContext::new(ids.to_vec()).with_allowed_ids(ids.iter().copied().collect())
903 }
904
905 fn serialize_blob(
906 array: &ArrayRef,
907 ctx: &ArrayContext,
908 session: &VortexSession,
909 ) -> VortexResult<ByteBuffer> {
910 let mut blob = ByteBufferMut::empty();
911 for buffer in array.serialize(ctx, session, &SerializeOptions::default())? {
912 blob.extend_from_slice(buffer.as_ref());
913 }
914 Ok(blob.freeze())
915 }
916
917 #[test]
918 fn one_serializer_selects_the_earliest_lossless_wire_id() -> VortexResult<()> {
919 let session = array_session();
920 session.arrays().register(CountingVersionedPrimitivePlugin);
921 let ctx = restricted_context(&[old_primitive_id(), new_primitive_id()]);
922 let array = PrimitiveArray::from_iter([1i32, 2, 3, 4]).into_array();
923
924 SERIALIZER_CALLS.store(0, Ordering::Relaxed);
925 let serialized = SerializedArray::try_from(serialize_blob(&array, &ctx, &session)?)?;
926 assert_eq!(SERIALIZER_CALLS.load(Ordering::Relaxed), 1);
927 assert_eq!(
928 ReadContext::new(ctx.to_ids()).resolve(serialized.encoding_id()),
929 Some(old_primitive_id())
930 );
931 Ok(())
932 }
933
934 #[test]
935 fn serializer_uses_a_newer_id_only_when_the_old_variant_cannot_represent_the_value()
936 -> VortexResult<()> {
937 let session = versioned_primitive_session();
938 let ctx = restricted_context(&[old_primitive_id(), new_primitive_id()]);
939 let array = PrimitiveArray::from_iter(0..8i32).into_array();
940 let serialized = SerializedArray::try_from(serialize_blob(&array, &ctx, &session)?)?;
941 let read_ctx = ReadContext::new(ctx.to_ids());
942
943 assert_eq!(
944 read_ctx.resolve(serialized.encoding_id()),
945 Some(new_primitive_id())
946 );
947 let decoded = serialized.decode(array.dtype(), array.len(), &read_ctx, &session)?;
948 assert_eq!(decoded.encoding_id(), old_primitive_id());
949 Ok(())
950 }
951
952 #[test]
953 fn serialization_fails_when_serialized_id_is_not_permitted() -> VortexResult<()> {
954 let session = versioned_primitive_session();
955 let ctx = restricted_context(&[old_primitive_id()]);
956 let array = PrimitiveArray::from_iter(0..8i32).into_array();
957
958 let error = array
959 .serialize(&ctx, &session, &SerializeOptions::default())
960 .expect_err("the serialized ID is not permitted");
961 assert!(error.to_string().contains("not permitted by ctx"));
962 Ok(())
963 }
964
965 #[test]
966 fn old_reader_rejects_a_new_serialized_id() -> VortexResult<()> {
967 let writer_session = versioned_primitive_session();
968 let ctx = restricted_context(&[new_primitive_id()]);
969 let array = PrimitiveArray::from_iter(0..8i32).into_array();
970 let serialized = SerializedArray::try_from(serialize_blob(&array, &ctx, &writer_session)?)?;
971 let read_ctx = ReadContext::new(ctx.to_ids());
972
973 let old_session = array_session();
974 let error = serialized
975 .decode(array.dtype(), array.len(), &read_ctx, &old_session)
976 .expect_err("an old reader must not recognize the new wire ID");
977 assert!(error.to_string().contains("Unknown encoding"));
978 Ok(())
979 }
980
981 #[test]
982 fn deserializer_enforces_the_exact_wire_id_contract() -> VortexResult<()> {
983 let session = versioned_primitive_session();
984 let write_ctx = restricted_context(&[new_primitive_id()]);
985 let array = PrimitiveArray::from_iter(0..8i32).into_array();
986 let serialized = SerializedArray::try_from(serialize_blob(&array, &write_ctx, &session)?)?;
987
988 let error = serialized
991 .decode(
992 array.dtype(),
993 array.len(),
994 &ReadContext::new([old_primitive_id()]),
995 &session,
996 )
997 .expect_err("the old wire contract must be enforced by the current deserializer");
998 assert!(error.to_string().contains("old primitive wire ID"));
999 Ok(())
1000 }
1001
1002 #[test]
1005 fn from_flatbuffer_and_segment_rejects_out_of_bounds_buffer() -> VortexResult<()> {
1006 let session = array_session();
1007 let array_ctx = ArrayContext::empty();
1008
1009 let serialized = PrimitiveArray::from_iter([1i32, 2, 3, 4])
1012 .into_array()
1013 .serialize(&array_ctx, &session, &SerializeOptions::default())?;
1014
1015 let mut concat = ByteBufferMut::empty();
1016 for buf in serialized {
1017 concat.extend_from_slice(buf.as_ref());
1018 }
1019 let value = concat.freeze().aligned(Alignment::none());
1020
1021 let fb_length = u32::try_from_le_bytes(&value.as_slice()[value.len() - 4..])? as usize;
1024 let fb_offset = value.len() - 4 - fb_length;
1025 assert!(
1026 fb_offset > 0,
1027 "the array must have at least one data buffer"
1028 );
1029 let array_tree = value.slice(fb_offset..fb_offset + fb_length);
1030
1031 let truncated = BufferHandle::new_host(value.slice(0..fb_offset - 1));
1033
1034 let Some(err) = SerializedArray::from_flatbuffer_and_segment(array_tree, truncated).err()
1035 else {
1036 vortex_bail!("out-of-bounds buffer must be rejected");
1037 };
1038 assert!(
1039 err.to_string().contains("out of bounds"),
1040 "unexpected error: {err}"
1041 );
1042
1043 Ok(())
1044 }
1045
1046 #[test]
1049 fn from_flatbuffer_and_segment_rejects_excessive_buffer_alignment() -> VortexResult<()> {
1050 let mut fbb = FlatBufferBuilder::new();
1053 let fb_root = fba::ArrayNode::create(&mut fbb, &fba::ArrayNodeArgs::default());
1054 let fb_buffers = fbb.create_vector(&[fba::Buffer::new(0, 40, Compression::None, 4)]);
1055 let fb_array = fba::Array::create(
1056 &mut fbb,
1057 &fba::ArrayArgs {
1058 root: Some(fb_root),
1059 buffers: Some(fb_buffers),
1060 },
1061 );
1062 fbb.finish_minimal(fb_array);
1063 let (fb_vec, fb_start) = fbb.collapse();
1064 let fb_end = fb_vec.len();
1065 let array_tree = ByteBuffer::from(fb_vec).slice(fb_start..fb_end);
1066
1067 let segment = BufferHandle::new_host(ByteBuffer::from(vec![0u8; 4]));
1068 let Some(err) = SerializedArray::from_flatbuffer_and_segment(array_tree, segment).err()
1069 else {
1070 vortex_bail!("excessive buffer alignment must be rejected");
1071 };
1072 assert!(
1073 err.to_string().contains("exceeds"),
1074 "unexpected error: {err}"
1075 );
1076
1077 Ok(())
1078 }
1079}