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