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

vortex-data / vortex / 16989953990

15 Aug 2025 12:27PM UTC coverage: 87.855% (+0.2%) from 87.664%
16989953990

Pull #4226

github

web-flow
Merge 08d70c493 into fde6f426a
Pull Request #4226: Support converting TimestampTZ to and from duckdb

430 of 442 new or added lines in 15 files covered. (97.29%)

270 existing lines in 23 files now uncovered.

56314 of 64099 relevant lines covered (87.85%)

631227.63 hits per line

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

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

4
use std::fmt::{Debug, Display, Formatter};
5
use std::hash::Hash;
6
use std::ops::Index;
7
use std::sync::Arc;
8

9
use DType::*;
10
use itertools::Itertools;
11
use static_assertions::const_assert_eq;
12
use vortex_error::vortex_panic;
13

14
use crate::decimal::DecimalDType;
15
use crate::nullability::Nullability;
16
use crate::{ExtDType, FieldDType, PType, StructFields};
17

18
/// A name for a field in a struct
19
pub type FieldName = Arc<str>;
20

21
/// An ordered list of field names in a struct
22
#[derive(Clone, PartialEq, Eq, Debug, Default, Hash)]
23
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24
pub struct FieldNames(Arc<[FieldName]>);
25

26
impl PartialEq<&FieldNames> for FieldNames {
27
    fn eq(&self, other: &&FieldNames) -> bool {
1✔
28
        self == *other
1✔
29
    }
1✔
30
}
31

32
impl PartialEq<&[&str]> for FieldNames {
33
    fn eq(&self, other: &&[&str]) -> bool {
292✔
34
        self.len() == other.len() && self.iter().zip_eq(other.iter()).all(|(l, r)| &**l == *r)
822✔
35
    }
292✔
36
}
37

38
impl PartialEq<&[&str]> for &FieldNames {
39
    fn eq(&self, other: &&[&str]) -> bool {
289✔
40
        *self == other
289✔
41
    }
289✔
42
}
43

44
impl<const N: usize> PartialEq<[&str; N]> for FieldNames {
45
    fn eq(&self, other: &[&str; N]) -> bool {
4✔
46
        self == other.as_slice()
4✔
47
    }
4✔
48
}
49

50
impl<const N: usize> PartialEq<[&str; N]> for &FieldNames {
51
    fn eq(&self, other: &[&str; N]) -> bool {
11✔
52
        *self == other.as_slice()
11✔
53
    }
11✔
54
}
55

56
impl PartialEq<&[FieldName]> for FieldNames {
57
    fn eq(&self, other: &&[FieldName]) -> bool {
1✔
58
        self.0.as_ref() == *other
1✔
59
    }
1✔
60
}
61

62
impl PartialEq<&[FieldName]> for &FieldNames {
63
    fn eq(&self, other: &&[FieldName]) -> bool {
1✔
64
        self.0.as_ref() == *other
1✔
65
    }
1✔
66
}
67

68
impl FieldNames {
69
    /// Returns the number of elements.
70
    pub fn len(&self) -> usize {
1,265,139✔
71
        self.0.len()
1,265,139✔
72
    }
1,265,139✔
73

74
    /// Returns true if the number of elements is 0.
UNCOV
75
    pub fn is_empty(&self) -> bool {
×
UNCOV
76
        self.len() == 0
×
UNCOV
77
    }
×
78

79
    /// Returns a borrowed iterator over the field names.
80
    pub fn iter(&self) -> impl ExactSizeIterator<Item = &FieldName> {
140,201✔
81
        FieldNamesIter {
140,201✔
82
            inner: self,
140,201✔
83
            idx: 0,
140,201✔
84
        }
140,201✔
85
    }
140,201✔
86

87
    /// Returns a reference to a field name, or None if `index` is out of bounds.
88
    pub fn get(&self, index: usize) -> Option<&FieldName> {
562✔
89
        self.0.get(index)
562✔
90
    }
562✔
91
}
92

93
impl AsRef<[FieldName]> for FieldNames {
94
    fn as_ref(&self) -> &[FieldName] {
1,040✔
95
        &self.0
1,040✔
96
    }
1,040✔
97
}
98

99
impl Index<usize> for FieldNames {
100
    type Output = FieldName;
101

102
    fn index(&self, index: usize) -> &Self::Output {
25,632✔
103
        &self.0[index]
25,632✔
104
    }
25,632✔
105
}
106

107
/// Iterator of references to field names
108
pub struct FieldNamesIter<'a> {
109
    inner: &'a FieldNames,
110
    idx: usize,
111
}
112

113
impl<'a> Iterator for FieldNamesIter<'a> {
114
    type Item = &'a FieldName;
115

116
    fn next(&mut self) -> Option<Self::Item> {
459,127✔
117
        if self.idx >= self.inner.len() {
459,127✔
118
            return None;
35,112✔
119
        }
424,015✔
120

121
        let i = &self.inner.0[self.idx];
424,015✔
122
        self.idx += 1;
424,015✔
123
        Some(i)
424,015✔
124
    }
459,127✔
125

126
    fn size_hint(&self) -> (usize, Option<usize>) {
13,166✔
127
        let len = self.inner.len() - self.idx;
13,166✔
128
        (len, Some(len))
13,166✔
129
    }
13,166✔
130
}
131

132
impl ExactSizeIterator for FieldNamesIter<'_> {}
133

134
/// Owned iterator of field names.
135
pub struct FieldNamesIntoIter {
136
    inner: FieldNames,
137
    idx: usize,
138
}
139

140
impl Iterator for FieldNamesIntoIter {
141
    type Item = FieldName;
142

143
    fn next(&mut self) -> Option<Self::Item> {
3✔
144
        if self.idx >= self.inner.len() {
3✔
145
            return None;
1✔
146
        }
2✔
147

148
        let i = self.inner.0[self.idx].clone();
2✔
149
        self.idx += 1;
2✔
150
        Some(i)
2✔
151
    }
3✔
152

153
    fn size_hint(&self) -> (usize, Option<usize>) {
1✔
154
        let len = self.inner.len() - self.idx;
1✔
155
        (len, Some(len))
1✔
156
    }
1✔
157
}
158

159
impl ExactSizeIterator for FieldNamesIntoIter {}
160

161
impl IntoIterator for FieldNames {
162
    type Item = FieldName;
163

164
    type IntoIter = FieldNamesIntoIter;
165

166
    fn into_iter(self) -> Self::IntoIter {
2✔
167
        FieldNamesIntoIter {
2✔
168
            inner: self,
2✔
169
            idx: 0,
2✔
170
        }
2✔
171
    }
2✔
172
}
173

174
impl From<Vec<FieldName>> for FieldNames {
175
    fn from(value: Vec<FieldName>) -> Self {
42,413✔
176
        Self(value.into())
42,413✔
177
    }
42,413✔
178
}
179

180
impl From<&[&'static str]> for FieldNames {
UNCOV
181
    fn from(value: &[&'static str]) -> Self {
×
UNCOV
182
        Self(value.iter().cloned().map(Arc::from).collect())
×
UNCOV
183
    }
×
184
}
185

186
impl From<&[FieldName]> for FieldNames {
187
    fn from(value: &[FieldName]) -> Self {
120✔
188
        Self(Arc::from(value))
120✔
189
    }
120✔
190
}
191

192
impl<const N: usize> From<[&'static str; N]> for FieldNames {
193
    fn from(value: [&'static str; N]) -> Self {
267,628✔
194
        Self(value.into_iter().map(Arc::from).collect())
267,628✔
195
    }
267,628✔
196
}
197

198
impl<const N: usize> From<[FieldName; N]> for FieldNames {
199
    fn from(value: [FieldName; N]) -> Self {
11✔
200
        Self(value.into())
11✔
201
    }
11✔
202
}
203

204
impl<F: Into<FieldName>> FromIterator<F> for FieldNames {
205
    fn from_iter<T: IntoIterator<Item = F>>(iter: T) -> Self {
9,172✔
206
        Self(iter.into_iter().map(|v| v.into()).collect())
15,319✔
207
    }
9,172✔
208
}
209

210
/// The logical types of elements in Vortex arrays.
211
///
212
/// Vortex arrays preserve a single logical type, while the encodings allow for multiple
213
/// physical ways to encode that type.
214
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
215
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216
pub enum DType {
217
    /// The logical null type (only has a single value, `null`)
218
    Null,
219
    /// The logical boolean type (`true` or `false` if non-nullable; `true`, `false`, or `null` if nullable)
220
    Bool(Nullability),
221
    /// Primitive, fixed-width numeric types (e.g., `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `f32`, `f64`)
222
    Primitive(PType, Nullability),
223
    /// Real numbers with fixed exact precision and scale.
224
    Decimal(DecimalDType, Nullability),
225
    /// UTF-8 strings
226
    Utf8(Nullability),
227
    /// Binary data
228
    Binary(Nullability),
229
    /// A struct is composed of an ordered list of fields, each with a corresponding name and DType
230
    Struct(StructFields, Nullability),
231
    /// A variable-length list type, parameterized by a single element DType
232
    List(Arc<DType>, Nullability),
233
    /// User-defined extension types
234
    Extension(Arc<ExtDType>),
235
}
236

237
#[cfg(not(target_arch = "wasm32"))]
238
const_assert_eq!(size_of::<DType>(), 16);
239

240
#[cfg(target_arch = "wasm32")]
241
const_assert_eq!(size_of::<DType>(), 8);
242

243
impl DType {
244
    /// The default DType for bytes
245
    pub const BYTES: Self = Primitive(PType::U8, Nullability::NonNullable);
246

247
    /// Get the nullability of the DType
248
    pub fn nullability(&self) -> Nullability {
84,339,112✔
249
        self.is_nullable().into()
84,339,112✔
250
    }
84,339,112✔
251

252
    /// Check if the DType is nullable
253
    pub fn is_nullable(&self) -> bool {
98,894,926✔
254
        use crate::nullability::Nullability::*;
255

256
        match self {
98,894,926✔
257
            Null => true,
487,360✔
258
            Extension(ext_dtype) => ext_dtype.storage_dtype().is_nullable(),
142,719✔
259
            Bool(n)
38,033,756✔
260
            | Primitive(_, n)
57,938,251✔
261
            | Decimal(_, n)
329,925✔
262
            | Utf8(n)
1,213,145✔
263
            | Binary(n)
606,282✔
264
            | Struct(_, n)
36,189✔
265
            | List(_, n) => matches!(n, Nullable),
98,264,847✔
266
        }
267
    }
98,894,926✔
268

269
    /// Get a new DType with `Nullability::NonNullable` (but otherwise the same as `self`)
270
    pub fn as_nonnullable(&self) -> Self {
202✔
271
        self.with_nullability(Nullability::NonNullable)
202✔
272
    }
202✔
273

274
    /// Get a new DType with `Nullability::Nullable` (but otherwise the same as `self`)
275
    pub fn as_nullable(&self) -> Self {
4,652,928✔
276
        self.with_nullability(Nullability::Nullable)
4,652,928✔
277
    }
4,652,928✔
278

279
    /// Get a new DType with the given nullability (but otherwise the same as `self`)
280
    pub fn with_nullability(&self, nullability: Nullability) -> Self {
5,261,043✔
281
        match self {
5,261,043✔
282
            Null => Null,
49,080✔
283
            Bool(_) => Bool(nullability),
2,187,116✔
284
            Primitive(p, _) => Primitive(*p, nullability),
2,579,987✔
285
            Decimal(d, _) => Decimal(*d, nullability),
160,594✔
286
            Utf8(_) => Utf8(nullability),
165,250✔
287
            Binary(_) => Binary(nullability),
12,640✔
288
            Struct(st, _) => Struct(st.clone(), nullability),
13,960✔
289
            List(c, _) => List(c.clone(), nullability),
12,280✔
290
            Extension(ext) => Extension(Arc::new(ext.with_nullability(nullability))),
80,136✔
291
        }
292
    }
5,261,043✔
293

294
    /// Union the nullability of this dtype with the other nullability, returning a new dtype.
295
    pub fn union_nullability(&self, other: Nullability) -> Self {
433,694✔
296
        let nullability = self.nullability() | other;
433,694✔
297
        self.with_nullability(nullability)
433,694✔
298
    }
433,694✔
299

300
    /// Check if `self` and `other` are equal, ignoring nullability
301
    pub fn eq_ignore_nullability(&self, other: &Self) -> bool {
72,558,825✔
302
        match (self, other) {
72,558,825✔
303
            (Null, Null) => true,
241,880✔
304
            (Bool(_), Bool(_)) => true,
26,817,405✔
305
            (Primitive(lhs_ptype, _), Primitive(rhs_ptype, _)) => lhs_ptype == rhs_ptype,
41,871,962✔
306
            (Decimal(lhs, _), Decimal(rhs, _)) => lhs == rhs,
1,586,694✔
307
            (Utf8(_), Utf8(_)) => true,
1,398,646✔
308
            (Binary(_), Binary(_)) => true,
24,660✔
309
            (List(lhs_dtype, _), List(rhs_dtype, _)) => lhs_dtype.eq_ignore_nullability(rhs_dtype),
107,484✔
310
            (Struct(lhs_dtype, _), Struct(rhs_dtype, _)) => {
112,800✔
311
                (lhs_dtype.names() == rhs_dtype.names())
112,800✔
312
                    && (lhs_dtype
112,760✔
313
                        .fields()
112,760✔
314
                        .zip_eq(rhs_dtype.fields())
112,760✔
315
                        .all(|(l, r)| l.eq_ignore_nullability(&r)))
232,640✔
316
            }
317
            (Extension(lhs_extdtype), Extension(rhs_extdtype)) => {
389,886✔
318
                lhs_extdtype.as_ref().eq_ignore_nullability(rhs_extdtype)
389,886✔
319
            }
320
            _ => false,
7,408✔
321
        }
322
    }
72,558,825✔
323

324
    /// Check if `self` is a `StructDType`
325
    pub fn is_struct(&self) -> bool {
282,876✔
326
        matches!(self, Struct(_, _))
282,876✔
327
    }
282,876✔
328

329
    /// Check if `self` is a `ListDType`
UNCOV
330
    pub fn is_list(&self) -> bool {
×
UNCOV
331
        matches!(self, List(_, _))
×
UNCOV
332
    }
×
333

334
    /// Check if `self` is a primitive tpye
335
    pub fn is_primitive(&self) -> bool {
254,120✔
336
        matches!(self, Primitive(_, _))
254,120✔
337
    }
254,120✔
338

339
    /// Returns this DType's `PType` if it is a primitive type, otherwise panics.
340
    pub fn as_ptype(&self) -> PType {
259,661,417✔
341
        match self {
259,661,417✔
342
            Primitive(ptype, _) => *ptype,
259,661,417✔
UNCOV
343
            _ => vortex_panic!("DType is not a primitive type"),
×
344
        }
345
    }
259,661,417✔
346

347
    /// Check if `self` is an unsigned integer
348
    pub fn is_unsigned_int(&self) -> bool {
136,946✔
349
        if let Primitive(ptype, _) = self {
136,946✔
350
            return ptype.is_unsigned_int();
136,946✔
UNCOV
351
        }
×
UNCOV
352
        false
×
353
    }
136,946✔
354

355
    /// Check if `self` is a signed integer
356
    pub fn is_signed_int(&self) -> bool {
20,163✔
357
        if let Primitive(ptype, _) = self {
20,163✔
358
            return ptype.is_signed_int();
20,163✔
359
        }
×
360
        false
×
361
    }
20,163✔
362

363
    /// Check if `self` is an integer (signed or unsigned)
364
    pub fn is_int(&self) -> bool {
1,255,630✔
365
        if let Primitive(ptype, _) = self {
1,255,630✔
366
            return ptype.is_int();
1,255,630✔
UNCOV
367
        }
×
UNCOV
368
        false
×
369
    }
1,255,630✔
370

371
    /// Check if `self` is a floating point number
372
    pub fn is_float(&self) -> bool {
16,890✔
373
        if let Primitive(ptype, _) = self {
16,890✔
374
            return ptype.is_float();
16,890✔
UNCOV
375
        }
×
UNCOV
376
        false
×
377
    }
16,890✔
378

379
    /// Check if `self` is a boolean
380
    pub fn is_boolean(&self) -> bool {
25,402✔
381
        matches!(self, Bool(_))
25,402✔
382
    }
25,402✔
383

384
    /// Check if `self` is a binary
385
    pub fn is_binary(&self) -> bool {
1,400✔
386
        matches!(self, Binary(_))
1,400✔
387
    }
1,400✔
388

389
    /// Check if `self` is a utf8
390
    pub fn is_utf8(&self) -> bool {
12,370✔
391
        matches!(self, Utf8(_))
12,370✔
392
    }
12,370✔
393

394
    /// Check if `self` is an extension type
UNCOV
395
    pub fn is_extension(&self) -> bool {
×
UNCOV
396
        matches!(self, Extension(_))
×
UNCOV
397
    }
×
398

399
    /// Check if `self` is a decimal type
UNCOV
400
    pub fn is_decimal(&self) -> bool {
×
UNCOV
401
        matches!(self, Decimal(..))
×
UNCOV
402
    }
×
403

404
    /// Check returns the inner decimal type if the dtype is a decimal
405
    pub fn as_decimal(&self) -> Option<&DecimalDType> {
18,240✔
406
        match self {
18,240✔
407
            Decimal(decimal, _) => Some(decimal),
18,240✔
UNCOV
408
            _ => None,
×
409
        }
410
    }
18,240✔
411

412
    /// Get the `StructDType` if `self` is a `StructDType`, otherwise `None`
413
    pub fn as_struct(&self) -> Option<&StructFields> {
668,112✔
414
        match self {
668,112✔
415
            Struct(s, _) => Some(s),
667,431✔
416
            _ => None,
681✔
417
        }
418
    }
668,112✔
419

420
    /// Get the inner dtype if `self` is a `ListDType`, otherwise `None`
421
    pub fn as_list_element(&self) -> Option<&Arc<DType>> {
1,520✔
422
        match self {
1,520✔
423
            List(s, _) => Some(s),
1,480✔
424
            _ => None,
40✔
425
        }
426
    }
1,520✔
427

428
    /// Convenience method for creating a struct dtype
429
    pub fn struct_<I: IntoIterator<Item = (impl Into<FieldName>, impl Into<FieldDType>)>>(
19✔
430
        iter: I,
19✔
431
        nullability: Nullability,
19✔
432
    ) -> Self {
19✔
433
        Struct(StructFields::from_iter(iter), nullability)
19✔
434
    }
19✔
435

436
    /// Convenience method for creating a list dtype
437
    pub fn list(dtype: impl Into<DType>, nullability: Nullability) -> Self {
2✔
438
        List(Arc::new(dtype.into()), nullability)
2✔
439
    }
2✔
440
}
441

442
impl Display for DType {
443
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
14,761✔
444
        match self {
14,761✔
UNCOV
445
            Null => write!(f, "null"),
×
446
            Bool(n) => write!(f, "bool{n}"),
2,881✔
447
            Primitive(pt, n) => write!(f, "{pt}{n}"),
8,240✔
448
            Decimal(dt, n) => write!(f, "{dt}{n}"),
40✔
449
            Utf8(n) => write!(f, "utf8{n}"),
1,760✔
450
            Binary(n) => write!(f, "binary{n}"),
1,520✔
451
            Struct(sdt, n) => write!(
120✔
452
                f,
120✔
453
                "{{{}}}{}",
120✔
454
                sdt.names()
120✔
455
                    .iter()
120✔
456
                    .zip(sdt.fields())
120✔
457
                    .map(|(n, dt)| format!("{n}={dt}"))
280✔
458
                    .join(", "),
120✔
459
                n
460
            ),
461
            List(edt, n) => write!(f, "list({edt}){n}"),
40✔
462
            Extension(ext) => write!(
160✔
463
                f,
160✔
464
                "ext({}, {}{}){}",
160✔
465
                ext.id(),
160✔
466
                ext.storage_dtype()
160✔
467
                    .with_nullability(Nullability::NonNullable),
160✔
468
                ext.metadata()
160✔
469
                    .map(|m| format!(", {m:?}"))
160✔
470
                    .unwrap_or_else(|| "".to_string()),
160✔
471
                ext.storage_dtype().nullability(),
160✔
472
            ),
473
        }
474
    }
14,761✔
475
}
476

477
#[cfg(test)]
478
mod tests {
479
    use super::*;
480

481
    #[test]
482
    fn test_field_names_iter() {
1✔
483
        let names = ["a", "b"];
1✔
484
        let field_names = FieldNames::from(names);
1✔
485
        assert_eq!(field_names.iter().len(), names.len());
1✔
486
        let mut iter = field_names.iter();
1✔
487
        assert_eq!(iter.next(), Some(&"a".into()));
1✔
488
        assert_eq!(iter.next(), Some(&"b".into()));
1✔
489
        assert_eq!(iter.next(), None);
1✔
490
    }
1✔
491

492
    #[test]
493
    fn test_field_names_owned_iter() {
1✔
494
        let names = ["a", "b"];
1✔
495
        let field_names = FieldNames::from(names);
1✔
496
        assert_eq!(field_names.clone().into_iter().len(), names.len());
1✔
497
        let mut iter = field_names.into_iter();
1✔
498
        assert_eq!(iter.next(), Some("a".into()));
1✔
499
        assert_eq!(iter.next(), Some("b".into()));
1✔
500
        assert_eq!(iter.next(), None);
1✔
501
    }
1✔
502

503
    #[test]
504
    fn test_field_names_equality() {
1✔
505
        let field_names = FieldNames::from(["field1", "field2", "field3"]);
1✔
506

507
        // FieldNames == &FieldNames
508
        let field_names_ref = &field_names;
1✔
509
        assert_eq!(field_names, field_names_ref);
1✔
510

511
        // FieldNames == &[&str]
512
        let str_slice = &["field1", "field2", "field3"][..];
1✔
513
        assert_eq!(field_names, str_slice);
1✔
514

515
        // &FieldNames == &[&str]
516
        assert_eq!(&field_names, str_slice);
1✔
517

518
        // FieldNames == [&str; N] (array)
519
        assert_eq!(field_names, ["field1", "field2", "field3"]);
1✔
520

521
        // &FieldNames == [&str; N] (array)
522
        assert_eq!(&field_names, ["field1", "field2", "field3"]);
1✔
523

524
        // FieldNames == &[FieldName]
525
        let field_name_vec: Vec<FieldName> =
1✔
526
            vec!["field1".into(), "field2".into(), "field3".into()];
1✔
527
        let field_name_slice = field_name_vec.as_slice();
1✔
528
        assert_eq!(field_names, field_name_slice);
1✔
529

530
        // &FieldNames == &[FieldName]
531
        assert_eq!(&field_names, field_name_slice);
1✔
532

533
        // Test inequality cases
534
        assert_ne!(field_names, &["field1", "field2"][..]);
1✔
535
        assert_ne!(field_names, ["different", "fields", "here"]);
1✔
536
        assert_ne!(field_names, &["field1", "field2", "field3", "extra"][..]);
1✔
537
    }
1✔
538
}
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