Skip to main content

spacetimedb_sats/
product_value.rs

1use crate::algebraic_value::AlgebraicValue;
2use crate::product_type::ProductType;
3use crate::{ArrayValue, SumValue, ValueWithType};
4use spacetimedb_primitives::{ColId, ColList};
5
6/// A product value is made of a list of
7/// "elements" / "fields" / "factors" of other `AlgebraicValue`s.
8///
9/// The type of a product value is a [product type](`ProductType`).
10#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
11pub struct ProductValue {
12    /// The values that make up this product value.
13    pub elements: Box<[AlgebraicValue]>,
14}
15
16/// Constructs a product value from a list of fields with syntax `product![v1, v2, ...]`.
17///
18/// Repeat notation from `vec![x; n]` is not supported.
19#[macro_export]
20macro_rules! product {
21    [$($elems:expr),*$(,)?] => {
22        $crate::ProductValue {
23            elements: [$($crate::AlgebraicValue::from($elems)),*].into(),
24        }
25    }
26}
27
28impl FromIterator<AlgebraicValue> for ProductValue {
29    fn from_iter<T: IntoIterator<Item = AlgebraicValue>>(iter: T) -> Self {
30        let elements = iter.into_iter().collect();
31        Self { elements }
32    }
33}
34
35impl IntoIterator for ProductValue {
36    type Item = AlgebraicValue;
37    type IntoIter = std::vec::IntoIter<AlgebraicValue>;
38    fn into_iter(self) -> Self::IntoIter {
39        Vec::from(self.elements).into_iter()
40    }
41}
42
43impl<'a> IntoIterator for &'a ProductValue {
44    type Item = &'a AlgebraicValue;
45    type IntoIter = std::slice::Iter<'a, AlgebraicValue>;
46    fn into_iter(self) -> Self::IntoIter {
47        self.elements.iter()
48    }
49}
50
51impl crate::Value for ProductValue {
52    type Type = ProductType;
53}
54
55/// An error that occurs when a field, of a product value, is accessed that doesn't exist.
56#[derive(thiserror::Error, Debug, Copy, Clone)]
57#[error("Field at position {col_pos} named: {name:?} not found or has an invalid type")]
58pub struct InvalidFieldError {
59    /// The claimed col_pos of the field within the product value.
60    pub col_pos: ColId,
61    /// The name of the field, if any.
62    pub name: Option<&'static str>,
63}
64
65impl From<ColId> for InvalidFieldError {
66    fn from(col_pos: ColId) -> Self {
67        Self { col_pos, name: None }
68    }
69}
70
71impl ProductValue {
72    /// Pushes a single value to he product.
73    pub fn push(self, val: impl Into<AlgebraicValue>) -> Self {
74        let mut vals: Vec<_> = self.elements.into();
75        vals.reserve(1);
76        vals.push(val.into());
77        Self::from(vals)
78    }
79
80    /// Borrow the value at field of `self` identified by `col_pos`.
81    ///
82    /// The `name` is non-functional and is only used for error-messages.
83    pub fn get_field(&self, col_pos: usize, name: Option<&'static str>) -> Result<&AlgebraicValue, InvalidFieldError> {
84        self.elements.get(col_pos).ok_or(InvalidFieldError {
85            col_pos: col_pos.into(),
86            name,
87        })
88    }
89
90    /// This utility function is designed to project fields based on the supplied `indexes`.
91    ///
92    /// **Important:**
93    ///
94    /// The resulting [AlgebraicValue] will wrap into a [ProductValue] when projecting multiple
95    /// (including zero) fields, otherwise it will consist of a single [AlgebraicValue].
96    /// If you want to wrap single elements in a [ProductValue] as well, see [Self::project_product].
97    ///
98    /// **Parameters:**
99    /// - `cols`: A [ColList] containing the indexes of fields to be projected.
100    pub fn project(&self, cols: &ColList) -> Result<AlgebraicValue, InvalidFieldError> {
101        if let Some(head) = cols.as_singleton() {
102            self.get_field(head.idx(), None).cloned()
103        } else {
104            let mut fields = Vec::with_capacity(cols.len() as usize);
105            for col in cols.iter() {
106                fields.push(self.get_field(col.idx(), None)?.clone());
107            }
108            Ok(AlgebraicValue::product(fields))
109        }
110    }
111
112    /// This utility function is designed to project fields based on the supplied `indexes`.
113    ///
114    /// **Important:**
115    ///
116    /// Returns a [ProductValue] even when projecting a single element.
117    /// If you don't want to wrap a single element in a [ProductValue], see [Self::project].
118    ///
119    /// **Parameters:**
120    /// - `cols`: A [ColList] containing the indexes of fields to be projected.
121    pub fn project_product(&self, cols: &ColList) -> Result<ProductValue, InvalidFieldError> {
122        let mut fields = Vec::with_capacity(cols.len() as usize);
123        for col in cols.iter() {
124            fields.push(self.get_field(col.idx(), None)?.clone());
125        }
126        Ok(ProductValue::from(fields))
127    }
128
129    /// Extracts the `value` at field of `self` identified by `index`
130    /// and then runs it through the function `f` which possibly returns a `T` derived from `value`.
131    pub fn extract_field<'a, T>(
132        &'a self,
133        col_pos: usize,
134        name: Option<&'static str>,
135        f: impl 'a + Fn(&'a AlgebraicValue) -> Option<T>,
136    ) -> Result<T, InvalidFieldError> {
137        f(self.get_field(col_pos, name)?).ok_or(InvalidFieldError {
138            col_pos: col_pos.into(),
139            name,
140        })
141    }
142
143    /// Interprets the value at field of `self` identified by `index` as a `bool`.
144    pub fn field_as_bool(&self, index: usize, named: Option<&'static str>) -> Result<bool, InvalidFieldError> {
145        self.extract_field(index, named, |f| f.as_bool().copied())
146    }
147
148    /// Interprets the value at field of `self` identified by `index` as a `u8`.
149    pub fn field_as_u8(&self, index: usize, named: Option<&'static str>) -> Result<u8, InvalidFieldError> {
150        self.extract_field(index, named, |f| f.as_u8().copied())
151    }
152
153    /// Interprets the value at field of `self` identified by `index` as a `u32`.
154    pub fn field_as_u32(&self, index: usize, named: Option<&'static str>) -> Result<u32, InvalidFieldError> {
155        self.extract_field(index, named, |f| f.as_u32().copied())
156    }
157
158    /// Interprets the value at field of `self` identified by `index` as a `u64`.
159    pub fn field_as_u64(&self, index: usize, named: Option<&'static str>) -> Result<u64, InvalidFieldError> {
160        self.extract_field(index, named, |f| f.as_u64().copied())
161    }
162
163    /// Interprets the value at field of `self` identified by `index` as a `i64`.
164    pub fn field_as_i64(&self, index: usize, named: Option<&'static str>) -> Result<i64, InvalidFieldError> {
165        self.extract_field(index, named, |f| f.as_i64().copied())
166    }
167
168    /// Interprets the value at field of `self` identified by `index` as a `i128`.
169    pub fn field_as_i128(&self, index: usize, named: Option<&'static str>) -> Result<i128, InvalidFieldError> {
170        self.extract_field(index, named, |f| f.as_i128().copied().map(|x| x.0))
171    }
172
173    /// Interprets the value at field of `self` identified by `index` as a `u128`.
174    pub fn field_as_u128(&self, index: usize, named: Option<&'static str>) -> Result<u128, InvalidFieldError> {
175        self.extract_field(index, named, |f| f.as_u128().copied().map(|x| x.0))
176    }
177
178    /// Interprets the value at field of `self` identified by `index` as a string slice.
179    pub fn field_as_str(&self, index: usize, named: Option<&'static str>) -> Result<&str, InvalidFieldError> {
180        self.extract_field(index, named, |f| f.as_string()).map(|x| &**x)
181    }
182
183    /// Interprets the value at field of `self` identified by `index` as a byte slice.
184    pub fn field_as_bytes(&self, index: usize, named: Option<&'static str>) -> Result<&[u8], InvalidFieldError> {
185        self.extract_field(index, named, |f| f.as_bytes())
186    }
187
188    /// Interprets the value at field of `self` identified by `index` as an `ArrayValue`.
189    pub fn field_as_array(&self, index: usize, named: Option<&'static str>) -> Result<&ArrayValue, InvalidFieldError> {
190        self.extract_field(index, named, |f| f.as_array())
191    }
192
193    /// Interprets the value at field of `self` identified by `index` as a `SumValue`.
194    pub fn field_as_sum(&self, index: usize, named: Option<&'static str>) -> Result<SumValue, InvalidFieldError> {
195        self.extract_field(index, named, |f| f.as_sum().cloned())
196    }
197}
198
199impl<'a> ValueWithType<'a, ProductValue> {
200    pub fn elements(&self) -> impl ExactSizeIterator<Item = ValueWithType<'a, AlgebraicValue>> + use<'a> {
201        self.ty_s().with_values(self.value())
202    }
203}