1use std::ops::Range;
21
22use arrow_array::{Array, ArrayRef};
23use arrow_buffer::BooleanBuffer;
24use arrow_schema::{ArrowError, SortOptions};
25
26use crate::cmp::{distinct, supports_distinct};
27use crate::ord::make_comparator;
28
29#[derive(Debug, Clone)]
31pub struct Partitions(Option<BooleanBuffer>);
32
33impl Partitions {
34 pub fn ranges(&self) -> Vec<Range<usize>> {
39 let Some(boundaries) = &self.0 else {
40 return vec![];
41 };
42
43 let mut out = vec![];
44 let mut current = 0;
45 for idx in boundaries.set_indices() {
46 let t = current;
47 current = idx + 1;
48 out.push(t..current)
49 }
50 let last = boundaries.len() + 1;
51 if current != last {
52 out.push(current..last)
53 }
54 out
55 }
56
57 pub fn len(&self) -> usize {
59 match &self.0 {
60 Some(b) => b.count_set_bits() + 1,
61 None => 0,
62 }
63 }
64
65 pub fn is_empty(&self) -> bool {
67 self.0.is_none()
68 }
69}
70
71pub fn partition(columns: &[ArrayRef]) -> Result<Partitions, ArrowError> {
127 if columns.is_empty() {
128 return Err(ArrowError::InvalidArgumentError(
129 "Partition requires at least one column".to_string(),
130 ));
131 }
132 let num_rows = columns[0].len();
133 if columns.iter().any(|item| item.len() != num_rows) {
134 return Err(ArrowError::InvalidArgumentError(
135 "Partition columns have different row counts".to_string(),
136 ));
137 }
138
139 match num_rows {
140 0 => return Ok(Partitions(None)),
141 1 => return Ok(Partitions(Some(BooleanBuffer::new_unset(0)))),
142 _ => {}
143 }
144
145 let acc = find_boundaries(&columns[0])?;
146 let acc = columns.iter().skip(1).try_fold(acc, |mut acc, c| {
147 find_boundaries(c.as_ref()).map(|b| {
148 acc |= &b;
150
151 acc
152 })
153 })?;
154
155 Ok(Partitions(Some(acc)))
156}
157
158fn find_boundaries(v: &dyn Array) -> Result<BooleanBuffer, ArrowError> {
160 let slice_len = v.len() - 1;
161 let v1 = v.slice(0, slice_len);
162 let v2 = v.slice(1, slice_len);
163
164 if supports_distinct(v.data_type()) {
165 return Ok(distinct(&v1, &v2)?.values().clone());
166 }
167 let cmp = make_comparator(&v1, &v2, SortOptions::default())?;
170 Ok((0..slice_len).map(|i| !cmp(i, i).is_eq()).collect())
171}
172
173#[cfg(test)]
174mod tests {
175 use std::sync::Arc;
176
177 use super::*;
178 use arrow_array::*;
179 use arrow_schema::DataType;
180
181 #[test]
182 fn test_partition_empty() {
183 let err = partition(&[]).unwrap_err();
184 assert_eq!(
185 err.to_string(),
186 "Invalid argument error: Partition requires at least one column"
187 );
188 }
189
190 #[test]
191 fn test_partition_unaligned_rows() {
192 let input = vec![
193 Arc::new(Int64Array::from(vec![None, Some(-1)])) as _,
194 Arc::new(StringArray::from(vec![Some("foo")])) as _,
195 ];
196 let err = partition(&input).unwrap_err();
197 assert_eq!(
198 err.to_string(),
199 "Invalid argument error: Partition columns have different row counts"
200 )
201 }
202
203 #[test]
204 fn test_partition_small() {
205 let results = partition(&[
206 Arc::new(Int32Array::new(vec![].into(), None)) as _,
207 Arc::new(Int32Array::new(vec![].into(), None)) as _,
208 Arc::new(Int32Array::new(vec![].into(), None)) as _,
209 ])
210 .unwrap();
211 assert_eq!(results.len(), 0);
212 assert!(results.is_empty());
213
214 let results = partition(&[
215 Arc::new(Int32Array::from(vec![1])) as _,
216 Arc::new(Int32Array::from(vec![1])) as _,
217 ])
218 .unwrap()
219 .ranges();
220 assert_eq!(results.len(), 1);
221 assert_eq!(results[0], 0..1);
222 }
223
224 #[test]
225 fn test_partition_single_column() {
226 let a = Int64Array::from(vec![1, 2, 2, 2, 2, 2, 2, 2, 9]);
227 let input = vec![Arc::new(a) as _];
228 assert_eq!(
229 partition(&input).unwrap().ranges(),
230 vec![(0..1), (1..8), (8..9)],
231 );
232 }
233
234 #[test]
235 fn test_partition_all_equal_values() {
236 let a = Int64Array::from_value(1, 1000);
237 let input = vec![Arc::new(a) as _];
238 assert_eq!(partition(&input).unwrap().ranges(), vec![(0..1000)]);
239 }
240
241 #[test]
242 fn test_partition_all_null_values() {
243 let input = vec![
244 new_null_array(&DataType::Int8, 1000),
245 new_null_array(&DataType::UInt16, 1000),
246 ];
247 assert_eq!(partition(&input).unwrap().ranges(), vec![(0..1000)]);
248 }
249
250 #[test]
251 fn test_partition_unique_column_1() {
252 let input = vec![
253 Arc::new(Int64Array::from(vec![None, Some(-1)])) as _,
254 Arc::new(StringArray::from(vec![Some("foo"), Some("bar")])) as _,
255 ];
256 assert_eq!(partition(&input).unwrap().ranges(), vec![(0..1), (1..2)],);
257 }
258
259 #[test]
260 fn test_partition_unique_column_2() {
261 let input = vec![
262 Arc::new(Int64Array::from(vec![None, Some(-1), Some(-1)])) as _,
263 Arc::new(StringArray::from(vec![
264 Some("foo"),
265 Some("bar"),
266 Some("apple"),
267 ])) as _,
268 ];
269 assert_eq!(
270 partition(&input).unwrap().ranges(),
271 vec![(0..1), (1..2), (2..3),],
272 );
273 }
274
275 #[test]
276 fn test_partition_non_unique_column_1() {
277 let input = vec![
278 Arc::new(Int64Array::from(vec![None, Some(-1), Some(-1), Some(1)])) as _,
279 Arc::new(StringArray::from(vec![
280 Some("foo"),
281 Some("bar"),
282 Some("bar"),
283 Some("bar"),
284 ])) as _,
285 ];
286 assert_eq!(
287 partition(&input).unwrap().ranges(),
288 vec![(0..1), (1..3), (3..4),],
289 );
290 }
291
292 #[test]
293 fn test_partition_masked_nulls() {
294 let input = vec![
295 Arc::new(Int64Array::new(vec![1; 9].into(), None)) as _,
296 Arc::new(Int64Array::new(
297 vec![1, 1, 2, 2, 2, 3, 3, 3, 3].into(),
298 Some(vec![false, true, true, true, true, false, false, true, false].into()),
299 )) as _,
300 Arc::new(Int64Array::new(
301 vec![1, 1, 2, 2, 2, 2, 2, 3, 7].into(),
302 Some(vec![true, true, true, true, false, true, true, true, false].into()),
303 )) as _,
304 ];
305
306 assert_eq!(
307 partition(&input).unwrap().ranges(),
308 vec![(0..1), (1..2), (2..4), (4..5), (5..7), (7..8), (8..9)],
309 );
310 }
311
312 #[test]
313 fn test_partition_run_end_encoded() {
314 let run_ends = Int32Array::from(vec![2, 3, 5]);
315 let values = StringArray::from(vec!["x", "y", "x"]);
316 let ree = RunArray::try_new(&run_ends, &values).unwrap();
317 let input = vec![Arc::new(ree) as _];
319 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5],);
320 }
321
322 #[test]
323 fn test_partition_nested_run_end_encoded() {
324 let inner_run_ends = Int32Array::from(vec![1, 2, 3]);
327 let inner_values = StringArray::from(vec!["x", "y", "x"]);
328 let inner_ree = RunArray::try_new(&inner_run_ends, &inner_values).unwrap();
329
330 let outer_run_ends = Int32Array::from(vec![2, 3, 5]);
334 let outer_ree = RunArray::try_new(&outer_run_ends, &inner_ree).unwrap();
335
336 let input = vec![Arc::new(outer_ree) as ArrayRef];
337 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5]);
338 }
339
340 #[test]
341 fn test_partition_ree_with_dictionary_values() {
342 let dict_values = StringArray::from(vec!["x", "y"]);
344 let keys = Int32Array::from(vec![0, 1, 0]);
345 let dict = DictionaryArray::try_new(keys, Arc::new(dict_values)).unwrap();
346
347 let run_ends = Int32Array::from(vec![2, 3, 5]);
350 let ree = RunArray::try_new(&run_ends, &dict).unwrap();
351 let input = vec![Arc::new(ree) as ArrayRef];
352 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5],);
353 }
354
355 #[test]
356 fn test_partition_dictionary() {
357 let values = StringArray::from(vec!["x", "y"]);
358 let keys = Int32Array::from(vec![0, 0, 1, 0, 0]);
359 let dict = DictionaryArray::try_new(keys, Arc::new(values)).unwrap();
360 let input = vec![Arc::new(dict) as _];
362 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5],);
363 }
364
365 #[test]
366 fn test_partition_nested_dictionary() {
367 let inner_values = StringArray::from(vec!["x", "y"]);
368 let inner_keys = Int32Array::from(vec![0, 1, 0]);
369 let inner_dict = DictionaryArray::try_new(inner_keys, Arc::new(inner_values)).unwrap();
370
371 let outer_keys = Int32Array::from(vec![0, 0, 1, 2, 2]);
374 let outer_dict = DictionaryArray::try_new(outer_keys, Arc::new(inner_dict)).unwrap();
375 let input = vec![Arc::new(outer_dict) as ArrayRef];
376 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5],);
377 }
378
379 #[test]
380 fn test_partition_dictionary_with_ree_values() {
381 let run_ends = Int32Array::from(vec![2, 3]);
383 let str_values = StringArray::from(vec!["x", "y"]);
384 let ree = RunArray::try_new(&run_ends, &str_values).unwrap();
385
386 let keys = Int32Array::from(vec![0, 0, 2, 0, 0]);
389 let dict = DictionaryArray::try_new(keys, Arc::new(ree)).unwrap();
390 let input = vec![Arc::new(dict) as ArrayRef];
391 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5],);
392 }
393
394 #[test]
395 fn test_partition_nested() {
396 let input = vec![
397 Arc::new(
398 StructArray::try_from(vec![(
399 "f1",
400 Arc::new(Int64Array::from(vec![
401 None,
402 None,
403 Some(1),
404 Some(2),
405 Some(2),
406 Some(2),
407 Some(3),
408 Some(4),
409 ])) as _,
410 )])
411 .unwrap(),
412 ) as _,
413 Arc::new(Int64Array::from(vec![1, 1, 1, 2, 3, 3, 3, 4])) as _,
414 ];
415 assert_eq!(
416 partition(&input).unwrap().ranges(),
417 vec![0..2, 2..3, 3..4, 4..6, 6..7, 7..8]
418 )
419 }
420}