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 use DataType::*;
102 let out = match dtype {
103 BinaryOffset => unsafe { BinaryOffsetChunked::from_chunks(name, chunks) }.into_series(),
105 _ => {
106 let out = Series::try_from((name, chunks))?;
107 match dtype {
108 Date => out.into_date(),
109 Datetime(tu, tz) => match tz {
110 #[cfg(feature = "timezones")]
111 Some(tz) => {
112 TimeZone::validate_time_zone(tz)?;
113 out.into_datetime(*tu, Some(tz.clone()))
114 },
115 _ => out.into_datetime(*tu, None),
116 },
117 Duration(tu) => out.into_duration(*tu),
118 #[cfg(feature = "dtype-time")]
119 Time => out.into_time(),
120 #[cfg(feature = "dtype-decimal")]
121 Decimal(precision, scale) => out.into_decimal(*precision, *scale)?,
122 #[cfg(feature = "dtype-extension")]
123 Extension(typ, _) => out.into_extension(typ.clone()),
124 _ => out,
125 }
126 },
127 };
128
129 Ok(out)
130}
131
132fn cast_impl(
133 name: PlSmallStr,
134 chunks: &[ArrayRef],
135 dtype: &DataType,
136 options: CastOptions,
137) -> PolarsResult<Series> {
138 cast_impl_inner(name, chunks, dtype, options)
139}
140
141#[cfg(feature = "dtype-struct")]
142fn cast_single_to_struct(
143 name: PlSmallStr,
144 chunks: &[ArrayRef],
145 fields: &[Field],
146 options: CastOptions,
147) -> PolarsResult<Series> {
148 polars_ensure!(fields.len() == 1, InvalidOperation: "must specify one field in the struct");
149 let mut new_fields = Vec::with_capacity(fields.len());
150 let mut fields = fields.iter();
152 let fld = fields.next().unwrap();
153 let s = cast_impl_inner(fld.name.clone(), chunks, &fld.dtype, options)?;
154 let length = s.len();
155 new_fields.push(s);
156
157 for fld in fields {
158 new_fields.push(Series::full_null(fld.name.clone(), length, &fld.dtype));
159 }
160
161 StructChunked::from_series(name, length, new_fields.iter()).map(|ca| ca.into_series())
162}
163
164impl<T> ChunkedArray<T>
165where
166 T: PolarsNumericType,
167{
168 fn cast_impl(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
169 if self.dtype() == dtype {
170 let mut out = unsafe {
172 Series::from_chunks_and_dtype_unchecked(
173 self.name().clone(),
174 self.chunks.clone(),
175 dtype,
176 )
177 };
178 out.set_sorted_flag(self.is_sorted_flag());
179 return Ok(out);
180 }
181 match dtype {
182 #[cfg(feature = "dtype-categorical")]
183 DataType::Categorical(..) | DataType::Enum(..) => {
184 polars_bail!(
185 ComputeError:
186 "casting from {} to {dtype} is not supported.\n\
187 Instead of `.cast({dtype:?}`, use `.cat.to({dtype:?})`.",
188 T::get_static_dtype()
189 );
190 },
191
192 #[cfg(feature = "dtype-struct")]
193 DataType::Struct(fields) => {
194 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
195 },
196 _ => cast_impl_inner(self.name().clone(), &self.chunks, dtype, options).map(|mut s| {
197 let to_signed = dtype.is_signed_integer();
202 let unsigned2unsigned =
203 self.dtype().is_unsigned_integer() && dtype.is_unsigned_integer();
204 let allowed = to_signed || unsigned2unsigned;
205
206 if (allowed)
207 && (s.null_count() == self.null_count())
208 || (self.dtype().to_physical() == dtype.to_physical())
210 {
211 let is_sorted = self.is_sorted_flag();
212 s.set_sorted_flag(is_sorted)
213 }
214 s
215 }),
216 }
217 }
218}
219
220impl<T> ChunkCast for ChunkedArray<T>
221where
222 T: PolarsNumericType,
223{
224 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
225 self.cast_impl(dtype, options)
226 }
227
228 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
229 match dtype {
230 #[cfg(feature = "dtype-categorical")]
233 DataType::Categorical(cats, _mapping) => {
234 polars_ensure!(
235 self.dtype() == &cats.physical().dtype(),
236 ComputeError: "cannot cast `{}` to `Categorical`; only the physical type `{}` is supported",
237 self.dtype(), cats.physical().dtype()
238 );
239 with_match_categorical_physical_type!(cats.physical(), |$C| {
240 type PhysCa = ChunkedArray<<$C as PolarsCategoricalType>::PolarsPhysical>;
242 let ca = unsafe { &*(self as *const ChunkedArray<T> as *const PhysCa) };
243 Ok(CategoricalChunked::<$C>::from_cats_and_dtype_unchecked(ca.clone(), dtype.clone())
244 .into_series())
245 })
246 },
247
248 #[cfg(feature = "dtype-categorical")]
251 DataType::Enum(fcats, _mapping) => {
252 polars_ensure!(
253 self.dtype() == &fcats.physical().dtype(),
254 ComputeError: "cannot cast `{}` to `Enum`; only the physical type `{}` is supported",
255 self.dtype(), fcats.physical().dtype()
256 );
257 with_match_categorical_physical_type!(fcats.physical(), |$C| {
258 type PhysCa = ChunkedArray<<$C as PolarsCategoricalType>::PolarsPhysical>;
260 let ca = unsafe { &*(self as *const ChunkedArray<T> as *const PhysCa) };
261 Ok(CategoricalChunked::<$C>::from_cats_and_dtype_unchecked(ca.clone(), dtype.clone()).into_series())
262 })
263 },
264
265 _ => self.cast_impl(dtype, CastOptions::Overflowing),
266 }
267 }
268}
269
270impl ChunkCast for StringChunked {
271 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
272 match dtype {
273 #[cfg(feature = "dtype-categorical")]
274 DataType::Categorical(cats, _mapping) => {
275 with_match_categorical_physical_type!(cats.physical(), |$C| {
276 Ok(CategoricalChunked::<$C>::from_str_iter(self.name().clone(), dtype.clone(), self.iter())?
277 .into_series())
278 })
279 },
280 #[cfg(feature = "dtype-categorical")]
281 DataType::Enum(fcats, _mapping) => {
282 let ret = with_match_categorical_physical_type!(fcats.physical(), |$C| {
283 CategoricalChunked::<$C>::from_str_iter(self.name().clone(), dtype.clone(), self.iter())?
284 .into_series()
285 });
286
287 if options.is_strict() && self.null_count() != ret.null_count() {
288 handle_casting_failures(&self.clone().into_series(), &ret)?;
289 }
290
291 Ok(ret)
292 },
293 #[cfg(feature = "dtype-struct")]
294 DataType::Struct(fields) => {
295 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
296 },
297 #[cfg(feature = "dtype-decimal")]
298 DataType::Decimal(precision, scale) => {
299 let chunks = self.downcast_iter().map(|arr| {
300 polars_compute::cast::binview_to_decimal(&arr.to_binview(), *precision, *scale)
301 .to(ArrowDataType::Int128)
302 });
303 let ca = Int128Chunked::from_chunk_iter(self.name().clone(), chunks);
304 Ok(ca.into_decimal_unchecked(*precision, *scale).into_series())
305 },
306 #[cfg(feature = "dtype-date")]
307 DataType::Date => {
308 let result = cast_chunks(&self.chunks, dtype, options)?;
309 let out = Series::try_from((self.name().clone(), result))?;
310 Ok(out)
311 },
312 #[cfg(feature = "dtype-time")]
313 DataType::Time => {
314 let result = cast_chunks(&self.chunks, dtype, options)?;
315 Series::try_from((self.name().clone(), result))
316 },
317 #[cfg(feature = "dtype-datetime")]
318 DataType::Datetime(time_unit, time_zone) => match time_zone {
319 #[cfg(feature = "timezones")]
320 Some(time_zone) => {
321 TimeZone::validate_time_zone(time_zone)?;
322 let result = cast_chunks(
323 &self.chunks,
324 &Datetime(time_unit.to_owned(), Some(time_zone.clone())),
325 options,
326 )?;
327 Series::try_from((self.name().clone(), result))
328 },
329 _ => {
330 let result =
331 cast_chunks(&self.chunks, &Datetime(time_unit.to_owned(), None), options)?;
332 Series::try_from((self.name().clone(), result))
333 },
334 },
335 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
336 }
337 }
338
339 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
340 self.cast_with_options(dtype, CastOptions::Overflowing)
341 }
342}
343
344impl BinaryChunked {
345 pub unsafe fn to_string_unchecked(&self) -> StringChunked {
348 let chunks = self
349 .downcast_iter()
350 .map(|arr| unsafe { arr.to_utf8view_unchecked() }.boxed())
351 .collect();
352 let field = Arc::new(Field::new(self.name().clone(), DataType::String));
353
354 let mut ca = StringChunked::new_with_compute_len(field, chunks);
355
356 use StatisticsFlags as F;
357 ca.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
358 ca
359 }
360}
361
362impl StringChunked {
363 pub fn as_binary(&self) -> BinaryChunked {
364 let chunks = self
365 .downcast_iter()
366 .map(|arr| arr.to_binview().boxed())
367 .collect();
368 let field = Arc::new(Field::new(self.name().clone(), DataType::Binary));
369
370 let mut ca = BinaryChunked::new_with_compute_len(field, chunks);
371
372 use StatisticsFlags as F;
373 ca.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
374 ca
375 }
376}
377
378impl ChunkCast for BinaryChunked {
379 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
380 match dtype {
381 #[cfg(feature = "dtype-struct")]
382 DataType::Struct(fields) => {
383 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
384 },
385 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
386 }
387 }
388
389 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
390 match dtype {
391 DataType::String => unsafe { Ok(self.to_string_unchecked().into_series()) },
392 _ => self.cast_with_options(dtype, CastOptions::Overflowing),
393 }
394 }
395}
396
397impl ChunkCast for BinaryOffsetChunked {
398 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
399 match dtype {
400 #[cfg(feature = "dtype-struct")]
401 DataType::Struct(fields) => {
402 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
403 },
404 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
405 }
406 }
407
408 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
409 self.cast_with_options(dtype, CastOptions::Overflowing)
410 }
411}
412
413impl ChunkCast for BooleanChunked {
414 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
415 match dtype {
416 #[cfg(feature = "dtype-struct")]
417 DataType::Struct(fields) => {
418 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
419 },
420 #[cfg(feature = "dtype-categorical")]
421 DataType::Categorical(_, _) | DataType::Enum(_, _) => {
422 polars_bail!(InvalidOperation: "cannot cast Boolean to Categorical");
423 },
424 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
425 }
426 }
427
428 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
429 self.cast_with_options(dtype, CastOptions::Overflowing)
430 }
431}
432
433impl ChunkCast for ListChunked {
434 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
435 let ca = self
436 .trim_lists_to_normalized_offsets()
437 .map_or(Cow::Borrowed(self), Cow::Owned);
438 let ca = ca.propagate_nulls().map_or(ca, Cow::Owned);
439
440 use DataType::*;
441 match dtype {
442 List(child_type) => {
443 match (ca.inner_dtype(), &**child_type) {
444 (old, new) if old == new => Ok(ca.into_owned().into_series()),
445 #[cfg(feature = "dtype-categorical")]
447 (dt, Categorical(_, _) | Enum(_, _))
448 if !matches!(dt, Categorical(_, _) | Enum(_, _) | String | Null) =>
449 {
450 polars_bail!(InvalidOperation: "cannot cast List inner type: '{:?}' to Categorical", dt)
451 },
452 _ => {
453 let (arr, child_type) = cast_list(ca.as_ref(), child_type, options)?;
455 unsafe {
458 Ok(Series::from_chunks_and_dtype_unchecked(
459 ca.name().clone(),
460 vec![arr],
461 &List(Box::new(child_type)),
462 ))
463 }
464 },
465 }
466 },
467 #[cfg(feature = "dtype-array")]
468 Array(child_type, width) => {
469 let s = ca.cast_with_options(&List(child_type.clone()), options)?;
471 let ca = s.list().unwrap();
472 let inner_dtype = ca.inner_dtype();
473 let physical_type = Array(Box::new(inner_dtype.to_physical()), *width);
474 let chunks = cast_chunks(ca.chunks(), &physical_type, options)?;
475 unsafe {
477 Ok(Series::from_chunks_and_dtype_unchecked(
478 ca.name().clone(),
479 chunks,
480 &Array(Box::new(inner_dtype.clone()), *width),
481 ))
482 }
483 },
484 #[cfg(feature = "dtype-u8")]
485 Binary => {
486 polars_ensure!(
487 matches!(self.inner_dtype(), UInt8),
488 InvalidOperation: "cannot cast List type (inner: '{:?}', to: '{:?}')",
489 self.inner_dtype(),
490 dtype,
491 );
492 let chunks = cast_chunks(self.chunks(), &DataType::Binary, options)?;
493
494 unsafe {
496 Ok(Series::from_chunks_and_dtype_unchecked(
497 self.name().clone(),
498 chunks,
499 &DataType::Binary,
500 ))
501 }
502 },
503 #[cfg(feature = "dtype-map")]
504 Map(to_key, to_value) => {
505 let storage = if ca.inner_dtype().is_nested_null() {
506 ca.cast_with_options(&dtype.map_storage_dtype().unwrap(), options)?
508 } else {
509 try_apply_map_entries(ca.as_ref(), |key, value| {
510 Ok((
511 key.cast_with_options(to_key, options)?,
512 value.cast_with_options(to_value, options)?,
513 ))
514 })?
515 .into_series()
516 };
517
518 Ok(MapChunked::try_from_storage(dtype.clone(), storage)?.into_series())
519 },
520 _ => {
521 polars_bail!(
522 InvalidOperation: "cannot cast List type (inner: '{:?}', to: '{:?}')",
523 ca.inner_dtype(),
524 dtype,
525 )
526 },
527 }
528 }
529
530 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
531 use DataType::*;
532 match dtype {
533 List(child_type) => cast_list_unchecked(self, child_type),
534 _ => self.cast_with_options(dtype, CastOptions::Overflowing),
535 }
536 }
537}
538
539#[cfg(feature = "dtype-array")]
542impl ChunkCast for ArrayChunked {
543 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
544 let ca = self
545 .trim_lists_to_normalized_offsets()
546 .map_or(Cow::Borrowed(self), Cow::Owned);
547 let ca = ca.propagate_nulls().map_or(ca, Cow::Owned);
548
549 use DataType::*;
550 match dtype {
551 Array(child_type, width) => {
552 polars_ensure!(
553 *width == ca.width(),
554 InvalidOperation: "cannot cast Array to a different width (from {} to {})",
555 ca.width(), width
556 );
557
558 match (ca.inner_dtype(), &**child_type) {
559 (old, new) if old == new => Ok(ca.into_owned().into_series()),
560 #[cfg(feature = "dtype-categorical")]
562 (dt, Categorical(_, _) | Enum(_, _)) if !matches!(dt, String | Null) => {
563 polars_bail!(InvalidOperation: "cannot cast Array inner type: '{:?}' to dtype: {:?}", dt, child_type)
564 },
565 _ => {
566 let (arr, child_type) =
568 cast_fixed_size_list(ca.as_ref(), child_type, options)?;
569 unsafe {
572 Ok(Series::from_chunks_and_dtype_unchecked(
573 ca.name().clone(),
574 vec![arr],
575 &Array(Box::new(child_type), *width),
576 ))
577 }
578 },
579 }
580 },
581 List(_) => ca.to_list().cast_with_options(dtype, options),
582 _ => {
583 polars_bail!(
584 InvalidOperation: "cannot cast Array type (inner: '{:?}', to: '{:?}')",
585 ca.inner_dtype(),
586 dtype,
587 )
588 },
589 }
590 }
591
592 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
593 self.cast_with_options(dtype, CastOptions::Overflowing)
594 }
595}
596
597fn cast_list(
600 ca: &ListChunked,
601 child_type: &DataType,
602 options: CastOptions,
603) -> PolarsResult<(ArrayRef, DataType)> {
604 let ca = ca.rechunk();
607 let arr = ca.downcast_as_array();
608 let s = unsafe {
610 Series::from_chunks_and_dtype_unchecked(
611 PlSmallStr::EMPTY,
612 vec![arr.values().clone()],
613 ca.inner_dtype(),
614 )
615 };
616 let new_inner = s.cast_with_options(child_type, options)?;
617
618 let inner_dtype = new_inner.dtype().clone();
619
620 let new_values = new_inner.array_ref(0).clone();
621
622 let dtype = ListArray::<i64>::default_datatype(new_values.dtype().clone());
623 let new_arr = ListArray::<i64>::new(
624 dtype,
625 arr.offsets().clone(),
626 new_values,
627 arr.validity().cloned(),
628 );
629 Ok((new_arr.boxed(), inner_dtype))
630}
631
632unsafe fn cast_list_unchecked(ca: &ListChunked, child_type: &DataType) -> PolarsResult<Series> {
633 let ca = ca.rechunk();
635 let arr = ca.downcast_as_array();
636 let s = unsafe {
638 Series::from_chunks_and_dtype_unchecked(
639 PlSmallStr::EMPTY,
640 vec![arr.values().clone()],
641 ca.inner_dtype(),
642 )
643 };
644 let new_inner = s.cast_unchecked(child_type)?;
645 let new_values = new_inner.array_ref(0).clone();
646
647 let dtype = ListArray::<i64>::default_datatype(new_values.dtype().clone());
648 let new_arr = ListArray::<i64>::new(
649 dtype,
650 arr.offsets().clone(),
651 new_values,
652 arr.validity().cloned(),
653 );
654 Ok(ListChunked::from_chunks_and_dtype_unchecked(
655 ca.name().clone(),
656 vec![Box::new(new_arr)],
657 DataType::List(Box::new(child_type.clone())),
658 )
659 .into_series())
660}
661
662#[cfg(feature = "dtype-array")]
665fn cast_fixed_size_list(
666 ca: &ArrayChunked,
667 child_type: &DataType,
668 options: CastOptions,
669) -> PolarsResult<(ArrayRef, DataType)> {
670 let ca = ca.rechunk();
671 let arr = ca.downcast_as_array();
672 let s = unsafe {
674 Series::from_chunks_and_dtype_unchecked(
675 PlSmallStr::EMPTY,
676 vec![arr.values().clone()],
677 ca.inner_dtype(),
678 )
679 };
680 let new_inner = s.cast_with_options(child_type, options)?;
681
682 let inner_dtype = new_inner.dtype().clone();
683
684 let new_values = new_inner.array_ref(0).clone();
685
686 let dtype = FixedSizeListArray::default_datatype(new_values.dtype().clone(), ca.width());
687 let new_arr = FixedSizeListArray::new(dtype, ca.len(), new_values, arr.validity().cloned());
688 Ok((Box::new(new_arr), inner_dtype))
689}
690
691#[cfg(test)]
692mod test {
693 use crate::chunked_array::cast::CastOptions;
694 use crate::prelude::*;
695
696 #[test]
697 fn test_cast_list() -> PolarsResult<()> {
698 let mut builder = ListPrimitiveChunkedBuilder::<Int32Type>::new(
699 PlSmallStr::from_static("a"),
700 10,
701 10,
702 DataType::Int32,
703 );
704 builder.append_opt_slice(Some(&[1i32, 2, 3]));
705 builder.append_opt_slice(Some(&[1i32, 2, 3]));
706 let ca = builder.finish();
707
708 let new = ca.cast_with_options(
709 &DataType::List(DataType::Float64.into()),
710 CastOptions::Strict,
711 )?;
712
713 assert_eq!(new.dtype(), &DataType::List(DataType::Float64.into()));
714 Ok(())
715 }
716
717 #[test]
718 #[cfg(feature = "dtype-categorical")]
719 fn test_cast_noop() {
720 let ca = StringChunked::new(PlSmallStr::from_static("foo"), &["bar", "ham"]);
722 let cats = Categories::global();
723 let out = ca
724 .cast_with_options(
725 &DataType::from_categories(cats.clone()),
726 CastOptions::Strict,
727 )
728 .unwrap();
729 let out = out.cast(&DataType::from_categories(cats)).unwrap();
730 assert!(matches!(out.dtype(), &DataType::Categorical(_, _)))
731 }
732}