1use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::hash::Hasher;
8use std::sync::Arc;
9use std::sync::OnceLock;
10
11use fsst::Compressor;
12use fsst::Decompressor;
13use fsst::Symbol;
14use prost::Message as _;
15use vortex_array::Array;
16use vortex_array::ArrayEq;
17use vortex_array::ArrayHash;
18use vortex_array::ArrayId;
19use vortex_array::ArrayParts;
20use vortex_array::ArrayRef;
21use vortex_array::ArraySlots;
22use vortex_array::ArrayView;
23use vortex_array::Canonical;
24use vortex_array::EqMode;
25use vortex_array::ExecutionCtx;
26use vortex_array::ExecutionResult;
27use vortex_array::IntoArray;
28use vortex_array::LEGACY_SESSION;
29use vortex_array::TypedArrayRef;
30use vortex_array::VortexSessionExecute;
31use vortex_array::arrays::VarBin;
32use vortex_array::arrays::VarBinArray;
33use vortex_array::arrays::varbin::VarBinArrayExt;
34use vortex_array::buffer::BufferHandle;
35use vortex_array::builders::ArrayBuilder;
36use vortex_array::builders::VarBinViewBuilder;
37use vortex_array::dtype::DType;
38use vortex_array::dtype::Nullability;
39use vortex_array::dtype::PType;
40use vortex_array::serde::ArrayChildren;
41use vortex_array::smallvec::smallvec;
42use vortex_array::validity::Validity;
43use vortex_array::vtable::VTable;
44use vortex_array::vtable::ValidityVTable;
45use vortex_array::vtable::child_to_validity;
46use vortex_array::vtable::validity_to_child;
47use vortex_buffer::Buffer;
48use vortex_buffer::ByteBuffer;
49use vortex_error::VortexExpect;
50use vortex_error::VortexResult;
51use vortex_error::vortex_bail;
52use vortex_error::vortex_ensure;
53use vortex_error::vortex_err;
54use vortex_error::vortex_panic;
55use vortex_session::VortexSession;
56use vortex_session::registry::CachedId;
57
58use crate::canonical::canonicalize_fsst;
59use crate::canonical::fsst_decode_views;
60use crate::rules::RULES;
61
62pub type FSSTArray = Array<FSST>;
64
65#[derive(Clone, prost::Message)]
66pub struct FSSTMetadata {
67 #[prost(enumeration = "PType", tag = "1")]
68 uncompressed_lengths_ptype: i32,
69
70 #[prost(enumeration = "PType", tag = "2")]
71 codes_offsets_ptype: i32,
72}
73
74impl FSSTMetadata {
75 pub fn get_uncompressed_lengths_ptype(&self) -> VortexResult<PType> {
76 PType::try_from(self.uncompressed_lengths_ptype)
77 .map_err(|_| vortex_err!("Invalid PType {}", self.uncompressed_lengths_ptype))
78 }
79}
80
81impl ArrayHash for FSSTData {
82 fn array_hash<H: Hasher>(&self, state: &mut H, precision: EqMode) {
83 self.symbol_table.symbols.array_hash(state, precision);
84 self.symbol_table
85 .symbol_lengths
86 .array_hash(state, precision);
87 self.codes_bytes.as_host().array_hash(state, precision);
88 }
89}
90
91impl ArrayEq for FSSTData {
92 fn array_eq(&self, other: &Self, precision: EqMode) -> bool {
93 self.symbol_table
94 .symbols
95 .array_eq(&other.symbol_table.symbols, precision)
96 && self
97 .symbol_table
98 .symbol_lengths
99 .array_eq(&other.symbol_table.symbol_lengths, precision)
100 && self
101 .codes_bytes
102 .as_host()
103 .array_eq(other.codes_bytes.as_host(), precision)
104 }
105}
106
107impl VTable for FSST {
108 type TypedArrayData = FSSTData;
109 type OperationsVTable = Self;
110 type ValidityVTable = Self;
111
112 fn id(&self) -> ArrayId {
113 static ID: CachedId = CachedId::new("vortex.fsst");
114 *ID
115 }
116
117 fn validate(
118 &self,
119 data: &Self::TypedArrayData,
120 dtype: &DType,
121 len: usize,
122 slots: &[Option<ArrayRef>],
123 ) -> VortexResult<()> {
124 let mut ctx = LEGACY_SESSION.create_execution_ctx();
126 data.validate(dtype, len, slots, &mut ctx)
127 }
128
129 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
130 3
131 }
132
133 fn buffer(array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
134 match idx {
135 0 => BufferHandle::new_host(array.symbols().clone().into_byte_buffer()),
136 1 => BufferHandle::new_host(array.symbol_lengths().clone().into_byte_buffer()),
137 2 => array.codes_bytes_handle().clone(),
138 _ => vortex_panic!("FSSTArray buffer index {idx} out of bounds"),
139 }
140 }
141
142 fn buffer_name(_array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
143 match idx {
144 0 => Some("symbols".to_string()),
145 1 => Some("symbol_lengths".to_string()),
146 2 => Some("compressed_codes".to_string()),
147 _ => vortex_panic!("FSSTArray buffer_name index {idx} out of bounds"),
148 }
149 }
150
151 fn with_buffers(
152 &self,
153 array: ArrayView<'_, Self>,
154 buffers: &[BufferHandle],
155 ) -> VortexResult<ArrayParts<Self>> {
156 vortex_ensure!(
157 buffers.len() == 3,
158 "Expected 3 buffers, got {}",
159 buffers.len()
160 );
161 let symbols = Buffer::<Symbol>::from_byte_buffer(buffers[0].clone().try_to_host_sync()?);
162 let symbol_lengths = Buffer::<u8>::from_byte_buffer(buffers[1].clone().try_to_host_sync()?);
163 let data = FSSTData::try_new(symbols, symbol_lengths, buffers[2].clone(), array.len())?;
164 Ok(
165 ArrayParts::new(self.clone(), array.dtype().clone(), array.len(), data)
166 .with_slots(array.slots().iter().cloned().collect()),
167 )
168 }
169
170 fn serialize(
171 array: ArrayView<'_, Self>,
172 _session: &VortexSession,
173 ) -> VortexResult<Option<Vec<u8>>> {
174 let codes_offsets = array.as_ref().slots()[CODES_OFFSETS_SLOT]
175 .as_ref()
176 .vortex_expect("FSSTArray codes_offsets slot");
177 Ok(Some(
178 FSSTMetadata {
179 uncompressed_lengths_ptype: array.uncompressed_lengths().dtype().as_ptype().into(),
180 codes_offsets_ptype: codes_offsets.dtype().as_ptype().into(),
181 }
182 .encode_to_vec(),
183 ))
184 }
185
186 fn deserialize(
211 &self,
212 dtype: &DType,
213 len: usize,
214 metadata: &[u8],
215 buffers: &[BufferHandle],
216 children: &dyn ArrayChildren,
217 session: &VortexSession,
218 ) -> VortexResult<ArrayParts<Self>> {
219 let metadata = FSSTMetadata::decode(metadata)?;
220 let symbols = Buffer::<Symbol>::from_byte_buffer(buffers[0].clone().try_to_host_sync()?);
221 let symbol_lengths = Buffer::<u8>::from_byte_buffer(buffers[1].clone().try_to_host_sync()?);
222
223 let mut ctx = session.create_execution_ctx();
224 if buffers.len() == 2 {
225 return Self::deserialize_legacy(
226 self,
227 dtype,
228 len,
229 &metadata,
230 &symbols,
231 &symbol_lengths,
232 children,
233 &mut ctx,
234 );
235 }
236
237 if buffers.len() == 3 {
238 let uncompressed_lengths = children.get(
239 0,
240 &DType::Primitive(
241 metadata.get_uncompressed_lengths_ptype()?,
242 Nullability::NonNullable,
243 ),
244 len,
245 )?;
246
247 let codes_bytes = buffers[2].clone();
248 let codes_offsets = children.get(
249 1,
250 &DType::Primitive(
251 PType::try_from(metadata.codes_offsets_ptype)?,
252 Nullability::NonNullable,
253 ),
254 len + 1,
256 )?;
257
258 let codes_validity = if children.len() == 2 {
259 Validity::from(dtype.nullability())
260 } else if children.len() == 3 {
261 let validity = children.get(2, &Validity::DTYPE, len)?;
262 Validity::Array(validity)
263 } else {
264 vortex_bail!("Expected 2 or 3 children, got {}", children.len());
265 };
266
267 FSSTData::validate_parts(
268 &symbols,
269 &symbol_lengths,
270 &codes_bytes,
271 &codes_offsets,
272 dtype.nullability(),
273 &uncompressed_lengths,
274 dtype,
275 len,
276 &mut ctx,
277 )?;
278 let slots = smallvec![
279 Some(uncompressed_lengths),
280 Some(codes_offsets),
281 validity_to_child(&codes_validity, len),
282 ];
283 let data = FSSTData::try_new(symbols, symbol_lengths, codes_bytes, len)?;
284 return Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots));
285 }
286
287 vortex_bail!(
288 "InvalidArgument: Expected 2 or 3 buffers, got {}",
289 buffers.len()
290 );
291 }
292
293 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
294 SLOT_NAMES[idx].to_string()
295 }
296
297 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
298 canonicalize_fsst(array.as_view(), ctx).map(ExecutionResult::done)
299 }
300
301 fn append_to_builder(
302 array: ArrayView<'_, Self>,
303 builder: &mut dyn ArrayBuilder,
304 ctx: &mut ExecutionCtx,
305 ) -> VortexResult<()> {
306 let Some(builder) = builder.as_any_mut().downcast_mut::<VarBinViewBuilder>() else {
307 builder.extend_from_array(
308 &array
309 .array()
310 .clone()
311 .execute::<Canonical>(ctx)?
312 .into_array(),
313 );
314 return Ok(());
315 };
316
317 let next_buffer_index = builder.completed_block_count() + u32::from(builder.in_progress());
321 let (buffers, views) = fsst_decode_views(array, next_buffer_index, ctx)?;
322
323 builder.push_buffer_and_adjusted_views(
324 &buffers,
325 &views,
326 array
327 .array()
328 .validity()?
329 .execute_mask(array.array().len(), ctx)?,
330 );
331 Ok(())
332 }
333
334 fn reduce_parent(
335 array: ArrayView<'_, Self>,
336 parent: &ArrayRef,
337 child_idx: usize,
338 ) -> VortexResult<Option<ArrayRef>> {
339 RULES.evaluate(array, parent, child_idx)
340 }
341}
342
343pub(crate) const UNCOMPRESSED_LENGTHS_SLOT: usize = 0;
345pub(crate) const CODES_OFFSETS_SLOT: usize = 1;
347pub(crate) const CODES_VALIDITY_SLOT: usize = 2;
349pub(crate) const NUM_SLOTS: usize = 3;
350pub(crate) const SLOT_NAMES: [&str; NUM_SLOTS] =
351 ["uncompressed_lengths", "codes_offsets", "codes_validity"];
352
353#[derive(Clone)]
362pub struct FSSTData {
363 symbol_table: Arc<FSSTSymbolTable>,
364 codes_bytes: BufferHandle,
366 len: usize,
368}
369
370impl Display for FSSTData {
371 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
372 write!(
373 f,
374 "len: {}, nsymbols: {}",
375 self.len,
376 self.symbol_table.symbols.len()
377 )
378 }
379}
380
381impl Debug for FSSTData {
382 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
383 f.debug_struct("FSSTArray")
384 .field("symbols", &self.symbol_table.symbols)
385 .field("symbol_lengths", &self.symbol_table.symbol_lengths)
386 .field("codes_bytes_len", &self.codes_bytes.len())
387 .field("len", &self.len)
388 .field("uncompressed_lengths", &"<outer slot>")
389 .field("codes_offsets", &"<outer slot>")
390 .field("codes_validity", &"<outer slot>")
391 .finish()
392 }
393}
394
395pub(crate) struct FSSTSymbolTable {
396 symbols: Buffer<Symbol>,
397 symbol_lengths: Buffer<u8>,
398 compressor: OnceLock<Compressor>,
400}
401
402impl FSSTSymbolTable {
403 fn new(symbols: Buffer<Symbol>, symbol_lengths: Buffer<u8>) -> Self {
404 Self {
405 symbols,
406 symbol_lengths,
407 compressor: OnceLock::new(),
408 }
409 }
410
411 fn compressor(&self) -> &Compressor {
412 self.compressor.get_or_init(|| {
413 Compressor::rebuild_from(self.symbols.as_slice(), self.symbol_lengths.as_slice())
414 })
415 }
416}
417
418#[derive(Clone, Debug)]
419pub struct FSST;
420
421impl FSST {
422 pub fn try_new(
428 dtype: DType,
429 symbols: Buffer<Symbol>,
430 symbol_lengths: Buffer<u8>,
431 codes: VarBinArray,
432 uncompressed_lengths: ArrayRef,
433 ctx: &mut ExecutionCtx,
434 ) -> VortexResult<FSSTArray> {
435 let len = codes.len();
436 FSSTData::validate_parts_from_codes(
437 &symbols,
438 &symbol_lengths,
439 &codes,
440 &uncompressed_lengths,
441 &dtype,
442 len,
443 ctx,
444 )?;
445 let slots = FSSTData::make_slots(&codes, &uncompressed_lengths);
446 let codes_bytes = codes.bytes_handle().clone();
447 let data = FSSTData::try_new(symbols, symbol_lengths, codes_bytes, len)?;
448 Ok(unsafe {
449 Array::from_parts_unchecked(ArrayParts::new(FSST, dtype, len, data).with_slots(slots))
450 })
451 }
452
453 pub(crate) fn try_new_with_symbol_table(
454 dtype: DType,
455 symbol_table: Arc<FSSTSymbolTable>,
456 codes: VarBinArray,
457 uncompressed_lengths: ArrayRef,
458 ctx: &mut ExecutionCtx,
459 ) -> VortexResult<FSSTArray> {
460 let len = codes.len();
461 FSSTData::validate_parts_from_codes(
462 &symbol_table.symbols,
463 &symbol_table.symbol_lengths,
464 &codes,
465 &uncompressed_lengths,
466 &dtype,
467 len,
468 ctx,
469 )?;
470 let slots = FSSTData::make_slots(&codes, &uncompressed_lengths);
471 let codes_bytes = codes.bytes_handle().clone();
472 let data =
473 unsafe { FSSTData::new_unchecked_with_symbol_table(symbol_table, codes_bytes, len) };
474 Ok(unsafe {
475 Array::from_parts_unchecked(ArrayParts::new(FSST, dtype, len, data).with_slots(slots))
476 })
477 }
478
479 #[allow(clippy::too_many_arguments)]
483 fn deserialize_legacy(
484 &self,
485 dtype: &DType,
486 len: usize,
487 metadata: &FSSTMetadata,
488 symbols: &Buffer<Symbol>,
489 symbol_lengths: &Buffer<u8>,
490 children: &dyn ArrayChildren,
491 ctx: &mut ExecutionCtx,
492 ) -> VortexResult<ArrayParts<Self>> {
493 if children.len() != 2 {
494 vortex_bail!(InvalidArgument: "Expected 2 children, got {}", children.len());
495 }
496 let codes = children.get(0, &DType::Binary(dtype.nullability()), len)?;
497 let codes: VarBinArray = codes
498 .as_opt::<VarBin>()
499 .ok_or_else(|| {
500 vortex_err!(
501 "Expected VarBinArray for codes, got {}",
502 codes.encoding_id()
503 )
504 })?
505 .into_owned();
506 let uncompressed_lengths = children.get(
507 1,
508 &DType::Primitive(
509 metadata.get_uncompressed_lengths_ptype()?,
510 Nullability::NonNullable,
511 ),
512 len,
513 )?;
514
515 FSSTData::validate_parts_from_codes(
516 symbols,
517 symbol_lengths,
518 &codes,
519 &uncompressed_lengths,
520 dtype,
521 len,
522 ctx,
523 )?;
524 let slots = FSSTData::make_slots(&codes, &uncompressed_lengths);
525 let codes_bytes = codes.bytes_handle().clone();
526 let data = FSSTData::try_new(symbols.clone(), symbol_lengths.clone(), codes_bytes, len)?;
527 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
528 }
529
530 pub(crate) unsafe fn new_unchecked_with_symbol_table(
531 dtype: DType,
532 symbol_table: Arc<FSSTSymbolTable>,
533 codes: VarBinArray,
534 uncompressed_lengths: ArrayRef,
535 ) -> FSSTArray {
536 let len = codes.len();
537 let slots = FSSTData::make_slots(&codes, &uncompressed_lengths);
538 let codes_bytes = codes.bytes_handle().clone();
539 let data =
540 unsafe { FSSTData::new_unchecked_with_symbol_table(symbol_table, codes_bytes, len) };
541 unsafe {
542 Array::from_parts_unchecked(ArrayParts::new(FSST, dtype, len, data).with_slots(slots))
543 }
544 }
545}
546
547impl FSSTData {
548 fn make_slots(codes: &VarBinArray, uncompressed_lengths: &ArrayRef) -> ArraySlots {
549 smallvec![
550 Some(uncompressed_lengths.clone()),
551 Some(codes.offsets().clone()),
552 validity_to_child(
553 &codes
554 .validity()
555 .vortex_expect("FSST codes validity should be derivable"),
556 codes.len(),
557 ),
558 ]
559 }
560
561 pub fn try_new(
574 symbols: Buffer<Symbol>,
575 symbol_lengths: Buffer<u8>,
576 codes_bytes: BufferHandle,
577 len: usize,
578 ) -> VortexResult<Self> {
579 unsafe {
581 Ok(Self::new_unchecked(
582 symbols,
583 symbol_lengths,
584 codes_bytes,
585 len,
586 ))
587 }
588 }
589
590 pub fn validate(
591 &self,
592 dtype: &DType,
593 len: usize,
594 slots: &[Option<ArrayRef>],
595 ctx: &mut ExecutionCtx,
596 ) -> VortexResult<()> {
597 let codes_offsets = slots[CODES_OFFSETS_SLOT]
598 .as_ref()
599 .vortex_expect("FSSTArray codes_offsets slot");
600 Self::validate_parts(
601 &self.symbol_table.symbols,
602 &self.symbol_table.symbol_lengths,
603 &self.codes_bytes,
604 codes_offsets,
605 dtype.nullability(),
606 uncompressed_lengths_from_slots(slots),
607 dtype,
608 len,
609 ctx,
610 )
611 }
612
613 #[expect(clippy::too_many_arguments)]
615 fn validate_parts(
616 symbols: &Buffer<Symbol>,
617 symbol_lengths: &Buffer<u8>,
618 codes_bytes: &BufferHandle,
619 codes_offsets: &ArrayRef,
620 codes_nullability: Nullability,
621 uncompressed_lengths: &ArrayRef,
622 dtype: &DType,
623 len: usize,
624 ctx: &mut ExecutionCtx,
625 ) -> VortexResult<()> {
626 vortex_ensure!(
627 matches!(dtype, DType::Binary(_) | DType::Utf8(_)),
628 "FSST arrays must be Binary or Utf8, found {dtype}"
629 );
630
631 if symbols.len() > 255 {
632 vortex_bail!(InvalidArgument: "symbols array must have length <= 255");
633 }
634
635 if symbols.len() != symbol_lengths.len() {
636 vortex_bail!(InvalidArgument: "symbols and symbol_lengths arrays must have same length");
637 }
638
639 Self::validate_symbol_lengths(symbol_lengths.as_slice())?;
640
641 let codes_len = codes_offsets.len().saturating_sub(1);
643 if codes_len != len {
644 vortex_bail!(InvalidArgument: "codes must have same len as outer array");
645 }
646
647 if uncompressed_lengths.len() != len {
648 vortex_bail!(InvalidArgument: "uncompressed_lengths must be same len as codes");
649 }
650
651 if !uncompressed_lengths.dtype().is_int() || uncompressed_lengths.dtype().is_nullable() {
652 vortex_bail!(InvalidArgument: "uncompressed_lengths must have integer type and cannot be nullable, found {}", uncompressed_lengths.dtype());
653 }
654
655 if !codes_offsets.dtype().is_int() || codes_offsets.dtype().is_nullable() {
657 vortex_bail!(InvalidArgument: "codes offsets must be non-nullable integer type, found {}", codes_offsets.dtype());
658 }
659
660 if codes_nullability != dtype.nullability() {
661 vortex_bail!(InvalidArgument: "codes nullability must match outer dtype nullability");
662 }
663
664 if codes_bytes.is_on_host() && codes_offsets.is_host() && !codes_offsets.is_empty() {
666 let last_offset: usize = (&codes_offsets
667 .execute_scalar(codes_offsets.len() - 1, ctx)
668 .vortex_expect("offsets must support scalar_at"))
669 .try_into()
670 .vortex_expect("Failed to convert offset to usize");
671 vortex_ensure!(
672 last_offset <= codes_bytes.len(),
673 InvalidArgument: "Last codes offset {} exceeds codes bytes length {}",
674 last_offset,
675 codes_bytes.len()
676 );
677 }
678
679 Ok(())
680 }
681
682 fn validate_symbol_lengths(symbol_lengths: &[u8]) -> VortexResult<()> {
683 let mut expected = 2;
684 for (idx, &len) in symbol_lengths.iter().enumerate() {
685 if len > 8 || len == 0 {
686 vortex_bail!(InvalidArgument: "symbol length at index {idx} must be between 1 and 8, found {len}");
687 }
688
689 if expected == 1 {
690 if len != 1 {
691 vortex_bail!(InvalidArgument: "symbol length at index {idx} must be 1 after one-byte symbols begin, found {len}");
692 }
693 } else {
694 if len == 1 {
695 expected = 1;
696 }
697
698 if len < expected {
699 vortex_bail!(InvalidArgument: "symbol length at index {idx} violates FSST symbol table ordering");
700 }
701 expected = len;
702 }
703 }
704
705 Ok(())
706 }
707
708 fn validate_parts_from_codes(
710 symbols: &Buffer<Symbol>,
711 symbol_lengths: &Buffer<u8>,
712 codes: &VarBinArray,
713 uncompressed_lengths: &ArrayRef,
714 dtype: &DType,
715 len: usize,
716 ctx: &mut ExecutionCtx,
717 ) -> VortexResult<()> {
718 Self::validate_parts(
719 symbols,
720 symbol_lengths,
721 codes.bytes_handle(),
722 codes.offsets(),
723 codes.dtype().nullability(),
724 uncompressed_lengths,
725 dtype,
726 len,
727 ctx,
728 )
729 }
730
731 pub(crate) unsafe fn new_unchecked(
732 symbols: Buffer<Symbol>,
733 symbol_lengths: Buffer<u8>,
734 codes_bytes: BufferHandle,
735 len: usize,
736 ) -> Self {
737 let symbol_table = Arc::new(FSSTSymbolTable::new(symbols, symbol_lengths));
738 unsafe { Self::new_unchecked_with_symbol_table(symbol_table, codes_bytes, len) }
739 }
740
741 pub(crate) unsafe fn new_unchecked_with_symbol_table(
742 symbol_table: Arc<FSSTSymbolTable>,
743 codes_bytes: BufferHandle,
744 len: usize,
745 ) -> Self {
746 Self {
747 symbol_table,
748 codes_bytes,
749 len,
750 }
751 }
752
753 pub fn len(&self) -> usize {
755 self.len
756 }
757
758 pub fn is_empty(&self) -> bool {
760 self.len == 0
761 }
762
763 pub fn symbols(&self) -> &Buffer<Symbol> {
765 &self.symbol_table.symbols
766 }
767
768 pub fn symbol_lengths(&self) -> &Buffer<u8> {
770 &self.symbol_table.symbol_lengths
771 }
772
773 pub(crate) fn symbol_table(&self) -> Arc<FSSTSymbolTable> {
774 Arc::clone(&self.symbol_table)
775 }
776
777 pub fn codes_bytes_handle(&self) -> &BufferHandle {
779 &self.codes_bytes
780 }
781
782 pub fn codes_bytes(&self) -> &ByteBuffer {
784 self.codes_bytes.as_host()
785 }
786
787 pub fn decompressor(&self) -> Decompressor<'_> {
790 Decompressor::new(self.symbols().as_slice(), self.symbol_lengths().as_slice())
791 }
792
793 pub fn compressor(&self) -> &Compressor {
795 self.symbol_table.compressor()
796 }
797}
798
799fn uncompressed_lengths_from_slots(slots: &[Option<ArrayRef>]) -> &ArrayRef {
800 slots[UNCOMPRESSED_LENGTHS_SLOT]
801 .as_ref()
802 .vortex_expect("FSSTArray uncompressed_lengths slot")
803}
804
805pub trait FSSTArrayExt: TypedArrayRef<FSST> {
806 fn uncompressed_lengths(&self) -> &ArrayRef {
807 uncompressed_lengths_from_slots(self.as_ref().slots())
808 }
809
810 fn uncompressed_lengths_dtype(&self) -> &DType {
811 self.uncompressed_lengths().dtype()
812 }
813
814 fn codes(&self) -> VarBinArray {
817 let offsets = self.as_ref().slots()[CODES_OFFSETS_SLOT]
818 .as_ref()
819 .vortex_expect("FSSTArray codes_offsets slot")
820 .clone();
821 let validity = child_to_validity(
822 self.as_ref().slots()[CODES_VALIDITY_SLOT].as_ref(),
823 self.as_ref().dtype().nullability(),
824 );
825 let codes_bytes = self.codes_bytes_handle().clone();
826 unsafe {
828 VarBinArray::new_unchecked_from_handle(
829 offsets,
830 codes_bytes,
831 DType::Binary(self.as_ref().dtype().nullability()),
832 validity,
833 )
834 }
835 }
836
837 fn codes_dtype(&self) -> DType {
839 DType::Binary(self.as_ref().dtype().nullability())
840 }
841}
842
843impl<T: TypedArrayRef<FSST>> FSSTArrayExt for T {}
844
845impl ValidityVTable<FSST> for FSST {
846 fn validity(array: ArrayView<'_, FSST>) -> VortexResult<Validity> {
847 Ok(child_to_validity(
848 array.slots()[CODES_VALIDITY_SLOT].as_ref(),
849 array.dtype().nullability(),
850 ))
851 }
852}
853
854#[cfg(test)]
855mod test {
856 use fsst::Compressor;
857 use fsst::Symbol;
858 use prost::Message;
859 use vortex_array::ArrayPlugin;
860 use vortex_array::IntoArray;
861 use vortex_array::VortexSessionExecute;
862 use vortex_array::array_session;
863 use vortex_array::arrays::VarBinViewArray;
864 use vortex_array::buffer::BufferHandle;
865 use vortex_array::dtype::DType;
866 use vortex_array::dtype::Nullability;
867 use vortex_array::dtype::PType;
868 use vortex_array::test_harness::check_metadata;
869 use vortex_buffer::Buffer;
870 use vortex_error::VortexResult;
871 use vortex_error::vortex_err;
872
873 use crate::FSST;
874 use crate::array::FSSTArrayExt;
875 use crate::array::FSSTMetadata;
876 use crate::fsst_compress;
877
878 #[test]
879 fn slice_reuses_initialized_compressor() -> VortexResult<()> {
880 let symbols = Buffer::<Symbol>::copy_from([
881 Symbol::from_slice(b"abc00000"),
882 Symbol::from_slice(b"defghijk"),
883 ]);
884 let symbol_lengths = Buffer::<u8>::copy_from([3, 8]);
885
886 let compressor = Compressor::rebuild_from(symbols.as_slice(), symbol_lengths.as_slice());
887 let mut ctx = array_session().create_execution_ctx();
888 let strings = VarBinViewArray::from_iter_str(["abcabcab", "defghijk", "abcxyz"]);
889 let fsst_array = fsst_compress(&strings.into_array(), &compressor, &mut ctx)?;
890
891 let compressor_ptr = fsst_array.compressor() as *const Compressor;
892 let sliced = fsst_array
893 .slice(1..3)?
894 .try_downcast::<FSST>()
895 .map_err(|_| vortex_err!("slice must return an FSST array"))?;
896 let sliced_compressor_ptr = sliced.compressor() as *const Compressor;
897
898 assert_eq!(compressor_ptr, sliced_compressor_ptr);
899 Ok(())
900 }
901
902 #[cfg_attr(miri, ignore)]
903 #[test]
904 fn test_fsst_metadata() {
905 check_metadata(
906 "fsst.metadata",
907 &FSSTMetadata {
908 uncompressed_lengths_ptype: PType::U64 as i32,
909 codes_offsets_ptype: PType::I32 as i32,
910 }
911 .encode_to_vec(),
912 );
913 }
914
915 #[test]
923 fn test_back_compat() -> VortexResult<()> {
924 let symbols = Buffer::<Symbol>::copy_from([
925 Symbol::from_slice(b"abc00000"),
926 Symbol::from_slice(b"defghijk"),
927 ]);
928 let symbol_lengths = Buffer::<u8>::copy_from([3, 8]);
929
930 let compressor = Compressor::rebuild_from(symbols.as_slice(), symbol_lengths.as_slice());
931 let mut ctx = array_session().create_execution_ctx();
932 let input = VarBinViewArray::from_iter_str(["abcabcab", "defghijk"]);
933 let fsst_array = fsst_compress(&input.into_array(), &compressor, &mut ctx)?;
934
935 let compressed_codes = fsst_array.codes();
936
937 let buffers = [
941 BufferHandle::new_host(symbols.into_byte_buffer()),
942 BufferHandle::new_host(symbol_lengths.into_byte_buffer()),
943 ];
944
945 let children = vec![
949 compressed_codes.into_array(),
950 fsst_array.uncompressed_lengths().clone(),
951 ];
952
953 let fsst = ArrayPlugin::deserialize(
954 &FSST,
955 &DType::Utf8(Nullability::NonNullable),
956 2,
957 &FSSTMetadata {
958 uncompressed_lengths_ptype: fsst_array
959 .uncompressed_lengths()
960 .dtype()
961 .as_ptype()
962 .into(),
963 codes_offsets_ptype: 0,
965 }
966 .encode_to_vec(),
967 &buffers,
968 &children.as_slice(),
969 &array_session(),
970 )?;
971
972 let decompressed =
973 fsst.execute::<VarBinViewArray>(&mut array_session().create_execution_ctx())?;
974 let mask = decompressed
975 .validity()?
976 .execute_mask(decompressed.len(), &mut ctx)?;
977 assert!(mask.value(0));
978 assert_eq!(decompressed.bytes_at(0).as_slice(), b"abcabcab".as_ref());
979 assert!(mask.value(1));
980 assert_eq!(decompressed.bytes_at(1).as_slice(), b"defghijk".as_ref());
981 Ok(())
982 }
983}