Skip to main content

vortex_array/scalar/
scalar_value.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Core [`ScalarValue`] type definition.
5
6use std::fmt::Display;
7use std::fmt::Formatter;
8
9use itertools::Itertools;
10use vortex_buffer::BufferString;
11use vortex_buffer::ByteBuffer;
12use vortex_error::VortexExpect;
13use vortex_error::vortex_panic;
14
15use crate::dtype::DType;
16use crate::scalar::DecimalValue;
17use crate::scalar::PValue;
18use crate::scalar::Scalar;
19use crate::scalar::UnionValue;
20
21/// The value stored in a [`Scalar`].
22///
23/// This enum represents the possible non-null values that can be stored in a scalar. When the
24/// scalar is null, the value is represented as `None` in the `Option<ScalarValue>` field.
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
26pub enum ScalarValue {
27    /// A boolean value.
28    Bool(bool),
29    /// A primitive numeric value.
30    Primitive(PValue),
31    /// A decimal value.
32    Decimal(DecimalValue),
33    /// A UTF-8 encoded string value.
34    Utf8(BufferString),
35    /// A binary (byte array) value.
36    Binary(ByteBuffer),
37    /// A tuple of potentially null scalar values.
38    ///
39    /// Used as the underlying representation for list, fixed-size list, and struct scalars.
40    Tuple(Vec<Option<ScalarValue>>),
41    /// A present union value carrying its selected type ID and raw child value.
42    Union(UnionValue),
43    /// A row-specific scalar wrapped by `DType::Variant`.
44    Variant(Box<Scalar>),
45}
46
47impl ScalarValue {
48    /// Returns the zero / identity value for the given [`DType`].
49    pub(super) fn zero_value(dtype: &DType) -> Self {
50        Self::try_zero_value(dtype)
51            .unwrap_or_else(|| vortex_panic!("{dtype} has no non-null zero value"))
52    }
53
54    /// Returns the non-null zero value for `dtype`, or [`None`] if no such value exists.
55    pub(super) fn try_zero_value(dtype: &DType) -> Option<Self> {
56        Some(match dtype {
57            DType::Null => return None,
58            DType::Bool(_) => Self::Bool(false),
59            DType::Primitive(ptype, _) => Self::Primitive(PValue::zero(ptype)),
60            DType::Decimal(dt, ..) => Self::Decimal(DecimalValue::zero(dt)),
61            DType::Utf8(_) => Self::Utf8(BufferString::empty()),
62            DType::Binary(_) => Self::Binary(ByteBuffer::empty()),
63            DType::List(..) => Self::Tuple(vec![]),
64            DType::FixedSizeList(edt, size, _) => {
65                let elements = (0..*size)
66                    .map(|_| Self::try_zero_value(edt).map(Some))
67                    .collect::<Option<Vec<_>>>()?;
68                Self::Tuple(elements)
69            }
70            DType::Struct(fields, _) => {
71                let field_values = fields
72                    .fields()
73                    .map(|f| Self::try_zero_value(&f).map(Some))
74                    .collect::<Option<Vec<_>>>()?;
75                Self::Tuple(field_values)
76            }
77            DType::Union(variants, _) => {
78                let child_dtype = variants
79                    .variant_by_index(0)
80                    .vortex_expect("union must have at least one variant");
81                let child_value = Self::try_zero_value(&child_dtype)?;
82
83                Self::Union(UnionValue::new(
84                    variants.child_index_to_tag(0),
85                    Some(child_value),
86                ))
87            }
88            DType::Variant(_) => Self::Variant(Box::new(Scalar::null(DType::Null))),
89            DType::Extension(ext_dtype) => {
90                // Since we have no way to define a "zero" extension value (since we have no idea
91                // what the semantics of the extension is), a best effort attempt is to just use the
92                // zero storage value and try to make an extension scalar from that.
93                Self::try_zero_value(ext_dtype.storage_dtype())?
94            }
95        })
96    }
97
98    /// Returns a valid default value for `dtype`, or [`None`] if the dtype has no default.
99    ///
100    /// The outer [`Option`] distinguishes a dtype with no default from a nullable dtype, whose valid
101    /// default is represented by the inner [`None`].
102    pub(super) fn try_default_value(dtype: &DType) -> Option<Option<Self>> {
103        if dtype.is_nullable() {
104            return Some(None);
105        }
106
107        let value = match dtype {
108            DType::Null => return Some(None),
109            DType::Bool(_) => Self::Bool(false),
110            DType::Primitive(ptype, _) => Self::Primitive(PValue::zero(ptype)),
111            DType::Decimal(dt, ..) => Self::Decimal(DecimalValue::zero(dt)),
112            DType::Utf8(_) => Self::Utf8(BufferString::empty()),
113            DType::Binary(_) => Self::Binary(ByteBuffer::empty()),
114            DType::List(..) => Self::Tuple(vec![]),
115            DType::FixedSizeList(edt, size, _) => {
116                let elements = (0..*size)
117                    .map(|_| Self::try_default_value(edt))
118                    .collect::<Option<Vec<_>>>()?;
119                Self::Tuple(elements)
120            }
121            DType::Struct(fields, _) => {
122                let field_values = fields
123                    .fields()
124                    .map(|field| Self::try_default_value(&field))
125                    .collect::<Option<Vec<_>>>()?;
126                Self::Tuple(field_values)
127            }
128            DType::Union(variants, _) => {
129                let child_dtype = variants
130                    .variant_by_index(0)
131                    .vortex_expect("union must have at least one variant");
132                let child_value = Self::try_default_value(&child_dtype)?;
133
134                Self::Union(UnionValue::new(variants.child_index_to_tag(0), child_value))
135            }
136            DType::Variant(_) => Self::Variant(Box::new(Scalar::null(DType::Null))),
137            DType::Extension(ext_dtype) => {
138                // Since we have no way to define a "default" extension value (since we have no idea
139                // what the semantics of the extension is), a best effort attempt is to just use the
140                // default storage value and try to make an extension scalar from that.
141                Self::try_default_value(ext_dtype.storage_dtype())??
142            }
143        };
144
145        Some(Some(value))
146    }
147}
148
149impl Display for ScalarValue {
150    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
151        match self {
152            ScalarValue::Bool(b) => write!(f, "{b}"),
153            ScalarValue::Primitive(p) => write!(f, "{p}"),
154            ScalarValue::Decimal(d) => write!(f, "{d}"),
155            ScalarValue::Utf8(s) => {
156                let bufstr = s.as_str();
157                let str_len = bufstr.chars().count();
158
159                if str_len > 10 {
160                    let prefix = String::from_iter(bufstr.chars().take(5));
161                    let suffix = String::from_iter(bufstr.chars().skip(str_len - 5));
162
163                    write!(f, "\"{prefix}..{suffix}\"")
164                } else {
165                    write!(f, "\"{bufstr}\"")
166                }
167            }
168            ScalarValue::Binary(b) => {
169                if b.len() > 10 {
170                    write!(
171                        f,
172                        "{}..{}",
173                        to_hex(&b[0..5]),
174                        to_hex(&b[b.len() - 5..b.len()]),
175                    )
176                } else {
177                    write!(f, "{}", to_hex(b))
178                }
179            }
180            ScalarValue::Tuple(elements) => {
181                write!(f, "[")?;
182                for (i, element) in elements.iter().enumerate() {
183                    if i > 0 {
184                        write!(f, ", ")?;
185                    }
186                    match element {
187                        None => write!(f, "null")?,
188                        Some(e) => write!(f, "{}", e)?,
189                    }
190                }
191                write!(f, "]")
192            }
193            ScalarValue::Union(value) => {
194                write!(f, "union@{}(", value.type_id())?;
195                match value.child_value() {
196                    Some(value) => write!(f, "{value}"),
197                    None => write!(f, "null"),
198                }?;
199                write!(f, ")")
200            }
201            ScalarValue::Variant(value) => write!(f, "{value}"),
202        }
203    }
204}
205
206/// Formats a byte slice as a hexadecimal string.
207fn to_hex(slice: &[u8]) -> String {
208    slice
209        .iter()
210        .format_with("", |f, b| b(&format_args!("{f:02x}")))
211        .to_string()
212}