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

vortex-data / vortex / 16829852101

08 Aug 2025 11:59AM UTC coverage: 84.902% (+0.9%) from 83.993%
16829852101

Pull #4155

github

web-flow
Merge 80dcef7b7 into 3c012755d
Pull Request #4155: chore[bench-website]: add back tpc-ds to query_bench

50710 of 59728 relevant lines covered (84.9%)

567842.86 hits per line

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

66.86
/vortex-scalar/src/binary.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use std::fmt::{Display, Formatter};
5
use std::sync::Arc;
6

7
use itertools::Itertools;
8
use vortex_buffer::ByteBuffer;
9
use vortex_dtype::{DType, Nullability};
10
use vortex_error::{VortexError, VortexExpect as _, VortexResult, vortex_bail, vortex_err};
11

12
use crate::{InnerScalarValue, Scalar, ScalarValue};
13

14
/// A scalar value representing binary data.
15
///
16
/// This type provides a view into a binary scalar value, which can be either
17
/// a valid byte buffer or null.
18
#[derive(Debug, Hash)]
19
pub struct BinaryScalar<'a> {
20
    dtype: &'a DType,
21
    value: Option<Arc<ByteBuffer>>,
22
}
23

24
impl Display for BinaryScalar<'_> {
25
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2✔
26
        match &self.value {
2✔
27
            None => write!(f, "null"),
1✔
28
            Some(v) => write!(
1✔
29
                f,
1✔
30
                "\"{}\"",
1✔
31
                v.as_slice().iter().map(|b| format!("{b:x}")).format(" ")
12✔
32
            ),
33
        }
34
    }
2✔
35
}
36

37
impl PartialEq for BinaryScalar<'_> {
38
    fn eq(&self, other: &Self) -> bool {
8,234✔
39
        self.dtype.eq_ignore_nullability(other.dtype) && self.value == other.value
8,234✔
40
    }
8,234✔
41
}
42

43
impl Eq for BinaryScalar<'_> {}
44

45
impl PartialOrd for BinaryScalar<'_> {
46
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
78✔
47
        Some(self.cmp(other))
78✔
48
    }
78✔
49
}
50

51
impl Ord for BinaryScalar<'_> {
52
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
78✔
53
        self.value.cmp(&other.value)
78✔
54
    }
78✔
55
}
56

57
impl<'a> BinaryScalar<'a> {
58
    /// Creates a binary scalar from a data type and scalar value.
59
    ///
60
    /// # Errors
61
    ///
62
    /// Returns an error if the data type is not a binary type.
63
    pub fn from_scalar_value(dtype: &'a DType, value: ScalarValue) -> VortexResult<Self> {
×
64
        if !matches!(dtype, DType::Binary(..)) {
×
65
            vortex_bail!("Can only construct binary scalar from binary dtype, found {dtype}")
×
66
        }
×
67
        Ok(Self {
68
            dtype,
×
69
            value: value.as_buffer()?,
×
70
        })
71
    }
×
72

73
    /// Returns the data type of this binary scalar.
74
    #[inline]
75
    pub fn dtype(&self) -> &'a DType {
4✔
76
        self.dtype
4✔
77
    }
4✔
78

79
    /// Returns the binary value as a byte buffer, or None if null.
80
    pub fn value(&self) -> Option<ByteBuffer> {
496,309✔
81
        self.value.as_ref().map(|v| v.as_ref().clone())
496,309✔
82
    }
496,309✔
83

84
    /// Returns a reference to the binary value, or None if null.
85
    /// This avoids cloning the underlying ByteBuffer.
86
    pub fn value_ref(&self) -> Option<&ByteBuffer> {
×
87
        self.value.as_ref().map(|v| v.as_ref())
×
88
    }
×
89

90
    /// Constructs a value at most `max_length` in size that's greater than this value.
91
    ///
92
    /// Returns None if constructing a greater value would overflow.
93
    pub fn upper_bound(self, max_length: usize) -> Option<Self> {
41✔
94
        if let Some(value) = self.value {
41✔
95
            if value.len() > max_length {
41✔
96
                let sliced = value.slice(0..max_length);
41✔
97
                drop(value);
41✔
98
                let mut sliced_mut = sliced.into_mut();
41✔
99
                for b in sliced_mut.iter_mut().rev() {
43✔
100
                    let (incr, overflow) = b.overflowing_add(1);
43✔
101
                    *b = incr;
43✔
102
                    if !overflow {
43✔
103
                        return Some(Self {
40✔
104
                            dtype: self.dtype,
40✔
105
                            value: Some(Arc::new(sliced_mut.freeze())),
40✔
106
                        });
40✔
107
                    }
3✔
108
                }
109
                None
1✔
110
            } else {
111
                Some(Self {
×
112
                    dtype: self.dtype,
×
113
                    value: Some(value),
×
114
                })
×
115
            }
116
        } else {
117
            Some(self)
×
118
        }
119
    }
41✔
120

121
    /// Construct a value at most `max_length` in size that's less than ourselves.
122
    pub fn lower_bound(self, max_length: usize) -> Self {
40✔
123
        if let Some(value) = self.value {
40✔
124
            if value.len() > max_length {
40✔
125
                Self {
40✔
126
                    dtype: self.dtype,
40✔
127
                    value: Some(Arc::new(value.slice(0..max_length))),
40✔
128
                }
40✔
129
            } else {
130
                Self {
×
131
                    dtype: self.dtype,
×
132
                    value: Some(value),
×
133
                }
×
134
            }
135
        } else {
136
            self
×
137
        }
138
    }
40✔
139

140
    pub(crate) fn cast(&self, dtype: &DType) -> VortexResult<Scalar> {
×
141
        if !matches!(dtype, DType::Binary(..)) {
×
142
            vortex_bail!("Cannot cast binary to {}: unsupported conversion", dtype)
×
143
        }
×
144
        Ok(Scalar::new(
×
145
            dtype.clone(),
×
146
            ScalarValue(InnerScalarValue::Buffer(
×
147
                self.value
×
148
                    .as_ref()
×
149
                    .vortex_expect("nullness handled in Scalar::cast")
×
150
                    .clone(),
×
151
            )),
×
152
        ))
×
153
    }
×
154

155
    /// Length of the scalar value or None if value is null
156
    pub fn len(&self) -> Option<usize> {
156✔
157
        self.value.as_ref().map(|v| v.len())
156✔
158
    }
156✔
159

160
    /// Returns whether its value is non-null and empty, otherwise `None`.
161
    pub fn is_empty(&self) -> Option<bool> {
1,671✔
162
        self.value.as_ref().map(|v| v.is_empty())
1,671✔
163
    }
1,671✔
164
}
165

166
impl Scalar {
167
    /// Creates a new binary scalar from a byte buffer.
168
    pub fn binary(buffer: impl Into<ByteBuffer>, nullability: Nullability) -> Self {
508,762✔
169
        Self {
508,762✔
170
            dtype: DType::Binary(nullability),
508,762✔
171
            value: ScalarValue(InnerScalarValue::Buffer(Arc::new(buffer.into()))),
508,762✔
172
        }
508,762✔
173
    }
508,762✔
174
}
175

176
impl<'a> TryFrom<&'a Scalar> for BinaryScalar<'a> {
177
    type Error = VortexError;
178

179
    fn try_from(value: &'a Scalar) -> Result<Self, Self::Error> {
514,607✔
180
        if !matches!(value.dtype(), DType::Binary(_)) {
514,607✔
181
            vortex_bail!("Expected binary scalar, found {}", value.dtype())
×
182
        }
514,607✔
183
        Ok(Self {
184
            dtype: value.dtype(),
514,607✔
185
            value: value.value.as_buffer()?,
514,607✔
186
        })
187
    }
514,607✔
188
}
189

190
impl<'a> TryFrom<&'a Scalar> for ByteBuffer {
191
    type Error = VortexError;
192

193
    fn try_from(scalar: &'a Scalar) -> VortexResult<Self> {
1✔
194
        let binary = scalar
1✔
195
            .as_binary_opt()
1✔
196
            .ok_or_else(|| vortex_err!("Cannot extract buffer from non-buffer scalar"))?;
1✔
197

198
        binary
1✔
199
            .value()
1✔
200
            .ok_or_else(|| vortex_err!("Cannot extract present value from null scalar"))
1✔
201
    }
1✔
202
}
203

204
impl<'a> TryFrom<&'a Scalar> for Option<ByteBuffer> {
205
    type Error = VortexError;
206

207
    fn try_from(scalar: &'a Scalar) -> VortexResult<Self> {
×
208
        Ok(scalar
×
209
            .as_binary_opt()
×
210
            .ok_or_else(|| vortex_err!("Cannot extract buffer from non-buffer scalar"))?
×
211
            .value())
×
212
    }
×
213
}
214

215
impl TryFrom<Scalar> for ByteBuffer {
216
    type Error = VortexError;
217

218
    fn try_from(scalar: Scalar) -> VortexResult<Self> {
×
219
        Self::try_from(&scalar)
×
220
    }
×
221
}
222

223
impl TryFrom<Scalar> for Option<ByteBuffer> {
224
    type Error = VortexError;
225

226
    fn try_from(scalar: Scalar) -> VortexResult<Self> {
×
227
        Self::try_from(&scalar)
×
228
    }
×
229
}
230

231
impl From<&[u8]> for Scalar {
232
    fn from(value: &[u8]) -> Self {
×
233
        Scalar::from(ByteBuffer::from(value.to_vec()))
×
234
    }
×
235
}
236

237
impl From<ByteBuffer> for Scalar {
238
    fn from(value: ByteBuffer) -> Self {
39✔
239
        Self {
39✔
240
            dtype: DType::Binary(Nullability::NonNullable),
39✔
241
            value: value.into(),
39✔
242
        }
39✔
243
    }
39✔
244
}
245

246
impl From<Arc<ByteBuffer>> for Scalar {
247
    fn from(value: Arc<ByteBuffer>) -> Self {
×
248
        Self {
×
249
            dtype: DType::Binary(Nullability::NonNullable),
×
250
            value: ScalarValue(InnerScalarValue::Buffer(value)),
×
251
        }
×
252
    }
×
253
}
254

255
impl From<&[u8]> for ScalarValue {
256
    fn from(value: &[u8]) -> Self {
156✔
257
        ScalarValue::from(ByteBuffer::from(value.to_vec()))
156✔
258
    }
156✔
259
}
260

261
impl From<ByteBuffer> for ScalarValue {
262
    fn from(value: ByteBuffer) -> Self {
1,047✔
263
        ScalarValue(InnerScalarValue::Buffer(Arc::new(value)))
1,047✔
264
    }
1,047✔
265
}
266

267
#[cfg(test)]
268
mod tests {
269
    use vortex_buffer::buffer;
270
    use vortex_dtype::Nullability;
271
    use vortex_error::{VortexExpect, VortexUnwrap};
272

273
    use crate::{BinaryScalar, Scalar};
274

275
    #[test]
276
    fn lower_bound() {
1✔
277
        let binary = Scalar::binary(buffer![0u8, 5, 47, 33, 129], Nullability::NonNullable);
1✔
278
        let expected = Scalar::binary(buffer![0u8, 5], Nullability::NonNullable);
1✔
279
        assert_eq!(
1✔
280
            BinaryScalar::try_from(&binary)
1✔
281
                .vortex_unwrap()
1✔
282
                .lower_bound(2),
1✔
283
            BinaryScalar::try_from(&expected).vortex_unwrap()
1✔
284
        );
285
    }
1✔
286

287
    #[test]
288
    fn upper_bound() {
1✔
289
        let binary = Scalar::binary(buffer![0u8, 5, 255, 234, 23], Nullability::NonNullable);
1✔
290
        let expected = Scalar::binary(buffer![0u8, 6, 0], Nullability::NonNullable);
1✔
291
        assert_eq!(
1✔
292
            BinaryScalar::try_from(&binary)
1✔
293
                .vortex_unwrap()
1✔
294
                .upper_bound(3)
1✔
295
                .vortex_expect("must have upper bound"),
1✔
296
            BinaryScalar::try_from(&expected).vortex_unwrap()
1✔
297
        );
298
    }
1✔
299

300
    #[test]
301
    fn upper_bound_overflow() {
1✔
302
        let binary = Scalar::binary(buffer![255u8, 255, 255], Nullability::NonNullable);
1✔
303
        assert!(
1✔
304
            BinaryScalar::try_from(&binary)
1✔
305
                .vortex_unwrap()
1✔
306
                .upper_bound(2)
1✔
307
                .is_none()
1✔
308
        );
309
    }
1✔
310
}
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