lance_encoding/encodings/physical/
range.rs1use super::{checked_fixed_values, constant::ConstantEncoder, try_vec_with_capacity};
7use crate::{
8 buffer::LanceBuffer,
9 compression::{BlockCompressor, BlockDecompressor, require_no_block_payload},
10 data::{BlockInfo, DataBlock, FixedWidthDataBlock},
11 format::{EncodingUtils, metadata::CompressiveEncoding},
12};
13use lance_core::{Error, Result};
14
15pub(crate) fn checked_range_last(
16 bits_per_value: u64,
17 start: u64,
18 step: u64,
19 num_values: u64,
20) -> Result<u64> {
21 if !matches!(bits_per_value, 32 | 64) {
22 return Err(Error::invalid_input(format!(
23 "Range only supports 32 or 64-bit values, got {bits_per_value}"
24 )));
25 }
26 if step == 0 {
27 return Err(Error::invalid_input("Range step must be positive"));
28 }
29 if num_values < 2 {
30 return Err(Error::invalid_input(format!(
31 "Range requires at least 2 values, got {num_values}"
32 )));
33 }
34
35 let distance = step.checked_mul(num_values - 1).ok_or_else(|| {
36 Error::invalid_input(format!(
37 "Range step multiplication overflows: step={step}, num_values={num_values}"
38 ))
39 })?;
40 let last = start.checked_add(distance).ok_or_else(|| {
41 Error::invalid_input(format!(
42 "Range final value overflows: start={start}, step={step}, num_values={num_values}"
43 ))
44 })?;
45 if bits_per_value == 32 && last > u32::MAX as u64 {
46 return Err(Error::invalid_input(format!(
47 "Range final value {last} exceeds u32::MAX"
48 )));
49 }
50 Ok(last)
51}
52
53#[derive(Debug)]
54pub struct RangeEncoder {
56 bits_per_value: u64,
57 start: u64,
58 step: u64,
59}
60
61impl RangeEncoder {
62 pub fn new(bits_per_value: u64, start: u64, step: u64) -> Self {
64 Self {
65 bits_per_value,
66 start,
67 step,
68 }
69 }
70
71 pub(crate) fn encoding(&self, num_values: u64) -> Result<CompressiveEncoding> {
74 if num_values < 2 {
75 return ConstantEncoder::new(self.bits_per_value, self.start).encoding(num_values);
76 }
77 checked_range_last(self.bits_per_value, self.start, self.step, num_values)?;
78 Ok(EncodingUtils::range(
79 self.bits_per_value,
80 self.start,
81 self.step,
82 ))
83 }
84}
85
86impl BlockCompressor for RangeEncoder {
87 fn compress(&self, data: DataBlock) -> Result<(Option<LanceBuffer>, CompressiveEncoding)> {
88 let DataBlock::FixedWidth(data) = data else {
89 return Err(Error::invalid_input(
90 "Range encoding requires a fixed-width data block",
91 ));
92 };
93 if data.bits_per_value != self.bits_per_value {
94 return Err(Error::invalid_input(format!(
95 "Range codec expects {}-bit values, got {}",
96 self.bits_per_value, data.bits_per_value
97 )));
98 }
99 let encoding = self.encoding(data.num_values)?;
100
101 match self.bits_per_value {
102 32 => validate_values(
103 checked_fixed_values::<u32>(&data, "Range input")?
104 .iter()
105 .map(|value| u64::from(*value)),
106 self.start,
107 self.step,
108 )?,
109 64 => validate_values(
110 checked_fixed_values::<u64>(&data, "Range input")?
111 .iter()
112 .copied(),
113 self.start,
114 self.step,
115 )?,
116 _ => unreachable!("range width was validated above"),
117 }
118 Ok((None, encoding))
119 }
120}
121
122fn validate_values(values: impl Iterator<Item = u64>, start: u64, step: u64) -> Result<()> {
123 for (index, value) in values.enumerate() {
124 let expected = step
125 .checked_mul(index as u64)
126 .and_then(|distance| start.checked_add(distance))
127 .ok_or_else(|| Error::invalid_input("Range value calculation overflows"))?;
128 if value != expected {
129 return Err(Error::invalid_input(format!(
130 "Range input mismatch at index {index}: expected {expected}, got {value}"
131 )));
132 }
133 }
134 Ok(())
135}
136
137#[derive(Debug)]
138pub(crate) struct RangeDecompressor {
139 bits_per_value: u64,
140 start: u64,
141 step: u64,
142}
143
144impl RangeDecompressor {
145 pub(crate) fn new(bits_per_value: u64, start: u64, step: u64) -> Self {
146 Self {
147 bits_per_value,
148 start,
149 step,
150 }
151 }
152}
153
154impl BlockDecompressor for RangeDecompressor {
155 fn decompress(&self, data: Option<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
156 require_no_block_payload(data, "Range")?;
157 checked_range_last(self.bits_per_value, self.start, self.step, num_values)?;
158 materialize_range(self.bits_per_value, self.start, self.step, num_values)
159 }
160
161 fn requires_payload(&self) -> bool {
162 false
163 }
164}
165
166fn materialize_range(
167 bits_per_value: u64,
168 start: u64,
169 step: u64,
170 num_values: u64,
171) -> Result<DataBlock> {
172 let data = match bits_per_value {
173 32 => {
174 let mut current = u32::try_from(start).map_err(|_| {
175 Error::invalid_input(format!("Range value {start} exceeds u32::MAX"))
176 })?;
177 let step = u32::try_from(step)
178 .map_err(|_| Error::invalid_input(format!("Range step {step} exceeds u32::MAX")))?;
179 let mut values = try_vec_with_capacity::<u32>(num_values, "Range output")?;
180 for index in 0..num_values {
181 values.push(current);
182 if index + 1 < num_values {
183 current += step;
184 }
185 }
186 LanceBuffer::reinterpret_vec(values)
187 }
188 64 => {
189 let mut current = start;
190 let mut values = try_vec_with_capacity::<u64>(num_values, "Range output")?;
191 for index in 0..num_values {
192 values.push(current);
193 if index + 1 < num_values {
194 current += step;
195 }
196 }
197 LanceBuffer::reinterpret_vec(values)
198 }
199 _ => unreachable!("range width was validated before materialization"),
200 };
201 Ok(DataBlock::FixedWidth(FixedWidthDataBlock {
202 bits_per_value,
203 data,
204 num_values,
205 block_info: BlockInfo::default(),
206 }))
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[rstest::rstest]
214 #[case(32)]
215 #[case(64)]
216 fn range_metadata_normalizes_short_sequences(#[case] bits: u64) {
217 let encoder = RangeEncoder::new(bits, 3, 5);
218 for count in 0..=2 {
219 let data = if bits == 32 {
220 LanceBuffer::reinterpret_vec(
221 (0..count).map(|i| (3 + 5 * i) as u32).collect::<Vec<_>>(),
222 )
223 } else {
224 LanceBuffer::reinterpret_vec((0..count).map(|i| 3 + 5 * i).collect::<Vec<u64>>())
225 };
226 let (payload, encoding) = encoder
227 .compress(DataBlock::FixedWidth(FixedWidthDataBlock {
228 data: data.clone(),
229 bits_per_value: bits,
230 num_values: count,
231 block_info: Default::default(),
232 }))
233 .unwrap();
234 assert!(payload.is_none());
235 let decoder = crate::encodings::physical::sequence::SequenceDecoder::try_new(
236 &encoding, bits, count,
237 )
238 .unwrap();
239 let decoded = decoder.decode(payload).unwrap().as_fixed_width().unwrap();
240 assert_eq!(decoded.num_values, count);
241 assert_eq!(decoded.data.as_ref(), data.as_ref());
242 }
243 }
244
245 #[test]
246 fn range_metadata_checks_bounds_without_materializing() {
247 let count = 1_000_000_000_000;
248 let encoding = RangeEncoder::new(64, 3, 5).encoding(count).unwrap();
249 let decoder =
250 crate::encodings::physical::sequence::SequenceDecoder::try_new(&encoding, 64, count)
251 .unwrap();
252 assert_eq!(
253 decoder.metadata(),
254 Some(
255 crate::encodings::physical::sequence::SequenceMetadata::Range { start: 3, step: 5 }
256 )
257 );
258 for encoder in [
259 RangeEncoder::new(32, u32::MAX as u64, 1),
260 RangeEncoder::new(64, u64::MAX, 1),
261 RangeEncoder::new(64, 0, 0),
262 ] {
263 let error = encoder.encoding(2).unwrap_err();
264 assert!(matches!(error, Error::InvalidInput { .. }));
265 assert!(error.to_string().contains("Range"));
266 }
267 }
268 #[test]
269 fn range_round_trip_u32() {
270 let input = DataBlock::FixedWidth(FixedWidthDataBlock {
271 bits_per_value: 32,
272 data: LanceBuffer::reinterpret_vec(vec![3_u32, 8, 13, 18]),
273 num_values: 4,
274 block_info: BlockInfo::default(),
275 });
276 let (payload, encoding) = RangeEncoder::new(32, 3, 5).compress(input).unwrap();
277 assert!(payload.is_none());
278 assert_eq!(encoding, EncodingUtils::range(32, 3, 5));
279
280 let decoded = RangeDecompressor::new(32, 3, 5)
281 .decompress(payload, 4)
282 .unwrap()
283 .as_fixed_width()
284 .unwrap();
285 assert_eq!(
286 decoded.data.borrow_to_typed_slice::<u32>().as_ref(),
287 &[3, 8, 13, 18]
288 );
289 }
290
291 #[test]
292 fn range_rejects_overflow() {
293 let error = checked_range_last(32, u32::MAX as u64, 1, 2).unwrap_err();
294 assert!(error.to_string().contains("exceeds u32::MAX"));
295 }
296}