Skip to main content

vortex_array/scalar/
constructor.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Typed constructors for [`Scalar`].
5
6use std::sync::Arc;
7
8use vortex_buffer::BufferString;
9use vortex_buffer::ByteBuffer;
10use vortex_error::VortexExpect;
11use vortex_error::VortexResult;
12use vortex_error::vortex_bail;
13use vortex_error::vortex_ensure_eq;
14use vortex_error::vortex_err;
15use vortex_error::vortex_panic;
16
17use crate::dtype::DType;
18use crate::dtype::DecimalDType;
19use crate::dtype::NativePType;
20use crate::dtype::Nullability;
21use crate::dtype::PType;
22use crate::dtype::UnionVariants;
23use crate::dtype::extension::ExtDType;
24use crate::dtype::extension::ExtDTypeRef;
25use crate::dtype::extension::ExtVTable;
26use crate::scalar::DecimalValue;
27use crate::scalar::PValue;
28use crate::scalar::Scalar;
29use crate::scalar::ScalarValue;
30use crate::scalar::UnionValue;
31
32// TODO(connor): Really, we want `try_` constructors that return errors instead of just panic.
33impl Scalar {
34    /// Creates a new boolean scalar with the given value and nullability.
35    pub fn bool(value: bool, nullability: Nullability) -> Self {
36        // SAFETY: A `Bool` value is always valid for a `Bool` dtype.
37        unsafe { Self::new_unchecked(DType::Bool(nullability), Some(ScalarValue::Bool(value))) }
38    }
39
40    /// Creates a new primitive scalar from a native value.
41    pub fn primitive<T: NativePType + Into<PValue>>(value: T, nullability: Nullability) -> Self {
42        Self::primitive_value(value.into(), T::PTYPE, nullability)
43    }
44
45    /// Create a PrimitiveScalar from a PValue.
46    ///
47    /// Note that an explicit PType is passed since any compatible PValue may be used as the value
48    /// for a primitive type.
49    pub fn primitive_value(value: PValue, ptype: PType, nullability: Nullability) -> Self {
50        if value.ptype() != ptype {
51            return Self::try_new(
52                DType::Primitive(ptype, nullability),
53                Some(ScalarValue::Primitive(value)),
54            )
55            .vortex_expect("unable to construct a primitive `Scalar`");
56        }
57
58        // SAFETY: The value has the same ptype as the dtype.
59        unsafe {
60            Self::new_unchecked(
61                DType::Primitive(ptype, nullability),
62                Some(ScalarValue::Primitive(value)),
63            )
64        }
65    }
66
67    /// Creates a new decimal scalar with the given value, precision, scale, and nullability.
68    pub fn decimal(
69        value: DecimalValue,
70        decimal_type: DecimalDType,
71        nullability: Nullability,
72    ) -> Self {
73        Self::try_new(
74            DType::Decimal(decimal_type, nullability),
75            Some(ScalarValue::Decimal(value)),
76        )
77        .vortex_expect("unable to construct a decimal `Scalar`")
78    }
79
80    /// Creates a new UTF-8 scalar from a string-like value.
81    ///
82    /// # Panics
83    ///
84    /// Panics if the input cannot be converted to a valid UTF-8 string.
85    pub fn utf8<B>(str: B, nullability: Nullability) -> Self
86    where
87        B: Into<BufferString>,
88    {
89        Self::try_utf8(str, nullability).unwrap()
90    }
91
92    /// Tries to create a new UTF-8 scalar from a string-like value.
93    ///
94    /// # Errors
95    ///
96    /// Returns an error if the input cannot be converted to a valid UTF-8 string.
97    pub fn try_utf8<B>(
98        str: B,
99        nullability: Nullability,
100    ) -> Result<Self, <B as TryInto<BufferString>>::Error>
101    where
102        B: TryInto<BufferString>,
103    {
104        let value = ScalarValue::Utf8(str.try_into()?);
105
106        // SAFETY: A `Utf8` value is always valid for a `Utf8` dtype.
107        Ok(unsafe { Self::new_unchecked(DType::Utf8(nullability), Some(value)) })
108    }
109
110    /// Creates a new binary scalar from a byte buffer.
111    pub fn binary(buffer: impl Into<ByteBuffer>, nullability: Nullability) -> Self {
112        // SAFETY: A `Binary` value is always valid for a `Binary` dtype.
113        unsafe {
114            Self::new_unchecked(
115                DType::Binary(nullability),
116                Some(ScalarValue::Binary(buffer.into())),
117            )
118        }
119    }
120
121    /// Creates a new list scalar with the given element type and children.
122    ///
123    /// # Panics
124    ///
125    /// Panics if any child scalar has a different type than the element type, or if there are too
126    /// many children.
127    pub fn list(
128        element_dtype: impl Into<Arc<DType>>,
129        children: Vec<Scalar>,
130        nullability: Nullability,
131    ) -> Self {
132        Self::create_list(element_dtype, children, nullability, ListKind::Variable)
133    }
134
135    /// Creates a new empty list scalar with the given element type.
136    pub fn list_empty(element_dtype: Arc<DType>, nullability: Nullability) -> Self {
137        Self::create_list(element_dtype, vec![], nullability, ListKind::Variable)
138    }
139
140    /// Creates a new fixed-size list scalar with the given element type and children.
141    ///
142    /// # Panics
143    ///
144    /// Panics if any child scalar has a different type than the element type, or if there are too
145    /// many children.
146    pub fn fixed_size_list(
147        element_dtype: impl Into<Arc<DType>>,
148        children: Vec<Scalar>,
149        nullability: Nullability,
150    ) -> Self {
151        Self::create_list(element_dtype, children, nullability, ListKind::FixedSize)
152    }
153
154    /// Creates a map scalar with the given dtype and ordered key/value entries.
155    ///
156    /// # Panics
157    ///
158    /// Panics when `dtype` is not a map or an entry has an incompatible key or value dtype.
159    pub fn map(dtype: DType, entries: impl IntoIterator<Item = (Scalar, Scalar)>) -> Self {
160        Self::try_map(dtype, entries).vortex_expect("unable to construct a map `Scalar`")
161    }
162
163    /// Attempts to create a map scalar with the given dtype and ordered key/value entries.
164    ///
165    /// # Errors
166    ///
167    /// Returns an error when `dtype` is not a map or an entry has an incompatible key or value
168    /// dtype.
169    pub fn try_map(
170        dtype: DType,
171        entries: impl IntoIterator<Item = (Scalar, Scalar)>,
172    ) -> VortexResult<Self> {
173        let map = dtype
174            .as_map_opt()
175            .ok_or_else(|| vortex_error::vortex_err!("Expected map dtype, found {dtype}"))?;
176        let key_dtype = map.key_dtype();
177        let value_dtype = map.value_dtype();
178
179        let entries = entries
180            .into_iter()
181            .enumerate()
182            .map(|(index, (key, value))| {
183                if key.dtype() != &key_dtype {
184                    vortex_bail!(
185                        "map entry {index} expected key dtype {key_dtype}, got {}",
186                        key.dtype()
187                    );
188                }
189                if value.dtype() != &value_dtype {
190                    vortex_bail!(
191                        "map entry {index} expected value dtype {value_dtype}, got {}",
192                        value.dtype()
193                    );
194                }
195
196                Ok(Some(ScalarValue::Tuple(vec![
197                    key.into_value(),
198                    value.into_value(),
199                ])))
200            })
201            .collect::<VortexResult<Vec<_>>>()?;
202
203        Self::try_new(dtype, Some(ScalarValue::Tuple(entries)))
204    }
205
206    /// Creates a list [`Scalar`] from an element dtype, children, nullability, and list kind.
207    fn create_list(
208        element_dtype: impl Into<Arc<DType>>,
209        children: Vec<Scalar>,
210        nullability: Nullability,
211        list_kind: ListKind,
212    ) -> Self {
213        let element_dtype = element_dtype.into();
214
215        let children: Vec<Option<ScalarValue>> = children
216            .into_iter()
217            .map(|child| {
218                if child.dtype() != &*element_dtype {
219                    vortex_panic!(
220                        "tried to create list of {} with values of type {}",
221                        element_dtype,
222                        child.dtype()
223                    );
224                }
225                child.into_value()
226            })
227            .collect();
228        let size: u32 = children
229            .len()
230            .try_into()
231            .vortex_expect("tried to create a list that was too large");
232
233        let dtype = match list_kind {
234            ListKind::Variable => DType::List(element_dtype, nullability),
235            ListKind::FixedSize => DType::FixedSizeList(element_dtype, size, nullability),
236        };
237
238        Self::try_new(dtype, Some(ScalarValue::Tuple(children)))
239            .vortex_expect("unable to construct a list `Scalar`")
240    }
241
242    /// Creates a new extension scalar wrapping the given storage value.
243    pub fn extension<V: ExtVTable + Default>(options: V::Metadata, storage_scalar: Scalar) -> Self {
244        let ext_dtype = ExtDType::<V>::try_new(options, storage_scalar.dtype().clone())
245            .vortex_expect("Failed to create extension dtype");
246
247        Self::extension_ref(ext_dtype.erased(), storage_scalar)
248    }
249
250    /// Creates a new extension scalar wrapping the given storage value.
251    ///
252    /// # Panics
253    ///
254    /// Panics if the storage dtype of `ext_dtype` does not match `value`'s dtype.
255    pub fn extension_ref(ext_dtype: ExtDTypeRef, storage_scalar: Scalar) -> Self {
256        assert_eq!(ext_dtype.storage_dtype(), storage_scalar.dtype());
257
258        Self::try_new(DType::Extension(ext_dtype), storage_scalar.into_value())
259            .vortex_expect("unable to construct an extension `Scalar`")
260    }
261
262    /// Creates a union scalar from a type ID and child scalar.
263    ///
264    /// The selected child's dtype is verified and then discarded. Its raw value is stored so an
265    /// inner null child remains distinct from a null at the outer union level. Passing a null child
266    /// creates a non-null union with a selected type ID; use [`Scalar::null`] with a nullable
267    /// [`DType::Union`] to create an outer null union with no selected type ID.
268    ///
269    /// # Errors
270    ///
271    /// Returns an error if the type ID is not present in `variants` or if the child scalar's dtype
272    /// does not exactly match the selected variant dtype.
273    pub fn union(
274        variants: UnionVariants,
275        type_id: u8,
276        child: Scalar,
277        nullability: Nullability,
278    ) -> VortexResult<Self> {
279        let child_index = variants.tag_to_child_index(type_id).ok_or_else(|| {
280            vortex_err!(
281                "union type ID {type_id} is not present in {:?}",
282                variants.type_ids()
283            )
284        })?;
285
286        let expected_dtype = variants
287            .variant_by_index(child_index)
288            .vortex_expect("type ID resolved to a valid child index");
289
290        vortex_ensure_eq!(
291            child.dtype(),
292            &expected_dtype,
293            "union type ID {type_id} selects a different child dtype"
294        );
295
296        Self::try_new(
297            DType::Union(variants, nullability),
298            Some(ScalarValue::Union(UnionValue::new(
299                type_id,
300                child.into_value(),
301            ))),
302        )
303    }
304
305    /// Creates a new variant scalar from a row-specific nested scalar.
306    ///
307    /// Use [`Scalar::null(DType::Variant(Nullability::Nullable))`][Scalar::null] for a top-level
308    /// null variant value, and
309    /// `Scalar::variant(Scalar::null(DType::Null))` for a defined variant-null.
310    pub fn variant(value: Scalar) -> Self {
311        Self::try_new(
312            DType::Variant(Nullability::NonNullable),
313            Some(ScalarValue::Variant(Box::new(value))),
314        )
315        .vortex_expect("unable to construct a variant `Scalar`")
316    }
317}
318
319/// A helper enum for creating a `ListScalar`.
320enum ListKind {
321    /// Variable-length list.
322    Variable,
323    /// Fixed-size list.
324    FixedSize,
325}