lance_encoding/encodings/physical/
constant.rs1use crate::{
7 buffer::LanceBuffer,
8 compression::{
9 BlockCompressor, BlockDecompressor, FixedPerValueDecompressor, require_no_block_payload,
10 },
11 data::{AllNullDataBlock, ConstantDataBlock, DataBlock, FixedWidthDataBlock},
12 encodings::physical::{checked_fixed_values, try_vec_with_capacity},
13 format::{EncodingUtils, metadata::CompressiveEncoding},
14};
15
16use lance_core::{Error, Result};
17
18#[derive(Debug)]
20pub struct ConstantEncoder {
21 bits_per_value: u64,
22 value: u64,
23}
24
25impl ConstantEncoder {
26 pub fn new(bits_per_value: u64, value: u64) -> Self {
27 Self {
28 bits_per_value,
29 value,
30 }
31 }
32
33 pub(crate) fn encoding(&self, num_values: u64) -> Result<CompressiveEncoding> {
36 if !matches!(self.bits_per_value, 32 | 64) {
37 return Err(Error::invalid_input(format!(
38 "Constant block compression only supports 32 or 64-bit values, got {}",
39 self.bits_per_value
40 )));
41 }
42 if num_values == 0 {
43 return Ok(EncodingUtils::constant(None));
44 }
45 let scalar = if self.bits_per_value == 32 {
46 let value = u32::try_from(self.value).map_err(|_| {
47 Error::invalid_input(format!("Constant value {} exceeds u32::MAX", self.value))
48 })?;
49 bytes::Bytes::copy_from_slice(&value.to_le_bytes())
50 } else {
51 bytes::Bytes::copy_from_slice(&self.value.to_le_bytes())
52 };
53 Ok(EncodingUtils::constant(Some(scalar)))
54 }
55}
56
57impl BlockCompressor for ConstantEncoder {
58 fn compress(&self, data: DataBlock) -> Result<(Option<LanceBuffer>, CompressiveEncoding)> {
59 let DataBlock::FixedWidth(data) = data else {
60 return Err(Error::invalid_input(
61 "Constant block compression requires fixed-width data",
62 ));
63 };
64 if data.bits_per_value != self.bits_per_value {
65 return Err(Error::invalid_input(format!(
66 "Constant codec expects {}-bit values, got {}",
67 self.bits_per_value, data.bits_per_value
68 )));
69 }
70 let encoding = self.encoding(data.num_values)?;
71 match self.bits_per_value {
72 32 => {
73 if checked_fixed_values::<u32>(&data, "Constant input")?
74 .iter()
75 .any(|candidate| u64::from(*candidate) != self.value)
76 {
77 return Err(Error::invalid_input(
78 "Constant input contains a different value",
79 ));
80 }
81 }
82 64 => {
83 if checked_fixed_values::<u64>(&data, "Constant input")?
84 .iter()
85 .any(|candidate| *candidate != self.value)
86 {
87 return Err(Error::invalid_input(
88 "Constant input contains a different value",
89 ));
90 }
91 }
92 _ => unreachable!("constant width was validated above"),
93 };
94 Ok((None, encoding))
95 }
96}
97
98#[derive(Debug)]
100pub(crate) struct ConstantBlockDecompressor {
101 bits_per_value: u64,
102 value: Option<u64>,
103}
104
105impl ConstantBlockDecompressor {
106 pub(crate) fn new(bits_per_value: u64, value: Option<u64>) -> Self {
107 Self {
108 bits_per_value,
109 value,
110 }
111 }
112}
113
114impl BlockDecompressor for ConstantBlockDecompressor {
115 fn decompress(&self, data: Option<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
116 require_no_block_payload(data, "Constant")?;
117 if self.value.is_none() != (num_values == 0) {
118 return Err(Error::invalid_input(
119 "Constant scalar must be absent exactly when cardinality is zero",
120 ));
121 }
122 let value = self.value.unwrap_or(0);
123 let output_len = usize::try_from(num_values)
124 .map_err(|_| Error::invalid_input("Constant output cardinality does not fit usize"))?;
125 let data = match self.bits_per_value {
126 32 => {
127 let value = u32::try_from(value).map_err(|_| {
128 Error::invalid_input(format!("Constant value {value} exceeds u32::MAX"))
129 })?;
130 let mut values = try_vec_with_capacity::<u32>(num_values, "Constant output")?;
131 values.resize(output_len, value);
132 LanceBuffer::reinterpret_vec(values)
133 }
134 64 => {
135 let mut values = try_vec_with_capacity::<u64>(num_values, "Constant output")?;
136 values.resize(output_len, value);
137 LanceBuffer::reinterpret_vec(values)
138 }
139 bits_per_value => {
140 return Err(Error::invalid_input(format!(
141 "Constant block decompression only supports 32 or 64-bit values, got {bits_per_value}"
142 )));
143 }
144 };
145 Ok(DataBlock::FixedWidth(FixedWidthDataBlock {
146 bits_per_value: self.bits_per_value,
147 data,
148 num_values,
149 block_info: Default::default(),
150 }))
151 }
152
153 fn requires_payload(&self) -> bool {
154 false
155 }
156}
157
158#[derive(Debug)]
160pub struct ConstantDecompressor {
161 scalar: Option<LanceBuffer>,
162}
163
164impl ConstantDecompressor {
165 pub fn new(scalar: Option<LanceBuffer>) -> Self {
166 Self { scalar }
167 }
168}
169
170impl BlockDecompressor for ConstantDecompressor {
171 fn decompress(&self, data: Option<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
172 require_no_block_payload(data, "Constant")?;
173 if let Some(scalar) = self.scalar.clone() {
174 Ok(DataBlock::Constant(ConstantDataBlock {
175 data: scalar,
176 num_values,
177 }))
178 } else {
179 Ok(DataBlock::AllNull(AllNullDataBlock { num_values }))
180 }
181 }
182
183 fn requires_payload(&self) -> bool {
184 false
185 }
186}
187
188impl FixedPerValueDecompressor for ConstantDecompressor {
189 fn decompress(&self, _data: FixedWidthDataBlock, num_values: u64) -> Result<DataBlock> {
190 if let Some(scalar) = self.scalar.clone() {
191 Ok(DataBlock::Constant(ConstantDataBlock {
192 data: scalar,
193 num_values,
194 }))
195 } else {
196 Ok(DataBlock::AllNull(AllNullDataBlock { num_values }))
197 }
198 }
199
200 fn bits_per_value(&self) -> u64 {
201 self.scalar
202 .as_ref()
203 .map(|s| s.len() as u64 * 8)
204 .unwrap_or(0)
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[rstest::rstest]
213 #[case(32)]
214 #[case(64)]
215 fn constant_metadata_round_trip(#[case] bits: u64) {
216 let encoder = ConstantEncoder::new(bits, 7);
217 for count in [0, 3] {
218 let expected = if bits == 32 {
219 LanceBuffer::reinterpret_vec(vec![7_u32; count as usize])
220 } else {
221 LanceBuffer::reinterpret_vec(vec![7_u64; count as usize])
222 };
223 let (payload, encoding) = encoder
224 .compress(DataBlock::FixedWidth(FixedWidthDataBlock {
225 data: expected.clone(),
226 bits_per_value: bits,
227 num_values: count,
228 block_info: Default::default(),
229 }))
230 .unwrap();
231 assert!(payload.is_none());
232 let decoder = crate::encodings::physical::sequence::SequenceDecoder::try_new(
233 &encoding, bits, count,
234 )
235 .unwrap();
236 let decoded = decoder.decode(payload).unwrap().as_fixed_width().unwrap();
237 assert_eq!(decoded.num_values, count);
238 assert_eq!(decoded.data.as_ref(), expected.as_ref());
239 }
240 assert_eq!(
241 encoder.encoding(u64::MAX).unwrap(),
242 encoder.encoding(1).unwrap()
243 );
244 }
245
246 #[test]
247 fn constant_metadata_validates_width_and_scalar() {
248 for encoder in [
249 ConstantEncoder::new(16, 1),
250 ConstantEncoder::new(32, u64::MAX),
251 ] {
252 let error = encoder.encoding(1).unwrap_err();
253 assert!(matches!(error, Error::InvalidInput { .. }));
254 assert!(error.to_string().contains("Constant"));
255 }
256 }
257
258 #[test]
259 fn block_constant_requires_no_payload() {
260 let decompressor = ConstantDecompressor::new(None);
261
262 assert!(!decompressor.requires_payload());
263 assert!(matches!(
264 BlockDecompressor::decompress(&decompressor, None, 3).unwrap(),
265 DataBlock::AllNull(AllNullDataBlock { num_values: 3 })
266 ));
267 assert!(
268 BlockDecompressor::decompress(&decompressor, Some(LanceBuffer::empty()), 3).is_err()
269 );
270 }
271}