1use std::borrow::Cow;
4
5use polars_compute::cast::CastOptionsImpl;
6#[cfg(feature = "serde-lazy")]
7use serde::{Deserialize, Serialize};
8
9use super::flags::StatisticsFlags;
10#[cfg(feature = "dtype-datetime")]
11use crate::prelude::DataType::Datetime;
12use crate::prelude::*;
13use crate::utils::{handle_array_casting_failures, handle_casting_failures};
14
15#[derive(Copy, Clone, Debug, Default, PartialEq, Hash, Eq)]
16#[cfg_attr(feature = "serde-lazy", derive(Serialize, Deserialize))]
17#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
18#[repr(u8)]
19pub enum CastOptions {
20 #[default]
22 Strict,
23 NonStrict,
25 Overflowing,
27}
28
29impl CastOptions {
30 pub fn is_strict(&self) -> bool {
31 matches!(self, CastOptions::Strict)
32 }
33}
34
35impl From<CastOptions> for CastOptionsImpl {
36 fn from(value: CastOptions) -> Self {
37 let wrapped = match value {
38 CastOptions::Strict | CastOptions::NonStrict => false,
39 CastOptions::Overflowing => true,
40 };
41 CastOptionsImpl {
42 wrapped,
43 partial: false,
44 }
45 }
46}
47
48pub(crate) fn cast_chunks(
49 chunks: &[ArrayRef],
50 dtype: &DataType,
51 options: CastOptions,
52) -> PolarsResult<Vec<ArrayRef>> {
53 let check_nulls = matches!(options, CastOptions::Strict);
54 let options = options.into();
55
56 let arrow_dtype = dtype.try_to_arrow(CompatLevel::newest())?;
57 chunks
58 .iter()
59 .map(|arr| {
60 let out = polars_compute::cast::cast(arr.as_ref(), &arrow_dtype, options);
61 if check_nulls {
62 out.and_then(|new| {
63 if arr.null_count() != new.null_count() {
64 handle_array_casting_failures(&**arr, &*new)?;
65 }
66 Ok(new)
67 })
68 } else {
69 out
70 }
71 })
72 .collect::<PolarsResult<Vec<_>>>()
73}
74
75fn cast_impl_inner(
76 name: PlSmallStr,
77 chunks: &[ArrayRef],
78 dtype: &DataType,
79 options: CastOptions,
80) -> PolarsResult<Series> {
81 let chunks = match dtype {
82 #[cfg(feature = "dtype-decimal")]
83 DataType::Decimal(_, _) => {
84 let mut chunks = cast_chunks(chunks, dtype, options)?;
85 for chunk in chunks.iter_mut() {
87 *chunk = std::mem::take(
88 chunk
89 .as_any_mut()
90 .downcast_mut::<PrimitiveArray<i128>>()
91 .unwrap(),
92 )
93 .to(ArrowDataType::Int128)
94 .to_boxed();
95 }
96 chunks
97 },
98 _ => cast_chunks(chunks, &dtype.to_physical(), options)?,
99 };
100
101 let out = Series::try_from((name, chunks))?;
102 use DataType::*;
103 let out = match dtype {
104 Date => out.into_date(),
105 Datetime(tu, tz) => match tz {
106 #[cfg(feature = "timezones")]
107 Some(tz) => {
108 TimeZone::validate_time_zone(tz)?;
109 out.into_datetime(*tu, Some(tz.clone()))
110 },
111 _ => out.into_datetime(*tu, None),
112 },
113 Duration(tu) => out.into_duration(*tu),
114 #[cfg(feature = "dtype-time")]
115 Time => out.into_time(),
116 #[cfg(feature = "dtype-decimal")]
117 Decimal(precision, scale) => out.into_decimal(*precision, *scale)?,
118 #[cfg(feature = "dtype-extension")]
119 Extension(typ, _) => out.into_extension(typ.clone()),
120 _ => out,
121 };
122
123 Ok(out)
124}
125
126fn cast_impl(
127 name: PlSmallStr,
128 chunks: &[ArrayRef],
129 dtype: &DataType,
130 options: CastOptions,
131) -> PolarsResult<Series> {
132 cast_impl_inner(name, chunks, dtype, options)
133}
134
135#[cfg(feature = "dtype-struct")]
136fn cast_single_to_struct(
137 name: PlSmallStr,
138 chunks: &[ArrayRef],
139 fields: &[Field],
140 options: CastOptions,
141) -> PolarsResult<Series> {
142 polars_ensure!(fields.len() == 1, InvalidOperation: "must specify one field in the struct");
143 let mut new_fields = Vec::with_capacity(fields.len());
144 let mut fields = fields.iter();
146 let fld = fields.next().unwrap();
147 let s = cast_impl_inner(fld.name.clone(), chunks, &fld.dtype, options)?;
148 let length = s.len();
149 new_fields.push(s);
150
151 for fld in fields {
152 new_fields.push(Series::full_null(fld.name.clone(), length, &fld.dtype));
153 }
154
155 StructChunked::from_series(name, length, new_fields.iter()).map(|ca| ca.into_series())
156}
157
158impl<T> ChunkedArray<T>
159where
160 T: PolarsNumericType,
161{
162 fn cast_impl(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
163 if self.dtype() == dtype {
164 let mut out = unsafe {
166 Series::from_chunks_and_dtype_unchecked(
167 self.name().clone(),
168 self.chunks.clone(),
169 dtype,
170 )
171 };
172 out.set_sorted_flag(self.is_sorted_flag());
173 return Ok(out);
174 }
175 match dtype {
176 #[cfg(feature = "dtype-categorical")]
177 DataType::Categorical(..) | DataType::Enum(..) => {
178 polars_bail!(
179 ComputeError:
180 "casting from {} to {dtype} is not supported.\n\
181 Instead of `.cast({dtype:?}`, use `.cat.to({dtype:?})`.",
182 T::get_static_dtype()
183 );
184 },
185
186 #[cfg(feature = "dtype-struct")]
187 DataType::Struct(fields) => {
188 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
189 },
190 _ => cast_impl_inner(self.name().clone(), &self.chunks, dtype, options).map(|mut s| {
191 let to_signed = dtype.is_signed_integer();
196 let unsigned2unsigned =
197 self.dtype().is_unsigned_integer() && dtype.is_unsigned_integer();
198 let allowed = to_signed || unsigned2unsigned;
199
200 if (allowed)
201 && (s.null_count() == self.null_count())
202 || (self.dtype().to_physical() == dtype.to_physical())
204 {
205 let is_sorted = self.is_sorted_flag();
206 s.set_sorted_flag(is_sorted)
207 }
208 s
209 }),
210 }
211 }
212}
213
214impl<T> ChunkCast for ChunkedArray<T>
215where
216 T: PolarsNumericType,
217{
218 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
219 self.cast_impl(dtype, options)
220 }
221
222 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
223 match dtype {
224 #[cfg(feature = "dtype-categorical")]
227 DataType::Categorical(cats, _mapping) => {
228 polars_ensure!(
229 self.dtype() == &cats.physical().dtype(),
230 ComputeError: "cannot cast `{}` to `Categorical`; only the physical type `{}` is supported",
231 self.dtype(), cats.physical().dtype()
232 );
233 with_match_categorical_physical_type!(cats.physical(), |$C| {
234 type PhysCa = ChunkedArray<<$C as PolarsCategoricalType>::PolarsPhysical>;
236 let ca = unsafe { &*(self as *const ChunkedArray<T> as *const PhysCa) };
237 Ok(CategoricalChunked::<$C>::from_cats_and_dtype_unchecked(ca.clone(), dtype.clone())
238 .into_series())
239 })
240 },
241
242 #[cfg(feature = "dtype-categorical")]
245 DataType::Enum(fcats, _mapping) => {
246 polars_ensure!(self.dtype() == &fcats.physical().dtype(), ComputeError: "cannot cast numeric types to 'Enum'");
247 with_match_categorical_physical_type!(fcats.physical(), |$C| {
248 type PhysCa = ChunkedArray<<$C as PolarsCategoricalType>::PolarsPhysical>;
250 let ca = unsafe { &*(self as *const ChunkedArray<T> as *const PhysCa) };
251 Ok(CategoricalChunked::<$C>::from_cats_and_dtype_unchecked(ca.clone(), dtype.clone()).into_series())
252 })
253 },
254
255 _ => self.cast_impl(dtype, CastOptions::Overflowing),
256 }
257 }
258}
259
260impl ChunkCast for StringChunked {
261 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
262 match dtype {
263 #[cfg(feature = "dtype-categorical")]
264 DataType::Categorical(cats, _mapping) => {
265 with_match_categorical_physical_type!(cats.physical(), |$C| {
266 Ok(CategoricalChunked::<$C>::from_str_iter(self.name().clone(), dtype.clone(), self.iter())?
267 .into_series())
268 })
269 },
270 #[cfg(feature = "dtype-categorical")]
271 DataType::Enum(fcats, _mapping) => {
272 let ret = with_match_categorical_physical_type!(fcats.physical(), |$C| {
273 CategoricalChunked::<$C>::from_str_iter(self.name().clone(), dtype.clone(), self.iter())?
274 .into_series()
275 });
276
277 if options.is_strict() && self.null_count() != ret.null_count() {
278 handle_casting_failures(&self.clone().into_series(), &ret)?;
279 }
280
281 Ok(ret)
282 },
283 #[cfg(feature = "dtype-struct")]
284 DataType::Struct(fields) => {
285 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
286 },
287 #[cfg(feature = "dtype-decimal")]
288 DataType::Decimal(precision, scale) => {
289 let chunks = self.downcast_iter().map(|arr| {
290 polars_compute::cast::binview_to_decimal(&arr.to_binview(), *precision, *scale)
291 .to(ArrowDataType::Int128)
292 });
293 let ca = Int128Chunked::from_chunk_iter(self.name().clone(), chunks);
294 Ok(ca.into_decimal_unchecked(*precision, *scale).into_series())
295 },
296 #[cfg(feature = "dtype-date")]
297 DataType::Date => {
298 let result = cast_chunks(&self.chunks, dtype, options)?;
299 let out = Series::try_from((self.name().clone(), result))?;
300 Ok(out)
301 },
302 #[cfg(feature = "dtype-time")]
303 DataType::Time => {
304 let result = cast_chunks(&self.chunks, dtype, options)?;
305 Series::try_from((self.name().clone(), result))
306 },
307 #[cfg(feature = "dtype-datetime")]
308 DataType::Datetime(time_unit, time_zone) => match time_zone {
309 #[cfg(feature = "timezones")]
310 Some(time_zone) => {
311 TimeZone::validate_time_zone(time_zone)?;
312 let result = cast_chunks(
313 &self.chunks,
314 &Datetime(time_unit.to_owned(), Some(time_zone.clone())),
315 options,
316 )?;
317 Series::try_from((self.name().clone(), result))
318 },
319 _ => {
320 let result =
321 cast_chunks(&self.chunks, &Datetime(time_unit.to_owned(), None), options)?;
322 Series::try_from((self.name().clone(), result))
323 },
324 },
325 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
326 }
327 }
328
329 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
330 self.cast_with_options(dtype, CastOptions::Overflowing)
331 }
332}
333
334impl BinaryChunked {
335 pub unsafe fn to_string_unchecked(&self) -> StringChunked {
338 let chunks = self
339 .downcast_iter()
340 .map(|arr| unsafe { arr.to_utf8view_unchecked() }.boxed())
341 .collect();
342 let field = Arc::new(Field::new(self.name().clone(), DataType::String));
343
344 let mut ca = StringChunked::new_with_compute_len(field, chunks);
345
346 use StatisticsFlags as F;
347 ca.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
348 ca
349 }
350}
351
352impl StringChunked {
353 pub fn as_binary(&self) -> BinaryChunked {
354 let chunks = self
355 .downcast_iter()
356 .map(|arr| arr.to_binview().boxed())
357 .collect();
358 let field = Arc::new(Field::new(self.name().clone(), DataType::Binary));
359
360 let mut ca = BinaryChunked::new_with_compute_len(field, chunks);
361
362 use StatisticsFlags as F;
363 ca.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
364 ca
365 }
366}
367
368impl ChunkCast for BinaryChunked {
369 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
370 match dtype {
371 #[cfg(feature = "dtype-struct")]
372 DataType::Struct(fields) => {
373 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
374 },
375 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
376 }
377 }
378
379 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
380 match dtype {
381 DataType::String => unsafe { Ok(self.to_string_unchecked().into_series()) },
382 _ => self.cast_with_options(dtype, CastOptions::Overflowing),
383 }
384 }
385}
386
387impl ChunkCast for BinaryOffsetChunked {
388 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
389 match dtype {
390 #[cfg(feature = "dtype-struct")]
391 DataType::Struct(fields) => {
392 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
393 },
394 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
395 }
396 }
397
398 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
399 self.cast_with_options(dtype, CastOptions::Overflowing)
400 }
401}
402
403impl ChunkCast for BooleanChunked {
404 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
405 match dtype {
406 #[cfg(feature = "dtype-struct")]
407 DataType::Struct(fields) => {
408 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
409 },
410 #[cfg(feature = "dtype-categorical")]
411 DataType::Categorical(_, _) | DataType::Enum(_, _) => {
412 polars_bail!(InvalidOperation: "cannot cast Boolean to Categorical");
413 },
414 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
415 }
416 }
417
418 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
419 self.cast_with_options(dtype, CastOptions::Overflowing)
420 }
421}
422
423impl ChunkCast for ListChunked {
424 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
425 let ca = self
426 .trim_lists_to_normalized_offsets()
427 .map_or(Cow::Borrowed(self), Cow::Owned);
428 let ca = ca.propagate_nulls().map_or(ca, Cow::Owned);
429
430 use DataType::*;
431 match dtype {
432 List(child_type) => {
433 match (ca.inner_dtype(), &**child_type) {
434 (old, new) if old == new => Ok(ca.into_owned().into_series()),
435 #[cfg(feature = "dtype-categorical")]
437 (dt, Categorical(_, _) | Enum(_, _))
438 if !matches!(dt, Categorical(_, _) | Enum(_, _) | String | Null) =>
439 {
440 polars_bail!(InvalidOperation: "cannot cast List inner type: '{:?}' to Categorical", dt)
441 },
442 _ => {
443 let (arr, child_type) = cast_list(ca.as_ref(), child_type, options)?;
445 unsafe {
448 Ok(Series::from_chunks_and_dtype_unchecked(
449 ca.name().clone(),
450 vec![arr],
451 &List(Box::new(child_type)),
452 ))
453 }
454 },
455 }
456 },
457 #[cfg(feature = "dtype-array")]
458 Array(child_type, width) => {
459 let physical_type = dtype.to_physical();
460
461 let chunks = cast_chunks(ca.chunks(), &physical_type, options)?;
463 unsafe {
466 Ok(Series::from_chunks_and_dtype_unchecked(
467 ca.name().clone(),
468 chunks,
469 &Array(child_type.clone(), *width),
470 ))
471 }
472 },
473 #[cfg(feature = "dtype-u8")]
474 Binary => {
475 polars_ensure!(
476 matches!(self.inner_dtype(), UInt8),
477 InvalidOperation: "cannot cast List type (inner: '{:?}', to: '{:?}')",
478 self.inner_dtype(),
479 dtype,
480 );
481 let chunks = cast_chunks(self.chunks(), &DataType::Binary, options)?;
482
483 unsafe {
485 Ok(Series::from_chunks_and_dtype_unchecked(
486 self.name().clone(),
487 chunks,
488 &DataType::Binary,
489 ))
490 }
491 },
492 #[cfg(feature = "dtype-map")]
493 Map(to_key, to_value) => {
494 let storage = if ca.inner_dtype().is_nested_null() {
495 ca.cast_with_options(&dtype.map_storage_dtype().unwrap(), options)?
497 } else {
498 try_apply_map_entries(ca.as_ref(), |key, value| {
499 Ok((
500 key.cast_with_options(to_key, options)?,
501 value.cast_with_options(to_value, options)?,
502 ))
503 })?
504 .into_series()
505 };
506
507 Ok(MapChunked::try_from_storage(dtype.clone(), storage)?.into_series())
508 },
509 _ => {
510 polars_bail!(
511 InvalidOperation: "cannot cast List type (inner: '{:?}', to: '{:?}')",
512 ca.inner_dtype(),
513 dtype,
514 )
515 },
516 }
517 }
518
519 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
520 use DataType::*;
521 match dtype {
522 List(child_type) => cast_list_unchecked(self, child_type),
523 _ => self.cast_with_options(dtype, CastOptions::Overflowing),
524 }
525 }
526}
527
528#[cfg(feature = "dtype-array")]
531impl ChunkCast for ArrayChunked {
532 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
533 let ca = self
534 .trim_lists_to_normalized_offsets()
535 .map_or(Cow::Borrowed(self), Cow::Owned);
536 let ca = ca.propagate_nulls().map_or(ca, Cow::Owned);
537
538 use DataType::*;
539 match dtype {
540 Array(child_type, width) => {
541 polars_ensure!(
542 *width == ca.width(),
543 InvalidOperation: "cannot cast Array to a different width (from {} to {})",
544 ca.width(), width
545 );
546
547 match (ca.inner_dtype(), &**child_type) {
548 (old, new) if old == new => Ok(ca.into_owned().into_series()),
549 #[cfg(feature = "dtype-categorical")]
551 (dt, Categorical(_, _) | Enum(_, _)) if !matches!(dt, String | Null) => {
552 polars_bail!(InvalidOperation: "cannot cast Array inner type: '{:?}' to dtype: {:?}", dt, child_type)
553 },
554 _ => {
555 let (arr, child_type) =
557 cast_fixed_size_list(ca.as_ref(), child_type, options)?;
558 unsafe {
561 Ok(Series::from_chunks_and_dtype_unchecked(
562 ca.name().clone(),
563 vec![arr],
564 &Array(Box::new(child_type), *width),
565 ))
566 }
567 },
568 }
569 },
570 List(child_type) => {
571 let physical_type = dtype.to_physical();
572 let chunks = cast_chunks(ca.chunks(), &physical_type, options)?;
574 unsafe {
577 Ok(Series::from_chunks_and_dtype_unchecked(
578 ca.name().clone(),
579 chunks,
580 &List(child_type.clone()),
581 ))
582 }
583 },
584 _ => {
585 polars_bail!(
586 InvalidOperation: "cannot cast Array type (inner: '{:?}', to: '{:?}')",
587 ca.inner_dtype(),
588 dtype,
589 )
590 },
591 }
592 }
593
594 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
595 self.cast_with_options(dtype, CastOptions::Overflowing)
596 }
597}
598
599fn cast_list(
602 ca: &ListChunked,
603 child_type: &DataType,
604 options: CastOptions,
605) -> PolarsResult<(ArrayRef, DataType)> {
606 let ca = ca.rechunk();
609 let arr = ca.downcast_as_array();
610 let s = unsafe {
612 Series::from_chunks_and_dtype_unchecked(
613 PlSmallStr::EMPTY,
614 vec![arr.values().clone()],
615 ca.inner_dtype(),
616 )
617 };
618 let new_inner = s.cast_with_options(child_type, options)?;
619
620 let inner_dtype = new_inner.dtype().clone();
621 debug_assert_eq!(&inner_dtype, child_type);
622
623 let new_values = new_inner.array_ref(0).clone();
624
625 let dtype = ListArray::<i64>::default_datatype(new_values.dtype().clone());
626 let new_arr = ListArray::<i64>::new(
627 dtype,
628 arr.offsets().clone(),
629 new_values,
630 arr.validity().cloned(),
631 );
632 Ok((new_arr.boxed(), inner_dtype))
633}
634
635unsafe fn cast_list_unchecked(ca: &ListChunked, child_type: &DataType) -> PolarsResult<Series> {
636 let ca = ca.rechunk();
638 let arr = ca.downcast_as_array();
639 let s = unsafe {
641 Series::from_chunks_and_dtype_unchecked(
642 PlSmallStr::EMPTY,
643 vec![arr.values().clone()],
644 ca.inner_dtype(),
645 )
646 };
647 let new_inner = s.cast_unchecked(child_type)?;
648 let new_values = new_inner.array_ref(0).clone();
649
650 let dtype = ListArray::<i64>::default_datatype(new_values.dtype().clone());
651 let new_arr = ListArray::<i64>::new(
652 dtype,
653 arr.offsets().clone(),
654 new_values,
655 arr.validity().cloned(),
656 );
657 Ok(ListChunked::from_chunks_and_dtype_unchecked(
658 ca.name().clone(),
659 vec![Box::new(new_arr)],
660 DataType::List(Box::new(child_type.clone())),
661 )
662 .into_series())
663}
664
665#[cfg(feature = "dtype-array")]
668fn cast_fixed_size_list(
669 ca: &ArrayChunked,
670 child_type: &DataType,
671 options: CastOptions,
672) -> PolarsResult<(ArrayRef, DataType)> {
673 let ca = ca.rechunk();
674 let arr = ca.downcast_as_array();
675 let s = unsafe {
677 Series::from_chunks_and_dtype_unchecked(
678 PlSmallStr::EMPTY,
679 vec![arr.values().clone()],
680 ca.inner_dtype(),
681 )
682 };
683 let new_inner = s.cast_with_options(child_type, options)?;
684
685 let inner_dtype = new_inner.dtype().clone();
686 debug_assert_eq!(&inner_dtype, child_type);
687
688 let new_values = new_inner.array_ref(0).clone();
689
690 let dtype = FixedSizeListArray::default_datatype(new_values.dtype().clone(), ca.width());
691 let new_arr = FixedSizeListArray::new(dtype, ca.len(), new_values, arr.validity().cloned());
692 Ok((Box::new(new_arr), inner_dtype))
693}
694
695#[cfg(test)]
696mod test {
697 use crate::chunked_array::cast::CastOptions;
698 use crate::prelude::*;
699
700 #[test]
701 fn test_cast_list() -> PolarsResult<()> {
702 let mut builder = ListPrimitiveChunkedBuilder::<Int32Type>::new(
703 PlSmallStr::from_static("a"),
704 10,
705 10,
706 DataType::Int32,
707 );
708 builder.append_opt_slice(Some(&[1i32, 2, 3]));
709 builder.append_opt_slice(Some(&[1i32, 2, 3]));
710 let ca = builder.finish();
711
712 let new = ca.cast_with_options(
713 &DataType::List(DataType::Float64.into()),
714 CastOptions::Strict,
715 )?;
716
717 assert_eq!(new.dtype(), &DataType::List(DataType::Float64.into()));
718 Ok(())
719 }
720
721 #[test]
722 #[cfg(feature = "dtype-categorical")]
723 fn test_cast_noop() {
724 let ca = StringChunked::new(PlSmallStr::from_static("foo"), &["bar", "ham"]);
726 let cats = Categories::global();
727 let out = ca
728 .cast_with_options(
729 &DataType::from_categories(cats.clone()),
730 CastOptions::Strict,
731 )
732 .unwrap();
733 let out = out.cast(&DataType::from_categories(cats)).unwrap();
734 assert!(matches!(out.dtype(), &DataType::Categorical(_, _)))
735 }
736}