toasty_core/stmt/
sparse_record.rs

1use super::{PathFieldSet, Type, Value, ValueRecord};
2use std::hash::Hash;
3
4/// A record where only a subset of fields are populated.
5///
6/// Unlike [`ValueRecord`] (which stores values for every field by position),
7/// `SparseRecord` tracks which field indices are present via a
8/// [`PathFieldSet`] and stores corresponding values. Fields not in the set
9/// are absent (not merely null).
10///
11/// Iterating over a `SparseRecord` yields `(usize, Value)` pairs of field
12/// index and value.
13///
14/// # Examples
15///
16/// ```
17/// use toasty_core::stmt::{Value, PathFieldSet};
18///
19/// // Create a sparse record with field 0 populated
20/// let v = Value::empty_sparse_record();
21/// assert!(matches!(v, Value::SparseRecord(_)));
22/// ```
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24pub struct SparseRecord {
25    /// Bit set of field indices that are populated in this record.
26    pub fields: PathFieldSet,
27
28    /// Values indexed by field position. Indices not in `fields` contain
29    /// placeholder [`Value::Null`] entries.
30    pub values: Vec<Value>,
31}
32
33impl Value {
34    /// Creates an empty [`Value::SparseRecord`] with no populated fields.
35    pub fn empty_sparse_record() -> Self {
36        SparseRecord {
37            fields: PathFieldSet::new(),
38            values: vec![],
39        }
40        .into()
41    }
42
43    /// Creates a [`Value::SparseRecord`] by distributing values from `record`
44    /// into the positions specified by `fields`.
45    pub fn sparse_record(fields: PathFieldSet, record: ValueRecord) -> Self {
46        let mut values = vec![];
47
48        for (index, value) in fields.iter().zip(record.fields.into_iter()) {
49            while index >= values.len() {
50                values.push(Value::Null);
51            }
52
53            values[index] = value;
54        }
55
56        SparseRecord { fields, values }.into()
57    }
58
59    /// Consumes this value and returns the contained [`SparseRecord`],
60    /// panicking if this is not a [`Value::SparseRecord`].
61    ///
62    /// # Panics
63    ///
64    /// Panics if the value is not a `SparseRecord` variant.
65    pub fn into_sparse_record(self) -> SparseRecord {
66        match self {
67            Self::SparseRecord(value) => value,
68            _ => todo!(),
69        }
70    }
71}
72
73impl Type {
74    /// Creates a [`Type::SparseRecord`] type with the given field set.
75    pub fn sparse_record(fields: impl Into<PathFieldSet>) -> Self {
76        Self::SparseRecord(fields.into())
77    }
78
79    /// Creates a [`Type::SparseRecord`] type with no fields.
80    pub fn empty_sparse_record() -> Self {
81        Self::SparseRecord(PathFieldSet::default())
82    }
83}
84
85impl IntoIterator for SparseRecord {
86    type Item = (usize, Value);
87
88    type IntoIter = Box<dyn Iterator<Item = (usize, Value)>>;
89
90    fn into_iter(self) -> Self::IntoIter {
91        Box::new(
92            self.values
93                .into_iter()
94                .enumerate()
95                .filter_map(move |(i, value)| {
96                    if self.fields.contains(i) {
97                        Some((i, value))
98                    } else {
99                        None
100                    }
101                }),
102        )
103    }
104}
105
106impl From<SparseRecord> for Value {
107    fn from(value: SparseRecord) -> Self {
108        Self::SparseRecord(value)
109    }
110}