polars_core/chunked_array/ops/
nesting_utils.rs1use polars_arrow::array::{Array, IntoBoxedArray};
2use polars_compute::find_validity_mismatch::find_validity_mismatch;
3use polars_utils::IdxSize;
4
5use super::ListChunked;
6use crate::chunked_array::flags::StatisticsFlags;
7use crate::prelude::{ChunkedArray, FalseT, PolarsDataType};
8use crate::series::Series;
9use crate::series::implementations::null::NullChunked;
10use crate::utils::align_chunks_binary_ca_series;
11
12pub trait ChunkNestingUtils: Sized {
14 fn propagate_nulls(&self) -> Option<Self>;
16
17 fn trim_lists_to_normalized_offsets(&self) -> Option<Self>;
19
20 fn find_validity_mismatch(&self, other: &Series, idxs: &mut Vec<IdxSize>);
24}
25
26impl ChunkNestingUtils for ListChunked {
27 fn propagate_nulls(&self) -> Option<Self> {
28 use polars_compute::propagate_nulls::propagate_nulls_list;
29
30 let flags = self.get_flags();
31
32 if flags.has_propagated_nulls() {
33 return None;
34 }
35
36 if !self.inner_dtype().is_nested() && !self.has_nulls() {
37 self.flags
38 .set(flags | StatisticsFlags::HAS_PROPAGATED_NULLS);
39 return None;
40 }
41
42 let mut chunks = Vec::new();
43 for (i, chunk) in self.downcast_iter().enumerate() {
44 if let Some(propagated_chunk) = propagate_nulls_list(chunk) {
45 chunks.reserve(self.chunks.len());
46 chunks.extend(self.chunks[..i].iter().cloned());
47 chunks.push(propagated_chunk.into_boxed());
48 break;
49 }
50 }
51
52 let out = if chunks.is_empty() {
54 None
55 } else {
56 chunks.extend(self.downcast_iter().skip(chunks.len()).map(|chunk| {
57 match propagate_nulls_list(chunk) {
58 None => chunk.to_boxed(),
59 Some(chunk) => chunk.into_boxed(),
60 }
61 }));
62
63 Some(unsafe {
65 Self::new_with_dims(self.field.clone(), chunks, self.length, self.null_count)
66 })
67 };
68
69 finish_propagate_nulls(out, self, flags)
70 }
71
72 fn trim_lists_to_normalized_offsets(&self) -> Option<Self> {
73 use polars_compute::trim_lists_to_normalized_offsets::trim_lists_to_normalized_offsets_list;
74
75 let flags = self.get_flags();
76
77 if flags.has_trimmed_lists_to_normalized_offsets() {
78 return None;
79 }
80
81 let mut chunks = Vec::new();
82 for (i, chunk) in self.downcast_iter().enumerate() {
83 if let Some(trimmed) = trim_lists_to_normalized_offsets_list(chunk) {
84 chunks.reserve(self.chunks.len());
85 chunks.extend(self.chunks[..i].iter().cloned());
86 chunks.push(trimmed.into_boxed());
87 break;
88 }
89 }
90
91 if !chunks.is_empty() {
93 chunks.extend(self.downcast_iter().skip(chunks.len()).map(|chunk| {
94 match trim_lists_to_normalized_offsets_list(chunk) {
95 Some(chunk) => chunk.into_boxed(),
96 None => chunk.to_boxed(),
97 }
98 }));
99
100 let mut ca = unsafe {
102 Self::new_with_dims(self.field.clone(), chunks, self.length, self.null_count)
103 };
104
105 ca.set_flags(flags | StatisticsFlags::HAS_TRIMMED_LISTS_TO_NORMALIZED_OFFSETS);
106 return Some(ca);
107 }
108
109 self.flags
110 .set(flags | StatisticsFlags::HAS_TRIMMED_LISTS_TO_NORMALIZED_OFFSETS);
111 None
112 }
113
114 fn find_validity_mismatch(&self, other: &Series, idxs: &mut Vec<IdxSize>) {
115 let (slf, other) = align_chunks_binary_ca_series(self, other);
116 let mut offset: IdxSize = 0;
117 for (l, r) in slf.downcast_iter().zip(other.chunks()) {
118 let start_length = idxs.len();
119 find_validity_mismatch(l, r.as_ref(), idxs);
120 for idx in idxs[start_length..].iter_mut() {
121 *idx += offset;
122 }
123 offset += l.len() as IdxSize;
124 }
125 }
126}
127
128#[cfg(feature = "dtype-array")]
129impl ChunkNestingUtils for super::ArrayChunked {
130 fn propagate_nulls(&self) -> Option<Self> {
131 use polars_compute::propagate_nulls::propagate_nulls_fsl;
132
133 let flags = self.get_flags();
134
135 if flags.has_propagated_nulls() {
136 return None;
137 }
138
139 if !self.inner_dtype().is_nested() && !self.has_nulls() {
140 self.flags
141 .set(flags | StatisticsFlags::HAS_PROPAGATED_NULLS);
142 return None;
143 }
144
145 let mut chunks = Vec::new();
146 for (i, chunk) in self.downcast_iter().enumerate() {
147 if let Some(propagated_chunk) = propagate_nulls_fsl(chunk) {
148 chunks.reserve(self.chunks.len());
149 chunks.extend(self.chunks[..i].iter().cloned());
150 chunks.push(propagated_chunk.into_boxed());
151 break;
152 }
153 }
154
155 let out = if chunks.is_empty() {
156 None
157 } else {
158 chunks.extend(self.downcast_iter().skip(chunks.len()).map(|chunk| {
159 match propagate_nulls_fsl(chunk) {
160 None => chunk.to_boxed(),
161 Some(chunk) => chunk.into_boxed(),
162 }
163 }));
164
165 Some(unsafe {
167 Self::new_with_dims(self.field.clone(), chunks, self.length, self.null_count)
168 })
169 };
170
171 finish_propagate_nulls(out, self, flags)
172 }
173
174 fn trim_lists_to_normalized_offsets(&self) -> Option<Self> {
175 use polars_compute::trim_lists_to_normalized_offsets::trim_lists_to_normalized_offsets_fsl;
176
177 let flags = self.get_flags();
178
179 if flags.has_trimmed_lists_to_normalized_offsets()
180 || !self.inner_dtype().contains_list_recursive()
181 {
182 return None;
183 }
184
185 let mut chunks = Vec::new();
186 for (i, chunk) in self.downcast_iter().enumerate() {
187 if let Some(trimmed) = trim_lists_to_normalized_offsets_fsl(chunk) {
188 chunks.reserve(self.chunks.len());
189 chunks.extend(self.chunks[..i].iter().cloned());
190 chunks.push(trimmed.into_boxed());
191 break;
192 }
193 }
194
195 if !chunks.is_empty() {
197 chunks.extend(self.downcast_iter().skip(chunks.len()).map(|chunk| {
198 match trim_lists_to_normalized_offsets_fsl(chunk) {
199 Some(chunk) => chunk.into_boxed(),
200 None => chunk.to_boxed(),
201 }
202 }));
203
204 let mut ca = unsafe {
206 Self::new_with_dims(self.field.clone(), chunks, self.length, self.null_count)
207 };
208 ca.set_flags(flags | StatisticsFlags::HAS_TRIMMED_LISTS_TO_NORMALIZED_OFFSETS);
209 return Some(ca);
210 }
211
212 self.flags
213 .set(flags | StatisticsFlags::HAS_TRIMMED_LISTS_TO_NORMALIZED_OFFSETS);
214 None
215 }
216
217 fn find_validity_mismatch(&self, other: &Series, idxs: &mut Vec<IdxSize>) {
218 let (slf, other) = align_chunks_binary_ca_series(self, other);
219 let mut offset: IdxSize = 0;
220 for (l, r) in slf.downcast_iter().zip(other.chunks()) {
221 let start_length = idxs.len();
222 find_validity_mismatch(l, r.as_ref(), idxs);
223 for idx in idxs[start_length..].iter_mut() {
224 *idx += offset;
225 }
226 offset += l.len() as IdxSize;
227 }
228 }
229}
230
231#[cfg(feature = "dtype-struct")]
232impl ChunkNestingUtils for super::StructChunked {
233 fn propagate_nulls(&self) -> Option<Self> {
234 use polars_compute::propagate_nulls::propagate_nulls_struct;
235
236 let flags = self.get_flags();
237
238 if flags.has_propagated_nulls() {
239 return None;
240 }
241
242 if self.struct_fields().iter().all(|f| !f.dtype().is_nested()) && !self.has_nulls() {
243 self.flags
244 .set(flags | StatisticsFlags::HAS_PROPAGATED_NULLS);
245 return None;
246 }
247
248 let mut chunks = Vec::new();
249 for (i, chunk) in self.downcast_iter().enumerate() {
250 if let Some(propagated_chunk) = propagate_nulls_struct(chunk) {
251 chunks.reserve(self.chunks.len());
252 chunks.extend(self.chunks[..i].iter().cloned());
253 chunks.push(propagated_chunk.into_boxed());
254 break;
255 }
256 }
257
258 let out = if chunks.is_empty() {
259 None
260 } else {
261 chunks.extend(self.downcast_iter().skip(chunks.len()).map(|chunk| {
262 match propagate_nulls_struct(chunk) {
263 None => chunk.to_boxed(),
264 Some(chunk) => chunk.into_boxed(),
265 }
266 }));
267
268 Some(unsafe {
270 Self::new_with_dims(self.field.clone(), chunks, self.length, self.null_count)
271 })
272 };
273
274 finish_propagate_nulls(out, self, flags)
275 }
276
277 fn trim_lists_to_normalized_offsets(&self) -> Option<Self> {
278 use polars_compute::trim_lists_to_normalized_offsets::trim_lists_to_normalized_offsets_struct;
279
280 let flags = self.get_flags();
281
282 if flags.has_trimmed_lists_to_normalized_offsets()
283 || !self
284 .struct_fields()
285 .iter()
286 .any(|f| f.dtype().contains_list_recursive())
287 {
288 return None;
289 }
290
291 let mut chunks = Vec::new();
292 for (i, chunk) in self.downcast_iter().enumerate() {
293 if let Some(trimmed) = trim_lists_to_normalized_offsets_struct(chunk) {
294 chunks.reserve(self.chunks.len());
295 chunks.extend(self.chunks[..i].iter().cloned());
296 chunks.push(trimmed.into_boxed());
297 break;
298 }
299 }
300
301 if !chunks.is_empty() {
303 chunks.extend(self.downcast_iter().skip(chunks.len()).map(|chunk| {
304 match trim_lists_to_normalized_offsets_struct(chunk) {
305 Some(chunk) => chunk.into_boxed(),
306 None => chunk.to_boxed(),
307 }
308 }));
309
310 let mut ca = unsafe {
312 Self::new_with_dims(self.field.clone(), chunks, self.length, self.null_count)
313 };
314 ca.set_flags(flags | StatisticsFlags::HAS_TRIMMED_LISTS_TO_NORMALIZED_OFFSETS);
315 return Some(ca);
316 }
317
318 self.flags
319 .set(flags | StatisticsFlags::HAS_TRIMMED_LISTS_TO_NORMALIZED_OFFSETS);
320 None
321 }
322
323 fn find_validity_mismatch(&self, other: &Series, idxs: &mut Vec<IdxSize>) {
324 let (slf, other) = align_chunks_binary_ca_series(self, other);
325 let mut offset: IdxSize = 0;
326 for (l, r) in slf.downcast_iter().zip(other.chunks()) {
327 let start_length = idxs.len();
328 find_validity_mismatch(l, r.as_ref(), idxs);
329 for idx in idxs[start_length..].iter_mut() {
330 *idx += offset;
331 }
332 offset += l.len() as IdxSize;
333 }
334 }
335}
336
337fn finish_propagate_nulls<T: PolarsDataType>(
339 out: Option<ChunkedArray<T>>,
340 orig: &ChunkedArray<T>,
341 flags: StatisticsFlags,
342) -> Option<ChunkedArray<T>> {
343 match out {
344 Some(mut ca) => {
345 ca.set_flags(flags | StatisticsFlags::HAS_PROPAGATED_NULLS);
346 Some(ca)
347 },
348 None => {
349 orig.flags
350 .set(flags | StatisticsFlags::HAS_PROPAGATED_NULLS);
351 None
352 },
353 }
354}
355
356impl<T: PolarsDataType<IsNested = FalseT>> ChunkNestingUtils for ChunkedArray<T> {
357 fn propagate_nulls(&self) -> Option<Self> {
358 None
359 }
360
361 fn trim_lists_to_normalized_offsets(&self) -> Option<Self> {
362 None
363 }
364
365 fn find_validity_mismatch(&self, other: &Series, idxs: &mut Vec<IdxSize>) {
366 let slf_nc = self.null_count();
367 let other_nc = other.null_count();
368
369 if slf_nc == other_nc && (slf_nc == 0 || slf_nc == self.len()) {
371 return;
372 }
373
374 let (slf, other) = align_chunks_binary_ca_series(self, other);
375 let mut offset: IdxSize = 0;
376 for (l, r) in slf.downcast_iter().zip(other.chunks()) {
377 let start_length = idxs.len();
378 find_validity_mismatch(l, r.as_ref(), idxs);
379 for idx in idxs[start_length..].iter_mut() {
380 *idx += offset;
381 }
382 offset += l.len() as IdxSize;
383 }
384 }
385}
386
387impl ChunkNestingUtils for NullChunked {
388 fn propagate_nulls(&self) -> Option<Self> {
389 None
390 }
391
392 fn trim_lists_to_normalized_offsets(&self) -> Option<Self> {
393 None
394 }
395
396 fn find_validity_mismatch(&self, other: &Series, idxs: &mut Vec<IdxSize>) {
397 let other_nc = other.null_count();
398
399 if other_nc == self.len() {
401 return;
402 }
403
404 match other.rechunk_validity() {
405 None => idxs.extend(0..self.len() as IdxSize),
406 Some(v) => idxs.extend(v.true_idx_iter().map(|v| v as IdxSize)),
407 }
408 }
409}