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