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::array_slots;
31use vortex_array::arrays::VarBin;
32use vortex_array::arrays::VarBinArray;
33use vortex_array::arrays::varbin::VarBinArraySlotsExt;
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::legacy_session;
41use vortex_array::serde::ArrayChildren;
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.codes_offsets();
176 Ok(Some(
177 FSSTMetadata {
178 uncompressed_lengths_ptype: array.uncompressed_lengths().dtype().as_ptype().into(),
179 codes_offsets_ptype: codes_offsets.dtype().as_ptype().into(),
180 }
181 .encode_to_vec(),
182 ))
183 }
184
185 fn deserialize(
210 &self,
211 dtype: &DType,
212 len: usize,
213 metadata: &[u8],
214 buffers: &[BufferHandle],
215 children: &dyn ArrayChildren,
216 session: &VortexSession,
217 ) -> VortexResult<ArrayParts<Self>> {
218 let metadata = FSSTMetadata::decode(metadata)?;
219 let symbols = Buffer::<Symbol>::from_byte_buffer(buffers[0].clone().try_to_host_sync()?);
220 let symbol_lengths = Buffer::<u8>::from_byte_buffer(buffers[1].clone().try_to_host_sync()?);
221
222 let mut ctx = session.create_execution_ctx();
223 if buffers.len() == 2 {
224 return Self::deserialize_legacy(
225 self,
226 dtype,
227 len,
228 &metadata,
229 &symbols,
230 &symbol_lengths,
231 children,
232 &mut ctx,
233 );
234 }
235
236 if buffers.len() == 3 {
237 let uncompressed_lengths = children.get(
238 0,
239 &DType::Primitive(
240 metadata.get_uncompressed_lengths_ptype()?,
241 Nullability::NonNullable,
242 ),
243 len,
244 )?;
245
246 let codes_bytes = buffers[2].clone();
247 let codes_offsets = children.get(
248 1,
249 &DType::Primitive(
250 PType::try_from(metadata.codes_offsets_ptype)?,
251 Nullability::NonNullable,
252 ),
253 len + 1,
255 )?;
256
257 let codes_validity = if children.len() == 2 {
258 Validity::from(dtype.nullability())
259 } else if children.len() == 3 {
260 let validity = children.get(2, &Validity::DTYPE, len)?;
261 Validity::Array(validity)
262 } else {
263 vortex_bail!("Expected 2 or 3 children, got {}", children.len());
264 };
265
266 FSSTData::validate_parts(
267 &symbols,
268 &symbol_lengths,
269 &codes_bytes,
270 &codes_offsets,
271 dtype.nullability(),
272 &uncompressed_lengths,
273 dtype,
274 len,
275 &mut ctx,
276 )?;
277 let slots = FSSTSlots {
278 uncompressed_lengths,
279 codes_offsets,
280 codes_validity: validity_to_child(&codes_validity, len),
281 }
282 .into_slots();
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 FSSTSlots::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 return array
308 .array()
309 .clone()
310 .execute::<Canonical>(ctx)?
311 .into_array()
312 .append_to_builder(builder, ctx);
313 };
314
315 let next_buffer_index = builder.completed_block_count() + u32::from(builder.in_progress());
319 let (buffers, views) = fsst_decode_views(array, next_buffer_index, ctx)?;
320
321 builder.push_buffer_and_adjusted_views(
322 &buffers,
323 &views,
324 array
325 .array()
326 .validity()?
327 .execute_mask(array.array().len(), ctx)?,
328 );
329 Ok(())
330 }
331
332 fn reduce_parent(
333 array: ArrayView<'_, Self>,
334 parent: &ArrayRef,
335 child_idx: usize,
336 ) -> VortexResult<Option<ArrayRef>> {
337 RULES.evaluate(array, parent, child_idx)
338 }
339}
340
341#[array_slots(FSST)]
342pub struct FSSTSlots {
343 pub uncompressed_lengths: ArrayRef,
345 pub codes_offsets: ArrayRef,
347 pub codes_validity: Option<ArrayRef>,
349}
350
351#[derive(Clone)]
360pub struct FSSTData {
361 symbol_table: Arc<FSSTSymbolTable>,
362 codes_bytes: BufferHandle,
364 len: usize,
366}
367
368impl Display for FSSTData {
369 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
370 write!(
371 f,
372 "len: {}, nsymbols: {}",
373 self.len,
374 self.symbol_table.symbols.len()
375 )
376 }
377}
378
379impl Debug for FSSTData {
380 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
381 f.debug_struct("FSSTArray")
382 .field("symbols", &self.symbol_table.symbols)
383 .field("symbol_lengths", &self.symbol_table.symbol_lengths)
384 .field("codes_bytes_len", &self.codes_bytes.len())
385 .field("len", &self.len)
386 .field("uncompressed_lengths", &"<outer slot>")
387 .field("codes_offsets", &"<outer slot>")
388 .field("codes_validity", &"<outer slot>")
389 .finish()
390 }
391}
392
393pub(crate) struct FSSTSymbolTable {
394 symbols: Buffer<Symbol>,
395 symbol_lengths: Buffer<u8>,
396 compressor: OnceLock<Compressor>,
398}
399
400impl FSSTSymbolTable {
401 fn new(symbols: Buffer<Symbol>, symbol_lengths: Buffer<u8>) -> Self {
402 Self {
403 symbols,
404 symbol_lengths,
405 compressor: OnceLock::new(),
406 }
407 }
408
409 fn compressor(&self) -> &Compressor {
410 self.compressor.get_or_init(|| {
411 Compressor::rebuild_from(self.symbols.as_slice(), self.symbol_lengths.as_slice())
412 })
413 }
414}
415
416#[derive(Clone, Debug)]
417pub struct FSST;
418
419impl FSST {
420 pub fn try_new(
426 dtype: DType,
427 symbols: Buffer<Symbol>,
428 symbol_lengths: Buffer<u8>,
429 codes: VarBinArray,
430 uncompressed_lengths: ArrayRef,
431 ctx: &mut ExecutionCtx,
432 ) -> VortexResult<FSSTArray> {
433 let len = codes.len();
434 FSSTData::validate_parts_from_codes(
435 &symbols,
436 &symbol_lengths,
437 &codes,
438 &uncompressed_lengths,
439 &dtype,
440 len,
441 ctx,
442 )?;
443 let slots = FSSTData::make_slots(&codes, &uncompressed_lengths);
444 let codes_bytes = codes.bytes_handle().clone();
445 let data = FSSTData::try_new(symbols, symbol_lengths, codes_bytes, len)?;
446 Ok(unsafe {
447 Array::from_parts_unchecked(ArrayParts::new(FSST, dtype, len, data).with_slots(slots))
448 })
449 }
450
451 pub(crate) fn try_new_with_symbol_table(
452 dtype: DType,
453 symbol_table: Arc<FSSTSymbolTable>,
454 codes: VarBinArray,
455 uncompressed_lengths: ArrayRef,
456 ctx: &mut ExecutionCtx,
457 ) -> VortexResult<FSSTArray> {
458 let len = codes.len();
459 FSSTData::validate_parts_from_codes(
460 &symbol_table.symbols,
461 &symbol_table.symbol_lengths,
462 &codes,
463 &uncompressed_lengths,
464 &dtype,
465 len,
466 ctx,
467 )?;
468 let slots = FSSTData::make_slots(&codes, &uncompressed_lengths);
469 let codes_bytes = codes.bytes_handle().clone();
470 let data =
471 unsafe { FSSTData::new_unchecked_with_symbol_table(symbol_table, codes_bytes, len) };
472 Ok(unsafe {
473 Array::from_parts_unchecked(ArrayParts::new(FSST, dtype, len, data).with_slots(slots))
474 })
475 }
476
477 #[allow(clippy::too_many_arguments)]
481 fn deserialize_legacy(
482 &self,
483 dtype: &DType,
484 len: usize,
485 metadata: &FSSTMetadata,
486 symbols: &Buffer<Symbol>,
487 symbol_lengths: &Buffer<u8>,
488 children: &dyn ArrayChildren,
489 ctx: &mut ExecutionCtx,
490 ) -> VortexResult<ArrayParts<Self>> {
491 if children.len() != 2 {
492 vortex_bail!(InvalidArgument: "Expected 2 children, got {}", children.len());
493 }
494 let codes = children.get(0, &DType::Binary(dtype.nullability()), len)?;
495 let codes: VarBinArray = codes
496 .as_opt::<VarBin>()
497 .ok_or_else(|| {
498 vortex_err!(
499 "Expected VarBinArray for codes, got {}",
500 codes.encoding_id()
501 )
502 })?
503 .into_owned();
504 let uncompressed_lengths = children.get(
505 1,
506 &DType::Primitive(
507 metadata.get_uncompressed_lengths_ptype()?,
508 Nullability::NonNullable,
509 ),
510 len,
511 )?;
512
513 FSSTData::validate_parts_from_codes(
514 symbols,
515 symbol_lengths,
516 &codes,
517 &uncompressed_lengths,
518 dtype,
519 len,
520 ctx,
521 )?;
522 let slots = FSSTData::make_slots(&codes, &uncompressed_lengths);
523 let codes_bytes = codes.bytes_handle().clone();
524 let data = FSSTData::try_new(symbols.clone(), symbol_lengths.clone(), codes_bytes, len)?;
525 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
526 }
527
528 pub(crate) unsafe fn new_unchecked_with_symbol_table(
529 dtype: DType,
530 symbol_table: Arc<FSSTSymbolTable>,
531 codes: VarBinArray,
532 uncompressed_lengths: ArrayRef,
533 ) -> FSSTArray {
534 let len = codes.len();
535 let slots = FSSTData::make_slots(&codes, &uncompressed_lengths);
536 let codes_bytes = codes.bytes_handle().clone();
537 let data =
538 unsafe { FSSTData::new_unchecked_with_symbol_table(symbol_table, codes_bytes, len) };
539 unsafe {
540 Array::from_parts_unchecked(ArrayParts::new(FSST, dtype, len, data).with_slots(slots))
541 }
542 }
543}
544
545impl FSSTData {
546 fn make_slots(codes: &VarBinArray, uncompressed_lengths: &ArrayRef) -> ArraySlots {
547 FSSTSlots {
548 uncompressed_lengths: uncompressed_lengths.clone(),
549 codes_offsets: codes.offsets().clone(),
550 codes_validity: validity_to_child(
551 &codes
552 .validity()
553 .vortex_expect("FSST codes validity should be derivable"),
554 codes.len(),
555 ),
556 }
557 .into_slots()
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 fsst_slots = FSSTSlotsView::from_slots(slots);
597 Self::validate_parts(
598 &self.symbol_table.symbols,
599 &self.symbol_table.symbol_lengths,
600 &self.codes_bytes,
601 fsst_slots.codes_offsets,
602 dtype.nullability(),
603 fsst_slots.uncompressed_lengths,
604 dtype,
605 len,
606 ctx,
607 )
608 }
609
610 #[expect(clippy::too_many_arguments)]
612 fn validate_parts(
613 symbols: &Buffer<Symbol>,
614 symbol_lengths: &Buffer<u8>,
615 codes_bytes: &BufferHandle,
616 codes_offsets: &ArrayRef,
617 codes_nullability: Nullability,
618 uncompressed_lengths: &ArrayRef,
619 dtype: &DType,
620 len: usize,
621 ctx: &mut ExecutionCtx,
622 ) -> VortexResult<()> {
623 vortex_ensure!(
624 matches!(dtype, DType::Binary(_) | DType::Utf8(_)),
625 "FSST arrays must be Binary or Utf8, found {dtype}"
626 );
627
628 if symbols.len() > 255 {
629 vortex_bail!(InvalidArgument: "symbols array must have length <= 255");
630 }
631
632 if symbols.len() != symbol_lengths.len() {
633 vortex_bail!(InvalidArgument: "symbols and symbol_lengths arrays must have same length");
634 }
635
636 Self::validate_symbol_lengths(symbol_lengths.as_slice())?;
637
638 let codes_len = codes_offsets.len().saturating_sub(1);
640 if codes_len != len {
641 vortex_bail!(InvalidArgument: "codes must have same len as outer array");
642 }
643
644 if uncompressed_lengths.len() != len {
645 vortex_bail!(InvalidArgument: "uncompressed_lengths must be same len as codes");
646 }
647
648 if !uncompressed_lengths.dtype().is_int() || uncompressed_lengths.dtype().is_nullable() {
649 vortex_bail!(InvalidArgument: "uncompressed_lengths must have integer type and cannot be nullable, found {}", uncompressed_lengths.dtype());
650 }
651
652 if !codes_offsets.dtype().is_int() || codes_offsets.dtype().is_nullable() {
654 vortex_bail!(InvalidArgument: "codes offsets must be non-nullable integer type, found {}", codes_offsets.dtype());
655 }
656
657 if codes_nullability != dtype.nullability() {
658 vortex_bail!(InvalidArgument: "codes nullability must match outer dtype nullability");
659 }
660
661 if codes_bytes.is_on_host() && codes_offsets.is_host() && !codes_offsets.is_empty() {
663 let last_offset: usize = (&codes_offsets
664 .execute_scalar(codes_offsets.len() - 1, ctx)
665 .vortex_expect("offsets must support scalar_at"))
666 .try_into()
667 .vortex_expect("Failed to convert offset to usize");
668 vortex_ensure!(
669 last_offset <= codes_bytes.len(),
670 InvalidArgument: "Last codes offset {} exceeds codes bytes length {}",
671 last_offset,
672 codes_bytes.len()
673 );
674 }
675
676 Ok(())
677 }
678
679 fn validate_symbol_lengths(symbol_lengths: &[u8]) -> VortexResult<()> {
680 let mut expected = 2;
681 for (idx, &len) in symbol_lengths.iter().enumerate() {
682 if len > 8 || len == 0 {
683 vortex_bail!(InvalidArgument: "symbol length at index {idx} must be between 1 and 8, found {len}");
684 }
685
686 if expected == 1 {
687 if len != 1 {
688 vortex_bail!(InvalidArgument: "symbol length at index {idx} must be 1 after one-byte symbols begin, found {len}");
689 }
690 } else {
691 if len == 1 {
692 expected = 1;
693 }
694
695 if len < expected {
696 vortex_bail!(InvalidArgument: "symbol length at index {idx} violates FSST symbol table ordering");
697 }
698 expected = len;
699 }
700 }
701
702 Ok(())
703 }
704
705 fn validate_parts_from_codes(
707 symbols: &Buffer<Symbol>,
708 symbol_lengths: &Buffer<u8>,
709 codes: &VarBinArray,
710 uncompressed_lengths: &ArrayRef,
711 dtype: &DType,
712 len: usize,
713 ctx: &mut ExecutionCtx,
714 ) -> VortexResult<()> {
715 Self::validate_parts(
716 symbols,
717 symbol_lengths,
718 codes.bytes_handle(),
719 codes.offsets(),
720 codes.dtype().nullability(),
721 uncompressed_lengths,
722 dtype,
723 len,
724 ctx,
725 )
726 }
727
728 pub(crate) unsafe fn new_unchecked(
729 symbols: Buffer<Symbol>,
730 symbol_lengths: Buffer<u8>,
731 codes_bytes: BufferHandle,
732 len: usize,
733 ) -> Self {
734 let symbol_table = Arc::new(FSSTSymbolTable::new(symbols, symbol_lengths));
735 unsafe { Self::new_unchecked_with_symbol_table(symbol_table, codes_bytes, len) }
736 }
737
738 pub(crate) unsafe fn new_unchecked_with_symbol_table(
739 symbol_table: Arc<FSSTSymbolTable>,
740 codes_bytes: BufferHandle,
741 len: usize,
742 ) -> Self {
743 Self {
744 symbol_table,
745 codes_bytes,
746 len,
747 }
748 }
749
750 pub fn len(&self) -> usize {
752 self.len
753 }
754
755 pub fn is_empty(&self) -> bool {
757 self.len == 0
758 }
759
760 pub fn symbols(&self) -> &Buffer<Symbol> {
762 &self.symbol_table.symbols
763 }
764
765 pub fn symbol_lengths(&self) -> &Buffer<u8> {
767 &self.symbol_table.symbol_lengths
768 }
769
770 pub(crate) fn symbol_table(&self) -> Arc<FSSTSymbolTable> {
771 Arc::clone(&self.symbol_table)
772 }
773
774 pub fn codes_bytes_handle(&self) -> &BufferHandle {
776 &self.codes_bytes
777 }
778
779 pub fn codes_bytes(&self) -> &ByteBuffer {
781 self.codes_bytes.as_host()
782 }
783
784 pub fn decompressor(&self) -> Decompressor<'_> {
787 Decompressor::new(self.symbols().as_slice(), self.symbol_lengths().as_slice())
788 }
789
790 pub fn compressor(&self) -> &Compressor {
792 self.symbol_table.compressor()
793 }
794}
795
796pub trait FSSTArrayExt: FSSTArraySlotsExt {
797 fn uncompressed_lengths_dtype(&self) -> &DType {
798 self.uncompressed_lengths().dtype()
799 }
800
801 fn codes(&self) -> VarBinArray {
804 let offsets = self.codes_offsets().clone();
805 let validity =
806 child_to_validity(self.codes_validity(), self.as_ref().dtype().nullability());
807 let codes_bytes = self.codes_bytes_handle().clone();
808 unsafe {
810 VarBinArray::new_unchecked_from_handle(
811 offsets,
812 codes_bytes,
813 DType::Binary(self.as_ref().dtype().nullability()),
814 validity,
815 )
816 }
817 }
818
819 fn codes_dtype(&self) -> DType {
821 DType::Binary(self.as_ref().dtype().nullability())
822 }
823}
824
825impl<T: TypedArrayRef<FSST>> FSSTArrayExt for T {}
826
827impl ValidityVTable<FSST> for FSST {
828 fn validity(array: ArrayView<'_, FSST>) -> VortexResult<Validity> {
829 Ok(child_to_validity(
830 array.codes_validity(),
831 array.dtype().nullability(),
832 ))
833 }
834}
835
836#[cfg(test)]
837mod test {
838 use fsst::Compressor;
839 use fsst::Symbol;
840 use prost::Message;
841 use vortex_array::ArrayPlugin;
842 use vortex_array::IntoArray;
843 use vortex_array::VortexSessionExecute;
844 use vortex_array::array_session;
845 use vortex_array::arrays::VarBinViewArray;
846 use vortex_array::buffer::BufferHandle;
847 use vortex_array::dtype::DType;
848 use vortex_array::dtype::Nullability;
849 use vortex_array::dtype::PType;
850 use vortex_array::test_harness::check_metadata;
851 use vortex_buffer::Buffer;
852 use vortex_error::VortexResult;
853 use vortex_error::vortex_err;
854
855 use crate::FSST;
856 use crate::array::FSSTArrayExt;
857 use crate::array::FSSTArraySlotsExt;
858 use crate::array::FSSTMetadata;
859 use crate::fsst_compress;
860
861 #[test]
862 fn slice_reuses_initialized_compressor() -> VortexResult<()> {
863 let symbols = Buffer::<Symbol>::copy_from([
864 Symbol::from_slice(b"abc00000"),
865 Symbol::from_slice(b"defghijk"),
866 ]);
867 let symbol_lengths = Buffer::<u8>::copy_from([3, 8]);
868
869 let compressor = Compressor::rebuild_from(symbols.as_slice(), symbol_lengths.as_slice());
870 let mut ctx = array_session().create_execution_ctx();
871 let strings = VarBinViewArray::from_iter_str(["abcabcab", "defghijk", "abcxyz"]);
872 let fsst_array = fsst_compress(&strings.into_array(), &compressor, &mut ctx)?;
873
874 let compressor_ptr = fsst_array.compressor() as *const Compressor;
875 let sliced = fsst_array
876 .slice(1..3)?
877 .try_downcast::<FSST>()
878 .map_err(|_| vortex_err!("slice must return an FSST array"))?;
879 let sliced_compressor_ptr = sliced.compressor() as *const Compressor;
880
881 assert_eq!(compressor_ptr, sliced_compressor_ptr);
882 Ok(())
883 }
884
885 #[cfg_attr(miri, ignore)]
886 #[test]
887 fn test_fsst_metadata() {
888 check_metadata(
889 "fsst.metadata",
890 &FSSTMetadata {
891 uncompressed_lengths_ptype: PType::U64 as i32,
892 codes_offsets_ptype: PType::I32 as i32,
893 }
894 .encode_to_vec(),
895 );
896 }
897
898 #[test]
906 fn test_back_compat() -> VortexResult<()> {
907 let symbols = Buffer::<Symbol>::copy_from([
908 Symbol::from_slice(b"abc00000"),
909 Symbol::from_slice(b"defghijk"),
910 ]);
911 let symbol_lengths = Buffer::<u8>::copy_from([3, 8]);
912
913 let compressor = Compressor::rebuild_from(symbols.as_slice(), symbol_lengths.as_slice());
914 let mut ctx = array_session().create_execution_ctx();
915 let input = VarBinViewArray::from_iter_str(["abcabcab", "defghijk"]);
916 let fsst_array = fsst_compress(&input.into_array(), &compressor, &mut ctx)?;
917
918 let compressed_codes = fsst_array.codes();
919
920 let buffers = [
924 BufferHandle::new_host(symbols.into_byte_buffer()),
925 BufferHandle::new_host(symbol_lengths.into_byte_buffer()),
926 ];
927
928 let children = vec![
932 compressed_codes.into_array(),
933 fsst_array.uncompressed_lengths().clone(),
934 ];
935
936 let fsst = ArrayPlugin::deserialize(
937 &FSST,
938 &DType::Utf8(Nullability::NonNullable),
939 2,
940 &FSSTMetadata {
941 uncompressed_lengths_ptype: fsst_array
942 .uncompressed_lengths()
943 .dtype()
944 .as_ptype()
945 .into(),
946 codes_offsets_ptype: 0,
948 }
949 .encode_to_vec(),
950 &buffers,
951 &children.as_slice(),
952 &array_session(),
953 )?;
954
955 let decompressed =
956 fsst.execute::<VarBinViewArray>(&mut array_session().create_execution_ctx())?;
957 let mask = decompressed
958 .validity()?
959 .execute_mask(decompressed.len(), &mut ctx)?;
960 assert!(mask.value(0));
961 assert_eq!(decompressed.bytes_at(0).as_slice(), b"abcabcab".as_ref());
962 assert!(mask.value(1));
963 assert_eq!(decompressed.bytes_at(1).as_slice(), b"defghijk".as_ref());
964 Ok(())
965 }
966}