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
147 .iter()
148 .skip(1)
149 .try_fold(acc, |acc, c| find_boundaries(c.as_ref()).map(|b| &acc | &b))?;
150
151 Ok(Partitions(Some(acc)))
152}
153
154fn find_boundaries(v: &dyn Array) -> Result<BooleanBuffer, ArrowError> {
156 let slice_len = v.len() - 1;
157 let v1 = v.slice(0, slice_len);
158 let v2 = v.slice(1, slice_len);
159
160 if supports_distinct(v.data_type()) {
161 return Ok(distinct(&v1, &v2)?.values().clone());
162 }
163 let cmp = make_comparator(&v1, &v2, SortOptions::default())?;
166 Ok((0..slice_len).map(|i| !cmp(i, i).is_eq()).collect())
167}
168
169#[cfg(test)]
170mod tests {
171 use std::sync::Arc;
172
173 use super::*;
174 use arrow_array::*;
175 use arrow_schema::DataType;
176
177 #[test]
178 fn test_partition_empty() {
179 let err = partition(&[]).unwrap_err();
180 assert_eq!(
181 err.to_string(),
182 "Invalid argument error: Partition requires at least one column"
183 );
184 }
185
186 #[test]
187 fn test_partition_unaligned_rows() {
188 let input = vec![
189 Arc::new(Int64Array::from(vec![None, Some(-1)])) as _,
190 Arc::new(StringArray::from(vec![Some("foo")])) as _,
191 ];
192 let err = partition(&input).unwrap_err();
193 assert_eq!(
194 err.to_string(),
195 "Invalid argument error: Partition columns have different row counts"
196 )
197 }
198
199 #[test]
200 fn test_partition_small() {
201 let results = partition(&[
202 Arc::new(Int32Array::new(vec![].into(), None)) as _,
203 Arc::new(Int32Array::new(vec![].into(), None)) as _,
204 Arc::new(Int32Array::new(vec![].into(), None)) as _,
205 ])
206 .unwrap();
207 assert_eq!(results.len(), 0);
208 assert!(results.is_empty());
209
210 let results = partition(&[
211 Arc::new(Int32Array::from(vec![1])) as _,
212 Arc::new(Int32Array::from(vec![1])) as _,
213 ])
214 .unwrap()
215 .ranges();
216 assert_eq!(results.len(), 1);
217 assert_eq!(results[0], 0..1);
218 }
219
220 #[test]
221 fn test_partition_single_column() {
222 let a = Int64Array::from(vec![1, 2, 2, 2, 2, 2, 2, 2, 9]);
223 let input = vec![Arc::new(a) as _];
224 assert_eq!(
225 partition(&input).unwrap().ranges(),
226 vec![(0..1), (1..8), (8..9)],
227 );
228 }
229
230 #[test]
231 fn test_partition_all_equal_values() {
232 let a = Int64Array::from_value(1, 1000);
233 let input = vec![Arc::new(a) as _];
234 assert_eq!(partition(&input).unwrap().ranges(), vec![(0..1000)]);
235 }
236
237 #[test]
238 fn test_partition_all_null_values() {
239 let input = vec![
240 new_null_array(&DataType::Int8, 1000),
241 new_null_array(&DataType::UInt16, 1000),
242 ];
243 assert_eq!(partition(&input).unwrap().ranges(), vec![(0..1000)]);
244 }
245
246 #[test]
247 fn test_partition_unique_column_1() {
248 let input = vec![
249 Arc::new(Int64Array::from(vec![None, Some(-1)])) as _,
250 Arc::new(StringArray::from(vec![Some("foo"), Some("bar")])) as _,
251 ];
252 assert_eq!(partition(&input).unwrap().ranges(), vec![(0..1), (1..2)],);
253 }
254
255 #[test]
256 fn test_partition_unique_column_2() {
257 let input = vec![
258 Arc::new(Int64Array::from(vec![None, Some(-1), Some(-1)])) as _,
259 Arc::new(StringArray::from(vec![
260 Some("foo"),
261 Some("bar"),
262 Some("apple"),
263 ])) as _,
264 ];
265 assert_eq!(
266 partition(&input).unwrap().ranges(),
267 vec![(0..1), (1..2), (2..3),],
268 );
269 }
270
271 #[test]
272 fn test_partition_non_unique_column_1() {
273 let input = vec![
274 Arc::new(Int64Array::from(vec![None, Some(-1), Some(-1), Some(1)])) as _,
275 Arc::new(StringArray::from(vec![
276 Some("foo"),
277 Some("bar"),
278 Some("bar"),
279 Some("bar"),
280 ])) as _,
281 ];
282 assert_eq!(
283 partition(&input).unwrap().ranges(),
284 vec![(0..1), (1..3), (3..4),],
285 );
286 }
287
288 #[test]
289 fn test_partition_masked_nulls() {
290 let input = vec![
291 Arc::new(Int64Array::new(vec![1; 9].into(), None)) as _,
292 Arc::new(Int64Array::new(
293 vec![1, 1, 2, 2, 2, 3, 3, 3, 3].into(),
294 Some(vec![false, true, true, true, true, false, false, true, false].into()),
295 )) as _,
296 Arc::new(Int64Array::new(
297 vec![1, 1, 2, 2, 2, 2, 2, 3, 7].into(),
298 Some(vec![true, true, true, true, false, true, true, true, false].into()),
299 )) as _,
300 ];
301
302 assert_eq!(
303 partition(&input).unwrap().ranges(),
304 vec![(0..1), (1..2), (2..4), (4..5), (5..7), (7..8), (8..9)],
305 );
306 }
307
308 #[test]
309 fn test_partition_run_end_encoded() {
310 let run_ends = Int32Array::from(vec![2, 3, 5]);
311 let values = StringArray::from(vec!["x", "y", "x"]);
312 let ree = RunArray::try_new(&run_ends, &values).unwrap();
313 let input = vec![Arc::new(ree) as _];
315 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5],);
316 }
317
318 #[test]
319 fn test_partition_nested_run_end_encoded() {
320 let inner_run_ends = Int32Array::from(vec![1, 2, 3]);
323 let inner_values = StringArray::from(vec!["x", "y", "x"]);
324 let inner_ree = RunArray::try_new(&inner_run_ends, &inner_values).unwrap();
325
326 let outer_run_ends = Int32Array::from(vec![2, 3, 5]);
330 let outer_ree = RunArray::try_new(&outer_run_ends, &inner_ree).unwrap();
331
332 let input = vec![Arc::new(outer_ree) as ArrayRef];
333 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5]);
334 }
335
336 #[test]
337 fn test_partition_ree_with_dictionary_values() {
338 let dict_values = StringArray::from(vec!["x", "y"]);
340 let keys = Int32Array::from(vec![0, 1, 0]);
341 let dict = DictionaryArray::try_new(keys, Arc::new(dict_values)).unwrap();
342
343 let run_ends = Int32Array::from(vec![2, 3, 5]);
346 let ree = RunArray::try_new(&run_ends, &dict).unwrap();
347 let input = vec![Arc::new(ree) as ArrayRef];
348 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5],);
349 }
350
351 #[test]
352 fn test_partition_dictionary() {
353 let values = StringArray::from(vec!["x", "y"]);
354 let keys = Int32Array::from(vec![0, 0, 1, 0, 0]);
355 let dict = DictionaryArray::try_new(keys, Arc::new(values)).unwrap();
356 let input = vec![Arc::new(dict) as _];
358 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5],);
359 }
360
361 #[test]
362 fn test_partition_nested_dictionary() {
363 let inner_values = StringArray::from(vec!["x", "y"]);
364 let inner_keys = Int32Array::from(vec![0, 1, 0]);
365 let inner_dict = DictionaryArray::try_new(inner_keys, Arc::new(inner_values)).unwrap();
366
367 let outer_keys = Int32Array::from(vec![0, 0, 1, 2, 2]);
370 let outer_dict = DictionaryArray::try_new(outer_keys, Arc::new(inner_dict)).unwrap();
371 let input = vec![Arc::new(outer_dict) as ArrayRef];
372 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5],);
373 }
374
375 #[test]
376 fn test_partition_dictionary_with_ree_values() {
377 let run_ends = Int32Array::from(vec![2, 3]);
379 let str_values = StringArray::from(vec!["x", "y"]);
380 let ree = RunArray::try_new(&run_ends, &str_values).unwrap();
381
382 let keys = Int32Array::from(vec![0, 0, 2, 0, 0]);
385 let dict = DictionaryArray::try_new(keys, Arc::new(ree)).unwrap();
386 let input = vec![Arc::new(dict) as ArrayRef];
387 assert_eq!(partition(&input).unwrap().ranges(), vec![0..2, 2..3, 3..5],);
388 }
389
390 #[test]
391 fn test_partition_nested() {
392 let input = vec![
393 Arc::new(
394 StructArray::try_from(vec![(
395 "f1",
396 Arc::new(Int64Array::from(vec![
397 None,
398 None,
399 Some(1),
400 Some(2),
401 Some(2),
402 Some(2),
403 Some(3),
404 Some(4),
405 ])) as _,
406 )])
407 .unwrap(),
408 ) as _,
409 Arc::new(Int64Array::from(vec![1, 1, 1, 2, 3, 3, 3, 4])) as _,
410 ];
411 assert_eq!(
412 partition(&input).unwrap().ranges(),
413 vec![0..2, 2..3, 3..4, 4..6, 6..7, 7..8]
414 )
415 }
416}