1use arrow::datatypes::{IntervalUnit, Metadata};
2use arrow::offset::OffsetsBuffer;
3#[cfg(any(
4 feature = "dtype-date",
5 feature = "dtype-datetime",
6 feature = "dtype-time",
7 feature = "dtype-duration"
8))]
9use arrow::temporal_conversions::*;
10use arrow::types::months_days_ns;
11use polars_compute::cast::cast_unchecked as cast;
12#[cfg(feature = "dtype-decimal")]
13use polars_compute::decimal::dec128_fits;
14use polars_error::feature_gated;
15use polars_utils::itertools::Itertools;
16
17use crate::chunked_array::cast::{CastOptions, cast_chunks};
18#[cfg(feature = "object")]
19use crate::chunked_array::object::extension::polars_extension::PolarsExtension;
20#[cfg(feature = "object")]
21use crate::chunked_array::object::registry::get_object_builder;
22use crate::config::check_allow_importing_interval_as_struct;
23use crate::prelude::*;
24
25impl Series {
26 pub fn from_array<A: ParameterFreeDtypeStaticArray>(name: PlSmallStr, array: A) -> Self {
27 unsafe {
28 Self::from_chunks_and_dtype_unchecked(
29 name,
30 vec![Box::new(array)],
31 &DataType::from_arrow_dtype(&A::get_dtype()),
32 )
33 }
34 }
35
36 pub fn from_chunk_and_dtype(
40 name: PlSmallStr,
41 chunk: ArrayRef,
42 dtype: &DataType,
43 ) -> PolarsResult<Self> {
44 polars_ensure!(
46 !dtype.contains_objects(),
47 InvalidOperation: "cannot create a series of type '{dtype}' from an arrow chunk: objects are process-local"
48 );
49 polars_ensure!(
50 !dtype.contains_unknown(),
51 InvalidOperation: "cannot create a series of type '{dtype}' from an arrow chunk"
52 );
53 #[cfg(feature = "dtype-map")]
54 dtype.ensure_valid_map_dtypes()?;
55
56 let physical = dtype.to_physical();
57 if &physical.to_arrow(CompatLevel::newest()) != chunk.dtype() {
58 polars_bail!(
59 InvalidOperation: "cannot create a series of type '{dtype}' of arrow chunk with type '{:?}'",
60 chunk.dtype()
61 );
62 }
63
64 let physical =
66 unsafe { Self::from_chunks_and_dtype_unchecked(name, vec![chunk], &physical) };
67 physical.try_from_physical(dtype)
68 }
69
70 pub unsafe fn from_chunks_and_dtype_unchecked(
85 name: PlSmallStr,
86 chunks: Vec<ArrayRef>,
87 dtype: &DataType,
88 ) -> Self {
89 use DataType::*;
90 match dtype {
91 Int8 => Int8Chunked::from_chunks(name, chunks).into_series(),
92 Int16 => Int16Chunked::from_chunks(name, chunks).into_series(),
93 Int32 => Int32Chunked::from_chunks(name, chunks).into_series(),
94 Int64 => Int64Chunked::from_chunks(name, chunks).into_series(),
95 UInt8 => UInt8Chunked::from_chunks(name, chunks).into_series(),
96 UInt16 => UInt16Chunked::from_chunks(name, chunks).into_series(),
97 UInt32 => UInt32Chunked::from_chunks(name, chunks).into_series(),
98 UInt64 => UInt64Chunked::from_chunks(name, chunks).into_series(),
99 #[cfg(feature = "dtype-i128")]
100 Int128 => Int128Chunked::from_chunks(name, chunks).into_series(),
101 #[cfg(feature = "dtype-u128")]
102 UInt128 => UInt128Chunked::from_chunks(name, chunks).into_series(),
103 #[cfg(feature = "dtype-date")]
104 Date => Int32Chunked::from_chunks(name, chunks)
105 .into_date()
106 .into_series(),
107 #[cfg(feature = "dtype-time")]
108 Time => Int64Chunked::from_chunks(name, chunks)
109 .into_time()
110 .into_series(),
111 #[cfg(feature = "dtype-duration")]
112 Duration(tu) => Int64Chunked::from_chunks(name, chunks)
113 .into_duration(*tu)
114 .into_series(),
115 #[cfg(feature = "dtype-datetime")]
116 Datetime(tu, tz) => Int64Chunked::from_chunks(name, chunks)
117 .into_datetime(*tu, tz.clone())
118 .into_series(),
119 #[cfg(feature = "dtype-decimal")]
120 Decimal(precision, scale) => Int128Chunked::from_chunks(name, chunks)
121 .into_decimal_unchecked(*precision, *scale)
122 .into_series(),
123 #[cfg(feature = "dtype-array")]
124 Array(_, _) => {
125 ArrayChunked::from_chunks_and_dtype_unchecked(name, chunks, dtype.clone())
126 .into_series()
127 },
128 List(_) => ListChunked::from_chunks_and_dtype_unchecked(name, chunks, dtype.clone())
129 .into_series(),
130 String => StringChunked::from_chunks(name, chunks).into_series(),
131 Binary => BinaryChunked::from_chunks(name, chunks).into_series(),
132 #[cfg(feature = "dtype-categorical")]
133 dt @ (Categorical(_, _) | Enum(_, _)) => {
134 with_match_categorical_physical_type!(dt.cat_physical().unwrap(), |$C| {
135 let phys = ChunkedArray::from_chunks(name, chunks);
136 CategoricalChunked::<$C>::from_cats_and_dtype_unchecked(phys, dt.clone()).into_series()
137 })
138 },
139 Boolean => BooleanChunked::from_chunks(name, chunks).into_series(),
140 #[cfg(feature = "dtype-f16")]
141 Float16 => Float16Chunked::from_chunks(name, chunks).into_series(),
142 Float32 => Float32Chunked::from_chunks(name, chunks).into_series(),
143 Float64 => Float64Chunked::from_chunks(name, chunks).into_series(),
144 BinaryOffset => BinaryOffsetChunked::from_chunks(name, chunks).into_series(),
145 #[cfg(feature = "dtype-extension")]
146 Extension(typ, storage) => ExtensionChunked::from_storage(
147 typ.clone(),
148 Series::from_chunks_and_dtype_unchecked(name, chunks, storage),
149 )
150 .into_series(),
151 #[cfg(feature = "dtype-map")]
152 Map(_, _) => {
153 let storage = Series::from_chunks_and_dtype_unchecked(
154 name,
155 chunks,
156 &dtype.map_storage_dtype().unwrap(),
157 );
158 MapChunked::from_storage_unchecked(dtype.clone(), storage).into_series()
159 },
160 #[cfg(feature = "dtype-struct")]
161 Struct(_) => {
162 let mut ca =
163 StructChunked::from_chunks_and_dtype_unchecked(name, chunks, dtype.clone());
164 StructChunked::propagate_nulls_mut(&mut ca);
165 ca.into_series()
166 },
167 #[cfg(feature = "object")]
168 Object(_) => {
169 if let Some(arr) = chunks[0].as_any().downcast_ref::<FixedSizeBinaryArray>() {
170 assert_eq!(chunks.len(), 1);
171 {
176 let pe = PolarsExtension::new(arr.clone());
177 let s = pe.get_series(&name);
178 pe.take_and_forget();
179 s
180 }
181 } else {
182 unsafe { get_object_builder(name, 0).from_chunks(chunks) }
183 }
184 },
185 Null => new_null(name, &chunks),
186 Unknown(_) => {
187 panic!("dtype is unknown; consider supplying data-types for all operations")
188 },
189 #[allow(unreachable_patterns)]
190 _ => unreachable!(),
191 }
192 }
193
194 pub unsafe fn _try_from_arrow_unchecked(
197 name: PlSmallStr,
198 chunks: Vec<ArrayRef>,
199 dtype: &ArrowDataType,
200 ) -> PolarsResult<Self> {
201 Self::_try_from_arrow_unchecked_with_md(name, chunks, dtype, None)
202 }
203
204 pub unsafe fn _try_from_arrow_unchecked_with_md(
209 name: PlSmallStr,
210 mut chunks: Vec<ArrayRef>,
211 dtype: &ArrowDataType,
212 md: Option<&Metadata>,
213 ) -> PolarsResult<Self> {
214 match dtype {
215 ArrowDataType::Utf8View => Ok(StringChunked::from_chunks(name, chunks).into_series()),
216 ArrowDataType::Utf8 | ArrowDataType::LargeUtf8 => {
217 let chunks =
218 cast_chunks(&chunks, &DataType::String, CastOptions::NonStrict).unwrap();
219 Ok(StringChunked::from_chunks(name, chunks).into_series())
220 },
221 ArrowDataType::BinaryView => Ok(BinaryChunked::from_chunks(name, chunks).into_series()),
222 ArrowDataType::LargeBinary => {
223 if let Some(md) = md {
224 if md.maintain_type() {
225 return Ok(BinaryOffsetChunked::from_chunks(name, chunks).into_series());
226 }
227 }
228 let chunks =
229 cast_chunks(&chunks, &DataType::Binary, CastOptions::NonStrict).unwrap();
230 Ok(BinaryChunked::from_chunks(name, chunks).into_series())
231 },
232 ArrowDataType::Binary => {
233 let chunks =
234 cast_chunks(&chunks, &DataType::Binary, CastOptions::NonStrict).unwrap();
235 Ok(BinaryChunked::from_chunks(name, chunks).into_series())
236 },
237 ArrowDataType::List(_) | ArrowDataType::LargeList(_) => {
238 let (chunks, dtype) = to_physical_and_dtype(chunks, md)?;
239 unsafe {
240 Ok(
241 ListChunked::from_chunks_and_dtype_unchecked(name, chunks, dtype)
242 .into_series(),
243 )
244 }
245 },
246 #[cfg(feature = "dtype-array")]
247 ArrowDataType::FixedSizeList(_, _) => {
248 let (chunks, dtype) = to_physical_and_dtype(chunks, md)?;
249 unsafe {
250 Ok(
251 ArrayChunked::from_chunks_and_dtype_unchecked(name, chunks, dtype)
252 .into_series(),
253 )
254 }
255 },
256 ArrowDataType::Boolean => Ok(BooleanChunked::from_chunks(name, chunks).into_series()),
257 #[cfg(feature = "dtype-u8")]
258 ArrowDataType::UInt8 => Ok(UInt8Chunked::from_chunks(name, chunks).into_series()),
259 #[cfg(feature = "dtype-u16")]
260 ArrowDataType::UInt16 => Ok(UInt16Chunked::from_chunks(name, chunks).into_series()),
261 ArrowDataType::UInt32 => Ok(UInt32Chunked::from_chunks(name, chunks).into_series()),
262 ArrowDataType::UInt64 => Ok(UInt64Chunked::from_chunks(name, chunks).into_series()),
263 ArrowDataType::UInt128 => feature_gated!(
264 "dtype-u128",
265 Ok(UInt128Chunked::from_chunks(name, chunks).into_series())
266 ),
267 #[cfg(feature = "dtype-i8")]
268 ArrowDataType::Int8 => Ok(Int8Chunked::from_chunks(name, chunks).into_series()),
269 #[cfg(feature = "dtype-i16")]
270 ArrowDataType::Int16 => Ok(Int16Chunked::from_chunks(name, chunks).into_series()),
271 ArrowDataType::Int32 => Ok(Int32Chunked::from_chunks(name, chunks).into_series()),
272 ArrowDataType::Int64 => Ok(Int64Chunked::from_chunks(name, chunks).into_series()),
273 ArrowDataType::Int128 => feature_gated!(
274 "dtype-i128",
275 Ok(Int128Chunked::from_chunks(name, chunks).into_series())
276 ),
277 #[cfg(feature = "dtype-f16")]
278 ArrowDataType::Float16 => {
279 let chunks =
280 cast_chunks(&chunks, &DataType::Float16, CastOptions::NonStrict).unwrap();
281 Ok(Float16Chunked::from_chunks(name, chunks).into_series())
282 },
283 ArrowDataType::Float32 => Ok(Float32Chunked::from_chunks(name, chunks).into_series()),
284 ArrowDataType::Float64 => Ok(Float64Chunked::from_chunks(name, chunks).into_series()),
285 #[cfg(feature = "dtype-date")]
286 ArrowDataType::Date32 => {
287 let chunks =
288 cast_chunks(&chunks, &DataType::Int32, CastOptions::Overflowing).unwrap();
289 Ok(Int32Chunked::from_chunks(name, chunks)
290 .into_date()
291 .into_series())
292 },
293 #[cfg(feature = "dtype-datetime")]
294 ArrowDataType::Date64 => {
295 let chunks =
296 cast_chunks(&chunks, &DataType::Int64, CastOptions::Overflowing).unwrap();
297 let ca = Int64Chunked::from_chunks(name, chunks);
298 Ok(ca.into_datetime(TimeUnit::Milliseconds, None).into_series())
299 },
300 #[cfg(feature = "dtype-datetime")]
301 ArrowDataType::Timestamp(tu, tz) => {
302 let tz = TimeZone::opt_try_new(tz.clone())?;
303 let chunks =
304 cast_chunks(&chunks, &DataType::Int64, CastOptions::NonStrict).unwrap();
305 let s = Int64Chunked::from_chunks(name, chunks)
306 .into_datetime(tu.into(), tz)
307 .into_series();
308 Ok(match tu {
309 ArrowTimeUnit::Second => &s * MILLISECONDS,
310 ArrowTimeUnit::Millisecond => s,
311 ArrowTimeUnit::Microsecond => s,
312 ArrowTimeUnit::Nanosecond => s,
313 })
314 },
315 #[cfg(feature = "dtype-duration")]
316 ArrowDataType::Duration(tu) => {
317 let chunks =
318 cast_chunks(&chunks, &DataType::Int64, CastOptions::NonStrict).unwrap();
319 let s = Int64Chunked::from_chunks(name, chunks)
320 .into_duration(tu.into())
321 .into_series();
322 Ok(match tu {
323 ArrowTimeUnit::Second => &s * MILLISECONDS,
324 ArrowTimeUnit::Millisecond => s,
325 ArrowTimeUnit::Microsecond => s,
326 ArrowTimeUnit::Nanosecond => s,
327 })
328 },
329 #[cfg(feature = "dtype-time")]
330 ArrowDataType::Time64(tu) | ArrowDataType::Time32(tu) => {
331 let mut chunks = chunks;
332 if matches!(dtype, ArrowDataType::Time32(_)) {
333 chunks =
334 cast_chunks(&chunks, &DataType::Int32, CastOptions::NonStrict).unwrap();
335 }
336 let chunks =
337 cast_chunks(&chunks, &DataType::Int64, CastOptions::NonStrict).unwrap();
338 let s = Int64Chunked::from_chunks(name, chunks)
339 .into_time()
340 .into_series();
341 Ok(match tu {
342 ArrowTimeUnit::Second => &s * NANOSECONDS,
343 ArrowTimeUnit::Millisecond => &s * 1_000_000,
344 ArrowTimeUnit::Microsecond => &s * 1_000,
345 ArrowTimeUnit::Nanosecond => s,
346 })
347 },
348 ArrowDataType::Decimal32(precision, scale) => {
349 feature_gated!("dtype-decimal", {
350 polars_compute::decimal::dec128_verify_prec_scale(*precision, *scale)?;
351
352 let mut chunks = chunks;
353 for chunk in chunks.iter_mut() {
354 let old_chunk = chunk
355 .as_any_mut()
356 .downcast_mut::<PrimitiveArray<i32>>()
357 .unwrap();
358
359 let (_, values, validity) = std::mem::take(old_chunk).into_inner();
361 *chunk = PrimitiveArray::new(
362 ArrowDataType::Int128,
363 values.iter().map(|&v| v as i128).collect(),
364 validity,
365 )
366 .to_boxed();
367 }
368
369 let s = Int128Chunked::from_chunks(name, chunks)
370 .into_decimal_unchecked(*precision, *scale)
371 .into_series();
372 Ok(s)
373 })
374 },
375 ArrowDataType::Decimal64(precision, scale) => {
376 feature_gated!("dtype-decimal", {
377 polars_compute::decimal::dec128_verify_prec_scale(*precision, *scale)?;
378
379 let mut chunks = chunks;
380 for chunk in chunks.iter_mut() {
381 let old_chunk = chunk
382 .as_any_mut()
383 .downcast_mut::<PrimitiveArray<i64>>()
384 .unwrap();
385
386 let (_, values, validity) = std::mem::take(old_chunk).into_inner();
388 *chunk = PrimitiveArray::new(
389 ArrowDataType::Int128,
390 values.iter().map(|&v| v as i128).collect(),
391 validity,
392 )
393 .to_boxed();
394 }
395
396 let s = Int128Chunked::from_chunks(name, chunks)
397 .into_decimal_unchecked(*precision, *scale)
398 .into_series();
399 Ok(s)
400 })
401 },
402 ArrowDataType::Decimal(precision, scale) => {
403 feature_gated!("dtype-decimal", {
404 polars_compute::decimal::dec128_verify_prec_scale(*precision, *scale)?;
405
406 let mut chunks = chunks;
407 for chunk in chunks.iter_mut() {
408 *chunk = std::mem::take(
409 chunk
410 .as_any_mut()
411 .downcast_mut::<PrimitiveArray<i128>>()
412 .unwrap(),
413 )
414 .to(ArrowDataType::Int128)
415 .to_boxed();
416 }
417
418 let s = Int128Chunked::from_chunks(name, chunks)
419 .into_decimal_unchecked(*precision, *scale)
420 .into_series();
421 Ok(s)
422 })
423 },
424 ArrowDataType::Decimal256(precision, scale) => {
425 feature_gated!("dtype-decimal", {
426 use arrow::types::i256;
427
428 polars_compute::decimal::dec128_verify_prec_scale(*precision, *scale)?;
429
430 let mut chunks = chunks;
431 for chunk in chunks.iter_mut() {
432 let arr = std::mem::take(
433 chunk
434 .as_any_mut()
435 .downcast_mut::<PrimitiveArray<i256>>()
436 .unwrap(),
437 );
438 let arr_128: PrimitiveArray<i128> = arr.iter().map(|opt_v| {
439 if let Some(v) = opt_v {
440 let smaller: Option<i128> = (*v).try_into().ok();
441 let smaller = smaller.filter(|v| dec128_fits(*v, *precision));
442 smaller.ok_or_else(|| {
443 polars_err!(ComputeError: "Decimal256 to Decimal128 conversion overflowed, Decimal256 is not (yet) supported in Polars")
444 }).map(Some)
445 } else {
446 Ok(None)
447 }
448 }).try_collect_arr_trusted()?;
449
450 *chunk = arr_128.to(ArrowDataType::Int128).to_boxed();
451 }
452
453 let s = Int128Chunked::from_chunks(name, chunks)
454 .into_decimal_unchecked(*precision, *scale)
455 .into_series();
456 Ok(s)
457 })
458 },
459 ArrowDataType::Null => Ok(new_null(name, &chunks)),
460 #[cfg(not(feature = "dtype-categorical"))]
461 ArrowDataType::Dictionary(_, _, _) => {
462 panic!("activate dtype-categorical to convert dictionary arrays")
463 },
464 #[cfg(feature = "dtype-categorical")]
465 ArrowDataType::Dictionary(key_type, _, _) => {
466 let polars_dtype = DataType::from_arrow(chunks[0].dtype(), md);
467
468 let mut series_iter = chunks.into_iter().map(|arr| {
469 import_arrow_dictionary_array(name.clone(), arr, key_type, &polars_dtype)
470 });
471
472 let mut first = series_iter.next().unwrap()?;
473
474 for s in series_iter {
475 first.append_owned(s?)?;
476 }
477
478 Ok(first)
479 },
480 #[cfg(feature = "object")]
481 ArrowDataType::Extension(ext)
482 if ext.name == POLARS_OBJECT_EXTENSION_NAME && ext.metadata.is_some() =>
483 {
484 assert_eq!(chunks.len(), 1);
485 let arr = chunks[0]
486 .as_any()
487 .downcast_ref::<FixedSizeBinaryArray>()
488 .unwrap();
489 let s = {
494 let pe = PolarsExtension::new(arr.clone());
495 let s = pe.get_series(&name);
496 pe.take_and_forget();
497 s
498 };
499 Ok(s)
500 },
501 #[cfg(feature = "dtype-extension")]
502 ArrowDataType::Extension(ext) => {
503 use crate::datatypes::extension::get_extension_type_or_storage;
504
505 for chunk in &mut chunks {
506 debug_assert!(
507 chunk.dtype() == dtype,
508 "expected chunk dtype to be {:?}, got {:?}",
509 dtype,
510 chunk.dtype()
511 );
512 *chunk.dtype_mut() = ext.inner.clone();
513 }
514 let storage = Series::_try_from_arrow_unchecked_with_md(
515 name.clone(),
516 chunks,
517 &ext.inner,
518 md,
519 )?;
520
521 Ok(
522 match get_extension_type_or_storage(
523 &ext.name,
524 storage.dtype(),
525 ext.metadata.as_deref(),
526 ) {
527 Some(typ) => ExtensionChunked::from_storage(typ, storage).into_series(),
528 None => storage,
529 },
530 )
531 },
532
533 #[cfg(feature = "dtype-struct")]
534 ArrowDataType::Struct(_) => {
535 let (chunks, dtype) = to_physical_and_dtype(chunks, md)?;
536
537 unsafe {
538 let mut ca =
539 StructChunked::from_chunks_and_dtype_unchecked(name, chunks, dtype);
540 StructChunked::propagate_nulls_mut(&mut ca);
541 Ok(ca.into_series())
542 }
543 },
544 ArrowDataType::FixedSizeBinary(_) => {
545 let chunks = cast_chunks(&chunks, &DataType::Binary, CastOptions::NonStrict)?;
546 Ok(BinaryChunked::from_chunks(name, chunks).into_series())
547 },
548 ArrowDataType::Map(field, _keys_sorted) => {
549 let struct_arrays = chunks
550 .iter()
551 .map(|arr| {
552 let arr = arr.as_any().downcast_ref::<MapArray>().unwrap();
553 arr.field().clone()
554 })
555 .collect::<Vec<_>>();
556
557 let (phys_struct_arrays, entries_dtype) =
558 to_physical_and_dtype(struct_arrays, field.metadata.as_deref())?;
559
560 #[cfg(feature = "dtype-map")]
561 let map_dtype = entries_dtype.map_from_positional_entries_dtype();
562 #[cfg(not(feature = "dtype-map"))]
563 let map_dtype: Option<DataType> = None;
564
565 #[cfg(feature = "dtype-map")]
566 let phys_struct_arrays: Vec<ArrayRef> = if map_dtype.is_some() {
567 phys_struct_arrays
568 .into_iter()
569 .map(|mut entries| {
570 rename_map_entries(&mut entries);
571 entries
572 })
573 .collect()
574 } else {
575 phys_struct_arrays
576 };
577
578 let storage_dtype = match &map_dtype {
579 #[cfg(feature = "dtype-map")]
580 Some(dtype) => dtype.map_storage_dtype().unwrap(),
581 _ => DataType::List(Box::new(entries_dtype)),
582 };
583
584 let chunks = chunks
585 .iter()
586 .zip(phys_struct_arrays)
587 .map(|(arr, values)| {
588 let arr = arr.as_any().downcast_ref::<MapArray>().unwrap();
589 let offsets: &OffsetsBuffer<i32> = arr.offsets();
590
591 let validity = arr.validity().cloned();
592
593 Box::from(ListArray::<i64>::new(
594 ListArray::<i64>::default_datatype(values.dtype().clone()),
595 OffsetsBuffer::<i64>::from(offsets),
596 values,
597 validity,
598 )) as ArrayRef
599 })
600 .collect();
601
602 let storage = unsafe {
603 ListChunked::from_chunks_and_dtype_unchecked(name, chunks, storage_dtype)
604 }
605 .into_series();
606
607 match map_dtype {
608 #[cfg(feature = "dtype-map")]
609 Some(dtype) => {
610 use crate::chunked_array::logical::ensure_live_entries_non_null;
611
612 ensure_live_entries_non_null(storage.list().unwrap())?;
615 Ok(
618 unsafe { MapChunked::from_storage_unchecked(dtype, storage) }
619 .into_series(),
620 )
621 },
622 _ => Ok(storage),
623 }
624 },
625 ArrowDataType::Interval(IntervalUnit::MonthDayNano) => {
626 check_allow_importing_interval_as_struct("month_day_nano_interval")?;
627
628 feature_gated!("dtype-struct", {
629 let chunks = chunks
630 .into_iter()
631 .map(convert_month_day_nano_to_struct)
632 .collect::<PolarsResult<Vec<_>>>()?;
633
634 Ok(StructChunked::from_chunks_and_dtype_unchecked(
635 name,
636 chunks,
637 DataType::_month_days_ns_struct_type(),
638 )
639 .into_series())
640 })
641 },
642
643 dt => polars_bail!(ComputeError: "cannot create series from {:?}", dt),
644 }
645 }
646
647 #[cfg(feature = "dtype-categorical")]
648 pub fn from_cats_and_dtype(
649 cats: &Series,
650 dtype: &DataType,
651 strict: bool,
652 ) -> PolarsResult<Series> {
653 use std::borrow::Cow;
654
655 let phys = dtype.cat_physical()?;
656 let phys_dtype = DataType::from(phys);
657
658 let mut casted = Cow::Borrowed(cats);
659 if cats.dtype() != &phys_dtype {
660 casted = Cow::Owned(cats.cast(&phys_dtype)?);
661 }
662
663 let out = with_match_categorical_physical_type!(phys, |$C| {
664 type PhysCa = ChunkedArray<<$C as PolarsCategoricalType>::PolarsPhysical>;
666 let ca: &PhysCa = casted.as_ref().as_ref().as_ref();
667 CategoricalChunked::<$C>::from_cats_and_dtype(ca.clone(), dtype.clone()).into_series()
668 });
669
670 if strict && out.null_count() != casted.null_count() {
671 polars_bail!(
672 ComputeError:
673 "found invalid category value when converting from physical to {dtype}",
674 );
675 }
676
677 Ok(out)
678 }
679}
680
681fn convert<F: Fn(&dyn Array) -> ArrayRef>(arr: &[ArrayRef], f: F) -> Vec<ArrayRef> {
682 arr.iter().map(|arr| f(&**arr)).collect()
683}
684
685#[cfg(feature = "dtype-map")]
688fn rename_map_entries(entries: &mut ArrayRef) {
689 let ArrowDataType::Struct(fields) = entries.dtype_mut() else {
690 unreachable!("map entries are a struct")
691 };
692 let [key, value] = fields.as_mut_slice() else {
693 unreachable!("map entries have two fields")
694 };
695 key.name = MAP_KEY_NAME;
696 value.name = MAP_VALUE_NAME;
697}
698
699#[allow(clippy::only_used_in_recursion)]
703unsafe fn to_physical_and_dtype(
704 arrays: Vec<ArrayRef>,
705 md: Option<&Metadata>,
706) -> PolarsResult<(Vec<ArrayRef>, DataType)> {
707 match arrays[0].dtype() {
708 ArrowDataType::Utf8 | ArrowDataType::LargeUtf8 => {
709 let chunks = cast_chunks(&arrays, &DataType::String, CastOptions::NonStrict).unwrap();
710 Ok((chunks, DataType::String))
711 },
712 ArrowDataType::Binary | ArrowDataType::LargeBinary | ArrowDataType::FixedSizeBinary(_) => {
713 let chunks = cast_chunks(&arrays, &DataType::Binary, CastOptions::NonStrict).unwrap();
714 Ok((chunks, DataType::Binary))
715 },
716 #[allow(unused_variables)]
717 dt @ ArrowDataType::Dictionary(_, _, _) => {
718 feature_gated!("dtype-categorical", {
719 let s = unsafe {
720 let dt = dt.clone();
721 Series::_try_from_arrow_unchecked_with_md(PlSmallStr::EMPTY, arrays, &dt, md)
722 }?;
723 Ok((s.chunks().clone(), s.dtype().clone()))
724 })
725 },
726 dt @ ArrowDataType::Extension(_) => {
727 feature_gated!("dtype-extension", {
728 let s = unsafe {
729 let dt = dt.clone();
730 Series::_try_from_arrow_unchecked_with_md(PlSmallStr::EMPTY, arrays, &dt, md)
731 }?;
732 Ok((s.chunks().clone(), s.dtype().clone()))
733 })
734 },
735 ArrowDataType::List(field) => {
736 let out = convert(&arrays, |arr| {
737 cast(arr, &ArrowDataType::LargeList(field.clone())).unwrap()
738 });
739 to_physical_and_dtype(out, md)
740 },
741 #[cfg(feature = "dtype-array")]
742 ArrowDataType::FixedSizeList(field, size) => {
743 let values = arrays
744 .iter()
745 .map(|arr| {
746 let arr = arr.as_any().downcast_ref::<FixedSizeListArray>().unwrap();
747 arr.values().clone()
748 })
749 .collect::<Vec<_>>();
750
751 let (converted_values, dtype) =
752 to_physical_and_dtype(values, field.metadata.as_deref())?;
753
754 let arrays = arrays
755 .iter()
756 .zip(converted_values)
757 .map(|(arr, values)| {
758 let arr = arr.as_any().downcast_ref::<FixedSizeListArray>().unwrap();
759
760 let dtype = FixedSizeListArray::default_datatype(values.dtype().clone(), *size);
761 Box::from(FixedSizeListArray::new(
762 dtype,
763 arr.len(),
764 values,
765 arr.validity().cloned(),
766 )) as ArrayRef
767 })
768 .collect();
769 Ok((arrays, DataType::Array(Box::new(dtype), *size)))
770 },
771 ArrowDataType::LargeList(field) => {
772 let values = arrays
773 .iter()
774 .map(|arr| {
775 let arr = arr.as_any().downcast_ref::<ListArray<i64>>().unwrap();
776 arr.values().clone()
777 })
778 .collect::<Vec<_>>();
779
780 let (converted_values, dtype) =
781 to_physical_and_dtype(values, field.metadata.as_deref())?;
782
783 let arrays = arrays
784 .iter()
785 .zip(converted_values)
786 .map(|(arr, values)| {
787 let arr = arr.as_any().downcast_ref::<ListArray<i64>>().unwrap();
788
789 let dtype = ListArray::<i64>::default_datatype(values.dtype().clone());
790 Box::from(ListArray::<i64>::new(
791 dtype,
792 arr.offsets().clone(),
793 values,
794 arr.validity().cloned(),
795 )) as ArrayRef
796 })
797 .collect();
798 Ok((arrays, DataType::List(Box::new(dtype))))
799 },
800 ArrowDataType::Struct(_fields) => {
801 feature_gated!("dtype-struct", {
802 let mut pl_fields = None;
803 let mut out_arrays = Vec::with_capacity(arrays.len());
804 for arr in &arrays {
805 let arr = arr.as_any().downcast_ref::<StructArray>().unwrap();
806 let mut values = Vec::with_capacity(_fields.len());
807 let mut dtypes = Vec::with_capacity(_fields.len());
808 for (value, field) in arr.values().iter().zip(_fields.iter()) {
809 let (mut value, dtype) =
810 to_physical_and_dtype(vec![value.clone()], field.metadata.as_deref())?;
811 values.push(value.pop().unwrap());
812 dtypes.push(dtype);
813 }
814
815 let arrow_fields = values
816 .iter()
817 .zip(_fields.iter())
818 .map(|(arr, field)| {
819 ArrowField::new(field.name.clone(), arr.dtype().clone(), true)
820 })
821 .collect();
822 out_arrays.push(Box::new(StructArray::new(
823 ArrowDataType::Struct(arrow_fields),
824 arr.len(),
825 values,
826 arr.validity().cloned(),
827 )) as ArrayRef);
828
829 if pl_fields.is_none() {
830 pl_fields = Some(
831 _fields
832 .iter()
833 .zip(dtypes)
834 .map(|(field, dtype)| Field::new(field.name.clone(), dtype))
835 .collect_vec(),
836 )
837 }
838 }
839
840 Ok((out_arrays, DataType::Struct(pl_fields.unwrap())))
841 })
842 },
843 dt @ (ArrowDataType::Duration(_)
845 | ArrowDataType::Time32(_)
846 | ArrowDataType::Time64(_)
847 | ArrowDataType::Timestamp(_, _)
848 | ArrowDataType::Date32
849 | ArrowDataType::Decimal(_, _)
850 | ArrowDataType::Date64
851 | ArrowDataType::Map(_, _)) => {
852 let dt = dt.clone();
853 let mut s = Series::_try_from_arrow_unchecked(PlSmallStr::EMPTY, arrays, &dt)?;
854 let dtype = s.dtype().clone();
855 Ok((std::mem::take(s.chunks_mut()), dtype))
856 },
857 dt => {
858 let dtype = DataType::from_arrow(dt, md);
859 Ok((arrays, dtype))
860 },
861 }
862}
863
864#[cfg(feature = "dtype-categorical")]
865unsafe fn import_arrow_dictionary_array(
866 name: PlSmallStr,
867 arr: Box<dyn Array>,
868 key_type: &arrow::datatypes::IntegerType,
869 polars_dtype: &DataType,
870) -> PolarsResult<Series> {
871 use arrow::datatypes::IntegerType as I;
872
873 if matches!(
874 polars_dtype,
875 DataType::Categorical(_, _) | DataType::Enum(_, _)
876 ) {
877 macro_rules! unpack_categorical_chunked {
878 ($dt:ty) => {{
879 let arr = arr.as_any().downcast_ref::<DictionaryArray<$dt>>().unwrap();
880 let keys = arr.keys();
881 let values = arr.values();
882 let values = cast(&**values, &ArrowDataType::Utf8View)?;
883 let values = values.as_any().downcast_ref::<Utf8ViewArray>().unwrap();
884 with_match_categorical_physical_type!(polars_dtype.cat_physical().unwrap(), |$C| {
885 let ca = CategoricalChunked::<$C>::from_str_iter(
886 name,
887 polars_dtype.clone(),
888 keys.iter().map(|k| {
889 let k: usize = (*k?).try_into().ok()?;
890 values.get(k)
891 }),
892 )?;
893 Ok(ca.into_series())
894 })
895 }};
896 }
897
898 match key_type {
899 I::Int8 => unpack_categorical_chunked!(i8),
900 I::UInt8 => unpack_categorical_chunked!(u8),
901 I::Int16 => unpack_categorical_chunked!(i16),
902 I::UInt16 => unpack_categorical_chunked!(u16),
903 I::Int32 => unpack_categorical_chunked!(i32),
904 I::UInt32 => unpack_categorical_chunked!(u32),
905 I::Int64 => unpack_categorical_chunked!(i64),
906 I::UInt64 => unpack_categorical_chunked!(u64),
907 _ => polars_bail!(
908 ComputeError: "unsupported arrow key type: {key_type:?}"
909 ),
910 }
911 } else {
912 macro_rules! unpack_keys_values {
913 ($dt:ty) => {{
914 let arr = arr.as_any().downcast_ref::<DictionaryArray<$dt>>().unwrap();
915 let keys = arr.keys();
916 let keys = polars_compute::cast::primitive_to_primitive::<
917 $dt,
918 <IdxType as PolarsNumericType>::Native,
919 >(keys, &IDX_DTYPE.to_arrow(CompatLevel::newest()));
920 (keys, arr.values())
921 }};
922 }
923
924 let (keys, values) = match key_type {
925 I::Int8 => unpack_keys_values!(i8),
926 I::UInt8 => unpack_keys_values!(u8),
927 I::Int16 => unpack_keys_values!(i16),
928 I::UInt16 => unpack_keys_values!(u16),
929 I::Int32 => unpack_keys_values!(i32),
930 I::UInt32 => unpack_keys_values!(u32),
931 I::Int64 => unpack_keys_values!(i64),
932 I::UInt64 => unpack_keys_values!(u64),
933 _ => polars_bail!(
934 ComputeError: "unsupported arrow key type: {key_type:?}"
935 ),
936 };
937
938 let values = Series::_try_from_arrow_unchecked_with_md(
939 name,
940 vec![values.clone()],
941 values.dtype(),
942 None,
943 )?;
944
945 values.take(&IdxCa::from_chunks_and_dtype(
946 PlSmallStr::EMPTY,
947 vec![keys.to_boxed()],
948 IDX_DTYPE,
949 ))
950 }
951}
952
953#[cfg(feature = "dtype-struct")]
954fn convert_month_day_nano_to_struct(chunk: Box<dyn Array>) -> PolarsResult<Box<dyn Array>> {
955 let arr: &PrimitiveArray<months_days_ns> = chunk.as_any().downcast_ref().unwrap();
956
957 let values: &[months_days_ns] = arr.values();
958
959 let (months_out, days_out, nanoseconds_out): (Vec<i32>, Vec<i32>, Vec<i64>) = values
960 .iter()
961 .map(|x| (x.months(), x.days(), x.ns()))
962 .collect();
963
964 let out = StructArray::new(
965 DataType::_month_days_ns_struct_type()
966 .to_physical()
967 .to_arrow(CompatLevel::newest()),
968 arr.len(),
969 vec![
970 PrimitiveArray::<i32>::from_vec(months_out).boxed(),
971 PrimitiveArray::<i32>::from_vec(days_out).boxed(),
972 PrimitiveArray::<i64>::from_vec(nanoseconds_out).boxed(),
973 ],
974 arr.validity().cloned(),
975 );
976
977 Ok(out.boxed())
978}
979
980fn check_types(chunks: &[ArrayRef]) -> PolarsResult<ArrowDataType> {
981 let mut chunks_iter = chunks.iter();
982 let dtype: ArrowDataType = chunks_iter
983 .next()
984 .ok_or_else(|| polars_err!(NoData: "expected at least one array-ref"))?
985 .dtype()
986 .clone();
987
988 for chunk in chunks_iter {
989 if chunk.dtype() != &dtype {
990 polars_bail!(
991 ComputeError: "cannot create series from multiple arrays with different types"
992 );
993 }
994 }
995 Ok(dtype)
996}
997
998impl Series {
999 pub fn try_new<T>(
1000 name: PlSmallStr,
1001 data: T,
1002 ) -> Result<Self, <(PlSmallStr, T) as TryInto<Self>>::Error>
1003 where
1004 (PlSmallStr, T): TryInto<Self>,
1005 {
1006 <(PlSmallStr, T) as TryInto<Self>>::try_into((name, data))
1009 }
1010}
1011
1012impl TryFrom<(PlSmallStr, Vec<ArrayRef>)> for Series {
1013 type Error = PolarsError;
1014
1015 fn try_from(name_arr: (PlSmallStr, Vec<ArrayRef>)) -> PolarsResult<Self> {
1016 let (name, chunks) = name_arr;
1017
1018 let dtype = check_types(&chunks)?;
1019 unsafe { Series::_try_from_arrow_unchecked(name, chunks, &dtype) }
1022 }
1023}
1024
1025impl TryFrom<(PlSmallStr, ArrayRef)> for Series {
1026 type Error = PolarsError;
1027
1028 fn try_from(name_arr: (PlSmallStr, ArrayRef)) -> PolarsResult<Self> {
1029 let (name, arr) = name_arr;
1030 Series::try_from((name, vec![arr]))
1031 }
1032}
1033
1034impl TryFrom<(&ArrowField, Vec<ArrayRef>)> for Series {
1035 type Error = PolarsError;
1036
1037 fn try_from(field_arr: (&ArrowField, Vec<ArrayRef>)) -> PolarsResult<Self> {
1038 let (field, chunks) = field_arr;
1039 let arrow_dt = field.dtype();
1040 let dtype = check_types(&chunks)?;
1041 let compatible = match (&dtype, arrow_dt) {
1042 (
1044 ArrowDataType::Dictionary(int0, inner0, _ord0),
1045 ArrowDataType::Dictionary(int1, inner1, _ord1),
1046 ) => (int0, inner0) == (int1, inner1),
1047 (l, r) => l == r,
1048 };
1049 polars_ensure!(compatible, ComputeError: "Arrow Field dtype does not match the ArrayRef dtypes");
1050
1051 unsafe {
1054 Series::_try_from_arrow_unchecked_with_md(
1055 field.name.clone(),
1056 chunks,
1057 &dtype,
1058 field.metadata.as_deref(),
1059 )
1060 }
1061 }
1062}
1063
1064impl TryFrom<(&ArrowField, ArrayRef)> for Series {
1065 type Error = PolarsError;
1066
1067 fn try_from(field_arr: (&ArrowField, ArrayRef)) -> PolarsResult<Self> {
1068 let (field, arr) = field_arr;
1069 Series::try_from((field, vec![arr]))
1070 }
1071}
1072
1073pub unsafe trait IntoSeries {
1081 fn is_series() -> bool {
1082 false
1083 }
1084
1085 fn into_series(self) -> Series
1086 where
1087 Self: Sized;
1088}
1089
1090impl<T> From<ChunkedArray<T>> for Series
1091where
1092 T: PolarsDataType,
1093 ChunkedArray<T>: IntoSeries,
1094{
1095 fn from(ca: ChunkedArray<T>) -> Self {
1096 ca.into_series()
1097 }
1098}
1099
1100#[cfg(feature = "dtype-date")]
1101impl From<DateChunked> for Series {
1102 fn from(a: DateChunked) -> Self {
1103 a.into_series()
1104 }
1105}
1106
1107#[cfg(feature = "dtype-datetime")]
1108impl From<DatetimeChunked> for Series {
1109 fn from(a: DatetimeChunked) -> Self {
1110 a.into_series()
1111 }
1112}
1113
1114#[cfg(feature = "dtype-duration")]
1115impl From<DurationChunked> for Series {
1116 fn from(a: DurationChunked) -> Self {
1117 a.into_series()
1118 }
1119}
1120
1121#[cfg(feature = "dtype-time")]
1122impl From<TimeChunked> for Series {
1123 fn from(a: TimeChunked) -> Self {
1124 a.into_series()
1125 }
1126}
1127
1128unsafe impl IntoSeries for Arc<dyn SeriesTrait> {
1129 fn into_series(self) -> Series {
1130 Series(self)
1131 }
1132}
1133
1134unsafe impl IntoSeries for Series {
1135 fn is_series() -> bool {
1136 true
1137 }
1138
1139 #[inline]
1140 fn into_series(self) -> Series {
1141 self
1142 }
1143}
1144
1145fn new_null(name: PlSmallStr, chunks: &[ArrayRef]) -> Series {
1146 let len = chunks.iter().map(|arr| arr.len()).sum();
1147 Series::new_null(name, len)
1148}