lance_encoding/encodings/physical/
fsst.rs1use lance_core::{Error, Result};
19
20use crate::{
21 buffer::LanceBuffer,
22 compression::{MiniBlockDecompressor, VariablePerValueDecompressor},
23 data::{BlockInfo, DataBlock, VariableWidthBlock},
24 encodings::logical::primitive::{
25 fullzip::{PerValueCompressor, PerValueDataBlock},
26 miniblock::{MiniBlockCompressed, MiniBlockCompressionContext, MiniBlockCompressor},
27 },
28 format::{
29 ProtobufUtils21,
30 pb21::{self, CompressiveEncoding},
31 },
32};
33
34use super::binary::BinaryMiniBlockEncoder;
35
36struct FsstCompressed {
37 data: VariableWidthBlock,
38 symbol_table: Vec<u8>,
39}
40
41impl FsstCompressed {
42 fn fsst_compress(data: DataBlock) -> Result<Self> {
43 match data {
44 DataBlock::VariableWidth(variable_width) => {
45 match variable_width.bits_per_offset {
46 32 => {
47 let offsets = variable_width.offsets.borrow_to_typed_slice::<i32>();
48 let offsets_slice = offsets.as_ref();
49 let bytes_data = variable_width.data.into_buffer();
50
51 let mut dest_offsets = vec![0_i32; offsets_slice.len() * 2];
53 let mut dest_values = vec![0_u8; bytes_data.len() * 2];
54 let mut symbol_table = vec![0_u8; fsst::fsst::FSST_SYMBOL_TABLE_SIZE];
55
56 fsst::fsst::compress(
58 &mut symbol_table,
59 bytes_data.as_slice(),
60 offsets_slice,
61 &mut dest_values,
62 &mut dest_offsets,
63 )?;
64
65 let compressed = VariableWidthBlock {
67 data: LanceBuffer::reinterpret_vec(dest_values),
68 bits_per_offset: 32,
69 offsets: LanceBuffer::reinterpret_vec(dest_offsets),
70 num_values: variable_width.num_values,
71 block_info: BlockInfo::new(),
72 };
73
74 Ok(Self {
75 data: compressed,
76 symbol_table,
77 })
78 }
79 64 => {
80 let offsets = variable_width.offsets.borrow_to_typed_slice::<i64>();
81 let offsets_slice = offsets.as_ref();
82 let bytes_data = variable_width.data.into_buffer();
83
84 let mut dest_offsets = vec![0_i64; offsets_slice.len() * 2];
86 let mut dest_values = vec![0_u8; bytes_data.len() * 2];
87 let mut symbol_table = vec![0_u8; fsst::fsst::FSST_SYMBOL_TABLE_SIZE];
88
89 fsst::fsst::compress(
91 &mut symbol_table,
92 bytes_data.as_slice(),
93 offsets_slice,
94 &mut dest_values,
95 &mut dest_offsets,
96 )?;
97
98 let compressed = VariableWidthBlock {
100 data: LanceBuffer::reinterpret_vec(dest_values),
101 bits_per_offset: 64,
102 offsets: LanceBuffer::reinterpret_vec(dest_offsets),
103 num_values: variable_width.num_values,
104 block_info: BlockInfo::new(),
105 };
106
107 Ok(Self {
108 data: compressed,
109 symbol_table,
110 })
111 }
112 _ => panic!(
113 "Unsupported offsets type {}",
114 variable_width.bits_per_offset
115 ),
116 }
117 }
118 _ => Err(Error::invalid_input_source(
119 format!(
120 "Cannot compress a data block of type {} with FsstEncoder",
121 data.name()
122 )
123 .into(),
124 )),
125 }
126 }
127}
128
129#[derive(Debug, Default)]
130pub struct FsstMiniBlockEncoder {
131 minichunk_size: Option<i64>,
132}
133
134impl FsstMiniBlockEncoder {
135 pub fn new(minichunk_size: Option<i64>) -> Self {
136 Self { minichunk_size }
137 }
138}
139
140impl MiniBlockCompressor for FsstMiniBlockEncoder {
141 fn compress(
142 &self,
143 context: MiniBlockCompressionContext,
144 data: DataBlock,
145 ) -> Result<(MiniBlockCompressed, CompressiveEncoding)> {
146 let compressed = FsstCompressed::fsst_compress(data)?;
147
148 let data_block = DataBlock::VariableWidth(compressed.data);
149
150 let binary_compressor = Box::new(BinaryMiniBlockEncoder::new(self.minichunk_size))
152 as Box<dyn MiniBlockCompressor>;
153
154 let (binary_miniblock_compressed, binary_array_encoding) =
155 binary_compressor.compress(context, data_block)?;
156
157 Ok((
158 binary_miniblock_compressed,
159 ProtobufUtils21::fsst(binary_array_encoding, compressed.symbol_table),
160 ))
161 }
162}
163
164#[derive(Debug)]
165pub struct FsstPerValueEncoder {
166 inner: Box<dyn PerValueCompressor>,
167}
168
169impl FsstPerValueEncoder {
170 pub fn new(inner: Box<dyn PerValueCompressor>) -> Self {
171 Self { inner }
172 }
173}
174
175impl PerValueCompressor for FsstPerValueEncoder {
176 fn compress(&self, data: DataBlock) -> Result<(PerValueDataBlock, CompressiveEncoding)> {
177 let compressed = FsstCompressed::fsst_compress(data)?;
178
179 let data_block = DataBlock::VariableWidth(compressed.data);
180
181 let (binary_compressed, binary_array_encoding) = self.inner.compress(data_block)?;
182
183 Ok((
184 binary_compressed,
185 ProtobufUtils21::fsst(binary_array_encoding, compressed.symbol_table),
186 ))
187 }
188}
189
190#[derive(Debug)]
191pub struct FsstPerValueDecompressor {
192 symbol_table: LanceBuffer,
193 inner_decompressor: Box<dyn VariablePerValueDecompressor>,
194}
195
196impl FsstPerValueDecompressor {
197 pub fn new(
198 symbol_table: LanceBuffer,
199 inner_decompressor: Box<dyn VariablePerValueDecompressor>,
200 ) -> Self {
201 Self {
202 symbol_table,
203 inner_decompressor,
204 }
205 }
206}
207
208impl VariablePerValueDecompressor for FsstPerValueDecompressor {
209 fn decompress(&self, data: VariableWidthBlock) -> Result<DataBlock> {
210 let compressed_variable_data = self
212 .inner_decompressor
213 .decompress(data)?
214 .as_variable_width()
215 .unwrap();
216
217 let bytes = compressed_variable_data.data.borrow_to_typed_slice::<u8>();
219 let bytes = bytes.as_ref();
220
221 match compressed_variable_data.bits_per_offset {
222 32 => {
223 let offsets = compressed_variable_data
224 .offsets
225 .borrow_to_typed_slice::<i32>();
226 let offsets = offsets.as_ref();
227 let num_values = compressed_variable_data.num_values;
228
229 let mut decompress_bytes_buf = vec![0u8; bytes.len() * 8];
232 let mut decompress_offset_buf = vec![0i32; offsets.len()];
233 fsst::fsst::decompress(
234 &self.symbol_table,
235 bytes,
236 offsets,
237 &mut decompress_bytes_buf,
238 &mut decompress_offset_buf,
239 )?;
240
241 decompress_offset_buf.truncate((num_values + 1) as usize);
243
244 Ok(DataBlock::VariableWidth(VariableWidthBlock {
245 data: LanceBuffer::from(decompress_bytes_buf),
246 offsets: LanceBuffer::reinterpret_vec(decompress_offset_buf),
247 bits_per_offset: 32,
248 num_values,
249 block_info: BlockInfo::new(),
250 }))
251 }
252 64 => {
253 let offsets = compressed_variable_data
254 .offsets
255 .borrow_to_typed_slice::<i64>();
256 let offsets = offsets.as_ref();
257 let num_values = compressed_variable_data.num_values;
258
259 let mut decompress_bytes_buf = vec![0u8; bytes.len() * 8];
262 let mut decompress_offset_buf = vec![0i64; offsets.len()];
263 fsst::fsst::decompress(
264 &self.symbol_table,
265 bytes,
266 offsets,
267 &mut decompress_bytes_buf,
268 &mut decompress_offset_buf,
269 )?;
270
271 decompress_offset_buf.truncate((num_values + 1) as usize);
273
274 Ok(DataBlock::VariableWidth(VariableWidthBlock {
275 data: LanceBuffer::from(decompress_bytes_buf),
276 offsets: LanceBuffer::reinterpret_vec(decompress_offset_buf),
277 bits_per_offset: 64,
278 num_values,
279 block_info: BlockInfo::new(),
280 }))
281 }
282 _ => panic!(
283 "Unsupported offset type {}",
284 compressed_variable_data.bits_per_offset,
285 ),
286 }
287 }
288}
289
290#[derive(Debug)]
291pub struct FsstMiniBlockDecompressor {
292 symbol_table: LanceBuffer,
293 inner_decompressor: Box<dyn MiniBlockDecompressor>,
294}
295
296impl FsstMiniBlockDecompressor {
297 pub fn new(
298 description: &pb21::Fsst,
299 inner_decompressor: Box<dyn MiniBlockDecompressor>,
300 ) -> Self {
301 Self {
302 symbol_table: LanceBuffer::from_bytes(description.symbol_table.clone(), 1),
303 inner_decompressor,
304 }
305 }
306}
307
308impl MiniBlockDecompressor for FsstMiniBlockDecompressor {
309 fn decompress(&self, data: Vec<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
310 let compressed_data_block = self.inner_decompressor.decompress(data, num_values)?;
313 let DataBlock::VariableWidth(compressed_data_block) = compressed_data_block else {
314 panic!("BinaryMiniBlockDecompressor should output VariableWidth DataBlock")
315 };
316
317 let bytes = &compressed_data_block.data;
319 let (decompress_bytes_buf, decompress_offset_buf) =
320 if compressed_data_block.bits_per_offset == 64 {
321 let offsets = compressed_data_block.offsets.borrow_to_typed_slice::<i64>();
322 let offsets = offsets.as_ref();
323
324 let mut decompress_bytes_buf = vec![0u8; bytes.len() * 8];
327 let mut decompress_offset_buf = vec![0i64; offsets.len()];
328 fsst::fsst::decompress(
329 &self.symbol_table,
330 bytes.as_ref(),
331 offsets,
332 &mut decompress_bytes_buf,
333 &mut decompress_offset_buf,
334 )?;
335
336 decompress_offset_buf.truncate((num_values + 1) as usize);
338
339 (
340 decompress_bytes_buf,
341 LanceBuffer::reinterpret_vec(decompress_offset_buf),
342 )
343 } else {
344 let offsets = compressed_data_block.offsets.borrow_to_typed_slice::<i32>();
345 let offsets = offsets.as_ref();
346
347 let mut decompress_bytes_buf = vec![0u8; bytes.len() * 8];
350 let mut decompress_offset_buf = vec![0i32; offsets.len()];
351 fsst::fsst::decompress(
352 &self.symbol_table,
353 bytes.as_ref(),
354 offsets,
355 &mut decompress_bytes_buf,
356 &mut decompress_offset_buf,
357 )?;
358
359 decompress_offset_buf.truncate((num_values + 1) as usize);
361
362 (
363 decompress_bytes_buf,
364 LanceBuffer::reinterpret_vec(decompress_offset_buf),
365 )
366 };
367
368 Ok(DataBlock::VariableWidth(VariableWidthBlock {
369 data: LanceBuffer::from(decompress_bytes_buf),
370 offsets: decompress_offset_buf,
371 bits_per_offset: compressed_data_block.bits_per_offset,
372 num_values,
373 block_info: BlockInfo::new(),
374 }))
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use std::collections::HashMap;
381
382 use lance_datagen::{ByteCount, RowCount};
383
384 use crate::testing::{TestCases, check_round_trip_encoding_of_data};
385
386 #[test_log::test(tokio::test)]
387 async fn test_fsst() {
388 let test_cases = TestCases::default()
389 .with_expected_encoding("fsst")
390 .with_structural_encodings();
391
392 let arr = lance_datagen::gen_batch()
394 .anon_col(lance_datagen::array::rand_utf8(ByteCount::from(100), false))
395 .into_batch_rows(RowCount::from(5000))
396 .unwrap()
397 .column(0)
398 .clone();
399
400 let metadata_explicit =
403 HashMap::from([("lance-encoding:compression".to_string(), "fsst".to_string())]);
404 check_round_trip_encoding_of_data(vec![arr.clone()], &test_cases, metadata_explicit).await;
405
406 check_round_trip_encoding_of_data(vec![arr], &test_cases, HashMap::new()).await;
409 }
410}