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
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,14 @@ Examples:
cargo nextest run -p vortex-array
make -C docs doctest
uv run --all-packages pytest vortex-python/test
cargo test --doc
```

Run docs doctests from the docs directory with `make -C docs doctest` so the correct Sphinx
Makefile target is used.

If you touch documentation run doc tests via `cargo test --doc`.

## Linting, Formatting, and Generated Files

Run verification that matches the files changed. Do not run expensive Rust checks for changes that
Expand Down
59 changes: 23 additions & 36 deletions encodings/alp/src/alp/compute/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use vortex_array::ArrayView;
use vortex_array::IntoArray;
use vortex_array::builtins::ArrayBuiltins;
use vortex_array::dtype::DType;
use vortex_array::patches::Patches;
use vortex_array::scalar_fn::fns::cast::CastReduce;
use vortex_error::VortexResult;

Expand All @@ -17,41 +16,29 @@ use crate::alp::ALP;
impl CastReduce for ALP {
fn cast(array: ArrayView<'_, Self>, dtype: &DType) -> VortexResult<Option<ArrayRef>> {
// Check if this is just a nullability change
if array.dtype().eq_ignore_nullability(dtype) {
// For nullability-only changes, we can avoid decoding
// Cast the encoded array (integers) to handle nullability
let new_encoded = array.encoded().cast(
array
.encoded()
.dtype()
.with_nullability(dtype.nullability()),
)?;

let new_patches = array
.patches()
.map(|p| {
if p.values().dtype() == dtype {
Ok(p)
} else {
Patches::new(
p.array_len(),
p.offset(),
p.indices().clone(),
p.values().cast(dtype.clone())?,
p.chunk_offsets().clone(),
)
}
})
.transpose()?;

// SAFETY: casting nullability doesn't alter the invariants
unsafe {
Ok(Some(
ALP::new_unchecked(new_encoded, array.exponents(), new_patches).into_array(),
))
}
} else {
Ok(None)
if !array.dtype().eq_ignore_nullability(dtype) {
return Ok(None);
}

// For nullability-only changes, we can avoid decoding
// Cast the encoded array (integers) to handle nullability
let new_encoded = array.encoded().cast(
array
.encoded()
.dtype()
.with_nullability(dtype.nullability()),
)?;

let new_patches = array
.patches()
.map(|p| p.map_values(|v| v.cast(dtype.clone())))
.transpose()?;

// SAFETY: casting nullability doesn't alter the invariants
unsafe {
Ok(Some(
ALP::new_unchecked(new_encoded, array.exponents(), new_patches).into_array(),
))
}
}
}
Expand Down
58 changes: 29 additions & 29 deletions encodings/alp/src/alp_rd/compute/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
use vortex_array::ArrayRef;
use vortex_array::ArrayView;
use vortex_array::IntoArray;
use vortex_array::LEGACY_SESSION;
use vortex_array::VortexSessionExecute;
use vortex_array::builtins::ArrayBuiltins;
use vortex_array::dtype::DType;
use vortex_array::scalar_fn::fns::cast::CastReduce;
Expand All @@ -16,38 +14,35 @@ use crate::alp_rd::ALPRD;

impl CastReduce for ALPRD {
fn cast(array: ArrayView<'_, Self>, dtype: &DType) -> VortexResult<Option<ArrayRef>> {
// ALPRDArray stores floating-point values, so only cast between float types
// or if just changing nullability

// Check if this is just a nullability change
if array.dtype().eq_ignore_nullability(dtype) {
// For nullability-only changes, we need to cast the left_parts array
// since it carries the validity information
let new_left_parts = array.left_parts().cast(
array
.left_parts()
.dtype()
.with_nullability(dtype.nullability()),
)?;
if !array.dtype().eq_ignore_nullability(dtype) {
return Ok(None);
}

// For nullability-only changes, we need to cast the left_parts array
// since it carries the validity information
let new_left_parts = array.left_parts().cast(
array
.left_parts()
.dtype()
.with_nullability(dtype.nullability()),
)?;

// NOTE: `CastReduce::cast` has a fixed trait signature without `ExecutionCtx`, so we
// construct a legacy ctx locally at this trait boundary.
return Ok(Some(
ALPRD::try_new(
// NOTE: `CastReduce::cast` has a fixed trait signature without `ExecutionCtx`, so we
// construct a legacy ctx locally at this trait boundary.
Ok(Some(
unsafe {
ALPRD::new_unchecked(
dtype.clone(),
new_left_parts,
array.left_parts_dictionary().clone(),
array.right_parts().clone(),
array.right_bit_width(),
array.left_parts_patches(),
&mut LEGACY_SESSION.create_execution_ctx(),
)?
.into_array(),
));
}

// For other casts (e.g., f32 to f64), decode to canonical and let PrimitiveArray handle it
Ok(None)
)
}
.into_array(),
))
}
}

Expand Down Expand Up @@ -99,12 +94,17 @@ mod tests {
let encoder = RDEncoder::new(&values);
let alprd = encoder.encode(arr.as_view(), &mut ctx);

// Cast to NonNullable should fail since we have nulls
// Cast to NonNullable should fail since we have nulls. The failure surfaces during
// execution since the reduce path defers when the validity stat is not cached.
let result = alprd
.clone()
.into_array()
.cast(DType::Primitive(PType::F64, Nullability::NonNullable));
assert!(result.is_err());
.cast(DType::Primitive(PType::F64, Nullability::NonNullable))
.and_then(|a| {
a.execute::<PrimitiveArray>(&mut ctx)
.map(|p| p.into_array())
});
assert!(result.is_err(), "Expected error, got: {result:?}");

// Cast to same type with Nullable should succeed
let casted = alprd
Expand Down
4 changes: 4 additions & 0 deletions encodings/bytebool/public-api.lock
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_bytebool::ByteBool

pub fn vortex_bytebool::ByteBool::slice(array: vortex_array::array::view::ArrayView<'_, Self>, range: core::ops::range::Range<usize>) -> vortex_error::VortexResult<core::option::Option<vortex_array::array::erased::ArrayRef>>

impl vortex_array::scalar_fn::fns::cast::kernel::CastKernel for vortex_bytebool::ByteBool

pub fn vortex_bytebool::ByteBool::cast(array: vortex_array::array::view::ArrayView<'_, Self>, dtype: &vortex_array::dtype::DType, ctx: &mut vortex_array::executor::ExecutionCtx) -> vortex_error::VortexResult<core::option::Option<vortex_array::array::erased::ArrayRef>>

impl vortex_array::scalar_fn::fns::cast::kernel::CastReduce for vortex_bytebool::ByteBool

pub fn vortex_bytebool::ByteBool::cast(array: vortex_array::array::view::ArrayView<'_, Self>, dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult<core::option::Option<vortex_array::array::erased::ArrayRef>>
Expand Down
44 changes: 34 additions & 10 deletions encodings/bytebool/src/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use vortex_array::arrays::dict::TakeExecute;
use vortex_array::buffer::BufferHandle;
use vortex_array::dtype::DType;
use vortex_array::match_each_integer_ptype;
use vortex_array::scalar_fn::fns::cast::CastKernel;
use vortex_array::scalar_fn::fns::cast::CastReduce;
use vortex_array::scalar_fn::fns::mask::MaskReduce;
use vortex_array::validity::Validity;
Expand All @@ -24,20 +25,43 @@ impl CastReduce for ByteBool {
// ByteBool is essentially a bool array stored as bytes
// The main difference from BoolArray is the storage format
// For casting, we can decode to canonical (BoolArray) and let it handle the cast

// If just changing nullability, we can optimize
if array.dtype().eq_ignore_nullability(dtype) {
let new_validity = array
.validity()?
.cast_nullability(dtype.nullability(), array.len())?;
if !dtype.is_boolean() {
return Ok(None);
}

let Some(new_validity) = array
.validity()?
.try_cast_nullability(dtype.nullability(), array.len())?
else {
return Ok(None);
};

Ok(Some(
ByteBool::new(array.buffer().clone(), new_validity).into_array(),
))
}
}

return Ok(Some(
ByteBool::new(array.buffer().clone(), new_validity).into_array(),
));
impl CastKernel for ByteBool {
fn cast(
array: ArrayView<'_, Self>,
dtype: &DType,
ctx: &mut ExecutionCtx,
) -> VortexResult<Option<ArrayRef>> {
// Only handle nullability changes here; non-bool targets fall through to canonicalization.
if !dtype.is_boolean() {
return Ok(None);
}

// For other casts, decode to canonical and let BoolArray handle it
Ok(None)
let new_validity =
array
.validity()?
.cast_nullability(dtype.nullability(), array.len(), ctx)?;

Ok(Some(
ByteBool::new(array.buffer().clone(), new_validity).into_array(),
))
}
}

Expand Down
7 changes: 5 additions & 2 deletions encodings/bytebool/src/kernel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@

use vortex_array::arrays::dict::TakeExecuteAdaptor;
use vortex_array::kernel::ParentKernelSet;
use vortex_array::scalar_fn::fns::cast::CastExecuteAdaptor;

use crate::ByteBool;

pub(crate) const PARENT_KERNELS: ParentKernelSet<ByteBool> =
ParentKernelSet::new(&[ParentKernelSet::lift(&TakeExecuteAdaptor(ByteBool))]);
pub(crate) const PARENT_KERNELS: ParentKernelSet<ByteBool> = ParentKernelSet::new(&[
ParentKernelSet::lift(&CastExecuteAdaptor(ByteBool)),
ParentKernelSet::lift(&TakeExecuteAdaptor(ByteBool)),
]);
32 changes: 12 additions & 20 deletions encodings/decimal-byte-parts/src/decimal_byte_parts/compute/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,31 @@ use vortex_array::IntoArray;
use vortex_array::builtins::ArrayBuiltins;
use vortex_array::dtype::DType;
use vortex_array::scalar_fn::fns::cast::CastReduce;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;

use crate::DecimalByteParts;
use crate::decimal_byte_parts::DecimalBytePartsArrayExt;

impl CastReduce for DecimalByteParts {
fn cast(array: ArrayView<'_, Self>, dtype: &DType) -> VortexResult<Option<ArrayRef>> {
// Check if this is just a nullability change
if !dtype.eq_ignore_nullability(array.dtype()) {
return Ok(None);
}
// DecimalBytePartsArray can only have Decimal dtype, so we only handle decimal-to-decimal casts
let DType::Decimal(target_decimal, target_nullability) = dtype else {
// Cannot cast decimal to non-decimal types - delegate to canonical form
return Ok(None);
};

// Check if this is just a nullability change
if array
.dtype()
.as_decimal_opt()
.vortex_expect("must be a decimal dtype")
== target_decimal
&& array.dtype().nullability() != *target_nullability
{
// Cast the msp array to handle nullability change
let new_msp = array
.msp()
.cast(array.msp().dtype().with_nullability(*target_nullability))?;

return Ok(Some(
DecimalByteParts::try_new(new_msp, *target_decimal)?.into_array(),
));
}
// Cast the msp array to handle nullability change
let new_msp = array
.msp()
.cast(array.msp().dtype().with_nullability(*target_nullability))?;

// For precision/scale changes, decode to canonical and let DecimalArray handle it
Ok(None)
Ok(Some(
DecimalByteParts::try_new(new_msp, *target_decimal)?.into_array(),
))
}
}

Expand Down
4 changes: 4 additions & 0 deletions encodings/fastlanes/public-api.lock
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_fastlanes::BitPacked

pub fn vortex_fastlanes::BitPacked::slice(array: vortex_array::array::view::ArrayView<'_, Self>, range: core::ops::range::Range<usize>) -> vortex_error::VortexResult<core::option::Option<vortex_array::array::erased::ArrayRef>>

impl vortex_array::scalar_fn::fns::cast::kernel::CastKernel for vortex_fastlanes::BitPacked

pub fn vortex_fastlanes::BitPacked::cast(array: vortex_array::array::view::ArrayView<'_, Self>, dtype: &vortex_array::dtype::DType, ctx: &mut vortex_array::executor::ExecutionCtx) -> vortex_error::VortexResult<core::option::Option<vortex_array::array::erased::ArrayRef>>

impl vortex_array::scalar_fn::fns::cast::kernel::CastReduce for vortex_fastlanes::BitPacked

pub fn vortex_fastlanes::BitPacked::cast(array: vortex_array::array::view::ArrayView<'_, Self>, dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult<core::option::Option<vortex_array::array::erased::ArrayRef>>
Expand Down
14 changes: 10 additions & 4 deletions encodings/fastlanes/src/bitpacking/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,19 +337,25 @@ impl<T: TypedArrayRef<crate::BitPacked>> BitPackedArrayExt for T {}

#[cfg(test)]
mod test {
use std::sync::LazyLock;

use vortex_array::IntoArray;
use vortex_array::LEGACY_SESSION;
use vortex_array::VortexSessionExecute;
use vortex_array::arrays::PrimitiveArray;
use vortex_array::assert_arrays_eq;
use vortex_array::session::ArraySession;
use vortex_buffer::Buffer;
use vortex_session::VortexSession;

use crate::BitPackedData;
use crate::bitpacking::array::BitPackedArrayExt;

static SESSION: LazyLock<VortexSession> =
LazyLock::new(|| VortexSession::empty().with::<ArraySession>());

#[test]
fn test_encode() {
let mut ctx = LEGACY_SESSION.create_execution_ctx();
let mut ctx = SESSION.create_execution_ctx();
let values = [
Some(1u64),
None,
Expand All @@ -372,7 +378,7 @@ mod test {

#[test]
fn test_encode_too_wide() {
let mut ctx = LEGACY_SESSION.create_execution_ctx();
let mut ctx = SESSION.create_execution_ctx();
let values = [Some(1u8), None, Some(1), None, Some(1), None];
let uncompressed = PrimitiveArray::from_option_iter(values);
let _packed = BitPackedData::encode(&uncompressed.clone().into_array(), 8, &mut ctx)
Expand All @@ -383,7 +389,7 @@ mod test {

#[test]
fn signed_with_patches() {
let mut ctx = LEGACY_SESSION.create_execution_ctx();
let mut ctx = SESSION.create_execution_ctx();
let values: Buffer<i32> = (0i32..=512).collect();
let parray = values.clone().into_array();

Expand Down
Loading
Loading