Skip to content

Commit a3798cd

Browse files
committed
wip
Signed-off-by: Joe Isaacs <[email protected]>
1 parent da89aa2 commit a3798cd

File tree

3 files changed

+139
-39
lines changed

3 files changed

+139
-39
lines changed

vortex-vector/src/bool/vector.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,52 @@ impl VectorOps for BoolVector {
189189
}
190190
}
191191
}
192+
193+
#[cfg(test)]
194+
mod tests {
195+
use vortex_buffer::BitBuffer;
196+
use vortex_mask::Mask;
197+
198+
use super::*;
199+
200+
#[test]
201+
fn test_bool_vector_eq_with_validity_127() {
202+
// Test with 127 elements (not a multiple of 64, tests edge cases)
203+
let len = 127;
204+
205+
// Create bits: alternating true/false pattern
206+
let bits1: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
207+
let mut bits2: Vec<bool> = bits1.clone();
208+
209+
// Create validity: every 3rd element is invalid
210+
let validity_bools: Vec<bool> = (0..len).map(|i| i % 3 != 0).collect();
211+
let validity = Mask::from_buffer(BitBuffer::from(validity_bools));
212+
213+
let v1 = BoolVector::new(BitBuffer::from(bits1.clone()), validity.clone());
214+
let v2 = BoolVector::new(BitBuffer::from(bits2.clone()), validity.clone());
215+
216+
// Should be equal - same bits at valid positions
217+
assert_eq!(v1, v2);
218+
219+
// Now modify bits2 at an INVALID position - should still be equal
220+
bits2[0] = !bits2[0]; // Flip bit 0, which is invalid (0 % 3 == 0)
221+
let v3 = BoolVector::new(BitBuffer::from(bits2.clone()), validity.clone());
222+
assert_eq!(
223+
v1, v3,
224+
"Vectors should be equal when only invalid positions differ"
225+
);
226+
227+
// Now modify bits2 at a VALID position - should NOT be equal
228+
bits2[1] = !bits2[1]; // Flip bit 1, which is valid (1 % 3 != 0)
229+
let v4 = BoolVector::new(BitBuffer::from(bits2), validity);
230+
assert_ne!(v1, v4, "Vectors should differ when valid positions differ");
231+
232+
// Test with different validity patterns - should NOT be equal
233+
let validity2 = Mask::new_true(len);
234+
let v5 = BoolVector::new(BitBuffer::from(bits1), validity2);
235+
assert_ne!(
236+
v1, v5,
237+
"Vectors with different validity should not be equal"
238+
);
239+
}
240+
}

vortex-vector/src/listview/vector.rs

Lines changed: 73 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use super::ListViewScalar;
1717
use super::ListViewVectorMut;
1818
use crate::Vector;
1919
use crate::match_each_integer_pvector;
20+
use crate::match_each_integer_pvector_pair;
2021
use crate::primitive::PrimitiveVector;
2122
use crate::vector_ops::VectorMutOps;
2223
use crate::vector_ops::VectorOps;
@@ -65,58 +66,91 @@ pub struct ListViewVector {
6566
}
6667

6768
impl PartialEq for ListViewVector {
68-
#[allow(clippy::cast_possible_truncation)]
6969
fn eq(&self, other: &Self) -> bool {
7070
if self.len != other.len {
7171
return false;
7272
}
73-
// Validity patterns must match
7473
if self.validity != other.validity {
7574
return false;
7675
}
77-
// Elements lengths must match
7876
if self.elements.len() != other.elements.len() {
7977
return false;
8078
}
81-
// Build element-level mask: for each valid list, mark elements[offset..offset+size] as valid
82-
let valid_slices: Vec<(usize, usize)> = (0..self.len)
83-
.filter(|&i| self.validity.value(i))
84-
.map(|i| {
85-
let offset =
86-
match_each_integer_pvector!(&self.offsets, |v| { v.as_ref()[i] as usize });
87-
let size = match_each_integer_pvector!(&self.sizes, |v| { v.as_ref()[i] as usize });
88-
(offset, offset + size)
89-
})
90-
.collect();
91-
let element_mask = Mask::from_slices(self.elements.len(), valid_slices);
92-
93-
// Clone elements and apply the element-level mask
94-
let mut self_elements = self.elements.as_ref().clone();
95-
let mut other_elements = other.elements.as_ref().clone();
96-
self_elements.mask_validity(&element_mask);
97-
other_elements.mask_validity(&element_mask);
98-
99-
// Compare masked elements
100-
if self_elements != other_elements {
101-
return false;
102-
}
10379

104-
// Compare offsets and sizes at valid positions
105-
(0..self.len).all(|i| {
106-
if !self.validity.value(i) {
107-
return true;
108-
}
109-
let self_offset =
110-
match_each_integer_pvector!(&self.offsets, |v| { v.as_ref()[i] as usize });
111-
let other_offset =
112-
match_each_integer_pvector!(&other.offsets, |v| { v.as_ref()[i] as usize });
113-
let self_size =
114-
match_each_integer_pvector!(&self.sizes, |v| { v.as_ref()[i] as usize });
115-
let other_size =
116-
match_each_integer_pvector!(&other.sizes, |v| { v.as_ref()[i] as usize });
117-
self_offset == other_offset && self_size == other_size
80+
// Offsets and sizes must have matching types, then compare within the match
81+
match_each_integer_pvector_pair!(
82+
(&self.offsets, &other.offsets),
83+
|self_offsets, other_offsets| {
84+
match_each_integer_pvector_pair!(
85+
(&self.sizes, &other.sizes),
86+
|self_sizes, other_sizes| {
87+
listview_eq_impl(
88+
self.len,
89+
&self.validity,
90+
self.elements.as_ref(),
91+
other.elements.as_ref(),
92+
self_offsets,
93+
other_offsets,
94+
self_sizes,
95+
other_sizes,
96+
)
97+
},
98+
{ false } // Size types don't match
99+
)
100+
},
101+
{ false } // Offset types don't match
102+
)
103+
}
104+
}
105+
106+
/// Helper function for ListViewVector equality comparison.
107+
#[expect(clippy::too_many_arguments)]
108+
fn listview_eq_impl<O, S>(
109+
len: usize,
110+
validity: &Mask,
111+
self_elements: &Vector,
112+
other_elements: &Vector,
113+
self_offsets: &crate::primitive::PVector<O>,
114+
other_offsets: &crate::primitive::PVector<O>,
115+
self_sizes: &crate::primitive::PVector<S>,
116+
other_sizes: &crate::primitive::PVector<S>,
117+
) -> bool
118+
where
119+
O: vortex_dtype::NativePType + Copy,
120+
S: vortex_dtype::NativePType + Copy,
121+
usize: TryFrom<O> + TryFrom<S>,
122+
{
123+
// Build element-level mask: for each valid list, mark elements[offset..offset+size] as valid
124+
let valid_slices: Vec<(usize, usize)> = (0..len)
125+
.filter(|&i| validity.value(i))
126+
.map(|i| {
127+
let offset = self_offsets
128+
.get_as::<usize>(i)
129+
.vortex_expect("offset is valid and fits in usize");
130+
let size = self_sizes
131+
.get_as::<usize>(i)
132+
.vortex_expect("size is valid and fits in usize");
133+
(offset, offset + size)
118134
})
135+
.collect();
136+
let element_mask = Mask::from_slices(self_elements.len(), valid_slices);
137+
138+
// Clone elements and apply the element-level mask
139+
let mut self_elems = self_elements.clone();
140+
let mut other_elems = other_elements.clone();
141+
self_elems.mask_validity(&element_mask);
142+
other_elems.mask_validity(&element_mask);
143+
144+
if self_elems != other_elems {
145+
return false;
119146
}
147+
148+
// Compare offsets and sizes at valid positions
149+
(0..len).all(|i| {
150+
!validity.value(i)
151+
|| (self_offsets.get_as::<usize>(i) == other_offsets.get_as::<usize>(i)
152+
&& self_sizes.get_as::<usize>(i) == other_sizes.get_as::<usize>(i))
153+
})
120154
}
121155

122156
impl ListViewVector {

vortex-vector/src/primitive/generic.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,23 @@ impl<T> PVector<T> {
132132
self.validity.value(index).then(|| &self.elements[index])
133133
}
134134

135+
/// Gets an element at the given index and converts it to type `U`.
136+
///
137+
/// Returns `None` if:
138+
/// - The element at the given index is null
139+
/// - The conversion from `T` to `U` fails
140+
///
141+
/// # Panics
142+
///
143+
/// Panics if the index is out of bounds.
144+
pub fn get_as<U>(&self, index: usize) -> Option<U>
145+
where
146+
U: TryFrom<T>,
147+
T: Copy,
148+
{
149+
self.get(index).and_then(|&v| U::try_from(v).ok())
150+
}
151+
135152
/// Returns the internal [`Buffer`] of the [`PVector`].
136153
///
137154
/// Note that the internal buffer may hold garbage data in place of nulls. That information is

0 commit comments

Comments
 (0)