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

vortex-data / vortex / 17080888698

19 Aug 2025 08:21PM UTC coverage: 87.949%. Remained the same
17080888698

Pull #4291

github

web-flow
Merge 0091d3a3c into 60f60d996
Pull Request #4291: chore: `DType` documentation

90 of 96 new or added lines in 1 file covered. (93.75%)

24 existing lines in 1 file now uncovered.

56745 of 64520 relevant lines covered (87.95%)

624146.66 hits per line

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

85.53
/vortex-dtype/src/struct_.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::hash::Hash;
6
use std::ops::Index;
7
use std::sync::Arc;
8

9
use itertools::Itertools;
10
use vortex_error::{
11
    VortexExpect, VortexResult, VortexUnwrap, vortex_bail, vortex_err, vortex_panic,
12
};
13

14
use crate::flatbuffers::ViewedDType;
15
use crate::{DType, PType};
16

17
/// DType of a struct's field, either owned or a pointer to an underlying flatbuffer.
18
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
19
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20
pub struct FieldDType {
21
    inner: FieldDTypeInner,
22
}
23

24
impl From<ViewedDType> for FieldDType {
25
    fn from(value: ViewedDType) -> Self {
2,892✔
26
        Self {
2,892✔
27
            inner: FieldDTypeInner::View(value),
2,892✔
28
        }
2,892✔
29
    }
2,892✔
30
}
31

32
impl From<DType> for FieldDType {
33
    fn from(value: DType) -> Self {
77,026✔
34
        Self {
77,026✔
35
            inner: FieldDTypeInner::Owned(value),
77,026✔
36
        }
77,026✔
37
    }
77,026✔
38
}
39

40
impl From<PType> for FieldDType {
41
    fn from(value: PType) -> Self {
360✔
42
        Self {
360✔
43
            inner: FieldDTypeInner::Owned(DType::from(value)),
360✔
44
        }
360✔
45
    }
360✔
46
}
47

48
#[derive(Debug, Clone, Eq)]
49
enum FieldDTypeInner {
50
    /// Owned DType instance
51
    // TODO(ngates): we should consider making this an Arc<DType>.
52
    Owned(DType),
53
    /// A view over a flatbuffer, parsed only when accessed.
54
    View(ViewedDType),
55
}
56

57
impl PartialEq for FieldDTypeInner {
58
    fn eq(&self, other: &Self) -> bool {
304,487✔
59
        match (self, other) {
304,487✔
60
            (Self::Owned(lhs), Self::Owned(rhs)) => lhs == rhs,
304,365✔
61
            (Self::View(lhs), Self::View(rhs)) => {
×
62
                let lhs = DType::try_from(lhs.clone())
×
63
                    .vortex_expect("Failed to parse FieldDType into DType");
×
64
                let rhs = DType::try_from(rhs.clone())
×
UNCOV
65
                    .vortex_expect("Failed to parse FieldDType into DType");
×
66

UNCOV
67
                lhs == rhs
×
68
            }
69
            (Self::View(view), Self::Owned(owned)) | (Self::Owned(owned), Self::View(view)) => {
82✔
70
                let view = DType::try_from(view.clone())
122✔
71
                    .vortex_expect("Failed to parse FieldDType into DType");
122✔
72
                owned == &view
122✔
73
            }
74
        }
75
    }
304,487✔
76
}
77

78
impl Hash for FieldDTypeInner {
79
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
4✔
80
        match self {
4✔
81
            FieldDTypeInner::Owned(owned) => {
4✔
82
                owned.hash(state);
4✔
83
            }
4✔
84
            FieldDTypeInner::View(view) => {
×
85
                let owned = DType::try_from(view.clone())
×
86
                    .vortex_expect("Failed to parse FieldDType into DType");
×
87
                owned.hash(state);
×
UNCOV
88
            }
×
89
        }
90
    }
4✔
91
}
92

93
impl FieldDType {
94
    /// Returns the concrete DType, parsing it from the underlying buffer if necessary.
95
    pub fn value(&self) -> VortexResult<DType> {
1,761,006✔
96
        self.inner.value()
1,761,006✔
97
    }
1,761,006✔
98
}
99

100
impl FieldDTypeInner {
101
    fn value(&self) -> VortexResult<DType> {
1,761,006✔
102
        match &self {
1,761,006✔
103
            FieldDTypeInner::Owned(owned) => Ok(owned.clone()),
1,712,799✔
104
            FieldDTypeInner::View(view) => DType::try_from(view.clone()),
48,207✔
105
        }
106
    }
1,761,006✔
107
}
108

109
#[cfg(feature = "serde")]
110
impl serde::Serialize for FieldDTypeInner {
111
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
×
112
    where
×
UNCOV
113
        S: serde::Serializer,
×
114
    {
115
        use serde::ser::Error;
116

117
        let value = self.value().map_err(S::Error::custom)?;
×
118
        serializer.serialize_newtype_variant("FieldDType", 0, "Owned", &value)
×
UNCOV
119
    }
×
120
}
121

122
#[cfg(feature = "serde")]
123
impl<'de> serde::Deserialize<'de> for FieldDTypeInner {
124
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
×
125
    where
×
UNCOV
126
        D: serde::Deserializer<'de>,
×
127
    {
128
        deserializer.deserialize_enum("FieldDType", &["Owned", "View"], FieldDTypeDeVisitor)
×
UNCOV
129
    }
×
130
}
131

132
#[cfg(feature = "serde")]
133
struct FieldDTypeDeVisitor;
134

135
#[cfg(feature = "serde")]
136
impl<'de> serde::de::Visitor<'de> for FieldDTypeDeVisitor {
137
    type Value = FieldDTypeInner;
138

139
    fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
×
140
        write!(f, "variant identifier")
×
UNCOV
141
    }
×
142

143
    fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
×
144
    where
×
UNCOV
145
        A: serde::de::EnumAccess<'de>,
×
146
    {
147
        use serde::de::{Error, VariantAccess};
148

149
        #[derive(serde::Deserialize, Debug)]
150
        enum FieldDTypeVariant {
151
            Owned,
152
            View,
153
        }
UNCOV
154
        let (variant, variant_data): (FieldDTypeVariant, _) = data.variant()?;
×
155

UNCOV
156
        match variant {
×
157
            FieldDTypeVariant::Owned => {
158
                let inner = variant_data.newtype_variant::<DType>()?;
×
UNCOV
159
                Ok(FieldDTypeInner::Owned(inner))
×
160
            }
UNCOV
161
            other => Err(A::Error::custom(format!("unsupported variant {other:?}"))),
×
162
        }
UNCOV
163
    }
×
164
}
165

166
/// Type information for a struct column.
167
///
168
/// The `StructFields` holds all field names and field types, and provides
169
/// access to them by index or by name.
170
///
171
/// ## Duplicate field names
172
///
173
/// In memory, it is not an error for a `StructFields` to contain duplicate
174
/// field names. In that case, any name-based access to fields will resolve
175
/// to the first such field with a given name.
176
///
177
/// ```rust
178
/// # use vortex_dtype::{DType, Nullability, PType, StructFields};
179
///
180
/// let fields = StructFields::from_iter([
181
///     ("string_col", DType::Utf8(Nullability::NonNullable)),
182
///     ("binary_col", DType::Binary(Nullability::NonNullable)),
183
///     ("int_col", DType::Primitive(PType::I32, Nullability::Nullable)),
184
///     ("int_col", DType::Primitive(PType::I64, Nullability::Nullable)),
185
/// ]);
186
///
187
/// // Accessing a field by name will yield the first
188
/// assert_eq!(fields.field("int_col").unwrap(), DType::Primitive(PType::I32, Nullability::Nullable));
189
/// ```
190
#[derive(Clone, PartialEq, Eq, Hash)]
191
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
192
pub struct StructFields(Arc<StructFieldsInner>);
193

194
impl std::fmt::Debug for StructFields {
195
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
×
196
        f.debug_struct("StructFields")
×
197
            .field("names", &self.0.names)
×
198
            .field("dtypes", &self.0.dtypes)
×
199
            .finish()
×
UNCOV
200
    }
×
201
}
202

203
impl Display for StructFields {
204
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3✔
205
        write!(
3✔
206
            f,
3✔
207
            "{{{}}}",
3✔
208
            self.names()
3✔
209
                .iter()
3✔
210
                .zip(self.fields())
3✔
211
                .map(|(n, dt)| format!("{n}={dt}"))
5✔
212
                .join(", ")
3✔
213
        )
214
    }
3✔
215
}
216

217
#[derive(PartialEq, Eq, Hash, Default)]
218
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
219
struct StructFieldsInner {
220
    names: FieldNames,
221
    dtypes: Arc<[FieldDType]>,
222
}
223

224
impl Default for StructFields {
225
    fn default() -> Self {
×
226
        Self::empty()
×
UNCOV
227
    }
×
228
}
229

230
impl StructFields {
231
    /// The fields of the empty struct.
232
    pub fn empty() -> Self {
1✔
233
        Self(Arc::new(StructFieldsInner {
1✔
234
            names: FieldNames::default(),
1✔
235
            dtypes: Arc::from([]),
1✔
236
        }))
1✔
237
    }
1✔
238

239
    /// Create a new [`StructFields`] from a list of names and dtypes
240
    pub fn new(names: FieldNames, dtypes: Vec<DType>) -> Self {
387,957✔
241
        if names.len() != dtypes.len() {
387,957✔
242
            vortex_panic!(
×
243
                "length mismatch between names ({}) and dtypes ({})",
×
244
                names.len(),
×
UNCOV
245
                dtypes.len()
×
246
            );
247
        }
387,957✔
248

249
        let dtypes = dtypes
387,957✔
250
            .into_iter()
387,957✔
251
            .map(|dt| FieldDType {
387,957✔
252
                inner: FieldDTypeInner::Owned(dt),
767,038✔
253
            })
767,038✔
254
            .collect::<Vec<_>>();
387,957✔
255

256
        Self::from_fields(names, dtypes)
387,957✔
257
    }
387,957✔
258

259
    /// Create a new [`StructFields`] from a  list of names and [`FieldDType`] which can be either lazily or eagerly serialized.
260
    pub fn from_fields(names: FieldNames, dtypes: Vec<FieldDType>) -> Self {
406,356✔
261
        if names.len() != dtypes.len() {
406,356✔
262
            vortex_panic!(
×
263
                "length mismatch between names ({}) and dtypes ({})",
×
264
                names.len(),
×
UNCOV
265
                dtypes.len()
×
266
            );
267
        }
406,356✔
268

269
        let inner = Arc::new(StructFieldsInner {
406,356✔
270
            names,
406,356✔
271
            dtypes: dtypes.into(),
406,356✔
272
        });
406,356✔
273

274
        Self(inner)
406,356✔
275
    }
406,356✔
276

277
    /// Get the names of the fields in the struct
278
    pub fn names(&self) -> &FieldNames {
331,529✔
279
        &self.0.names
331,529✔
280
    }
331,529✔
281

282
    /// Returns the number of fields in the struct
283
    pub fn nfields(&self) -> usize {
15,387✔
284
        self.0.names.len()
15,387✔
285
    }
15,387✔
286

287
    /// Returns the name of the field at the given index
288
    pub fn field_name(&self, index: usize) -> Option<&FieldName> {
562✔
289
        self.0.names.get(index)
562✔
290
    }
562✔
291

292
    /// Find the index of a field by name
293
    /// Returns `None` if the field is not found
294
    pub fn find(&self, name: impl AsRef<str>) -> Option<usize> {
62,066✔
295
        let name = name.as_ref();
62,066✔
296
        self.0.names.iter().position(|n| n.as_ref() == name)
191,613✔
297
    }
62,066✔
298

299
    /// Get the [`DType`] of a field.
300
    ///
301
    /// It is possible for there to be more than one field with
302
    /// the same name, in which case, this will return the DType
303
    /// of the first field encountered with a given name.
304
    pub fn field(&self, name: impl AsRef<str>) -> Option<DType> {
39,257✔
305
        let index = self.find(name)?;
39,257✔
306
        Some(self.0.dtypes[index].value().vortex_unwrap())
39,255✔
307
    }
39,257✔
308

309
    /// Get the [`DType`] of a field by index.
310
    pub fn field_by_index(&self, index: usize) -> Option<DType> {
619,378✔
311
        Some(self.0.dtypes.get(index)?.value().vortex_unwrap())
619,378✔
312
    }
619,378✔
313

314
    /// Returns an ordered iterator over the fields.
315
    pub fn fields(&self) -> impl ExactSizeIterator<Item = DType> + '_ {
536,561✔
316
        self.0.dtypes.iter().map(|dt| dt.value().vortex_unwrap())
1,102,413✔
317
    }
536,561✔
318

319
    /// Project a subset of fields from the struct
320
    ///
321
    /// If any of the fields are not found, this method will return
322
    /// an error.
323
    pub fn project(&self, projection: &[FieldName]) -> VortexResult<Self> {
361✔
324
        let mut names = Vec::with_capacity(projection.len());
361✔
325
        let mut dtypes = Vec::with_capacity(projection.len());
361✔
326

327
        for field in projection {
843✔
328
            let idx = self
482✔
329
                .find(field)
482✔
330
                .ok_or_else(|| vortex_err!("{field} not found"))?;
482✔
331
            names.push(self.0.names[idx].clone());
482✔
332
            dtypes.push(self.0.dtypes[idx].clone());
482✔
333
        }
334

335
        Ok(StructFields::from_fields(names.into(), dtypes))
361✔
336
    }
361✔
337

338
    /// Returns a new [`StructFields`] without the field at the given index.
339
    ///
340
    /// ## Errors
341
    /// Returns an error if the index is out of bounds for the struct fields.
342
    pub fn without_field(&self, index: usize) -> VortexResult<Self> {
44✔
343
        if index >= self.nfields() {
44✔
344
            vortex_bail!(
2✔
345
                "index {} out of bounds for struct with {} fields",
2✔
346
                index,
347
                self.nfields()
2✔
348
            );
349
        }
42✔
350

351
        let names = self
42✔
352
            .0
42✔
353
            .names
42✔
354
            .iter()
42✔
355
            .enumerate()
42✔
356
            .filter(|&(i, _)| i != index)
84✔
357
            .map(|(_, name)| name.clone())
42✔
358
            .collect::<FieldNames>();
42✔
359

360
        let dtypes = self
42✔
361
            .0
42✔
362
            .dtypes
42✔
363
            .iter()
42✔
364
            .enumerate()
42✔
365
            .filter(|&(i, _)| i != index)
84✔
366
            .map(|(_, dtype)| dtype.clone())
42✔
367
            .collect::<Vec<_>>();
42✔
368

369
        Ok(StructFields::from_fields(names, dtypes))
42✔
370
    }
44✔
371

372
    /// Merge two [`StructFields`] instances into a new one.
373
    /// Order of fields in arguments is preserved
374
    ///
375
    /// # Errors
376
    /// Returns an error if the merged struct would have duplicate field names.
377
    pub fn disjoint_merge(&self, other: &Self) -> VortexResult<Self> {
2✔
378
        let names = self
2✔
379
            .0
2✔
380
            .names
2✔
381
            .iter()
2✔
382
            .chain(other.0.names.iter())
2✔
383
            .cloned()
2✔
384
            .collect::<FieldNames>();
2✔
385

386
        if !names.iter().all_unique() {
2✔
387
            vortex_bail!("Can't merge struct fields with duplicate names");
1✔
388
        }
1✔
389

390
        let dtypes = self
1✔
391
            .0
1✔
392
            .dtypes
1✔
393
            .iter()
1✔
394
            .chain(other.0.dtypes.iter())
1✔
395
            .cloned()
1✔
396
            .collect::<Vec<_>>();
1✔
397

398
        Ok(Self::from_fields(names, dtypes))
1✔
399
    }
2✔
400
}
401

402
impl<T, V> FromIterator<(T, V)> for StructFields
403
where
404
    T: Into<FieldName>,
405
    V: Into<FieldDType>,
406
{
407
    fn from_iter<I: IntoIterator<Item = (T, V)>>(iter: I) -> Self {
10,625✔
408
        let (names, dtypes): (Vec<_>, Vec<_>) = iter
10,625✔
409
            .into_iter()
10,625✔
410
            .map(|(name, dtype)| (name.into(), dtype.into()))
50,780✔
411
            .unzip();
10,625✔
412
        StructFields::from_fields(names.into(), dtypes)
10,625✔
413
    }
10,625✔
414
}
415

416
/// A name for a field in a struct
417
pub type FieldName = Arc<str>;
418

419
/// An ordered list of field names in a struct
420
#[derive(Clone, PartialEq, Eq, Debug, Default, Hash)]
421
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
422
pub struct FieldNames(Arc<[FieldName]>);
423

424
impl Display for FieldNames {
425
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1✔
426
        write!(
1✔
427
            f,
1✔
428
            "[{}]",
1✔
429
            itertools::join(self.0.iter().map(|n| format!("\"{n}\"")), ", ")
3✔
430
        )
431
    }
1✔
432
}
433

434
impl PartialEq<&FieldNames> for FieldNames {
435
    fn eq(&self, other: &&FieldNames) -> bool {
1✔
436
        self == *other
1✔
437
    }
1✔
438
}
439

440
impl PartialEq<&[&str]> for FieldNames {
441
    fn eq(&self, other: &&[&str]) -> bool {
292✔
442
        self.len() == other.len() && self.iter().zip_eq(other.iter()).all(|(l, r)| &**l == *r)
822✔
443
    }
292✔
444
}
445

446
impl PartialEq<&[&str]> for &FieldNames {
447
    fn eq(&self, other: &&[&str]) -> bool {
289✔
448
        *self == other
289✔
449
    }
289✔
450
}
451

452
impl<const N: usize> PartialEq<[&str; N]> for FieldNames {
453
    fn eq(&self, other: &[&str; N]) -> bool {
4✔
454
        self == other.as_slice()
4✔
455
    }
4✔
456
}
457

458
impl<const N: usize> PartialEq<[&str; N]> for &FieldNames {
459
    fn eq(&self, other: &[&str; N]) -> bool {
11✔
460
        *self == other.as_slice()
11✔
461
    }
11✔
462
}
463

464
impl PartialEq<&[FieldName]> for FieldNames {
465
    fn eq(&self, other: &&[FieldName]) -> bool {
1✔
466
        self.0.as_ref() == *other
1✔
467
    }
1✔
468
}
469

470
impl PartialEq<&[FieldName]> for &FieldNames {
471
    fn eq(&self, other: &&[FieldName]) -> bool {
1✔
472
        self.0.as_ref() == *other
1✔
473
    }
1✔
474
}
475

476
impl FieldNames {
477
    /// Returns the number of elements.
478
    pub fn len(&self) -> usize {
1,393,424✔
479
        self.0.len()
1,393,424✔
480
    }
1,393,424✔
481

482
    /// Returns true if the number of elements is 0.
NEW
UNCOV
483
    pub fn is_empty(&self) -> bool {
×
NEW
UNCOV
484
        self.len() == 0
×
NEW
UNCOV
485
    }
×
486

487
    /// Returns a borrowed iterator over the field names.
488
    pub fn iter(&self) -> impl ExactSizeIterator<Item = &FieldName> {
148,199✔
489
        FieldNamesIter {
148,199✔
490
            inner: self,
148,199✔
491
            idx: 0,
148,199✔
492
        }
148,199✔
493
    }
148,199✔
494

495
    /// Returns a reference to a field name, or None if `index` is out of bounds.
496
    pub fn get(&self, index: usize) -> Option<&FieldName> {
562✔
497
        self.0.get(index)
562✔
498
    }
562✔
499
}
500

501
impl AsRef<[FieldName]> for FieldNames {
502
    fn as_ref(&self) -> &[FieldName] {
1,040✔
503
        &self.0
1,040✔
504
    }
1,040✔
505
}
506

507
impl Index<usize> for FieldNames {
508
    type Output = FieldName;
509

510
    fn index(&self, index: usize) -> &Self::Output {
27,524✔
511
        &self.0[index]
27,524✔
512
    }
27,524✔
513
}
514

515
/// Iterator of references to field names
516
pub struct FieldNamesIter<'a> {
517
    inner: &'a FieldNames,
518
    idx: usize,
519
}
520

521
impl<'a> Iterator for FieldNamesIter<'a> {
522
    type Item = &'a FieldName;
523

524
    fn next(&mut self) -> Option<Self::Item> {
501,048✔
525
        if self.idx >= self.inner.len() {
501,048✔
526
            return None;
39,453✔
527
        }
461,595✔
528

529
        let i = &self.inner.0[self.idx];
461,595✔
530
        self.idx += 1;
461,595✔
531
        Some(i)
461,595✔
532
    }
501,048✔
533

534
    fn size_hint(&self) -> (usize, Option<usize>) {
14,307✔
535
        let len = self.inner.len() - self.idx;
14,307✔
536
        (len, Some(len))
14,307✔
537
    }
14,307✔
538
}
539

540
impl ExactSizeIterator for FieldNamesIter<'_> {}
541

542
/// Owned iterator of field names.
543
pub struct FieldNamesIntoIter {
544
    inner: FieldNames,
545
    idx: usize,
546
}
547

548
impl Iterator for FieldNamesIntoIter {
549
    type Item = FieldName;
550

551
    fn next(&mut self) -> Option<Self::Item> {
3✔
552
        if self.idx >= self.inner.len() {
3✔
553
            return None;
1✔
554
        }
2✔
555

556
        let i = self.inner.0[self.idx].clone();
2✔
557
        self.idx += 1;
2✔
558
        Some(i)
2✔
559
    }
3✔
560

561
    fn size_hint(&self) -> (usize, Option<usize>) {
1✔
562
        let len = self.inner.len() - self.idx;
1✔
563
        (len, Some(len))
1✔
564
    }
1✔
565
}
566

567
impl ExactSizeIterator for FieldNamesIntoIter {}
568

569
impl IntoIterator for FieldNames {
570
    type Item = FieldName;
571

572
    type IntoIter = FieldNamesIntoIter;
573

574
    fn into_iter(self) -> Self::IntoIter {
2✔
575
        FieldNamesIntoIter {
2✔
576
            inner: self,
2✔
577
            idx: 0,
2✔
578
        }
2✔
579
    }
2✔
580
}
581

582
impl From<Vec<FieldName>> for FieldNames {
583
    fn from(value: Vec<FieldName>) -> Self {
44,294✔
584
        Self(value.into())
44,294✔
585
    }
44,294✔
586
}
587

588
impl From<&[&'static str]> for FieldNames {
NEW
UNCOV
589
    fn from(value: &[&'static str]) -> Self {
×
NEW
UNCOV
590
        Self(value.iter().cloned().map(Arc::from).collect())
×
NEW
UNCOV
591
    }
×
592
}
593

594
impl From<&[FieldName]> for FieldNames {
595
    fn from(value: &[FieldName]) -> Self {
120✔
596
        Self(Arc::from(value))
120✔
597
    }
120✔
598
}
599

600
impl<const N: usize> From<[&'static str; N]> for FieldNames {
601
    fn from(value: [&'static str; N]) -> Self {
302,048✔
602
        Self(value.into_iter().map(Arc::from).collect())
302,048✔
603
    }
302,048✔
604
}
605

606
impl<const N: usize> From<[FieldName; N]> for FieldNames {
607
    fn from(value: [FieldName; N]) -> Self {
11✔
608
        Self(value.into())
11✔
609
    }
11✔
610
}
611

612
impl<F: Into<FieldName>> FromIterator<F> for FieldNames {
613
    fn from_iter<T: IntoIterator<Item = F>>(iter: T) -> Self {
10,309✔
614
        Self(iter.into_iter().map(|v| v.into()).collect())
16,473✔
615
    }
10,309✔
616
}
617

618
#[cfg(test)]
619
mod test {
620
    use itertools::Itertools;
621

622
    use crate::dtype::DType;
623
    use crate::{FieldNames, Nullability, PType, StructFields};
624

625
    #[test]
626
    fn nullability() {
1✔
627
        assert!(
1✔
628
            !DType::Struct(
1✔
629
                StructFields::new(FieldNames::default(), Vec::new()),
1✔
630
                Nullability::NonNullable
1✔
631
            )
1✔
632
            .is_nullable()
1✔
633
        );
634

635
        let primitive = DType::Primitive(PType::U8, Nullability::Nullable);
1✔
636
        assert!(primitive.is_nullable());
1✔
637
        assert!(!primitive.as_nonnullable().is_nullable());
1✔
638
        assert!(primitive.as_nonnullable().as_nullable().is_nullable());
1✔
639
    }
1✔
640

641
    #[test]
642
    fn test_struct() {
1✔
643
        let a_type = DType::Primitive(PType::I32, Nullability::Nullable);
1✔
644
        let b_type = DType::Bool(Nullability::NonNullable);
1✔
645

646
        let dtype = DType::Struct(
1✔
647
            StructFields::from_iter([("A", a_type.clone()), ("B", b_type.clone())]),
1✔
648
            Nullability::Nullable,
1✔
649
        );
1✔
650
        assert!(dtype.is_nullable());
1✔
651
        assert!(dtype.as_struct_opt().is_some());
1✔
652
        assert!(a_type.as_struct_opt().is_none());
1✔
653

654
        let sdt = dtype.as_struct_opt().unwrap();
1✔
655
        assert_eq!(sdt.names().len(), 2);
1✔
656
        assert_eq!(sdt.fields().len(), 2);
1✔
657
        assert_eq!(sdt.names(), ["A", "B"]);
1✔
658
        assert_eq!(sdt.field_by_index(0).unwrap(), a_type);
1✔
659
        assert_eq!(sdt.field_by_index(1).unwrap(), b_type);
1✔
660

661
        let proj = sdt.project(&["B".into(), "A".into()]).unwrap();
1✔
662
        assert_eq!(proj.names(), ["B", "A"]);
1✔
663
        assert_eq!(proj.field_by_index(0).unwrap(), b_type);
1✔
664
        assert_eq!(proj.field_by_index(1).unwrap(), a_type);
1✔
665

666
        assert_eq!(sdt.find("A").unwrap(), 0);
1✔
667
        assert_eq!(sdt.find("B").unwrap(), 1);
1✔
668
        assert!(sdt.find("C").is_none());
1✔
669

670
        let without_a = sdt.without_field(0).unwrap();
1✔
671
        assert_eq!(without_a.names(), ["B"]);
1✔
672
        assert_eq!(without_a.field_by_index(0).unwrap(), b_type);
1✔
673
        assert_eq!(without_a.nfields(), 1);
1✔
674
    }
1✔
675

676
    #[test]
677
    fn test_without_field_out_of_bounds() {
1✔
678
        let a_type = DType::Primitive(PType::I32, Nullability::Nullable);
1✔
679
        let b_type = DType::Bool(Nullability::NonNullable);
1✔
680
        let sdt = StructFields::from_iter([("A", a_type), ("B", b_type)]);
1✔
681

682
        let result = sdt.without_field(2);
1✔
683
        assert!(result.is_err());
1✔
684
        assert!(result.unwrap_err().to_string().contains("out of bounds"));
1✔
685

686
        let result = sdt.without_field(100);
1✔
687
        assert!(result.is_err());
1✔
688
    }
1✔
689

690
    #[test]
691
    fn test_without_field_deprecated() {
1✔
692
        let a_type = DType::Primitive(PType::I32, Nullability::Nullable);
1✔
693
        let b_type = DType::Bool(Nullability::NonNullable);
1✔
694
        let sdt = StructFields::from_iter([("A", a_type), ("B", b_type.clone())]);
1✔
695

696
        let without_a = sdt.without_field(0).unwrap();
1✔
697
        assert_eq!(without_a.names(), ["B"]);
1✔
698
        assert_eq!(without_a.field_by_index(0).unwrap(), b_type);
1✔
699
        assert_eq!(without_a.nfields(), 1);
1✔
700
    }
1✔
701

702
    #[test]
703
    fn test_merge() {
1✔
704
        let child_a = DType::Primitive(PType::I32, Nullability::NonNullable);
1✔
705
        let child_b = DType::Bool(Nullability::Nullable);
1✔
706
        let child_c = DType::Utf8(Nullability::NonNullable);
1✔
707

708
        let sf1 = StructFields::from_iter([("A", child_a.clone()), ("B", child_b.clone())]);
1✔
709

710
        let sf2 = StructFields::from_iter([("C", child_c.clone())]);
1✔
711

712
        let merged = StructFields::disjoint_merge(&sf1, &sf2).unwrap();
1✔
713
        assert_eq!(merged.names(), ["A", "B", "C"]);
1✔
714
        assert_eq!(
1✔
715
            merged.fields().collect_vec(),
1✔
716
            vec![child_a, child_b, child_c]
1✔
717
        );
718

719
        let err = StructFields::disjoint_merge(&sf1, &sf1).err().unwrap();
1✔
720
        assert!(err.to_string().contains("duplicate names"),);
1✔
721
    }
1✔
722

723
    #[test]
724
    fn test_display() {
1✔
725
        let fields = StructFields::from_iter([
1✔
726
            ("name", DType::Utf8(Nullability::NonNullable)),
1✔
727
            ("age", DType::Primitive(PType::I32, Nullability::Nullable)),
1✔
728
            ("active", DType::Bool(Nullability::NonNullable)),
1✔
729
        ]);
1✔
730

731
        assert_eq!(fields.to_string(), "{name=utf8, age=i32?, active=bool}");
1✔
732

733
        // Test empty struct
734
        let empty = StructFields::empty();
1✔
735
        assert_eq!(empty.to_string(), "{}");
1✔
736

737
        // Test nested struct
738
        let nested = StructFields::from_iter([
1✔
739
            ("id", DType::Primitive(PType::U64, Nullability::NonNullable)),
1✔
740
            ("data", DType::Struct(fields, Nullability::Nullable)),
1✔
741
        ]);
1✔
742
        assert_eq!(
1✔
743
            nested.to_string(),
1✔
744
            "{id=u64, data={name=utf8, age=i32?, active=bool}?}"
745
        );
746
    }
1✔
747
}
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