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