1use std::iter::once;
5use std::ops::Range;
6
7use vortex_array::dtype::FieldMask;
8use vortex_error::VortexExpect;
9use vortex_error::VortexResult;
10
11use crate::LayoutReader;
12use crate::RowSplits;
13use crate::SplitRange;
14use crate::scan::IDEAL_SPLIT_SIZE;
15
16const MAX_SPLIT_ROWS: u64 = IDEAL_SPLIT_SIZE;
21
22#[derive(Default, Copy, Clone, Debug)]
26pub enum SplitBy {
27 #[default]
28 Layout,
32 RowCount(usize),
34 }
36
37impl SplitBy {
38 pub fn splits(
41 &self,
42 layout_reader: &dyn LayoutReader,
43 row_range: &Range<u64>,
44 field_mask: &[FieldMask],
45 ) -> VortexResult<Vec<u64>> {
46 Ok(match *self {
47 SplitBy::Layout => {
48 let mut row_splits = RowSplits::new_capacity(128);
51 row_splits.push(row_range.start);
52 layout_reader.register_splits(
53 field_mask,
54 &SplitRange::root(row_range.clone())?,
55 &mut row_splits,
56 )?;
57 subdivide_large_spans(row_splits.into_sorted_deduped(), MAX_SPLIT_ROWS)
58 }
59 SplitBy::RowCount(n) => row_range
60 .clone()
61 .step_by(n)
62 .chain(once(row_range.end))
63 .collect(),
64 })
65 }
66}
67
68fn subdivide_large_spans(boundaries: Vec<u64>, max_span: u64) -> Vec<u64> {
80 debug_assert!(boundaries.is_sorted(), "boundaries must be sorted");
81 debug_assert!(max_span > 0, "max_span must be non-zero");
82
83 if boundaries.len() < 2 || boundaries.windows(2).all(|w| w[1] - w[0] <= max_span) {
85 return boundaries;
86 }
87
88 let mut out = Vec::with_capacity(boundaries.len() * 2);
89 for window in boundaries.windows(2) {
90 let lo = window[0];
91 let hi = window[1];
92 out.push(lo);
94
95 let span = hi - lo;
96 if span > max_span {
97 let sub_count = span.div_ceil(max_span);
100 let sub_size = span.div_ceil(sub_count);
103 let mut point = lo + sub_size;
104 while point < hi {
105 out.push(point);
106 point = point.saturating_add(sub_size);
108 }
109 }
110 }
111 out.push(*boundaries.last().vortex_expect("len >= 2 checked above"));
113
114 debug_assert!(out.is_sorted(), "subdivided boundaries must stay sorted");
115 debug_assert!(
116 out.windows(2).all(|w| w[0] < w[1]),
117 "subdivided boundaries must stay strictly increasing (deduped)"
118 );
119 out
120}
121
122#[cfg(test)]
123mod test {
124 use std::any::Any;
125 use std::sync::Arc;
126
127 use futures::future::BoxFuture;
128 use vortex_array::ArrayContext;
129 use vortex_array::ArrayRef;
130 use vortex_array::IntoArray;
131 use vortex_array::MaskFuture;
132 use vortex_array::dtype::DType;
133 use vortex_array::dtype::FieldPath;
134 use vortex_array::dtype::Nullability;
135 use vortex_array::dtype::PType;
136 use vortex_array::expr::Expression;
137 use vortex_buffer::buffer;
138 use vortex_io::runtime::single::block_on;
139 use vortex_io::session::RuntimeSessionExt;
140 use vortex_mask::Mask;
141
142 use super::*;
143 use crate::LayoutReaderRef;
144 use crate::LayoutStrategy;
145 use crate::RowSplits;
146 use crate::layouts::flat::writer::FlatLayoutStrategy;
147 use crate::scan::test::SCAN_SESSION;
148 use crate::segments::TestSegments;
149 use crate::sequence::SequenceId;
150 use crate::sequence::SequentialArrayStreamExt;
151
152 fn reader() -> LayoutReaderRef {
153 let ctx = ArrayContext::empty();
154 let segments = Arc::new(TestSegments::default());
155 let (ptr, eof) = SequenceId::root().split();
156 let layout = block_on(|handle| async {
157 let session = SCAN_SESSION.clone().with_handle(handle);
158 FlatLayoutStrategy::default()
159 .write_stream(
160 ctx,
161 Arc::<TestSegments>::clone(&segments),
162 buffer![1_i32; 10]
163 .into_array()
164 .to_array_stream()
165 .sequenced(ptr),
166 eof,
167 &session,
168 )
169 .await
170 })
171 .unwrap();
172
173 layout
174 .new_reader("".into(), segments, &SCAN_SESSION, &Default::default())
175 .unwrap()
176 }
177
178 #[test]
179 fn test_layout_splits_flat() {
180 let reader = reader();
181
182 let splits = SplitBy::Layout
183 .splits(
184 reader.as_ref(),
185 &(0..10),
186 &[FieldMask::Exact(FieldPath::root())],
187 )
188 .unwrap();
189 assert_eq!(splits, vec![0u64, 10]);
190 }
191
192 #[test]
193 fn test_row_count_splits() {
194 let reader = reader();
195
196 let splits = SplitBy::RowCount(3)
197 .splits(
198 reader.as_ref(),
199 &(0..10),
200 &[FieldMask::Exact(FieldPath::root())],
201 )
202 .unwrap();
203 assert_eq!(splits, vec![0u64, 3, 6, 9, 10]);
204 }
205
206 #[test]
207 fn test_layout_splits_dedup() {
208 struct DupReader {
209 name: Arc<str>,
210 dtype: DType,
211 }
212
213 impl LayoutReader for DupReader {
214 fn name(&self) -> &Arc<str> {
215 &self.name
216 }
217
218 fn as_any(&self) -> &dyn Any {
219 self
220 }
221
222 fn dtype(&self) -> &DType {
223 &self.dtype
224 }
225
226 fn row_count(&self) -> u64 {
227 10
228 }
229
230 fn register_splits(
231 &self,
232 _field_mask: &[FieldMask],
233 split_range: &SplitRange,
234 splits: &mut RowSplits,
235 ) -> VortexResult<()> {
236 splits.push(split_range.row_offset() + 5);
237 splits.push(split_range.row_offset() + 5);
238 splits.push(split_range.root_row_range().end);
239 Ok(())
240 }
241
242 fn pruning_evaluation(
243 &self,
244 _: &Range<u64>,
245 _: &Expression,
246 _: Mask,
247 ) -> VortexResult<MaskFuture> {
248 unimplemented!()
249 }
250
251 fn filter_evaluation(
252 &self,
253 _: &Range<u64>,
254 _: &Expression,
255 _: MaskFuture,
256 ) -> VortexResult<MaskFuture> {
257 unimplemented!()
258 }
259
260 fn projection_evaluation(
261 &self,
262 _: &Range<u64>,
263 _: &Expression,
264 _: MaskFuture,
265 ) -> VortexResult<BoxFuture<'static, VortexResult<ArrayRef>>> {
266 unimplemented!()
267 }
268 }
269
270 let reader = DupReader {
271 name: Arc::from("dup"),
272 dtype: DType::Primitive(PType::U8, Nullability::NonNullable),
273 };
274 let splits = SplitBy::Layout
275 .splits(&reader, &(0..10), &[FieldMask::All])
276 .unwrap();
277 assert_eq!(splits, vec![0u64, 5, 10]);
278 }
279
280 #[test]
281 fn subdivide_below_threshold_is_noop() {
282 assert_eq!(subdivide_large_spans(vec![0, 5, 10], 100), vec![0, 5, 10]);
284 assert_eq!(subdivide_large_spans(vec![0, 100], 100), vec![0, 100]);
285 assert_eq!(
286 subdivide_large_spans(Vec::<u64>::new(), 100),
287 Vec::<u64>::new()
288 );
289 assert_eq!(subdivide_large_spans(vec![7], 100), vec![7]);
290 }
291
292 #[test]
293 fn subdivide_near_u64_max_does_not_overflow() {
294 let hi = u64::MAX;
296 let out = subdivide_large_spans(vec![hi - 3, hi], 2);
297 assert_eq!(out, vec![hi - 3, hi - 1, hi]);
298 }
299
300 #[test]
301 fn subdivide_splits_large_single_chunk() {
302 let out = subdivide_large_spans(vec![0, 1000], 100);
304 assert_eq!(
305 out,
306 vec![0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]
307 );
308 }
309
310 #[test]
311 fn subdivide_only_large_gaps() {
312 let out = subdivide_large_spans(vec![0, 50, 350, 360], 100);
314 assert_eq!(out, vec![0, 50, 150, 250, 350, 360]);
315 }
316
317 #[test]
321 fn subdivide_preserves_exact_coverage() {
322 let cases: Vec<Vec<u64>> = vec![
323 vec![0, 1000],
324 vec![0, 7, 250_001],
325 vec![0, 5, 10, 15, 20, 25, 30],
326 vec![3, 1_000_003],
327 vec![0, 99_999, 100_000, 300_000],
328 ];
329 for boundaries in cases {
330 let out = subdivide_large_spans(boundaries.clone(), MAX_SPLIT_ROWS);
331 assert_eq!(out.first(), boundaries.first());
333 assert_eq!(out.last(), boundaries.last());
334 assert!(
336 out.windows(2).all(|w| w[0] < w[1]),
337 "not strictly increasing: {out:?}"
338 );
339 let total: u64 = out.windows(2).map(|w| w[1] - w[0]).sum();
343 let expected_total = boundaries.last().unwrap() - boundaries.first().unwrap();
344 assert_eq!(
345 total, expected_total,
346 "coverage span changed for {boundaries:?}"
347 );
348 for b in &boundaries {
349 assert!(
350 out.contains(b),
351 "original boundary {b} dropped from {out:?}"
352 );
353 }
354 }
355 }
356}