diff --git a/encodings/fastlanes/src/bitpacking/mod.rs b/encodings/fastlanes/src/bitpacking/mod.rs index 30334f1bbee..db3776ece8f 100644 --- a/encodings/fastlanes/src/bitpacking/mod.rs +++ b/encodings/fastlanes/src/bitpacking/mod.rs @@ -20,6 +20,8 @@ mod vtable; pub(crate) use plugin::BitPackedPatchedPlugin; pub use plugin::BitPackedPlugin; +pub use plugin::bitpacked_v1_id; +pub use plugin::bitpacked_v2_id; pub use vtable::BitPacked; pub use vtable::BitPackedArray; diff --git a/encodings/fastlanes/src/bitpacking/plugin.rs b/encodings/fastlanes/src/bitpacking/plugin.rs deleted file mode 100644 index 5637d69e5b1..00000000000 --- a/encodings/fastlanes/src/bitpacking/plugin.rs +++ /dev/null @@ -1,495 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -//! [`ArrayPlugin`] implementations for `BitPacked`. -//! -//! [`BitPackedPlugin`] owns serde for `fastlanes.bitpacked`. [`BitPackedPatchedPlugin`] lets you -//! load in and deserialize a `BitPacked` array with interior patches as a `PatchedArray` that wraps -//! a patchless `BitPacked` array. -//! -//! This enables zero-cost backward compatibility with previously written datasets. - -use prost::Message; -use vortex_array::Array; -use vortex_array::ArrayDeserialization; -use vortex_array::ArrayId; -use vortex_array::ArrayParts; -use vortex_array::ArrayPlugin; -use vortex_array::ArrayRef; -use vortex_array::ArraySerialization; -use vortex_array::ArraySlots; -use vortex_array::ArrayVTable; -use vortex_array::IntoArray; -use vortex_array::VortexSessionExecute; -use vortex_array::arrays::Patched; -use vortex_array::patches::Patches; -use vortex_array::patches::PatchesData; -use vortex_array::patches::PatchesMetadata; -use vortex_array::validity::Validity; -use vortex_array::vtable::validity_to_child; -use vortex_error::VortexResult; -use vortex_error::vortex_bail; -use vortex_error::vortex_ensure_eq; -use vortex_error::vortex_err; -use vortex_error::vortex_panic; -use vortex_session::VortexSession; - -use crate::BitPacked; -use crate::BitPackedArray; -use crate::BitPackedArrayExt; -use crate::BitPackedData; -use crate::BitWidthsView; -use crate::bitpacking::array::BitPackedSlots; - -#[derive(Clone, prost::Message)] -pub struct BitPackedMetadata { - #[prost(uint32, tag = "1")] - pub(crate) bit_width: u32, - #[prost(uint32, tag = "2")] - pub(crate) offset: u32, // must be <1024 - #[prost(message, optional, tag = "3")] - pub(crate) patches: Option, -} - -/// Serde for the [`BitPacked`] array. -/// -/// Register this plugin, or call [`crate::initialize`], to enable serde. Direct registration of -/// [`BitPacked`] does not support serde. -#[derive(Clone, Debug)] -pub struct BitPackedPlugin; - -impl ArrayPlugin for BitPackedPlugin { - fn id(&self) -> ArrayId { - ArrayVTable::id(&BitPacked) - } - - fn serialize( - &self, - array: &ArrayRef, - _session: &VortexSession, - ) -> VortexResult> { - let view = array.as_opt::().ok_or_else(|| { - vortex_err!("BitPacked plugin cannot serialize {}", array.encoding_id()) - })?; - let BitWidthsView::Global(bit_width) = view.bit_widths() else { - vortex_bail!("BitPacked plugin cannot serialize per-block bit widths"); - }; - let metadata = BitPackedMetadata { - bit_width: u32::from(bit_width), - offset: view.offset() as u32, - patches: view - .patches() - .map(|p| p.to_metadata(view.len(), view.dtype())) - .transpose()?, - } - .encode_to_vec(); - Ok(Some(ArraySerialization::from_array( - self.id(), - array, - metadata, - ))) - } - - fn deserialize( - &self, - parts: ArrayDeserialization<'_>, - _session: &VortexSession, - ) -> VortexResult { - vortex_ensure_eq!( - parts.serialized_id, - self.id(), - "BitPacked plugin does not recognize serialized ID" - ); - let ArrayDeserialization { - dtype, - len, - metadata, - buffers, - children, - .. - } = parts; - - let metadata = BitPackedMetadata::decode(metadata)?; - if buffers.len() != 1 { - vortex_bail!("Expected 1 buffer, got {}", buffers.len()); - } - let packed = buffers[0].clone(); - - let load_validity = |child_idx: usize| { - if children.len() == child_idx { - Ok(Validity::from(dtype.nullability())) - } else if children.len() == child_idx + 1 { - let validity = children.get(child_idx, &Validity::DTYPE, len)?; - Ok(Validity::Array(validity)) - } else { - vortex_bail!( - "Expected {} or {} children, got {}", - child_idx, - child_idx + 1, - children.len() - ); - } - }; - - let validity_idx = match &metadata.patches { - None => 0, - Some(patches_meta) if patches_meta.chunk_offsets_dtype()?.is_some() => 3, - Some(_) => 2, - }; - - let validity = load_validity(validity_idx)?; - - let patches = metadata - .patches - .map(|p| { - let indices = children.get(0, &p.indices_dtype()?, p.len()?)?; - let values = children.get(1, dtype, p.len()?)?; - let chunk_offsets = p - .chunk_offsets_dtype()? - .map(|dtype| children.get(2, &dtype, p.chunk_offsets_len() as usize)) - .transpose()?; - - Patches::new(len, p.offset()?, indices, values, chunk_offsets) - }) - .transpose()?; - - let slots = { - let mut s = ArraySlots::with_capacity(BitPackedSlots::COUNT); - PatchesData::push_slots(&mut s, patches.as_ref()); - s.push(validity_to_child(&validity, len)); - s.push(None); - s - }; - let data = BitPackedData::try_new( - packed, - patches, - u8::try_from(metadata.bit_width).map_err(|_| { - vortex_err!( - "BitPackedMetadata bit_width {} does not fit in u8", - metadata.bit_width - ) - })?, - u16::try_from(metadata.offset).map_err(|_| { - vortex_err!( - "BitPackedMetadata offset {} does not fit in u16", - metadata.offset - ) - })?, - )?; - Ok(Array::::try_from_parts(ArrayParts::new( - BitPacked, - dtype.clone(), - len, - data, - slots, - ))? - .into_array()) - } -} - -/// Custom deserialization plugin that converts a BitPacked array with interior -/// Patches into a PatchedArray holding a BitPacked array. -#[derive(Debug, Clone)] -pub(crate) struct BitPackedPatchedPlugin; - -impl ArrayPlugin for BitPackedPatchedPlugin { - fn id(&self) -> ArrayId { - // We reuse the existing `BitPacked` ID so that we can take over its - // deserialization pathway. - // TODO(joe): dedup method name - ArrayVTable::id(&BitPacked) - } - - fn serialize( - &self, - array: &ArrayRef, - session: &VortexSession, - ) -> VortexResult> { - // delegate to BitPackedPlugin for serialization - BitPackedPlugin.serialize(array, session) - } - - fn deserialize( - &self, - parts: ArrayDeserialization<'_>, - session: &VortexSession, - ) -> VortexResult { - vortex_ensure_eq!( - parts.serialized_id, - self.id(), - "BitPacked plugin does not recognize serialized ID" - ); - let bitpacked: BitPackedArray = BitPackedPlugin - .deserialize(parts, session)? - .try_downcast() - .map_err(|_| { - vortex_err!("BitPacked plugin should only deserialize fastlanes.bitpacked") - })?; - - // Create a new BitPackedArray without the interior patches installed. - let Some(patches) = bitpacked.patches() else { - return Ok(bitpacked.into_array()); - }; - - let packed = bitpacked.packed().clone(); - let ptype = bitpacked.dtype().as_ptype(); - let validity = bitpacked.validity()?; - let BitWidthsView::Global(bw) = bitpacked.bit_widths() else { - vortex_panic!("BitPacked plugin always deserializes a global bit width"); - }; - let len = bitpacked.len(); - let offset = bitpacked.offset(); - - let bitpacked_without_patches = - BitPacked::try_new(packed, ptype, validity, None, bw, len, offset)?.into_array(); - - let patched = Patched::from_array_and_patches( - bitpacked_without_patches, - &patches, - &mut session.create_execution_ctx(), - )?; - - Ok(patched.into_array()) - } - - fn is_supported_encoding(&self, id: &ArrayId) -> bool { - id == ArrayVTable::id(&BitPacked) || id == ArrayVTable::id(&Patched) - } -} - -#[cfg(test)] -mod tests { - use std::sync::LazyLock; - - use prost::Message; - use rstest::rstest; - use vortex_array::ArrayContext; - use vortex_array::ArrayDeserialization; - use vortex_array::ArrayPlugin; - use vortex_array::ArrayRef; - use vortex_array::IntoArray; - use vortex_array::VortexSessionExecute; - use vortex_array::arrays::PatchedArray; - use vortex_array::arrays::PrimitiveArray; - use vortex_array::arrays::patched::PatchedArraySlotsExt; - use vortex_array::assert_arrays_eq; - use vortex_array::buffer::BufferHandle; - use vortex_array::serde::SerializeOptions; - use vortex_array::serde::SerializedArray; - use vortex_array::session::ArraySessionExt; - use vortex_buffer::Buffer; - use vortex_buffer::ByteBufferMut; - use vortex_error::VortexResult; - use vortex_error::vortex_err; - use vortex_session::VortexSession; - use vortex_session::registry::ReadContext; - - use super::BitPackedMetadata; - use super::BitPackedPatchedPlugin; - use super::BitPackedPlugin; - use crate::BitPacked; - use crate::BitPackedArray; - use crate::BitPackedArrayExt; - use crate::BitPackedData; - - static SESSION: LazyLock = LazyLock::new(|| { - let session = vortex_array::array_session(); - session.arrays().register(BitPackedPatchedPlugin); - session - }); - - #[test] - fn test_decode_bitpacked_patches() -> VortexResult<()> { - let mut ctx = SESSION.create_execution_ctx(); - // Create values where some exceed the bit width, causing patches. - // With bit_width=9, max value is 511. Values >=512 become patches. - let values: Buffer = (0i32..=512).collect(); - let parray = values.into_array(); - let bitpacked = BitPackedData::encode(&parray, 9, &mut ctx)?; - - assert!( - bitpacked.patches().is_some(), - "Expected BitPacked array to have patches" - ); - - let array = bitpacked.as_array(); - - let serialization = SESSION.array_serialize(array)?.unwrap(); - let children = array.children(); - let buffers = array - .buffers() - .into_iter() - .map(BufferHandle::new_host) - .collect::>(); - - let deserialized = BitPackedPatchedPlugin.deserialize( - ArrayDeserialization::new( - BitPackedPatchedPlugin.id(), - array.dtype(), - array.len(), - &serialization.metadata, - &buffers, - &children, - ), - &SESSION, - )?; - - let patched: PatchedArray = deserialized - .try_downcast() - .map_err(|a| vortex_err!("Expected Patched, got {}", a.encoding_id()))?; - - let inner_bitpacked: BitPackedArray = patched - .inner() - .clone() - .try_downcast() - .map_err(|a| vortex_err!("Expected inner BitPacked, got {}", a.encoding_id()))?; - - assert!( - inner_bitpacked.patches().is_none(), - "Inner BitPacked should NOT have patches" - ); - - Ok(()) - } - - #[test] - fn bitpacked_without_patches_stays_bitpacked() -> VortexResult<()> { - let mut ctx = SESSION.create_execution_ctx(); - // With bit_width=16, max value is 65535. All values 0..100 fit. - let values: Buffer = (0i32..100).collect(); - let parray = values.into_array(); - let bitpacked = BitPackedData::encode(&parray, 16, &mut ctx)?; - - assert!( - bitpacked.patches().is_none(), - "Expected BitPacked array without patches" - ); - - let array = bitpacked.as_array(); - - let serialization = SESSION.array_serialize(array)?.unwrap(); - let children = array.children(); - let buffers = array - .buffers() - .into_iter() - .map(BufferHandle::new_host) - .collect::>(); - - let deserialized = BitPackedPatchedPlugin.deserialize( - ArrayDeserialization::new( - BitPackedPatchedPlugin.id(), - array.dtype(), - array.len(), - &serialization.metadata, - &buffers, - &children, - ), - &SESSION, - )?; - - let result = deserialized - .try_downcast::() - .map_err(|a| vortex_err!("Expected deserialize BitPacked, got {}", a.encoding_id()))?; - - assert!(result.patches().is_none(), "Result should not have patches"); - - Ok(()) - } - - #[test] - fn primitive_array_returns_error() -> VortexResult<()> { - let array = PrimitiveArray::from_iter([1i32, 2, 3]).into_array(); - - let serialization = SESSION.array_serialize(&array)?.unwrap(); - let children = array.children(); - let buffers = array - .buffers() - .into_iter() - .map(BufferHandle::new_host) - .collect::>(); - - let result = BitPackedPatchedPlugin.deserialize( - ArrayDeserialization::new( - BitPackedPatchedPlugin.id(), - array.dtype(), - array.len(), - &serialization.metadata, - &buffers, - &children, - ), - &SESSION, - ); - - assert!( - result.is_err(), - "Expected error when deserializing PrimitiveArray with BitPackedPatchedPlugin" - ); - - Ok(()) - } - - static PLUGIN_SESSION: LazyLock = LazyLock::new(|| { - let session = vortex_array::array_session(); - session.arrays().register(BitPackedPlugin); - session - }); - - fn roundtrip(array: &ArrayRef) -> VortexResult { - let array_ctx = ArrayContext::empty(); - let buffers = array.serialize(&array_ctx, &PLUGIN_SESSION, &SerializeOptions::default())?; - let mut bytes = ByteBufferMut::empty(); - for buffer in buffers { - bytes.extend_from_slice(&buffer); - } - SerializedArray::try_from(bytes.freeze())?.decode( - array.dtype(), - array.len(), - &ReadContext::new(array_ctx.to_ids()), - &PLUGIN_SESSION, - ) - } - - #[rstest] - #[case::no_patches(PrimitiveArray::from_iter(0u32..3000).into_array(), 12, 0..3000)] - #[case::patches(PrimitiveArray::from_iter(0i32..=2048).into_array(), 9, 0..2049)] - #[case::nullable( - PrimitiveArray::from_option_iter((0u16..3000).map(|i| (i % 5 != 0).then_some(i))).into_array(), - 8, - 0..3000, - )] - #[case::sliced(PrimitiveArray::from_iter(0u32..3000).into_array(), 12, 700..1900)] - fn serde_roundtrip( - #[case] values: ArrayRef, - #[case] bit_width: u8, - #[case] range: std::ops::Range, - ) -> VortexResult<()> { - let mut ctx = PLUGIN_SESSION.create_execution_ctx(); - let array = BitPackedData::encode(&values, bit_width, &mut ctx)? - .into_array() - .slice(range.clone())?; - let view = array.as_::(); - let serialization = PLUGIN_SESSION - .array_serialize(&array)? - .ok_or_else(|| vortex_err!("BitPacked must serialize"))?; - assert_eq!(serialization.serialized_id, BitPackedPlugin.id()); - let metadata = BitPackedMetadata::decode(serialization.metadata.as_slice())?; - assert_eq!(metadata.bit_width, u32::from(bit_width)); - assert_eq!(metadata.offset, u32::from(view.offset())); - assert_eq!(metadata.patches.is_some(), view.patches().is_some()); - - let read = roundtrip(&array)?; - assert_eq!(read.encoding_id(), BitPackedPlugin.id()); - assert_arrays_eq!(read, values.slice(range)?, &mut ctx); - Ok(()) - } - - #[test] - fn vtable_serde_requires_plugin() -> VortexResult<()> { - let values = PrimitiveArray::from_iter([1u8, 2, 3]).into_array(); - let array = BitPackedData::encode(&values, 2, &mut SESSION.create_execution_ctx())?; - let session = vortex_array::array_session(); - session.arrays().register(BitPacked); - assert!(session.array_serialize(&array.into_array()).is_err()); - Ok(()) - } -} diff --git a/encodings/fastlanes/src/bitpacking/plugin/mod.rs b/encodings/fastlanes/src/bitpacking/plugin/mod.rs new file mode 100644 index 00000000000..56f7ca89697 --- /dev/null +++ b/encodings/fastlanes/src/bitpacking/plugin/mod.rs @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +//! [`ArrayPlugin`] implementations for `BitPacked`. +//! +//! [`BitPackedPlugin`] owns serde for `fastlanes.bitpacked` and `fastlanes.bitpacked.v2`. +//! [`BitPackedPatchedPlugin`] lets you load in and deserialize a `BitPacked` array with interior +//! patches as a `PatchedArray` that wraps a patchless `BitPacked` array. +//! +//! This enables zero-cost backward compatibility with previously written datasets. + +use vortex_array::ArrayDeserialization; +use vortex_array::ArrayId; +use vortex_array::ArrayPlugin; +use vortex_array::ArrayRef; +use vortex_array::ArraySerialization; +use vortex_array::ArrayVTable; +use vortex_array::IntoArray; +use vortex_array::VortexSessionExecute; +use vortex_array::arrays::Patched; +use vortex_error::VortexResult; +use vortex_error::vortex_bail; +use vortex_error::vortex_ensure; +use vortex_error::vortex_err; +use vortex_session::VortexSession; +use vortex_session::registry::CachedId; + +use crate::BitPacked; +use crate::BitPackedArray; +use crate::BitPackedArrayExt; +use crate::BitWidthsView; + +#[cfg(test)] +mod tests; +mod v1; +mod v2; + +/// The frozen BitPacked serialized ID for arrays whose blocks share one bit width. +pub fn bitpacked_v1_id() -> ArrayId { + static ID: CachedId = CachedId::new("fastlanes.bitpacked"); + *ID +} + +/// The in-memory BitPacked ID, and the serialized ID for arrays whose blocks have their own bit +/// widths. +pub fn bitpacked_v2_id() -> ArrayId { + static ID: CachedId = CachedId::new("fastlanes.bitpacked.v2"); + *ID +} + +/// Serde for the [`BitPacked`] array. +/// +/// Arrays with a global bit width serialize as `fastlanes.bitpacked`, which stores the width in +/// its metadata. Arrays whose blocks have their own bit widths serialize as +/// `fastlanes.bitpacked.v2`, which stores the block offsets as a child. +/// +/// Register this plugin, or call [`crate::initialize`], to enable serde. Direct registration of +/// [`BitPacked`] does not support serde. +#[derive(Clone, Debug)] +pub struct BitPackedPlugin; + +impl ArrayPlugin for BitPackedPlugin { + fn id(&self) -> ArrayId { + ArrayVTable::id(&BitPacked) + } + + fn serialized_ids(&self) -> Vec { + vec![bitpacked_v1_id(), bitpacked_v2_id()] + } + + fn serialize( + &self, + array: &ArrayRef, + _session: &VortexSession, + ) -> VortexResult> { + let view = array.as_opt::().ok_or_else(|| { + vortex_err!("BitPacked plugin cannot serialize {}", array.encoding_id()) + })?; + let serialization = match view.bit_widths() { + BitWidthsView::Global(bit_width) => v1::serialize(array, view, bit_width)?, + BitWidthsView::Blocked(block_offsets) => v2::serialize(array, view, block_offsets)?, + }; + Ok(Some(serialization)) + } + + fn deserialize( + &self, + parts: ArrayDeserialization<'_>, + _session: &VortexSession, + ) -> VortexResult { + if parts.serialized_id == bitpacked_v1_id() { + v1::deserialize(parts) + } else if parts.serialized_id == bitpacked_v2_id() { + v2::deserialize(parts) + } else { + vortex_bail!( + "BitPacked plugin does not recognize serialized ID {}", + parts.serialized_id + ) + } + } +} + +/// Custom deserialization plugin that converts a BitPacked array with interior +/// Patches into a PatchedArray holding a BitPacked array. +#[derive(Debug, Clone)] +pub(crate) struct BitPackedPatchedPlugin; + +impl ArrayPlugin for BitPackedPatchedPlugin { + fn id(&self) -> ArrayId { + // We reuse the existing `BitPacked` ID so that we can take over its + // deserialization pathway. + // TODO(joe): dedup method name + ArrayVTable::id(&BitPacked) + } + + fn serialized_ids(&self) -> Vec { + BitPackedPlugin.serialized_ids() + } + + fn serialize( + &self, + array: &ArrayRef, + session: &VortexSession, + ) -> VortexResult> { + // delegate to BitPackedPlugin for serialization + BitPackedPlugin.serialize(array, session) + } + + fn deserialize( + &self, + parts: ArrayDeserialization<'_>, + session: &VortexSession, + ) -> VortexResult { + vortex_ensure!( + self.serialized_ids().contains(&parts.serialized_id), + "BitPacked plugin does not recognize serialized ID {}", + parts.serialized_id, + ); + let bitpacked: BitPackedArray = BitPackedPlugin + .deserialize(parts, session)? + .try_downcast() + .map_err(|_| { + vortex_err!("BitPacked plugin should only deserialize fastlanes.bitpacked") + })?; + + // Create a new BitPackedArray without the interior patches installed. + let Some(patches) = bitpacked.patches() else { + return Ok(bitpacked.into_array()); + }; + + let packed = bitpacked.packed().clone(); + let ptype = bitpacked.dtype().as_ptype(); + let validity = bitpacked.validity()?; + let len = bitpacked.len(); + let offset = bitpacked.offset(); + + let bitpacked_without_patches = match bitpacked.bit_widths() { + BitWidthsView::Global(bw) => { + BitPacked::try_new(packed, ptype, validity, None, bw, len, offset)? + } + BitWidthsView::Blocked(block_offsets) => BitPacked::try_new_with_block_offsets( + packed, + ptype, + validity, + None, + block_offsets.clone(), + len, + offset, + )?, + } + .into_array(); + + let patched = Patched::from_array_and_patches( + bitpacked_without_patches, + &patches, + &mut session.create_execution_ctx(), + )?; + + Ok(patched.into_array()) + } + + fn is_supported_encoding(&self, id: &ArrayId) -> bool { + id == ArrayVTable::id(&BitPacked) || id == ArrayVTable::id(&Patched) + } +} diff --git a/encodings/fastlanes/src/bitpacking/plugin/tests.rs b/encodings/fastlanes/src/bitpacking/plugin/tests.rs new file mode 100644 index 00000000000..7674e658518 --- /dev/null +++ b/encodings/fastlanes/src/bitpacking/plugin/tests.rs @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use std::sync::LazyLock; + +use prost::Message; +use rstest::rstest; +use vortex_array::ArrayContext; +use vortex_array::ArrayDeserialization; +use vortex_array::ArrayPlugin; +use vortex_array::ArrayRef; +use vortex_array::IntoArray; +use vortex_array::VortexSessionExecute; +use vortex_array::arrays::PatchedArray; +use vortex_array::arrays::PrimitiveArray; +use vortex_array::arrays::patched::PatchedArraySlotsExt; +use vortex_array::assert_arrays_eq; +use vortex_array::buffer::BufferHandle; +use vortex_array::builtins::ArrayBuiltins; +use vortex_array::dtype::DType; +use vortex_array::dtype::Nullability; +use vortex_array::dtype::PType; +use vortex_array::serde::SerializeOptions; +use vortex_array::serde::SerializedArray; +use vortex_array::session::ArraySessionExt; +use vortex_array::validity::Validity; +use vortex_buffer::Buffer; +use vortex_buffer::ByteBufferMut; +use vortex_error::VortexResult; +use vortex_error::vortex_bail; +use vortex_error::vortex_err; +use vortex_session::VortexSession; +use vortex_session::registry::ReadContext; + +use super::BitPackedPatchedPlugin; +use super::BitPackedPlugin; +use super::bitpacked_v1_id; +use super::bitpacked_v2_id; +use super::v1::BitPackedMetadata; +use super::v2::BitPackedV2Metadata; +use crate::BitPacked; +use crate::BitPackedArray; +use crate::BitPackedArrayExt; +use crate::BitPackedData; +use crate::BitWidthsView; +use crate::bitpack_compress::bitpack_blocked_to_best_bit_widths; +use crate::bitpack_compress::bitpack_encode_blocked; + +static SESSION: LazyLock = LazyLock::new(|| { + let session = vortex_array::array_session(); + session.arrays().register(BitPackedPatchedPlugin); + session +}); + +#[test] +fn test_decode_bitpacked_patches() -> VortexResult<()> { + let mut ctx = SESSION.create_execution_ctx(); + // Create values where some exceed the bit width, causing patches. + // With bit_width=9, max value is 511. Values >=512 become patches. + let values: Buffer = (0i32..=512).collect(); + let parray = values.into_array(); + let bitpacked = BitPackedData::encode(&parray, 9, &mut ctx)?; + + assert!( + bitpacked.patches().is_some(), + "Expected BitPacked array to have patches" + ); + + let array = bitpacked.as_array(); + + let serialization = SESSION.array_serialize(array)?.unwrap(); + let children = array.children(); + let buffers = array + .buffers() + .into_iter() + .map(BufferHandle::new_host) + .collect::>(); + + let deserialized = BitPackedPatchedPlugin.deserialize( + ArrayDeserialization::new( + bitpacked_v1_id(), + array.dtype(), + array.len(), + &serialization.metadata, + &buffers, + &children, + ), + &SESSION, + )?; + + let patched: PatchedArray = deserialized + .try_downcast() + .map_err(|a| vortex_err!("Expected Patched, got {}", a.encoding_id()))?; + + let inner_bitpacked: BitPackedArray = patched + .inner() + .clone() + .try_downcast() + .map_err(|a| vortex_err!("Expected inner BitPacked, got {}", a.encoding_id()))?; + + assert!( + inner_bitpacked.patches().is_none(), + "Inner BitPacked should NOT have patches" + ); + + Ok(()) +} + +#[test] +fn bitpacked_without_patches_stays_bitpacked() -> VortexResult<()> { + let mut ctx = SESSION.create_execution_ctx(); + // With bit_width=16, max value is 65535. All values 0..100 fit. + let values: Buffer = (0i32..100).collect(); + let parray = values.into_array(); + let bitpacked = BitPackedData::encode(&parray, 16, &mut ctx)?; + + assert!( + bitpacked.patches().is_none(), + "Expected BitPacked array without patches" + ); + + let array = bitpacked.as_array(); + + let serialization = SESSION.array_serialize(array)?.unwrap(); + let children = array.children(); + let buffers = array + .buffers() + .into_iter() + .map(BufferHandle::new_host) + .collect::>(); + + let deserialized = BitPackedPatchedPlugin.deserialize( + ArrayDeserialization::new( + bitpacked_v1_id(), + array.dtype(), + array.len(), + &serialization.metadata, + &buffers, + &children, + ), + &SESSION, + )?; + + let result = deserialized + .try_downcast::() + .map_err(|a| vortex_err!("Expected deserialize BitPacked, got {}", a.encoding_id()))?; + + assert!(result.patches().is_none(), "Result should not have patches"); + + Ok(()) +} + +#[test] +fn primitive_array_returns_error() -> VortexResult<()> { + let array = PrimitiveArray::from_iter([1i32, 2, 3]).into_array(); + + let serialization = SESSION.array_serialize(&array)?.unwrap(); + let children = array.children(); + let buffers = array + .buffers() + .into_iter() + .map(BufferHandle::new_host) + .collect::>(); + + let result = BitPackedPatchedPlugin.deserialize( + ArrayDeserialization::new( + bitpacked_v1_id(), + array.dtype(), + array.len(), + &serialization.metadata, + &buffers, + &children, + ), + &SESSION, + ); + + assert!( + result.is_err(), + "Expected error when deserializing PrimitiveArray with BitPackedPatchedPlugin" + ); + + Ok(()) +} + +static PLUGIN_SESSION: LazyLock = LazyLock::new(|| { + let session = vortex_array::array_session(); + session.arrays().register(BitPackedPlugin); + session +}); + +fn roundtrip(array: &ArrayRef) -> VortexResult { + let array_ctx = ArrayContext::empty(); + let buffers = array.serialize(&array_ctx, &PLUGIN_SESSION, &SerializeOptions::default())?; + let mut bytes = ByteBufferMut::empty(); + for buffer in buffers { + bytes.extend_from_slice(&buffer); + } + SerializedArray::try_from(bytes.freeze())?.decode( + array.dtype(), + array.len(), + &ReadContext::new(array_ctx.to_ids()), + &PLUGIN_SESSION, + ) +} + +#[rstest] +#[case::no_patches(PrimitiveArray::from_iter(0u32..3000).into_array(), 12, 0..3000)] +#[case::patches(PrimitiveArray::from_iter(0i32..=2048).into_array(), 9, 0..2049)] +#[case::nullable( + PrimitiveArray::from_option_iter((0u16..3000).map(|i| (i % 5 != 0).then_some(i))).into_array(), + 8, + 0..3000, +)] +#[case::sliced(PrimitiveArray::from_iter(0u32..3000).into_array(), 12, 700..1900)] +fn serde_roundtrip( + #[case] values: ArrayRef, + #[case] bit_width: u8, + #[case] range: std::ops::Range, +) -> VortexResult<()> { + let mut ctx = PLUGIN_SESSION.create_execution_ctx(); + let array = BitPackedData::encode(&values, bit_width, &mut ctx)? + .into_array() + .slice(range.clone())?; + let view = array.as_::(); + let serialization = PLUGIN_SESSION + .array_serialize(&array)? + .ok_or_else(|| vortex_err!("BitPacked must serialize"))?; + assert_eq!(serialization.serialized_id, bitpacked_v1_id()); + let metadata = BitPackedMetadata::decode(serialization.metadata.as_slice())?; + assert_eq!(metadata.bit_width, u32::from(bit_width)); + assert_eq!(metadata.offset, u32::from(view.offset())); + assert_eq!(metadata.patches.is_some(), view.patches().is_some()); + + let read = roundtrip(&array)?; + assert_eq!(read.encoding_id(), BitPackedPlugin.id()); + assert_arrays_eq!(read, values.slice(range)?, &mut ctx); + Ok(()) +} + +#[test] +fn vtable_serde_requires_plugin() -> VortexResult<()> { + let values = PrimitiveArray::from_iter([1u8, 2, 3]).into_array(); + let array = BitPackedData::encode(&values, 2, &mut SESSION.create_execution_ctx())?; + let session = vortex_array::array_session(); + session.arrays().register(BitPacked); + assert!(session.array_serialize(&array.into_array()).is_err()); + Ok(()) +} + +/// Values whose 1024-value blocks need 2 to 6 bits. +fn drifting() -> PrimitiveArray { + PrimitiveArray::from_iter((0..5000u32).map(|i| i % (4 << (i / 1024)))) +} + +#[rstest] +#[case::no_patches(drifting(), None)] +#[case::patches(drifting(), Some(vec![2, 3, 4, 5, 3]))] +#[case::nullable( + PrimitiveArray::from_option_iter( + (0..5000u32).map(|i| (i % 7 != 0).then_some(i % (4 << (i / 1024)))), + ), + None, +)] +fn v2_roundtrip( + #[case] values: PrimitiveArray, + #[case] bit_widths: Option>, +) -> VortexResult<()> { + let mut ctx = PLUGIN_SESSION.create_execution_ctx(); + let array = match bit_widths { + Some(bit_widths) => bitpack_encode_blocked(&values, &bit_widths, None, &mut ctx)?, + None => bitpack_blocked_to_best_bit_widths(&values, &mut ctx)?, + }; + let BitWidthsView::Blocked(block_offsets) = array.bit_widths() else { + vortex_bail!("expected block offsets"); + }; + let serialization = PLUGIN_SESSION + .array_serialize(array.as_array())? + .ok_or_else(|| vortex_err!("BitPacked must serialize"))?; + assert_eq!(serialization.serialized_id, bitpacked_v2_id()); + let metadata = BitPackedV2Metadata::decode(serialization.metadata.as_slice())?; + assert_eq!(metadata.offset, 0); + assert_eq!( + metadata.block_offsets_ptype, + PType::try_from(block_offsets.dtype())? as i32 + ); + assert_eq!(metadata.patches.is_some(), array.patches().is_some()); + + let read = roundtrip(array.as_array())?; + assert_eq!(read.encoding_id(), bitpacked_v2_id()); + assert!(!read.as_::().bit_widths().is_global()); + assert_arrays_eq!(read, values, &mut ctx); + Ok(()) +} + +#[test] +fn v2_roundtrip_offset_and_nonzero_base() -> VortexResult<()> { + let mut ctx = PLUGIN_SESSION.create_execution_ctx(); + let values = drifting(); + let encoded = bitpack_blocked_to_best_bit_widths(&values, &mut ctx)?; + let BitWidthsView::Blocked(block_offsets) = encoded.bit_widths() else { + vortex_bail!("expected block offsets"); + }; + let block_offsets = block_offsets + .cast(DType::Primitive(PType::U64, Nullability::NonNullable))? + .execute::(&mut ctx)?; + let block_offsets = + PrimitiveArray::from_iter(block_offsets.as_slice::().iter().map(|o| o + 128)); + let array = BitPacked::try_new_with_block_offsets( + encoded.packed().clone(), + PType::U32, + Validity::NonNullable, + None, + block_offsets.into_array(), + 4900, + 17, + )? + .into_array(); + + let serialization = PLUGIN_SESSION + .array_serialize(&array)? + .ok_or_else(|| vortex_err!("BitPacked must serialize"))?; + assert_eq!( + BitPackedV2Metadata::decode(serialization.metadata.as_slice())?.offset, + 17 + ); + assert_arrays_eq!( + roundtrip(&array)?, + values.into_array().slice(17..4917)?, + &mut ctx + ); + Ok(()) +} + +#[test] +fn v2_rejects_malformed_parts() -> VortexResult<()> { + let array = bitpack_blocked_to_best_bit_widths( + &drifting(), + &mut PLUGIN_SESSION.create_execution_ctx(), + )?; + let BitWidthsView::Blocked(block_offsets) = array.bit_widths() else { + vortex_bail!("expected block offsets"); + }; + let metadata = BitPackedV2Metadata { + offset: 0, + block_offsets_ptype: PType::try_from(block_offsets.dtype())? as i32, + patches: None, + }; + let buffers = [array.packed().clone()]; + let deserialize = |metadata: &BitPackedV2Metadata, children: &[ArrayRef]| { + BitPackedPlugin.deserialize( + ArrayDeserialization::new( + bitpacked_v2_id(), + array.dtype(), + array.len(), + &metadata.encode_to_vec(), + &buffers, + &children, + ), + &PLUGIN_SESSION, + ) + }; + + assert!(deserialize(&metadata, std::slice::from_ref(block_offsets)).is_ok()); + assert!(deserialize(&metadata, &[]).is_err()); + let offset_past_block = BitPackedV2Metadata { + offset: 1024, + block_offsets_ptype: metadata.block_offsets_ptype, + patches: None, + }; + assert!(deserialize(&offset_past_block, std::slice::from_ref(block_offsets)).is_err()); + let signed = BitPackedV2Metadata { + offset: 0, + block_offsets_ptype: PType::I32 as i32, + patches: None, + }; + let signed_offsets = + block_offsets.cast(DType::Primitive(PType::I32, Nullability::NonNullable))?; + assert!(deserialize(&signed, &[signed_offsets]).is_err()); + Ok(()) +} + +#[test] +fn test_decode_blocked_bitpacked_patches() -> VortexResult<()> { + let mut ctx = SESSION.create_execution_ctx(); + let values = drifting(); + let array = bitpack_encode_blocked(&values, &[2, 3, 4, 5, 3], None, &mut ctx)?.into_array(); + + let serialization = SESSION.array_serialize(&array)?.unwrap(); + assert_eq!(serialization.serialized_id, bitpacked_v2_id()); + let children = array.children(); + let buffers = array + .buffers() + .into_iter() + .map(BufferHandle::new_host) + .collect::>(); + + let deserialized = BitPackedPatchedPlugin.deserialize( + ArrayDeserialization::new( + bitpacked_v2_id(), + array.dtype(), + array.len(), + &serialization.metadata, + &buffers, + &children, + ), + &SESSION, + )?; + + let patched: PatchedArray = deserialized + .try_downcast() + .map_err(|a| vortex_err!("Expected Patched, got {}", a.encoding_id()))?; + let inner_bitpacked: BitPackedArray = patched + .inner() + .clone() + .try_downcast() + .map_err(|a| vortex_err!("Expected inner BitPacked, got {}", a.encoding_id()))?; + assert!(inner_bitpacked.patches().is_none()); + assert!(!inner_bitpacked.bit_widths().is_global()); + assert_arrays_eq!(patched, values, &mut ctx); + Ok(()) +} diff --git a/encodings/fastlanes/src/bitpacking/plugin/v1.rs b/encodings/fastlanes/src/bitpacking/plugin/v1.rs new file mode 100644 index 00000000000..d1a19d43f48 --- /dev/null +++ b/encodings/fastlanes/src/bitpacking/plugin/v1.rs @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +//! Serde for `fastlanes.bitpacked`, which stores a single bit width in its metadata. + +use prost::Message; +use vortex_array::Array; +use vortex_array::ArrayDeserialization; +use vortex_array::ArrayParts; +use vortex_array::ArrayRef; +use vortex_array::ArraySerialization; +use vortex_array::ArraySlots; +use vortex_array::ArrayView; +use vortex_array::IntoArray; +use vortex_array::patches::Patches; +use vortex_array::patches::PatchesData; +use vortex_array::patches::PatchesMetadata; +use vortex_array::validity::Validity; +use vortex_array::vtable::validity_to_child; +use vortex_error::VortexResult; +use vortex_error::vortex_bail; +use vortex_error::vortex_err; + +use super::bitpacked_v1_id; +use crate::BitPacked; +use crate::BitPackedArrayExt; +use crate::BitPackedData; +use crate::bitpacking::array::BitPackedSlots; + +#[derive(Clone, prost::Message)] +pub struct BitPackedMetadata { + #[prost(uint32, tag = "1")] + pub(crate) bit_width: u32, + #[prost(uint32, tag = "2")] + pub(crate) offset: u32, // must be <1024 + #[prost(message, optional, tag = "3")] + pub(crate) patches: Option, +} + +pub(super) fn serialize( + array: &ArrayRef, + view: ArrayView<'_, BitPacked>, + bit_width: u8, +) -> VortexResult { + let metadata = BitPackedMetadata { + bit_width: u32::from(bit_width), + offset: view.offset() as u32, + patches: view + .patches() + .map(|p| p.to_metadata(view.len(), view.dtype())) + .transpose()?, + } + .encode_to_vec(); + Ok(ArraySerialization::from_array( + bitpacked_v1_id(), + array, + metadata, + )) +} + +pub(super) fn deserialize(parts: ArrayDeserialization<'_>) -> VortexResult { + let ArrayDeserialization { + dtype, + len, + metadata, + buffers, + children, + .. + } = parts; + + let metadata = BitPackedMetadata::decode(metadata)?; + if buffers.len() != 1 { + vortex_bail!("Expected 1 buffer, got {}", buffers.len()); + } + let packed = buffers[0].clone(); + + let load_validity = |child_idx: usize| { + if children.len() == child_idx { + Ok(Validity::from(dtype.nullability())) + } else if children.len() == child_idx + 1 { + let validity = children.get(child_idx, &Validity::DTYPE, len)?; + Ok(Validity::Array(validity)) + } else { + vortex_bail!( + "Expected {} or {} children, got {}", + child_idx, + child_idx + 1, + children.len() + ); + } + }; + + let validity_idx = match &metadata.patches { + None => 0, + Some(patches_meta) if patches_meta.chunk_offsets_dtype()?.is_some() => 3, + Some(_) => 2, + }; + + let validity = load_validity(validity_idx)?; + + let patches = metadata + .patches + .map(|p| { + let indices = children.get(0, &p.indices_dtype()?, p.len()?)?; + let values = children.get(1, dtype, p.len()?)?; + let chunk_offsets = p + .chunk_offsets_dtype()? + .map(|dtype| children.get(2, &dtype, p.chunk_offsets_len() as usize)) + .transpose()?; + + Patches::new(len, p.offset()?, indices, values, chunk_offsets) + }) + .transpose()?; + + let slots = { + let mut s = ArraySlots::with_capacity(BitPackedSlots::COUNT); + PatchesData::push_slots(&mut s, patches.as_ref()); + s.push(validity_to_child(&validity, len)); + s.push(None); + s + }; + let data = BitPackedData::try_new( + packed, + patches, + u8::try_from(metadata.bit_width).map_err(|_| { + vortex_err!( + "BitPackedMetadata bit_width {} does not fit in u8", + metadata.bit_width + ) + })?, + u16::try_from(metadata.offset).map_err(|_| { + vortex_err!( + "BitPackedMetadata offset {} does not fit in u16", + metadata.offset + ) + })?, + )?; + Ok(Array::::try_from_parts(ArrayParts::new( + BitPacked, + dtype.clone(), + len, + data, + slots, + ))? + .into_array()) +} diff --git a/encodings/fastlanes/src/bitpacking/plugin/v2.rs b/encodings/fastlanes/src/bitpacking/plugin/v2.rs new file mode 100644 index 00000000000..1727e5a985a --- /dev/null +++ b/encodings/fastlanes/src/bitpacking/plugin/v2.rs @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +//! Serde for `fastlanes.bitpacked.v2`, which stores the byte boundaries of the packed blocks as a +//! child, so each block has its own bit width. + +use prost::Message; +use vortex_array::Array; +use vortex_array::ArrayDeserialization; +use vortex_array::ArrayParts; +use vortex_array::ArrayRef; +use vortex_array::ArraySerialization; +use vortex_array::ArraySlots; +use vortex_array::ArrayView; +use vortex_array::IntoArray; +use vortex_array::dtype::DType; +use vortex_array::dtype::Nullability; +use vortex_array::dtype::PType; +use vortex_array::patches::Patches; +use vortex_array::patches::PatchesData; +use vortex_array::patches::PatchesMetadata; +use vortex_array::validity::Validity; +use vortex_array::vtable::validity_to_child; +use vortex_error::VortexResult; +use vortex_error::vortex_bail; +use vortex_error::vortex_ensure; + +use super::bitpacked_v2_id; +use crate::BitPacked; +use crate::BitPackedArrayExt; +use crate::BitPackedData; +use crate::FL_CHUNK_SIZE; +use crate::bitpacking::array::BitPackedSlots; + +/// Metadata for `fastlanes.bitpacked.v2`. +/// +/// The children are the patch indices, values and chunk offsets when there are patches, then the +/// validity when there is a validity child, then the block offsets. +#[derive(Clone, prost::Message)] +pub(super) struct BitPackedV2Metadata { + /// The position of the first element within the first block. + #[prost(uint32, tag = "1")] + pub(super) offset: u32, + /// The unsigned integer type of the block offsets child. + #[prost(enumeration = "PType", tag = "2")] + pub(super) block_offsets_ptype: i32, + #[prost(message, optional, tag = "3")] + pub(super) patches: Option, +} + +pub(super) fn serialize( + array: &ArrayRef, + view: ArrayView<'_, BitPacked>, + block_offsets: &ArrayRef, +) -> VortexResult { + let metadata = BitPackedV2Metadata { + offset: u32::from(view.offset()), + block_offsets_ptype: PType::try_from(block_offsets.dtype())? as i32, + patches: view + .patches() + .map(|p| p.to_metadata(view.len(), view.dtype())) + .transpose()?, + }; + Ok(ArraySerialization::from_array( + bitpacked_v2_id(), + array, + metadata.encode_to_vec(), + )) +} + +pub(super) fn deserialize(parts: ArrayDeserialization<'_>) -> VortexResult { + let ArrayDeserialization { + dtype, + len, + metadata, + buffers, + children, + .. + } = parts; + + let metadata = BitPackedV2Metadata::decode(metadata)?; + vortex_ensure!( + buffers.len() == 1, + "Expected 1 buffer, got {}", + buffers.len() + ); + vortex_ensure!( + usize::try_from(metadata.offset).is_ok_and(|offset| offset < FL_CHUNK_SIZE), + "BitPacked offset must be less than {FL_CHUNK_SIZE}, got {}", + metadata.offset + ); + let offset = u16::try_from(metadata.offset)?; + let block_offsets_ptype = PType::try_from(metadata.block_offsets_ptype)?; + + let num_patch_children = match &metadata.patches { + None => 0, + Some(patches_meta) if patches_meta.chunk_offsets_dtype()?.is_some() => 3, + Some(_) => 2, + }; + let validity = if children.len() == num_patch_children + 1 { + Validity::from(dtype.nullability()) + } else if children.len() == num_patch_children + 2 { + Validity::Array(children.get(num_patch_children, &Validity::DTYPE, len)?) + } else { + vortex_bail!( + "Expected {} or {} children, got {}", + num_patch_children + 1, + num_patch_children + 2, + children.len() + ); + }; + + let num_blocks = (usize::from(offset) + len).div_ceil(FL_CHUNK_SIZE); + let block_offsets = children.get( + children.len() - 1, + &DType::Primitive(block_offsets_ptype, Nullability::NonNullable), + num_blocks + 1, + )?; + + let patches = metadata + .patches + .map(|p| { + let indices = children.get(0, &p.indices_dtype()?, p.len()?)?; + let values = children.get(1, dtype, p.len()?)?; + let chunk_offsets = p + .chunk_offsets_dtype()? + .map(|dtype| children.get(2, &dtype, p.chunk_offsets_len() as usize)) + .transpose()?; + + Patches::new(len, p.offset()?, indices, values, chunk_offsets) + }) + .transpose()?; + + let slots = { + let mut s = ArraySlots::with_capacity(BitPackedSlots::COUNT); + PatchesData::push_slots(&mut s, patches.as_ref()); + s.push(validity_to_child(&validity, len)); + s.push(Some(block_offsets)); + s + }; + let data = BitPackedData::try_new_blocked(buffers[0].clone(), patches, offset)?; + Ok(Array::::try_from_parts(ArrayParts::new( + BitPacked, + dtype.clone(), + len, + data, + slots, + ))? + .into_array()) +} diff --git a/encodings/fastlanes/src/bitpacking/vtable/mod.rs b/encodings/fastlanes/src/bitpacking/vtable/mod.rs index a4e6825e3ab..1877f089553 100644 --- a/encodings/fastlanes/src/bitpacking/vtable/mod.rs +++ b/encodings/fastlanes/src/bitpacking/vtable/mod.rs @@ -36,7 +36,6 @@ use vortex_error::vortex_bail; use vortex_error::vortex_ensure_eq; use vortex_error::vortex_panic; use vortex_session::VortexSession; -use vortex_session::registry::CachedId; use crate::BitPackedArrayExt; use crate::BitPackedData; @@ -48,6 +47,7 @@ use crate::bitpack_decompress::unpack_array; use crate::bitpack_decompress::unpack_array_blocked; use crate::bitpack_decompress::unpack_into_primitive_builder; use crate::bitpack_decompress::unpack_into_primitive_builder_blocked; +use crate::bitpacked_v2_id; use crate::bitpacking::array::BitPackedSlots; use crate::bitpacking::array::BitPackedSlotsView; use crate::bitpacking::array::PATCH_SLOTS; @@ -90,8 +90,7 @@ impl VTable for BitPacked { type ValidityVTable = Self; fn id(&self) -> ArrayId { - static ID: CachedId = CachedId::new("fastlanes.bitpacked"); - *ID + bitpacked_v2_id() } fn validate( diff --git a/vortex-btrblocks/src/schemes/integer/bitpacking.rs b/vortex-btrblocks/src/schemes/integer/bitpacking.rs index 8025633d835..4f81462ca3c 100644 --- a/vortex-btrblocks/src/schemes/integer/bitpacking.rs +++ b/vortex-btrblocks/src/schemes/integer/bitpacking.rs @@ -20,6 +20,7 @@ use vortex_fastlanes::BitPacked; use vortex_fastlanes::bitpack_compress::bit_width_histogram; use vortex_fastlanes::bitpack_compress::bitpack_encode; use vortex_fastlanes::bitpack_compress::find_best_bit_width; +use vortex_fastlanes::bitpacked_v1_id; use crate::ArrayAndStats; use crate::CascadingCompressor; @@ -41,7 +42,8 @@ impl Scheme for BitPackingScheme { } fn produced_encodings(&self) -> Vec { - let mut encodings = vec![BitPacked.id()]; + // Global-width arrays serialize under the frozen v1 ID. + let mut encodings = vec![bitpacked_v1_id()]; if use_experimental_patches() { encodings.push(Patched.id()); } diff --git a/vortex-btrblocks/tests/snapshots/golden__compact__binary_low_cardinality.snap b/vortex-btrblocks/tests/snapshots/golden__compact__binary_low_cardinality.snap index 788cf54ecd8..b53b1016145 100644 --- a/vortex-btrblocks/tests/snapshots/golden__compact__binary_low_cardinality.snap +++ b/vortex-btrblocks/tests/snapshots/golden__compact__binary_low_cardinality.snap @@ -5,7 +5,7 @@ expression: rendered input: binary, len=16384, nbytes=315856 root: vortex.dict(binary, len=16384) nbytes=6196 metadata: all_values_referenced: true - codes: fastlanes.bitpacked(u8, len=16384) nbytes=6144 + codes: fastlanes.bitpacked.v2(u8, len=16384) nbytes=6144 metadata: bit_width: 3, offset: 0 values: vortex.varbin(binary, len=5) nbytes=52 metadata: diff --git a/vortex-btrblocks/tests/snapshots/golden__compact__float_low_cardinality.snap b/vortex-btrblocks/tests/snapshots/golden__compact__float_low_cardinality.snap index 006f5a93b69..e6e54062b8e 100644 --- a/vortex-btrblocks/tests/snapshots/golden__compact__float_low_cardinality.snap +++ b/vortex-btrblocks/tests/snapshots/golden__compact__float_low_cardinality.snap @@ -5,7 +5,7 @@ expression: rendered input: f64, len=16384, nbytes=131072 root: vortex.dict(f64, len=16384) nbytes=6184 metadata: all_values_referenced: true - codes: fastlanes.bitpacked(u8, len=16384) nbytes=6144 + codes: fastlanes.bitpacked.v2(u8, len=16384) nbytes=6144 metadata: bit_width: 3, offset: 0 values: vortex.alp(f64, len=8) nbytes=40 metadata: exponents: e: 16, f: 11 diff --git a/vortex-btrblocks/tests/snapshots/golden__compact__int_low_cardinality.snap b/vortex-btrblocks/tests/snapshots/golden__compact__int_low_cardinality.snap index 0cbe2e06814..677fd20d6d1 100644 --- a/vortex-btrblocks/tests/snapshots/golden__compact__int_low_cardinality.snap +++ b/vortex-btrblocks/tests/snapshots/golden__compact__int_low_cardinality.snap @@ -5,7 +5,7 @@ expression: rendered input: i64, len=16384, nbytes=131072 root: vortex.dict(i64, len=16384) nbytes=6177 metadata: all_values_referenced: true - codes: fastlanes.bitpacked(u8, len=16384) nbytes=6144 + codes: fastlanes.bitpacked.v2(u8, len=16384) nbytes=6144 metadata: bit_width: 3, offset: 0 values: vortex.pco(i64, len=6) nbytes=33 metadata: ptype: i64, nrows: 6, slice: 0..6 diff --git a/vortex-btrblocks/tests/snapshots/golden__compact__int_negatives.snap b/vortex-btrblocks/tests/snapshots/golden__compact__int_negatives.snap index 71969bd5250..82c896b6082 100644 --- a/vortex-btrblocks/tests/snapshots/golden__compact__int_negatives.snap +++ b/vortex-btrblocks/tests/snapshots/golden__compact__int_negatives.snap @@ -5,7 +5,7 @@ expression: rendered input: i64, len=16384, nbytes=131072 root: fastlanes.for.v2(i64, len=16384) nbytes=16387 metadata: offset: 0 - encoded: fastlanes.bitpacked(i64, len=16384) nbytes=16384 + encoded: fastlanes.bitpacked.v2(i64, len=16384) nbytes=16384 metadata: bit_width: 8, offset: 0 references: vortex.constant(i64, len=16) nbytes=3 metadata: scalar: -128i64 diff --git a/vortex-btrblocks/tests/snapshots/golden__compact__string_low_cardinality.snap b/vortex-btrblocks/tests/snapshots/golden__compact__string_low_cardinality.snap index a65054ac178..1d5b1b7eab3 100644 --- a/vortex-btrblocks/tests/snapshots/golden__compact__string_low_cardinality.snap +++ b/vortex-btrblocks/tests/snapshots/golden__compact__string_low_cardinality.snap @@ -5,7 +5,7 @@ expression: rendered input: utf8, len=16384, nbytes=262144 root: vortex.dict(utf8, len=16384) nbytes=8287 metadata: all_values_referenced: true - codes: fastlanes.bitpacked(u8, len=16384) nbytes=8192 + codes: fastlanes.bitpacked.v2(u8, len=16384) nbytes=8192 metadata: bit_width: 4, offset: 0 values: vortex.zstd(utf8, len=12) nbytes=95 metadata: nrows: 12, slice: 0..12 diff --git a/vortex-btrblocks/tests/snapshots/golden__compact__struct_mixed.snap b/vortex-btrblocks/tests/snapshots/golden__compact__struct_mixed.snap index 83e34c583dc..ba975ae3c38 100644 --- a/vortex-btrblocks/tests/snapshots/golden__compact__struct_mixed.snap +++ b/vortex-btrblocks/tests/snapshots/golden__compact__struct_mixed.snap @@ -9,7 +9,7 @@ root: vortex.struct({id=i64, category=utf8, value=f64}, len=16384) nbytes=55986 metadata: base: 10000i64, multiplier: 7i64 category: vortex.dict(utf8, len=16384) nbytes=8287 metadata: all_values_referenced: true - codes: fastlanes.bitpacked(u8, len=16384) nbytes=8192 + codes: fastlanes.bitpacked.v2(u8, len=16384) nbytes=8192 metadata: bit_width: 4, offset: 0 values: vortex.zstd(utf8, len=12) nbytes=95 metadata: nrows: 12, slice: 0..12 diff --git a/vortex-btrblocks/tests/snapshots/golden__onpair__string_fsst_structured.snap b/vortex-btrblocks/tests/snapshots/golden__onpair__string_fsst_structured.snap index 028dbe81dc9..b20e50ae061 100644 --- a/vortex-btrblocks/tests/snapshots/golden__onpair__string_fsst_structured.snap +++ b/vortex-btrblocks/tests/snapshots/golden__onpair__string_fsst_structured.snap @@ -7,7 +7,7 @@ root: vortex.onpair(utf8, len=16384) nbytes=139746 metadata: dict_bytes_len: 11866 dict_offsets: vortex.primitive(u16, len=1626) nbytes=3252 metadata: ptype: u16 - codes: fastlanes.bitpacked(u16, len=63845) nbytes=88704 + codes: fastlanes.bitpacked.v2(u16, len=63845) nbytes=88704 metadata: bit_width: 11, offset: 0 codes_offsets: vortex.primitive(u16, len=16385) nbytes=32770 metadata: ptype: u16 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__binary_low_cardinality.snap b/vortex-btrblocks/tests/snapshots/golden__regular__binary_low_cardinality.snap index 788cf54ecd8..b53b1016145 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__binary_low_cardinality.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__binary_low_cardinality.snap @@ -5,7 +5,7 @@ expression: rendered input: binary, len=16384, nbytes=315856 root: vortex.dict(binary, len=16384) nbytes=6196 metadata: all_values_referenced: true - codes: fastlanes.bitpacked(u8, len=16384) nbytes=6144 + codes: fastlanes.bitpacked.v2(u8, len=16384) nbytes=6144 metadata: bit_width: 3, offset: 0 values: vortex.varbin(binary, len=5) nbytes=52 metadata: diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__decimal_prices.snap b/vortex-btrblocks/tests/snapshots/golden__regular__decimal_prices.snap index 6eb4ccfed8d..039178e7c35 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__decimal_prices.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__decimal_prices.snap @@ -5,5 +5,5 @@ expression: rendered input: decimal(12,2), len=16384, nbytes=131072 root: vortex.decimal_byte_parts.v2(decimal(12,2), len=16384) nbytes=49152 metadata: - msp: fastlanes.bitpacked(i32, len=16384) nbytes=49152 + msp: fastlanes.bitpacked.v2(i32, len=16384) nbytes=49152 metadata: bit_width: 24, offset: 0 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__float_alp_prices.snap b/vortex-btrblocks/tests/snapshots/golden__regular__float_alp_prices.snap index f70d90a301d..06364e4ddc6 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__float_alp_prices.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__float_alp_prices.snap @@ -5,5 +5,5 @@ expression: rendered input: f64, len=16384, nbytes=131072 root: vortex.alp(f64, len=16384) nbytes=49152 metadata: exponents: e: 14, f: 12 - encoded: fastlanes.bitpacked(i64, len=16384) nbytes=49152 + encoded: fastlanes.bitpacked.v2(i64, len=16384) nbytes=49152 metadata: bit_width: 24, offset: 0 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__float_full_precision.snap b/vortex-btrblocks/tests/snapshots/golden__regular__float_full_precision.snap index 487e7c7900b..319e63ef5fa 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__float_full_precision.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__float_full_precision.snap @@ -5,9 +5,9 @@ expression: rendered input: f64, len=16384, nbytes=131072 root: vortex.alprd(f64, len=16384) nbytes=112880 metadata: right_bit_width: 52, patch_offset: 0 - left_parts: fastlanes.bitpacked(u16, len=16384) nbytes=6144 + left_parts: fastlanes.bitpacked.v2(u16, len=16384) nbytes=6144 metadata: bit_width: 3, offset: 0 - right_parts: fastlanes.bitpacked(u64, len=16384) nbytes=106496 + right_parts: fastlanes.bitpacked.v2(u64, len=16384) nbytes=106496 metadata: bit_width: 52, offset: 0 patch_indices: vortex.primitive(u16, len=60) nbytes=120 metadata: ptype: u16 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__float_low_cardinality.snap b/vortex-btrblocks/tests/snapshots/golden__regular__float_low_cardinality.snap index 9c2192c70d7..59291fba1e2 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__float_low_cardinality.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__float_low_cardinality.snap @@ -7,7 +7,7 @@ root: vortex.alp(f64, len=16384) nbytes=6208 metadata: exponents: e: 16, f: 11 encoded: vortex.dict(i64, len=16384) nbytes=6208 metadata: all_values_referenced: true - codes: fastlanes.bitpacked(u8, len=16384) nbytes=6144 + codes: fastlanes.bitpacked.v2(u8, len=16384) nbytes=6144 metadata: bit_width: 3, offset: 0 values: vortex.primitive(i64, len=8) nbytes=64 metadata: ptype: i64 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__int_low_cardinality.snap b/vortex-btrblocks/tests/snapshots/golden__regular__int_low_cardinality.snap index 8648200a138..a6d01e4f5ea 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__int_low_cardinality.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__int_low_cardinality.snap @@ -5,7 +5,7 @@ expression: rendered input: i64, len=16384, nbytes=131072 root: vortex.dict(i64, len=16384) nbytes=6192 metadata: all_values_referenced: true - codes: fastlanes.bitpacked(u8, len=16384) nbytes=6144 + codes: fastlanes.bitpacked.v2(u8, len=16384) nbytes=6144 metadata: bit_width: 3, offset: 0 values: vortex.primitive(i64, len=6) nbytes=48 metadata: ptype: i64 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__int_monotone_jitter.snap b/vortex-btrblocks/tests/snapshots/golden__regular__int_monotone_jitter.snap index 47fd238df48..eb7d562744c 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__int_monotone_jitter.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__int_monotone_jitter.snap @@ -5,7 +5,7 @@ expression: rendered input: u64, len=16384, nbytes=131072 root: fastlanes.for.v2(u64, len=16384) nbytes=49159 metadata: offset: 0 - encoded: fastlanes.bitpacked(u64, len=16384) nbytes=49152 + encoded: fastlanes.bitpacked.v2(u64, len=16384) nbytes=49152 metadata: bit_width: 24, offset: 0 references: vortex.constant(u64, len=16) nbytes=7 metadata: scalar: 1700000001036u64 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__int_monotone_jitter_nullable.snap b/vortex-btrblocks/tests/snapshots/golden__regular__int_monotone_jitter_nullable.snap index 5450f58a46a..27b76466969 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__int_monotone_jitter_nullable.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__int_monotone_jitter_nullable.snap @@ -5,7 +5,7 @@ expression: rendered input: u64?, len=16384, nbytes=133120 root: fastlanes.for.v2(u64?, len=16384) nbytes=51207 metadata: offset: 0 - encoded: fastlanes.bitpacked(u64?, len=16384) nbytes=51200 + encoded: fastlanes.bitpacked.v2(u64?, len=16384) nbytes=51200 metadata: bit_width: 24, offset: 0 validity_child: vortex.bool(bool, len=16384) nbytes=2048 metadata: offset: 0 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__int_mostly_null.snap b/vortex-btrblocks/tests/snapshots/golden__regular__int_mostly_null.snap index 73566369279..2660f322248 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__int_mostly_null.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__int_mostly_null.snap @@ -7,7 +7,7 @@ root: vortex.sparse(i32?, len=16384) nbytes=3031 metadata: fill_value: null patch_indices: vortex.primitive(u16, len=823) nbytes=1646 metadata: ptype: u16 - patch_values: fastlanes.bitpacked(i32?, len=823) nbytes=1383 + patch_values: fastlanes.bitpacked.v2(i32?, len=823) nbytes=1383 metadata: bit_width: 10, offset: 0 validity_child: vortex.bool(bool, len=823) nbytes=103 metadata: offset: 0 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__int_negatives.snap b/vortex-btrblocks/tests/snapshots/golden__regular__int_negatives.snap index 71969bd5250..82c896b6082 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__int_negatives.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__int_negatives.snap @@ -5,7 +5,7 @@ expression: rendered input: i64, len=16384, nbytes=131072 root: fastlanes.for.v2(i64, len=16384) nbytes=16387 metadata: offset: 0 - encoded: fastlanes.bitpacked(i64, len=16384) nbytes=16384 + encoded: fastlanes.bitpacked.v2(i64, len=16384) nbytes=16384 metadata: bit_width: 8, offset: 0 references: vortex.constant(i64, len=16) nbytes=3 metadata: scalar: -128i64 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__int_runs.snap b/vortex-btrblocks/tests/snapshots/golden__regular__int_runs.snap index 17879983065..84e86906f6d 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__int_runs.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__int_runs.snap @@ -7,13 +7,13 @@ root: vortex.runend(i32, len=16384) nbytes=3974 metadata: offset: 0 ends: fastlanes.for.v2(u16, len=1020) nbytes=1794 metadata: offset: 0 - encoded: fastlanes.bitpacked(u16, len=1020) nbytes=1792 + encoded: fastlanes.bitpacked.v2(u16, len=1020) nbytes=1792 metadata: bit_width: 14, offset: 0 references: vortex.constant(u16, len=1) nbytes=2 metadata: scalar: 13u16 values: fastlanes.for.v2(i32, len=1020) nbytes=2180 metadata: offset: 0 - encoded: fastlanes.bitpacked(i32, len=1020) nbytes=2176 + encoded: fastlanes.bitpacked.v2(i32, len=1020) nbytes=2176 metadata: bit_width: 17, offset: 0 references: vortex.constant(i32, len=1) nbytes=4 metadata: scalar: -49931i32 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__int_sparse_outliers.snap b/vortex-btrblocks/tests/snapshots/golden__regular__int_sparse_outliers.snap index b4de2764333..38ca753c9ad 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__int_sparse_outliers.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__int_sparse_outliers.snap @@ -9,7 +9,7 @@ root: vortex.sparse(i64, len=16384) nbytes=5546 metadata: ptype: u16 patch_values: fastlanes.for.v2(i64, len=848) nbytes=3846 metadata: offset: 0 - encoded: fastlanes.bitpacked(i64, len=848) nbytes=3840 + encoded: fastlanes.bitpacked.v2(i64, len=848) nbytes=3840 metadata: bit_width: 30, offset: 0 references: vortex.constant(i64, len=1) nbytes=6 metadata: scalar: 1000830099i64 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__list_of_int_runs.snap b/vortex-btrblocks/tests/snapshots/golden__regular__list_of_int_runs.snap index 9ea63e9f0c6..612518768c6 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__list_of_int_runs.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__list_of_int_runs.snap @@ -9,17 +9,17 @@ root: vortex.list(list(i32), len=4066) nbytes=11152 metadata: offset: 0 ends: fastlanes.for.v2(u16, len=1020) nbytes=1794 metadata: offset: 0 - encoded: fastlanes.bitpacked(u16, len=1020) nbytes=1792 + encoded: fastlanes.bitpacked.v2(u16, len=1020) nbytes=1792 metadata: bit_width: 14, offset: 0 references: vortex.constant(u16, len=1) nbytes=2 metadata: scalar: 13u16 values: fastlanes.for.v2(i32, len=1020) nbytes=2180 metadata: offset: 0 - encoded: fastlanes.bitpacked(i32, len=1020) nbytes=2176 + encoded: fastlanes.bitpacked.v2(i32, len=1020) nbytes=2176 metadata: bit_width: 17, offset: 0 references: vortex.constant(i32, len=1) nbytes=4 metadata: scalar: -49931i32 - offsets: fastlanes.bitpacked(u16, len=4067) nbytes=7178 + offsets: fastlanes.bitpacked.v2(u16, len=4067) nbytes=7178 metadata: bit_width: 14, offset: 0 patch_indices: vortex.primitive(u16, len=1) nbytes=2 metadata: ptype: u16 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__string_fsst_structured.snap b/vortex-btrblocks/tests/snapshots/golden__regular__string_fsst_structured.snap index 327f050b0d7..20c4e76deb4 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__string_fsst_structured.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__string_fsst_structured.snap @@ -11,5 +11,5 @@ root: vortex.fsst(utf8, len=16384) nbytes=151382 metadata: ptype: u16 patch_values: vortex.constant(u8, len=1575) nbytes=2 metadata: scalar: 23u8 - codes_offsets: fastlanes.bitpacked(u32, len=16385) nbytes=36992 + codes_offsets: fastlanes.bitpacked.v2(u32, len=16385) nbytes=36992 metadata: bit_width: 17, offset: 0 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__string_low_cardinality.snap b/vortex-btrblocks/tests/snapshots/golden__regular__string_low_cardinality.snap index ec79989d46a..6217402155d 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__string_low_cardinality.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__string_low_cardinality.snap @@ -5,7 +5,7 @@ expression: rendered input: utf8, len=16384, nbytes=262144 root: vortex.dict(utf8, len=16384) nbytes=8373 metadata: all_values_referenced: true - codes: fastlanes.bitpacked(u8, len=16384) nbytes=8192 + codes: fastlanes.bitpacked.v2(u8, len=16384) nbytes=8192 metadata: bit_width: 4, offset: 0 values: vortex.fsst(utf8, len=12) nbytes=181 metadata: len: 12, nsymbols: 9 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__struct_mixed.snap b/vortex-btrblocks/tests/snapshots/golden__regular__struct_mixed.snap index be8f48b779b..81f37902b41 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__struct_mixed.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__struct_mixed.snap @@ -9,7 +9,7 @@ root: vortex.struct({id=i64, category=utf8, value=f64}, len=16384) nbytes=57525 metadata: base: 10000i64, multiplier: 7i64 category: vortex.dict(utf8, len=16384) nbytes=8373 metadata: all_values_referenced: true - codes: fastlanes.bitpacked(u8, len=16384) nbytes=8192 + codes: fastlanes.bitpacked.v2(u8, len=16384) nbytes=8192 metadata: bit_width: 4, offset: 0 values: vortex.fsst(utf8, len=12) nbytes=181 metadata: len: 12, nsymbols: 9 @@ -19,5 +19,5 @@ root: vortex.struct({id=i64, category=utf8, value=f64}, len=16384) nbytes=57525 metadata: ptype: u8 value: vortex.alp(f64, len=16384) nbytes=49152 metadata: exponents: e: 14, f: 12 - encoded: fastlanes.bitpacked(i64, len=16384) nbytes=49152 + encoded: fastlanes.bitpacked.v2(i64, len=16384) nbytes=49152 metadata: bit_width: 24, offset: 0 diff --git a/vortex-btrblocks/tests/snapshots/golden__regular__temporal_timestamp_micros.snap b/vortex-btrblocks/tests/snapshots/golden__regular__temporal_timestamp_micros.snap index 34339fc8d9a..5642e0c8bcf 100644 --- a/vortex-btrblocks/tests/snapshots/golden__regular__temporal_timestamp_micros.snap +++ b/vortex-btrblocks/tests/snapshots/golden__regular__temporal_timestamp_micros.snap @@ -7,7 +7,7 @@ root: vortex.ext(vortex.timestamp[µs, tz=UTC](i64), len=16384) nbytes=67593 metadata: storage: fastlanes.for.v2(i64, len=16384) nbytes=67593 metadata: offset: 0 - encoded: fastlanes.bitpacked(i64, len=16384) nbytes=67584 + encoded: fastlanes.bitpacked.v2(i64, len=16384) nbytes=67584 metadata: bit_width: 33, offset: 0 references: vortex.constant(i64, len=16) nbytes=9 metadata: scalar: 1700000000891673i64