Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion encodings/fastlanes/src/delta/array/delta_compress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ where

// Pad the remainder to 1024 elements and process as a full chunk.
if !remainder.is_empty() {
let mut padded_chunk = [T::default(); FL_CHUNK_SIZE];
// Repeat the last value for padding to prevent a value-to-zero step from producing
// huge wrapping deltas in the padded tail (same rationale as RLE compression).
let last = *remainder.last().unwrap_or(&T::default());
let mut padded_chunk = [last; FL_CHUNK_SIZE];
padded_chunk[..remainder.len()].copy_from_slice(remainder);
process_chunk(&padded_chunk, &mut output_deltas[full_chunks.len()]);
}
Expand Down Expand Up @@ -169,6 +172,29 @@ mod tests {
Ok(())
}

/// Zero-padding the trailing chunk inflated delta span on unaligned monotone columns,
/// causing DeltaScheme to reject encoding. Pad positions must repeat the last value.
#[test]
fn remainder_pad_preserves_small_delta_span() -> VortexResult<()> {
let mut ctx = SESSION.create_execution_ctx();
for n in [1025usize, 2049] {
let array = PrimitiveArray::from_iter((0..n as u32).map(|i| 1000 + i));
let (_bases, deltas) = delta_compress(&array, &mut ctx)?;
let d = deltas.as_slice::<u32>();
let min = *d.iter().min().unwrap();
let max = *d.iter().max().unwrap();
assert!(
max - min <= 1,
"n={n}: delta span should stay O(1), got min={min} max={max}",
);
assert!(
!d.iter().any(|&v| v > u32::MAX / 2),
"n={n}: padding must not produce wrapping deltas",
);
}
Ok(())
}

/// Regression test: delta + bitpacked encoding must correctly round-trip nullable arrays
/// where null positions contain arbitrary values. Without fill-forward, the delta cumulative
/// sum propagates corrupted values from null positions.
Expand Down
2 changes: 1 addition & 1 deletion encodings/fastlanes/src/delta/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct DeltaSlots {
///
/// A DeltaArray comprises a sequence of _chunks_ each representing exactly 1,024
/// delta-encoded values. If the input array length is not a multiple of 1,024, the last chunk
/// is padded with zeros to fill a complete 1,024-element chunk.
/// is padded with the last value to fill a complete 1,024-element chunk.
///
/// # Examples
///
Expand Down
33 changes: 33 additions & 0 deletions vortex-btrblocks/src/schemes/integer/scheme_selection_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,39 @@ fn test_delta_compressed() -> VortexResult<()> {
Ok(())
}

/// Same as [`test_delta_compressed`], but with a length that is not a multiple of 1024.
/// Zero-padding the trailing chunk used to inflate the delta span and cause DeltaScheme to skip.
#[cfg(feature = "unstable_encodings")]
#[test]
fn test_delta_compressed_unaligned_length() -> VortexResult<()> {
let mut ctx = SESSION.create_execution_ctx();
use vortex_array::assert_arrays_eq;
use vortex_fastlanes::Delta;

let mut rng = StdRng::seed_from_u64(7u64);
let mut value = 500_000i32;
let values: Vec<i32> = (0..1025)
.map(|_| {
value += 1 + (rng.next_u32() % 6) as i32;
value
})
.collect();
let array = PrimitiveArray::new(Buffer::copy_from(&values), Validity::NonNullable);

let btr = BtrBlocksCompressor::default();
let compressed = btr.compress(
&array.clone().into_array(),
&mut SESSION.create_execution_ctx(),
)?;
assert!(
compressed.is::<Delta>(),
"expected Delta for unaligned near-monotone column, got tree:\n{}",
compressed.display_tree()
);
assert_arrays_eq!(compressed, array.into_array(), &mut ctx);
Ok(())
}

/// Returns true if any `Delta` array appears below an ancestor `Delta` in the tree.
#[cfg(feature = "unstable_encodings")]
fn has_nested_delta(array: &vortex_array::ArrayRef, under_delta: bool) -> bool {
Expand Down
Loading