vortex_array/arrays/chunked/
array.rs1use std::fmt::Debug;
9use std::fmt::Display;
10use std::fmt::Formatter;
11
12use futures::stream;
13use vortex_buffer::BufferMut;
14use vortex_error::VortexExpect;
15use vortex_error::VortexResult;
16use vortex_error::vortex_bail;
17
18use crate::ArrayRef;
19use crate::ArraySlots;
20use crate::Canonical;
21use crate::ExecutionCtx;
22use crate::IntoArray;
23use crate::array::Array;
24use crate::array::ArrayParts;
25use crate::array::TypedArrayRef;
26use crate::array_slots;
27use crate::arrays::Chunked;
28use crate::arrays::PrimitiveArray;
29use crate::dtype::DType;
30use crate::iter::ArrayIterator;
31use crate::iter::ArrayIteratorAdapter;
32use crate::search_sorted::SearchSorted;
33use crate::search_sorted::SearchSortedSide;
34use crate::stream::ArrayStream;
35use crate::stream::ArrayStreamAdapter;
36use crate::validity::Validity;
37
38#[array_slots(Chunked)]
40pub struct ChunkedSlots {
41 #[slot(0)]
43 pub chunk_offsets: ArrayRef,
44 #[slot(1..)]
46 pub chunks: Vec<ArrayRef>,
47}
48
49#[derive(Clone, Debug)]
50pub struct ChunkedData {
51 pub(super) chunk_offsets: Vec<usize>,
52 pub(super) next_builder_slot: usize,
54}
55
56impl Display for ChunkedData {
57 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
58 write!(f, "nchunks: {}", self.chunk_offsets.len().saturating_sub(1))
59 }
60}
61
62pub trait ChunkedArrayExt: TypedArrayRef<Chunked> {
63 fn chunk_offsets_array(&self) -> &ArrayRef {
64 self.as_ref().slots()[ChunkedSlots::CHUNK_OFFSETS]
65 .as_ref()
66 .vortex_expect("validated chunk offsets slot")
67 }
68
69 fn nchunks(&self) -> usize {
70 self.as_ref()
71 .slots()
72 .len()
73 .saturating_sub(ChunkedSlots::CHUNKS_OFFSET)
74 }
75
76 fn chunk(&self, idx: usize) -> &ArrayRef {
77 self.as_ref().slots()[ChunkedSlots::CHUNKS_OFFSET + idx]
78 .as_ref()
79 .vortex_expect("validated chunk slot")
80 }
81
82 fn iter_chunks<'a>(&'a self) -> Box<dyn Iterator<Item = &'a ArrayRef> + 'a> {
83 Box::new(
84 self.as_ref().slots()[ChunkedSlots::CHUNKS_OFFSET..]
85 .iter()
86 .map(|slot| slot.as_ref().vortex_expect("validated chunk slot")),
87 )
88 }
89
90 fn chunks(&self) -> Vec<ArrayRef> {
91 self.iter_chunks().cloned().collect()
92 }
93
94 fn non_empty_chunks<'a>(&'a self) -> Box<dyn Iterator<Item = &'a ArrayRef> + 'a> {
95 Box::new(self.iter_chunks().filter(|chunk| !chunk.is_empty()))
96 }
97
98 fn chunk_offset_values(&self) -> &[usize] {
100 &self.chunk_offsets
101 }
102
103 fn find_chunk_idx(&self, index: usize) -> VortexResult<(usize, usize)> {
104 assert!(
105 index <= self.as_ref().len(),
106 "Index out of bounds of the array"
107 );
108 let chunk_offset_values = self.chunk_offset_values();
109 let index_chunk = chunk_offset_values
110 .search_sorted(&index, SearchSortedSide::Right)?
111 .to_ends_index(self.nchunks() + 1)
112 .saturating_sub(1);
113 let chunk_start = chunk_offset_values[index_chunk];
114 let index_in_chunk = index - chunk_start;
115 Ok((index_chunk, index_in_chunk))
116 }
117
118 fn array_iterator(&self) -> impl ArrayIterator + '_ {
119 ArrayIteratorAdapter::new(
120 self.as_ref().dtype().clone(),
121 self.iter_chunks().map(|chunk| Ok(chunk.clone())),
122 )
123 }
124
125 fn array_stream(&self) -> impl ArrayStream + '_ {
126 ArrayStreamAdapter::new(
127 self.as_ref().dtype().clone(),
128 stream::iter(self.iter_chunks().map(|chunk| Ok(chunk.clone()))),
129 )
130 }
131}
132impl<T: TypedArrayRef<Chunked>> ChunkedArrayExt for T {}
133
134impl ChunkedData {
135 pub(super) fn new(chunk_offsets: Vec<usize>) -> Self {
136 Self {
137 chunk_offsets,
138 next_builder_slot: ChunkedSlots::CHUNKS_OFFSET,
139 }
140 }
141
142 pub(super) fn make_chunk_offsets_array(chunk_offsets: &[usize]) -> ArrayRef {
143 let mut chunk_offsets_buf = BufferMut::<u64>::with_capacity(chunk_offsets.len());
144 for &offset in chunk_offsets {
145 let offset = u64::try_from(offset)
146 .vortex_expect("chunk offset must fit in u64 for serialization");
147 unsafe { chunk_offsets_buf.push_unchecked(offset) }
148 }
149 PrimitiveArray::new(chunk_offsets_buf.freeze(), Validity::NonNullable).into_array()
150 }
151
152 pub fn validate(chunks: &[ArrayRef], dtype: &DType) -> VortexResult<()> {
156 for chunk in chunks {
157 if chunk.dtype() != dtype {
158 vortex_bail!(MismatchedTypes: dtype, chunk.dtype());
159 }
160 }
161
162 Ok(())
163 }
164}
165
166impl Array<Chunked> {
167 fn parts_from_chunks<const VALIDATE: bool>(
168 chunks: impl IntoIterator<Item = ArrayRef>,
169 dtype: DType,
170 ) -> VortexResult<ArrayParts<Chunked>> {
171 let chunks = chunks.into_iter();
172 let (lower, _) = chunks.size_hint();
173 let mut slots = ArraySlots::with_capacity(ChunkedSlots::CHUNKS_OFFSET + lower);
174 slots.push(None);
175 let mut chunk_offsets = Vec::with_capacity(lower + 1);
176 chunk_offsets.push(0);
177 let mut len = 0usize;
178
179 for chunk in chunks {
180 if VALIDATE && chunk.dtype() != &dtype {
181 vortex_bail!(MismatchedTypes: &dtype, chunk.dtype());
182 }
183 len += chunk.len();
184 chunk_offsets.push(len);
185 slots.push(Some(chunk));
186 }
187
188 slots[ChunkedSlots::CHUNK_OFFSETS] =
189 Some(ChunkedData::make_chunk_offsets_array(&chunk_offsets));
190 Ok(ArrayParts::new(
191 Chunked,
192 dtype,
193 len,
194 ChunkedData::new(chunk_offsets),
195 slots,
196 ))
197 }
198
199 pub(super) fn with_next_builder_slot(mut self, next_builder_slot: usize) -> Self {
200 if let Some(data) = self.data_mut() {
201 data.next_builder_slot = next_builder_slot;
202 return self;
203 }
204 let stats = self.statistics().to_owned();
207 let mut data = self.data().clone();
208 data.next_builder_slot = next_builder_slot;
209 unsafe {
211 Array::from_parts_unchecked(ArrayParts::new(
212 Chunked,
213 self.dtype().clone(),
214 self.len(),
215 data,
216 self.slots().iter().cloned().collect::<ArraySlots>(),
217 ))
218 }
219 .with_stats_set(stats)
220 }
221
222 pub fn try_new(chunks: impl IntoIterator<Item = ArrayRef>, dtype: DType) -> VortexResult<Self> {
224 Array::try_from_parts(Self::parts_from_chunks::<true>(chunks, dtype)?)
225 }
226
227 pub fn rechunk(
228 &self,
229 target_bytesize: u64,
230 target_rowsize: usize,
231 ctx: &mut ExecutionCtx,
232 ) -> VortexResult<Self> {
233 let mut new_chunks = Vec::new();
234 let mut chunks_to_combine = Vec::new();
235 let mut new_chunk_n_bytes = 0;
236 let mut new_chunk_n_elements = 0;
237 for chunk in self.iter_chunks() {
238 let n_bytes = chunk.nbytes();
239 let n_elements = chunk.len();
240
241 if (new_chunk_n_bytes + n_bytes > target_bytesize
242 || new_chunk_n_elements + n_elements > target_rowsize)
243 && !chunks_to_combine.is_empty()
244 {
245 let canonical = unsafe {
246 Array::<Chunked>::new_unchecked(chunks_to_combine, self.dtype().clone())
247 }
248 .into_array()
249 .execute::<Canonical>(ctx)?
250 .into_array();
251 new_chunks.push(canonical);
252
253 new_chunk_n_bytes = 0;
254 new_chunk_n_elements = 0;
255 chunks_to_combine = Vec::new();
256 }
257
258 if n_bytes > target_bytesize || n_elements > target_rowsize {
259 new_chunks.push(chunk.clone());
260 } else {
261 new_chunk_n_bytes += n_bytes;
262 new_chunk_n_elements += n_elements;
263 chunks_to_combine.push(chunk.clone());
264 }
265 }
266
267 if !chunks_to_combine.is_empty() {
268 let canonical =
269 unsafe { Array::<Chunked>::new_unchecked(chunks_to_combine, self.dtype().clone()) }
270 .into_array()
271 .execute::<Canonical>(ctx)?
272 .into_array();
273 new_chunks.push(canonical);
274 }
275
276 unsafe { Ok(Self::new_unchecked(new_chunks, self.dtype().clone())) }
277 }
278
279 pub unsafe fn new_unchecked(chunks: impl IntoIterator<Item = ArrayRef>, dtype: DType) -> Self {
285 let parts = Self::parts_from_chunks::<false>(chunks, dtype)
286 .vortex_expect("unchecked chunked construction cannot fail");
287 unsafe { Array::from_parts_unchecked(parts) }
288 }
289}
290
291impl FromIterator<ArrayRef> for Array<Chunked> {
292 fn from_iter<T: IntoIterator<Item = ArrayRef>>(iter: T) -> Self {
293 let chunks: Vec<ArrayRef> = iter.into_iter().collect();
294 let dtype = chunks
295 .first()
296 .map(|c| c.dtype().clone())
297 .vortex_expect("Cannot infer DType from an empty iterator");
298 Array::<Chunked>::try_new(chunks, dtype)
299 .vortex_expect("Failed to create chunked array from iterator")
300 }
301}
302
303#[cfg(test)]
304mod test {
305 use vortex_buffer::buffer;
306 use vortex_error::VortexResult;
307
308 use crate::IntoArray;
309 use crate::VortexSessionExecute;
310 use crate::array_session;
311 use crate::arrays::ChunkedArray;
312 use crate::arrays::PrimitiveArray;
313 use crate::arrays::chunked::ChunkedArrayExt;
314 use crate::assert_arrays_eq;
315 use crate::dtype::DType;
316 use crate::dtype::Nullability;
317 use crate::dtype::PType;
318 use crate::validity::Validity;
319
320 #[test]
321 fn test_rechunk_one_chunk() {
322 let mut ctx = array_session().create_execution_ctx();
323 let chunked = ChunkedArray::try_new(
324 vec![buffer![0u64].into_array()],
325 DType::Primitive(PType::U64, Nullability::NonNullable),
326 )
327 .unwrap();
328
329 let rechunked = chunked.rechunk(1 << 16, 1 << 16, &mut ctx).unwrap();
330
331 assert_arrays_eq!(chunked, rechunked, &mut ctx);
332 }
333
334 #[test]
335 fn test_rechunk_two_chunks() {
336 let mut ctx = array_session().create_execution_ctx();
337 let chunked = ChunkedArray::try_new(
338 vec![buffer![0u64].into_array(), buffer![5u64].into_array()],
339 DType::Primitive(PType::U64, Nullability::NonNullable),
340 )
341 .unwrap();
342
343 let rechunked = chunked.rechunk(1 << 16, 1 << 16, &mut ctx).unwrap();
344
345 assert_eq!(rechunked.nchunks(), 1);
346 assert_arrays_eq!(chunked, rechunked, &mut ctx);
347 }
348
349 #[test]
350 fn test_rechunk_tiny_target_chunks() {
351 let mut ctx = array_session().create_execution_ctx();
352 let chunked = ChunkedArray::try_new(
353 vec![
354 buffer![0u64, 1, 2, 3].into_array(),
355 buffer![4u64, 5].into_array(),
356 ],
357 DType::Primitive(PType::U64, Nullability::NonNullable),
358 )
359 .unwrap();
360
361 let rechunked = chunked.rechunk(1 << 16, 5, &mut ctx).unwrap();
362
363 assert_eq!(rechunked.nchunks(), 2);
364 assert!(rechunked.iter_chunks().all(|c| c.len() < 5));
365 assert_arrays_eq!(chunked, rechunked, &mut ctx);
366 }
367
368 #[test]
369 fn test_rechunk_with_too_big_chunk() {
370 let mut ctx = array_session().create_execution_ctx();
371 let chunked = ChunkedArray::try_new(
372 vec![
373 buffer![0u64, 1, 2].into_array(),
374 buffer![42_u64; 6].into_array(),
375 buffer![4u64, 5].into_array(),
376 buffer![6u64, 7].into_array(),
377 buffer![8u64, 9].into_array(),
378 ],
379 DType::Primitive(PType::U64, Nullability::NonNullable),
380 )
381 .unwrap();
382
383 let rechunked = chunked.rechunk(1 << 16, 5, &mut ctx).unwrap();
384 assert_eq!(rechunked.nchunks(), 4);
387 assert_arrays_eq!(chunked, rechunked, &mut ctx);
388 }
389
390 #[test]
391 fn test_empty_chunks_all_valid() -> VortexResult<()> {
392 let mut ctx = array_session().create_execution_ctx();
393 let chunks = vec![
395 PrimitiveArray::new(buffer![1u64, 2, 3], Validity::AllValid).into_array(),
396 PrimitiveArray::new(buffer![0u64; 0], Validity::AllValid).into_array(), PrimitiveArray::new(buffer![4u64, 5], Validity::AllValid).into_array(),
398 PrimitiveArray::new(buffer![0u64; 0], Validity::AllValid).into_array(), ];
400
401 let chunked =
402 ChunkedArray::try_new(chunks, DType::Primitive(PType::U64, Nullability::Nullable))?;
403
404 assert!(chunked.all_valid(&mut ctx)?);
406 assert!(!chunked.into_array().all_invalid(&mut ctx)?);
407
408 Ok(())
409 }
410
411 #[test]
412 fn test_empty_chunks_all_invalid() -> VortexResult<()> {
413 let mut ctx = array_session().create_execution_ctx();
414 let chunks = vec![
416 PrimitiveArray::new(buffer![1u64, 2], Validity::AllInvalid).into_array(),
417 PrimitiveArray::new(buffer![0u64; 0], Validity::AllInvalid).into_array(), PrimitiveArray::new(buffer![3u64, 4, 5], Validity::AllInvalid).into_array(),
419 PrimitiveArray::new(buffer![0u64; 0], Validity::AllInvalid).into_array(), ];
421
422 let chunked =
423 ChunkedArray::try_new(chunks, DType::Primitive(PType::U64, Nullability::Nullable))?;
424
425 assert!(!chunked.all_valid(&mut ctx)?);
427 assert!(chunked.into_array().all_invalid(&mut ctx)?);
428
429 Ok(())
430 }
431
432 #[test]
433 fn test_empty_chunks_mixed_validity() -> VortexResult<()> {
434 let mut ctx = array_session().create_execution_ctx();
435 let chunks = vec![
437 PrimitiveArray::new(buffer![1u64, 2], Validity::AllValid).into_array(),
438 PrimitiveArray::new(buffer![0u64; 0], Validity::AllValid).into_array(), PrimitiveArray::new(buffer![3u64, 4], Validity::AllInvalid).into_array(),
440 PrimitiveArray::new(buffer![0u64; 0], Validity::AllInvalid).into_array(), ];
442
443 let chunked =
444 ChunkedArray::try_new(chunks, DType::Primitive(PType::U64, Nullability::Nullable))?;
445
446 assert!(!chunked.all_valid(&mut ctx)?);
448 assert!(!chunked.into_array().all_invalid(&mut ctx)?);
449
450 Ok(())
451 }
452}