Skip to main content

polars_core/frame/column/
scalar.rs

1use std::sync::OnceLock;
2
3use polars_error::PolarsResult;
4use polars_utils::broadcast::BroadcastLength;
5use polars_utils::pl_str::PlSmallStr;
6
7use super::{AnyValue, Column, DataType, IntoColumn, Scalar, Series};
8use crate::chunked_array::cast::CastOptions;
9
10/// A [`Column`] that consists of a repeated [`Scalar`]
11///
12/// This is lazily materialized into a [`Series`].
13#[derive(Debug, Clone)]
14pub struct ScalarColumn {
15    name: PlSmallStr,
16    // The value of this scalar may be incoherent when `length == 0`.
17    scalar: Scalar,
18    length: usize,
19
20    // invariants:
21    // materialized.name() == name
22    // materialized.len() == length
23    // materialized.dtype() == value.dtype
24    // materialized[i] == value, for all 0 <= i < length
25    /// A lazily materialized [`Series`] variant of this [`ScalarColumn`]
26    materialized: OnceLock<Series>,
27}
28
29impl ScalarColumn {
30    #[inline]
31    pub fn new(name: PlSmallStr, scalar: Scalar, length: usize) -> Self {
32        Self {
33            name,
34            scalar,
35            length,
36
37            materialized: OnceLock::new(),
38        }
39    }
40
41    #[inline]
42    pub fn new_empty(name: PlSmallStr, dtype: DataType) -> Self {
43        Self {
44            name,
45            scalar: Scalar::new(dtype, AnyValue::Null),
46            length: 0,
47
48            materialized: OnceLock::new(),
49        }
50    }
51
52    pub fn full_null(name: PlSmallStr, length: usize, dtype: DataType) -> Self {
53        Self::new(name, Scalar::null(dtype), length)
54    }
55
56    pub fn name(&self) -> &PlSmallStr {
57        &self.name
58    }
59
60    pub fn scalar(&self) -> &Scalar {
61        &self.scalar
62    }
63
64    pub fn dtype(&self) -> &DataType {
65        self.scalar.dtype()
66    }
67
68    pub fn len(&self) -> usize {
69        self.length
70    }
71
72    pub fn is_empty(&self) -> bool {
73        self.length == 0
74    }
75
76    pub fn is_full_null(&self) -> bool {
77        self.scalar.is_null()
78    }
79
80    fn _to_series(name: PlSmallStr, value: Scalar, length: usize) -> Series {
81        let series = if length == 0 {
82            Series::new_empty(name, value.dtype())
83        } else {
84            value.into_series(name).new_from_index(0, length)
85        };
86
87        debug_assert_eq!(series.len(), length);
88
89        series
90    }
91
92    /// Materialize the [`ScalarColumn`] into a [`Series`].
93    pub fn to_series(&self) -> Series {
94        Self::_to_series(self.name.clone(), self.scalar.clone(), self.length)
95    }
96
97    /// Get the [`ScalarColumn`] as [`Series`] if it was already materialized.
98    pub fn lazy_as_materialized_series(&self) -> Option<&Series> {
99        self.materialized.get()
100    }
101
102    /// Get the [`ScalarColumn`] as [`Series`]
103    ///
104    /// This needs to materialize upon the first call. Afterwards, this is cached.
105    pub fn as_materialized_series(&self) -> &Series {
106        self.materialized.get_or_init(|| self.to_series())
107    }
108
109    /// Take the [`ScalarColumn`] and materialize as a [`Series`] if not already done.
110    pub fn take_materialized_series(self) -> Series {
111        self.materialized
112            .into_inner()
113            .unwrap_or_else(|| Self::_to_series(self.name, self.scalar, self.length))
114    }
115
116    /// Take the [`ScalarColumn`] as a series with a single value.
117    ///
118    /// If the [`ScalarColumn`] has `length=0` the resulting `Series` will also have `length=0`.
119    pub fn as_single_value_series(&self) -> Series {
120        self.as_n_values_series(1)
121    }
122
123    /// Take the [`ScalarColumn`] as a series with a `n` values.
124    ///
125    /// If the [`ScalarColumn`] has `length=0` the resulting `Series` will also have `length=0`.
126    pub fn as_n_values_series(&self, n: usize) -> Series {
127        let length = usize::min(n, self.length);
128
129        match self.materialized.get() {
130            // Don't take a refcount if we only want length-1 (or empty) - the materialized series
131            // could be extremely large.
132            Some(s) if length == self.length || length > 1 => s.head(Some(length)),
133            _ => Self::_to_series(self.name.clone(), self.scalar.clone(), length),
134        }
135    }
136
137    /// Create a new [`ScalarColumn`] from a `length=1` Series and expand it `length`.
138    ///
139    /// This will panic if the value cannot be made static or if the series has length `0`.
140    #[inline]
141    pub fn unit_scalar_from_series(series: Series) -> Self {
142        assert_eq!(series.len(), 1);
143        // SAFETY: We just did the bounds check
144        let value = unsafe { series.get_unchecked(0) };
145        let value = value.into_static();
146        let value = Scalar::new(series.dtype().clone(), value);
147        let mut sc = ScalarColumn::new(series.name().clone(), value, 1);
148        sc.materialized = OnceLock::from(series);
149        sc
150    }
151
152    /// Create a new [`ScalarColumn`] from a `length<=1` Series and expand it `length`.
153    ///
154    /// If `series` is empty and `length` is non-zero, a full-NULL column of `length` will be returned.
155    ///
156    /// This will panic if the value cannot be made static.
157    pub fn from_single_value_series(series: Series, length: usize) -> Self {
158        debug_assert!(series.len() <= 1);
159
160        let value = if series.is_empty() {
161            AnyValue::Null
162        } else {
163            unsafe { series.get_unchecked(0) }.into_static()
164        };
165        let value = Scalar::new(series.dtype().clone(), value);
166        ScalarColumn::new(series.name().clone(), value, length)
167    }
168
169    /// Resize the [`ScalarColumn`] to new `length`.
170    ///
171    /// This reuses the materialized [`Series`], if `length <= self.length`.
172    pub fn resize(&self, length: usize) -> ScalarColumn {
173        if self.length == length {
174            return self.clone();
175        }
176
177        // This is violates an invariant if this triggers, the scalar value is undefined if the
178        // self.length == 0 so therefore we should never resize using that value.
179        debug_assert!(length == 0 || self.length > 0);
180
181        let mut resized = Self {
182            name: self.name.clone(),
183            scalar: self.scalar.clone(),
184            length,
185            materialized: OnceLock::new(),
186        };
187
188        if length == self.length || (length < self.length && length > 1) {
189            if let Some(materialized) = self.materialized.get() {
190                resized.materialized = OnceLock::from(materialized.head(Some(length)));
191                debug_assert_eq!(resized.materialized.get().unwrap().len(), length);
192            }
193        }
194
195        resized
196    }
197
198    pub fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Self> {
199        // @NOTE: We expect that when casting the materialized series mostly does not need change
200        // the physical array. Therefore, we try to cast the entire materialized array if it is
201        // available.
202
203        match self.materialized.get() {
204            Some(s) => {
205                let materialized = s.cast_with_options(dtype, options)?;
206                assert_eq!(self.length, materialized.len());
207
208                let mut casted = if materialized.is_empty() {
209                    Self::new_empty(materialized.name().clone(), materialized.dtype().clone())
210                } else {
211                    // SAFETY: Just did bounds check
212                    let scalar = unsafe { materialized.get_unchecked(0) }.into_static();
213                    Self::new(
214                        materialized.name().clone(),
215                        Scalar::new(materialized.dtype().clone(), scalar),
216                        self.length,
217                    )
218                };
219                casted.materialized = OnceLock::from(materialized);
220                Ok(casted)
221            },
222            None => {
223                let s = self
224                    .as_single_value_series()
225                    .cast_with_options(dtype, options)?;
226
227                if self.length == 0 {
228                    Ok(Self::new_empty(s.name().clone(), s.dtype().clone()))
229                } else {
230                    assert_eq!(1, s.len());
231                    Ok(Self::from_single_value_series(s, self.length))
232                }
233            },
234        }
235    }
236
237    pub fn strict_cast(&self, dtype: &DataType) -> PolarsResult<Self> {
238        self.cast_with_options(dtype, CastOptions::Strict)
239    }
240    pub fn cast(&self, dtype: &DataType) -> PolarsResult<Self> {
241        self.cast_with_options(dtype, CastOptions::NonStrict)
242    }
243    /// # Safety
244    ///
245    /// This can lead to invalid memory access in downstream code.
246    pub unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Self> {
247        // @NOTE: We expect that when casting the materialized series mostly does not need change
248        // the physical array. Therefore, we try to cast the entire materialized array if it is
249        // available.
250
251        match self.materialized.get() {
252            Some(s) => {
253                let materialized = s.cast_unchecked(dtype)?;
254                assert_eq!(self.length, materialized.len());
255
256                let mut casted = if materialized.is_empty() {
257                    Self::new_empty(materialized.name().clone(), materialized.dtype().clone())
258                } else {
259                    // SAFETY: Just did bounds check
260                    let scalar = unsafe { materialized.get_unchecked(0) }.into_static();
261                    Self::new(
262                        materialized.name().clone(),
263                        Scalar::new(materialized.dtype().clone(), scalar),
264                        self.length,
265                    )
266                };
267                casted.materialized = OnceLock::from(materialized);
268                Ok(casted)
269            },
270            None => {
271                let s = self.as_single_value_series().cast_unchecked(dtype)?;
272                assert_eq!(1, s.len());
273
274                if self.length == 0 {
275                    Ok(Self::new_empty(s.name().clone(), s.dtype().clone()))
276                } else {
277                    Ok(Self::from_single_value_series(s, self.length))
278                }
279            },
280        }
281    }
282
283    pub fn rename(&mut self, name: PlSmallStr) -> &mut Self {
284        if let Some(series) = self.materialized.get_mut() {
285            series.rename(name.clone());
286        }
287
288        self.name = name;
289        self
290    }
291
292    pub fn has_nulls(&self) -> bool {
293        self.length != 0 && self.scalar.is_null()
294    }
295
296    pub fn drop_nulls(&self) -> Self {
297        if self.scalar.is_null() {
298            self.resize(0)
299        } else {
300            self.clone()
301        }
302    }
303
304    pub fn into_nulls(mut self) -> Self {
305        self.scalar.update(AnyValue::Null);
306        self
307    }
308
309    /// Packs every element into a single-element list.
310    pub fn to_unit_list(&self) -> Self {
311        let mut slf = self.clone();
312        slf.map_scalar(|s| Scalar::new_list(s.into_series(PlSmallStr::EMPTY)));
313        slf
314    }
315
316    pub fn map_scalar(&mut self, map_scalar: impl Fn(Scalar) -> Scalar) {
317        self.scalar = map_scalar(std::mem::take(&mut self.scalar));
318        self.materialized.take();
319    }
320    pub fn with_value(&mut self, value: AnyValue<'static>) -> &mut Self {
321        self.scalar.update(value);
322        self.materialized.take();
323        self
324    }
325}
326
327impl IntoColumn for ScalarColumn {
328    #[inline(always)]
329    fn into_column(self) -> Column {
330        self.into()
331    }
332}
333
334impl From<ScalarColumn> for Column {
335    #[inline]
336    fn from(value: ScalarColumn) -> Self {
337        Self::Scalar(value)
338    }
339}
340
341impl BroadcastLength for ScalarColumn {
342    fn _broadcast_len(&self) -> usize {
343        self.len()
344    }
345
346    fn _column_name(&self) -> Option<&str> {
347        Some(self.name())
348    }
349}
350
351#[cfg(feature = "dsl-schema")]
352impl schemars::JsonSchema for ScalarColumn {
353    fn schema_name() -> std::borrow::Cow<'static, str> {
354        "ScalarColumn".into()
355    }
356
357    fn schema_id() -> std::borrow::Cow<'static, str> {
358        std::borrow::Cow::Borrowed(concat!(module_path!(), "::", "ScalarColumn"))
359    }
360
361    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
362        serde_impl::SerializeWrap::json_schema(generator)
363    }
364}
365
366#[cfg(feature = "serde")]
367mod serde_impl {
368    use std::sync::OnceLock;
369
370    use polars_error::PolarsError;
371    use polars_utils::pl_str::PlSmallStr;
372
373    use super::ScalarColumn;
374    use crate::frame::{Scalar, Series};
375
376    #[derive(serde::Serialize, serde::Deserialize)]
377    #[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
378    pub struct SerializeWrap {
379        name: PlSmallStr,
380        /// Unit-length series for dispatching to IPC serialize
381        unit_series: Series,
382        length: usize,
383    }
384
385    impl From<&ScalarColumn> for SerializeWrap {
386        fn from(value: &ScalarColumn) -> Self {
387            Self {
388                name: value.name.clone(),
389                unit_series: value.scalar.clone().into_series(PlSmallStr::EMPTY),
390                length: value.length,
391            }
392        }
393    }
394
395    impl TryFrom<SerializeWrap> for ScalarColumn {
396        type Error = PolarsError;
397
398        fn try_from(value: SerializeWrap) -> Result<Self, Self::Error> {
399            let slf = Self {
400                name: value.name,
401                scalar: Scalar::new(
402                    value.unit_series.dtype().clone(),
403                    value.unit_series.get(0)?.into_static(),
404                ),
405                length: value.length,
406                materialized: OnceLock::new(),
407            };
408
409            Ok(slf)
410        }
411    }
412
413    impl serde::ser::Serialize for ScalarColumn {
414        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
415        where
416            S: serde::Serializer,
417        {
418            SerializeWrap::from(self).serialize(serializer)
419        }
420    }
421
422    impl<'de> serde::de::Deserialize<'de> for ScalarColumn {
423        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
424        where
425            D: serde::Deserializer<'de>,
426        {
427            use serde::de::Error;
428
429            SerializeWrap::deserialize(deserializer)
430                .and_then(|x| ScalarColumn::try_from(x).map_err(D::Error::custom))
431        }
432    }
433}