• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vortex-data / vortex / 16619484690

30 Jul 2025 10:01AM UTC coverage: 82.762% (+0.08%) from 82.687%
16619484690

Pull #4054

github

web-flow
Merge 7c0a6629c into 5f86536fe
Pull Request #4054: varbinview zip kernel

66 of 68 new or added lines in 2 files covered. (97.06%)

5 existing lines in 1 file now uncovered.

45293 of 54727 relevant lines covered (82.76%)

184570.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.41
/vortex-array/src/compute/zip.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use std::sync::LazyLock;
5

6
use arcref::ArcRef;
7
use vortex_dtype::DType;
8
use vortex_error::{VortexError, VortexResult, vortex_bail, vortex_err};
9
use vortex_mask::{AllOr, Mask};
10

11
use super::{ComputeFnVTable, InvocationArgs, Output, cast};
12
use crate::builders::{ArrayBuilder, builder_with_capacity};
13
use crate::compute::{ComputeFn, Kernel};
14
use crate::vtable::VTable;
15
use crate::{Array, ArrayRef};
16

17
/// Performs element-wise conditional selection between two arrays based on a mask.
18
///
19
/// Returns a new array where `result[i] = if_true[i]` when `mask[i]` is true,
20
/// otherwise `result[i] = if_false[i]`.
21
pub fn zip(if_true: &dyn Array, if_false: &dyn Array, mask: &Mask) -> VortexResult<ArrayRef> {
115✔
22
    ZIP_FN
115✔
23
        .invoke(&InvocationArgs {
115✔
24
            inputs: &[if_true.into(), if_false.into(), mask.into()],
115✔
25
            options: &(),
115✔
26
        })?
115✔
27
        .unwrap_array()
77✔
28
}
115✔
29

30
pub static ZIP_FN: LazyLock<ComputeFn> = LazyLock::new(|| {
115✔
31
    let compute = ComputeFn::new("zip".into(), ArcRef::new_ref(&Zip));
115✔
32
    for kernel in inventory::iter::<ZipKernelRef> {
230✔
33
        compute.register_kernel(kernel.0.clone());
115✔
34
    }
115✔
35
    compute
115✔
36
});
115✔
37

38
struct Zip;
39

40
impl ComputeFnVTable for Zip {
41
    fn invoke(
77✔
42
        &self,
77✔
43
        args: &InvocationArgs,
77✔
44
        kernels: &[ArcRef<dyn Kernel>],
77✔
45
    ) -> VortexResult<Output> {
77✔
46
        let ZipArgs {
47
            if_true,
77✔
48
            if_false,
77✔
49
            mask,
77✔
50
        } = ZipArgs::try_from(args)?;
77✔
51

52
        if mask.all_true() {
77✔
53
            return Ok(cast(if_true, &zip_return_dtype(if_true, if_false))?.into());
38✔
54
        }
39✔
55

56
        if mask.all_false() {
39✔
57
            return Ok(cast(if_false, &zip_return_dtype(if_true, if_false))?.into());
×
58
        }
39✔
59

60
        // check if if_true supports zip directly
61
        for kernel in kernels {
77✔
62
            if let Some(output) = kernel.invoke(args)? {
39✔
63
                return Ok(output);
1✔
64
            }
38✔
65
        }
66

67
        if let Some(output) = if_true.invoke(&ZIP_FN, args)? {
38✔
68
            return Ok(output);
×
69
        }
38✔
70

71
        // TODO(os): add invert_mask opt and check if if_false has a kernel like:
72
        //           kernel.invoke(Args(if_false, if_true, mask, invert_mask = true))
73

74
        Ok(zip_impl(
38✔
75
            if_true.to_canonical()?.as_ref(),
38✔
76
            if_false.to_canonical()?.as_ref(),
38✔
77
            mask,
38✔
78
        )?
×
79
        .into())
38✔
80
    }
77✔
81

82
    fn return_dtype(&self, args: &InvocationArgs) -> VortexResult<DType> {
77✔
83
        let ZipArgs {
84
            if_true, if_false, ..
77✔
85
        } = ZipArgs::try_from(args)?;
77✔
86

87
        if !if_true.dtype().eq_ignore_nullability(if_false.dtype()) {
77✔
88
            vortex_bail!("input arrays to zip must have the same dtype");
×
89
        }
77✔
90
        Ok(zip_return_dtype(if_true, if_false))
77✔
91
    }
77✔
92

93
    fn return_len(&self, args: &InvocationArgs) -> VortexResult<usize> {
77✔
94
        let ZipArgs { if_true, .. } = ZipArgs::try_from(args)?;
77✔
95
        // ComputeFn::invoke asserts if_true.len() == if_false.len(), because zip is elementwise
96
        Ok(if_true.len())
77✔
97
    }
77✔
98

99
    fn is_elementwise(&self) -> bool {
115✔
100
        true
115✔
101
    }
115✔
102
}
103

104
struct ZipArgs<'a> {
105
    if_true: &'a dyn Array,
106
    if_false: &'a dyn Array,
107
    mask: &'a Mask,
108
}
109

110
impl<'a> TryFrom<&InvocationArgs<'a>> for ZipArgs<'a> {
111
    type Error = VortexError;
112

113
    fn try_from(value: &InvocationArgs<'a>) -> Result<Self, Self::Error> {
270✔
114
        if value.inputs.len() != 3 {
270✔
UNCOV
115
            vortex_bail!("Expected 3 inputs for zip, found {}", value.inputs.len());
×
116
        }
270✔
117
        let if_true = value.inputs[0]
270✔
118
            .array()
270✔
119
            .ok_or_else(|| vortex_err!("Expected input 0 to be an array"))?;
270✔
120

121
        let if_false = value.inputs[1]
270✔
122
            .array()
270✔
123
            .ok_or_else(|| vortex_err!("Expected input 1 to be an array"))?;
270✔
124

125
        let mask = value.inputs[2]
270✔
126
            .mask()
270✔
127
            .ok_or_else(|| vortex_err!("Expected input 2 to be a mask"))?;
270✔
128

129
        Ok(Self {
270✔
130
            if_true,
270✔
131
            if_false,
270✔
132
            mask,
270✔
133
        })
270✔
134
    }
270✔
135
}
136

137
pub trait ZipKernel: VTable {
138
    fn zip(
139
        &self,
140
        if_true: &Self::Array,
141
        if_false: &dyn Array,
142
        mask: &Mask,
143
    ) -> VortexResult<Option<ArrayRef>>;
144
}
145

146
pub struct ZipKernelRef(pub ArcRef<dyn Kernel>);
147
inventory::collect!(ZipKernelRef);
148

149
#[derive(Debug)]
150
pub struct ZipKernelAdapter<V: VTable>(pub V);
151

152
impl<V: VTable + ZipKernel> ZipKernelAdapter<V> {
UNCOV
153
    pub const fn lift(&'static self) -> ZipKernelRef {
×
UNCOV
154
        ZipKernelRef(ArcRef::new_ref(self))
×
UNCOV
155
    }
×
156
}
157

158
impl<V: VTable + ZipKernel> Kernel for ZipKernelAdapter<V> {
159
    fn invoke(&self, args: &InvocationArgs) -> VortexResult<Option<Output>> {
39✔
160
        let ZipArgs {
161
            if_true,
39✔
162
            if_false,
39✔
163
            mask,
39✔
164
        } = ZipArgs::try_from(args)?;
39✔
165
        let Some(if_true) = if_true.as_opt::<V>() else {
39✔
166
            return Ok(None);
38✔
167
        };
168
        Ok(V::zip(&self.0, if_true, if_false, mask)?.map(Into::into))
1✔
169
    }
39✔
170
}
171

172
pub(crate) fn zip_return_dtype(if_true: &dyn Array, if_false: &dyn Array) -> DType {
154✔
173
    if_true
154✔
174
        .dtype()
154✔
175
        .union_nullability(if_false.dtype().nullability())
154✔
176
}
154✔
177

178
fn zip_impl(if_true: &dyn Array, if_false: &dyn Array, mask: &Mask) -> VortexResult<ArrayRef> {
38✔
179
    // if_true.len() == if_false.len() from ComputeFn::invoke
180
    let builder = builder_with_capacity(&zip_return_dtype(if_true, if_false), if_true.len());
38✔
181
    zip_impl_with_builder(if_true, if_false, mask, builder)
38✔
182
}
38✔
183

184
pub(crate) fn zip_impl_with_builder(
39✔
185
    if_true: &dyn Array,
39✔
186
    if_false: &dyn Array,
39✔
187
    mask: &Mask,
39✔
188
    mut builder: Box<dyn ArrayBuilder>,
39✔
189
) -> VortexResult<ArrayRef> {
39✔
190
    match mask.slices() {
39✔
191
        AllOr::All => Ok(if_true.to_array()),
×
192
        AllOr::None => Ok(if_false.to_array()),
×
193
        AllOr::Some(slices) => {
39✔
194
            for (start, end) in slices {
148✔
195
                builder.extend_from_array(&if_false.slice(builder.len(), *start)?)?;
109✔
196
                builder.extend_from_array(&if_true.slice(*start, *end)?)?;
109✔
197
            }
198
            if builder.len() < if_false.len() {
39✔
199
                builder.extend_from_array(&if_false.slice(builder.len(), if_false.len())?)?;
39✔
UNCOV
200
            }
×
201
            Ok(builder.finish())
39✔
202
        }
203
    }
204
}
39✔
205

206
#[cfg(test)]
207
mod tests {
208
    use vortex_array::arrays::{BoolArray, PrimitiveArray};
209
    use vortex_array::compute::zip;
210
    use vortex_array::{IntoArray, ToCanonical};
211
    use vortex_mask::Mask;
212

213
    #[test]
214
    fn test_zip_basic() {
1✔
215
        let mask =
1✔
216
            Mask::try_from(&BoolArray::from_iter([true, false, false, true, false])).unwrap();
1✔
217
        let if_true = PrimitiveArray::from_iter([10, 20, 30, 40, 50]).into_array();
1✔
218
        let if_false = PrimitiveArray::from_iter([1, 2, 3, 4, 5]).into_array();
1✔
219

220
        let result = zip(&if_true, &if_false, &mask).unwrap();
1✔
221
        let expected = PrimitiveArray::from_iter([10, 2, 3, 40, 5]);
1✔
222

223
        assert_eq!(
1✔
224
            result.to_primitive().unwrap().as_slice::<i32>(),
1✔
225
            expected.as_slice::<i32>()
1✔
226
        );
227
    }
1✔
228

229
    #[test]
230
    fn test_zip_all_true() {
1✔
231
        let mask = Mask::new_true(4);
1✔
232
        let if_true = PrimitiveArray::from_iter([10, 20, 30, 40]).into_array();
1✔
233
        let if_false =
1✔
234
            PrimitiveArray::from_option_iter([Some(1), Some(2), Some(3), None]).into_array();
1✔
235

236
        let result = zip(&if_true, &if_false, &mask).unwrap();
1✔
237

238
        assert_eq!(
1✔
239
            result.to_primitive().unwrap().as_slice::<i32>(),
1✔
240
            if_true.to_primitive().unwrap().as_slice::<i32>()
1✔
241
        );
242

243
        // result must be nullable even if_true was not
244
        assert_eq!(result.dtype(), if_false.dtype())
1✔
245
    }
1✔
246

247
    #[test]
248
    #[should_panic]
249
    fn test_invalid_lengths() {
1✔
250
        let mask = Mask::new_false(4);
1✔
251
        let if_true = PrimitiveArray::from_iter([10, 20, 30]).into_array();
1✔
252
        let if_false = PrimitiveArray::from_iter([1, 2, 3, 4]).into_array();
1✔
253

254
        zip(&if_true, &if_false, &mask).unwrap();
1✔
255
    }
1✔
256
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc