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