1use crate::prelude::*;
2
3impl Series {
4 pub fn try_from_physical(&self, dtype: &DataType) -> PolarsResult<Series> {
16 polars_ensure!(
19 !dtype.contains_objects(),
20 InvalidOperation:
21 "cannot restore `{dtype}` from its physical representation: objects are process-local"
22 );
23 polars_ensure!(
24 !dtype.contains_unknown(),
25 InvalidOperation: "cannot restore an unknown dtype from its physical representation"
26 );
27
28 let physical = dtype.to_physical();
29 polars_ensure!(
30 self.dtype() == &physical,
31 InvalidOperation:
32 "cannot restore `{dtype}` on a Series of type `{}`: its physical type is `{physical}`",
33 self.dtype()
34 );
35 unsafe { try_from_physical_rec(self, dtype) }
37 }
38}
39
40unsafe fn try_from_physical_rec(series: &Series, dtype: &DataType) -> PolarsResult<Series> {
43 use DataType as D;
44
45 if series.dtype() == dtype {
47 return Ok(series.clone());
48 }
49
50 match dtype {
52 #[cfg(feature = "dtype-map")]
53 D::Map(_, _) => {
54 let storage_dtype = dtype.map_storage_dtype().unwrap();
55 let storage = unsafe { try_from_physical_rec(series, &storage_dtype)? };
56 Ok(MapChunked::try_from_storage(dtype.clone(), storage)?.into_series())
57 },
58 D::List(inner) => {
59 let ca = series.list().unwrap();
60 let values = unsafe { try_from_physical_rec(&ca.get_inner(), inner)? };
61 Ok(ca.with_inner_values(&values).into_series())
62 },
63 #[cfg(feature = "dtype-array")]
64 D::Array(inner, _) => {
65 let ca = series.array().unwrap();
66 let values = unsafe { try_from_physical_rec(&ca.get_inner(), inner)? };
67 Ok(ca.with_inner_values(&values).into_series())
68 },
69 #[cfg(feature = "dtype-struct")]
70 D::Struct(fields) => {
71 let mut dtypes = fields.iter().map(|field| &field.dtype);
72 let ca = series.struct_().unwrap().try_apply_fields(|field| unsafe {
74 try_from_physical_rec(field, dtypes.next().unwrap())
75 })?;
76 Ok(ca.into_series())
77 },
78 #[cfg(feature = "dtype-extension")]
79 D::Extension(typ, storage_dtype) => {
80 polars_ensure!(
82 !storage_dtype.is_extension(),
83 InvalidOperation: "cannot restore `{dtype}`: extension types cannot be nested directly"
84 );
85 let storage = unsafe { try_from_physical_rec(series, storage_dtype)? };
86 Ok(storage.into_extension(typ.clone()))
87 },
88 #[cfg(feature = "dtype-categorical")]
89 D::Categorical(_, _) | D::Enum(_, _) => Series::from_cats_and_dtype(series, dtype, true),
90 #[cfg(feature = "dtype-decimal")]
91 D::Decimal(precision, scale) => {
92 Ok(series
94 .i128()?
95 .clone()
96 .into_decimal(*precision, *scale)?
97 .into_series())
98 },
99 D::Date | D::Datetime(_, _) | D::Duration(_) | D::Time => unsafe {
100 series.from_physical_unchecked(dtype)
101 },
102 _ => polars_bail!(
103 InvalidOperation: "cannot validate the physical representation of `{dtype}`"
104 ),
105 }
106}
107
108#[cfg(test)]
109mod test {
110 use polars_arrow::array::PrimitiveArray;
111
112 use crate::prelude::*;
113
114 #[test]
115 fn try_from_physical_rejects_objects_and_unknown() {
116 let s = Series::new(PlSmallStr::from_static("x"), &[1i64]);
119
120 #[cfg(feature = "object")]
121 {
122 let dtype = DataType::List(Box::new(DataType::Object("x")));
123 let err = s.try_from_physical(&dtype).unwrap_err();
124 assert!(
125 err.to_string().contains("objects are process-local"),
126 "{err}"
127 );
128 }
129
130 let err = s
131 .try_from_physical(&DataType::Unknown(UnknownKind::Any))
132 .unwrap_err();
133 assert!(err.to_string().contains("unknown dtype"), "{err}");
134 }
135
136 #[cfg(feature = "dtype-extension")]
137 #[test]
138 fn try_from_physical_rejects_directly_nested_extensions() {
139 use crate::datatypes::extension::get_extension_type_or_generic;
140
141 let inner = DataType::Extension(
142 get_extension_type_or_generic("inner", &DataType::Int64, None),
143 Box::new(DataType::Int64),
144 );
145 let dtype = DataType::Extension(
146 get_extension_type_or_generic("outer", &inner, None),
147 Box::new(inner),
148 );
149 let s = Series::new(PlSmallStr::from_static("e"), &[1i64, 2]);
150 assert_eq!(s.dtype(), &dtype.to_physical());
151 let err = s.try_from_physical(&dtype).err().unwrap();
152 assert!(err.to_string().contains("nested directly"), "{err}");
153 }
154
155 #[cfg(feature = "dtype-date")]
158 #[test]
159 fn from_chunk_and_dtype_builds_dates() {
160 let chunk = PrimitiveArray::<i32>::from_vec(vec![0, 1]).boxed();
161 let s = Series::from_chunk_and_dtype(PlSmallStr::from_static("d"), chunk, &DataType::Date)
162 .unwrap();
163 assert_eq!(s.dtype(), &DataType::Date);
164 assert_eq!(s.len(), 2);
165 }
166
167 #[cfg(feature = "dtype-categorical")]
168 #[test]
169 fn from_chunk_and_dtype_rejects_out_of_range_enum_codes() {
170 use polars_dtype::categorical::FrozenCategories;
171
172 let dtype = DataType::from_frozen_categories(FrozenCategories::new(["a", "b"]).unwrap());
173 let physical = dtype.to_physical();
174 let codes = |codes: &[u32]| {
175 Series::new(PlSmallStr::from_static("e"), codes)
176 .cast(&physical)
177 .unwrap()
178 .chunks()[0]
179 .clone()
180 };
181
182 let err =
183 Series::from_chunk_and_dtype(PlSmallStr::from_static("e"), codes(&[0, 7]), &dtype)
184 .unwrap_err();
185 assert!(err.to_string().contains("invalid category"), "{err}");
186
187 let s = Series::from_chunk_and_dtype(PlSmallStr::from_static("e"), codes(&[0, 1]), &dtype)
188 .unwrap();
189 assert_eq!(s.dtype(), &dtype);
190 assert_eq!(s.null_count(), 0);
191 }
192
193 #[cfg(feature = "object")]
194 #[test]
195 fn from_chunk_and_dtype_rejects_objects_before_reinterpreting() {
196 let chunk = PrimitiveArray::<i64>::from_vec(vec![1]).boxed();
197 for dtype in [
198 DataType::Object("x"),
199 DataType::List(Box::new(DataType::Object("x"))),
200 ] {
201 let err =
202 Series::from_chunk_and_dtype(PlSmallStr::from_static("o"), chunk.clone(), &dtype)
203 .unwrap_err();
204 assert!(err.to_string().contains("objects"), "{err}");
205 }
206 }
207
208 #[cfg(feature = "dtype-decimal")]
209 #[test]
210 fn from_chunk_and_dtype_validates_decimals() {
211 let name = PlSmallStr::from_static("d");
212 let empty = PrimitiveArray::<i128>::new_empty(ArrowDataType::Int128).boxed();
213
214 let err =
216 Series::from_chunk_and_dtype(name.clone(), empty.clone(), &DataType::Decimal(50, 2))
217 .unwrap_err();
218 assert!(err.to_string().contains("precision"), "{err}");
219 let err = Series::from_chunk_and_dtype(name.clone(), empty, &DataType::Decimal(5, 9))
220 .unwrap_err();
221 assert!(err.to_string().contains("scale"), "{err}");
222
223 let too_big = PrimitiveArray::<i128>::from_vec(vec![100_000]).boxed();
224 let err = Series::from_chunk_and_dtype(name.clone(), too_big, &DataType::Decimal(5, 0))
225 .unwrap_err();
226 assert!(err.to_string().contains("can't fit"), "{err}");
227
228 let fits = PrimitiveArray::<i128>::from_vec(vec![99_999]).boxed();
229 let s = Series::from_chunk_and_dtype(name, fits, &DataType::Decimal(5, 0)).unwrap();
230 assert_eq!(s.dtype(), &DataType::Decimal(5, 0));
231 }
232}