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