| // Licensed to the Apache Software Foundation (ASF) under one or more | |
| // contributor license agreements. See the NOTICE file distributed with | |
| // this work for additional information regarding copyright ownership. | |
| // The ASF licenses this file to You under the Apache License, Version 2.0 | |
| // (the "License"); you may not use this file except in compliance with | |
| // the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| use std::{ptr::NonNull, sync::Arc}; | |
| use arrow::{ | |
| array::{ | |
| Array, ArrayRef, ArrowPrimitiveType, BinaryArray, BooleanBufferBuilder, GenericByteArray, | |
| PrimitiveArray, StringArray, downcast_primitive, | |
| }, | |
| buffer::{MutableBuffer, NullBuffer, OffsetBuffer, ScalarBuffer}, | |
| datatypes::{ArrowNativeType, ByteArrayType}, | |
| record_batch::{RecordBatch, RecordBatchOptions}, | |
| }; | |
| use arrow_schema::DataType; | |
| use datafusion::common::Result; | |
| use crate::{downcast_any, prefetch_read_data, unchecked}; | |
| pub fn take_batch<T: ArrowPrimitiveType>( | |
| batch: RecordBatch, | |
| indices: impl Into<PrimitiveArray<T>>, | |
| ) -> Result<RecordBatch> { | |
| let indices = indices.into(); | |
| let taken_num_batch_rows = indices.len(); | |
| Ok(RecordBatch::try_new_with_options( | |
| batch.schema(), | |
| take_cols_internal(batch.columns(), &indices)?, | |
| &RecordBatchOptions::new().with_row_count(Some(taken_num_batch_rows)), | |
| )?) | |
| } | |
| pub fn take_cols<T: ArrowPrimitiveType>( | |
| cols: &[ArrayRef], | |
| indices: impl Into<PrimitiveArray<T>>, | |
| ) -> Result<Vec<ArrayRef>> { | |
| take_cols_internal(cols, &indices.into()) | |
| } | |
| fn take_cols_internal<T: ArrowPrimitiveType>( | |
| cols: &[ArrayRef], | |
| indices: &PrimitiveArray<T>, | |
| ) -> Result<Vec<ArrayRef>> { | |
| cols.iter() | |
| .map(|c| Ok(arrow::compute::take(&c, indices, None)?)) | |
| .collect::<Result<_>>() | |
| } | |
| pub type ArrayInterleaver = Box<dyn Fn(&[(usize, usize)]) -> Result<ArrayRef> + Send>; | |
| pub type BatchInterleaver = Box<dyn Fn(&[(usize, usize)]) -> Result<RecordBatch> + Send>; | |
| #[inline] | |
| pub fn create_batch_interleaver( | |
| batches: &[RecordBatch], | |
| with_prefetching: bool, | |
| ) -> Result<BatchInterleaver> { | |
| let batch_schema = batches[0].schema(); | |
| let mut col_arrays = vec![vec![]; batches[0].num_columns()]; | |
| for batch in batches { | |
| for (col_idx, col) in batch.columns().iter().enumerate() { | |
| col_arrays[col_idx].push(col.clone()); | |
| } | |
| } | |
| let col_interleavers = col_arrays | |
| .iter() | |
| .map(|arrays| create_array_interleaver(arrays, with_prefetching)) | |
| .collect::<Result<Vec<_>>>()?; | |
| Ok(Box::new(move |indices| { | |
| let cols = col_interleavers | |
| .iter() | |
| .map(|col_interleaver| col_interleaver(indices)) | |
| .collect::<Result<Vec<_>>>(); | |
| let batch = RecordBatch::try_new_with_options( | |
| batch_schema.clone(), | |
| cols?, | |
| &RecordBatchOptions::new().with_row_count(Some(indices.len())), | |
| )?; | |
| Ok(batch) | |
| })) | |
| } | |
| #[inline] | |
| pub fn create_array_interleaver( | |
| values: &[ArrayRef], | |
| with_prefetching: bool, | |
| ) -> Result<ArrayInterleaver> { | |
| struct Interleave<T> { | |
| arrays: Vec<T>, | |
| has_nulls: bool, | |
| } | |
| impl<T: Array> Interleave<T> { | |
| fn new(arrays: Vec<T>) -> Self { | |
| let has_nulls = arrays.iter().any(|v| v.null_count() > 0); | |
| Self { arrays, has_nulls } | |
| } | |
| fn nulls(&self, indices: &[(usize, usize)]) -> Option<NullBuffer> { | |
| match self.has_nulls { | |
| true => { | |
| let mut builder = BooleanBufferBuilder::new(indices.len()); | |
| for (a, b) in indices { | |
| let v = self.arrays[*a].is_valid(*b); | |
| builder.append(v) | |
| } | |
| Some(NullBuffer::new(builder.finish())) | |
| } | |
| false => None, | |
| } | |
| } | |
| } | |
| #[inline] | |
| fn interleave_primitive<T: ArrowPrimitiveType, const WITH_PREFETCHING: bool>( | |
| interleaver: &Interleave<PrimitiveArray<T>>, | |
| indices: &[(usize, usize)], | |
| dt: &DataType, | |
| ) -> Result<ArrayRef> { | |
| let nulls = interleaver.nulls(indices); | |
| let mut values = Vec::with_capacity(indices.len()); | |
| for (i, &(array_idx, value_idx)) in indices.iter().enumerate() { | |
| if WITH_PREFETCHING { | |
| const PREFETCH_AHEAD: usize = 4; | |
| if i + PREFETCH_AHEAD < indices.len() { | |
| let (prefetch_array_idx, prefetch_value_idx) = indices[i + PREFETCH_AHEAD]; | |
| prefetch_read_data!({ | |
| let array = interleaver.arrays.get_unchecked(prefetch_array_idx); | |
| (array | |
| .values() | |
| .get_unchecked(array.offset() + prefetch_value_idx)) | |
| as _ | |
| }); | |
| } | |
| } | |
| let array = &interleaver.arrays[array_idx]; | |
| if array.is_valid(value_idx) { | |
| values.push(array.value(value_idx)); | |
| } else { | |
| values.push(Default::default()); | |
| } | |
| } | |
| let array = PrimitiveArray::<T>::new(values.into(), nulls); | |
| Ok(Arc::new(array.with_data_type(dt.clone()))) | |
| } | |
| #[inline] | |
| fn interleave_bytes<T: ByteArrayType, const WITH_PREFETCHING: bool>( | |
| interleaver: &Interleave<GenericByteArray<T>>, | |
| indices: &[(usize, usize)], | |
| ) -> Result<ArrayRef> { | |
| let nulls = interleaver.nulls(indices); | |
| let mut offsets = Vec::with_capacity(indices.len() + 1); | |
| let mut take_value_ptrs = Vec::with_capacity(indices.len()); | |
| let mut capacity = 0; | |
| let zero = T::Offset::default(); | |
| offsets.push(zero); | |
| for &(array_idx, value_idx) in indices { | |
| let array = &interleaver.arrays[array_idx]; | |
| if array.is_valid(value_idx) { | |
| let value_offsets = unchecked!(array.value_offsets()); | |
| let value_start = value_offsets[value_idx]; | |
| let value_end = value_offsets[value_idx + 1]; | |
| let value_len = value_end - value_start; | |
| capacity += value_len.as_usize(); | |
| if value_len > zero { | |
| let value_ptr = array.values().as_ptr().wrapping_add(value_start.as_usize()); | |
| take_value_ptrs.push(value_ptr); | |
| } | |
| } | |
| offsets.push(T::Offset::from_usize(capacity).expect("overflow")); | |
| } | |
| let array = unsafe { | |
| let mut values = MutableBuffer::new(capacity); | |
| let mut dest_ptr = NonNull::new_unchecked(values.as_mut_ptr()); | |
| values.set_len(capacity); | |
| let mut src_start_ptr = NonNull::dangling(); // initial value is unused | |
| let mut src_end_ptr = src_start_ptr; | |
| macro_rules! apply_copy { | |
| () => {{ | |
| if src_end_ptr != src_start_ptr { | |
| let src_len = src_end_ptr.offset_from(src_start_ptr) as usize; | |
| dest_ptr.copy_from_nonoverlapping(src_start_ptr, src_len); | |
| dest_ptr = dest_ptr.add(src_len); | |
| } | |
| }}; | |
| } | |
| let mut take_value_ptr_idx = 0; | |
| for i in 0..indices.len() { | |
| let take_value_ptrs = unchecked!(&take_value_ptrs); | |
| let offsets = unchecked!(&offsets); | |
| let value_len = (offsets[i + 1] - offsets[i]).as_usize(); | |
| if value_len == 0 { | |
| continue; | |
| } | |
| let value_ptr = | |
| NonNull::new_unchecked(take_value_ptrs[take_value_ptr_idx] as *mut u8); | |
| take_value_ptr_idx += 1; | |
| // for continuous elements, just extend the area to copy | |
| // otherwise, copy current area end move to the next area | |
| if src_end_ptr != value_ptr { | |
| prefetch_read_data!(value_ptr.as_ptr()); // prefetch next while copying current | |
| apply_copy!(); | |
| src_start_ptr = value_ptr; | |
| } | |
| src_end_ptr = value_ptr.add(value_len); | |
| } | |
| // copy last area | |
| apply_copy!(); | |
| assert_eq!( | |
| dest_ptr.as_ptr(), | |
| values.as_mut_ptr().wrapping_add(capacity) | |
| ); | |
| // build array | |
| let offsets = OffsetBuffer::new_unchecked(ScalarBuffer::from(offsets)); | |
| GenericByteArray::<T>::new_unchecked(offsets, values.into(), nulls) | |
| }; | |
| Ok(Arc::new(array)) | |
| } | |
| if !values.is_empty() { | |
| let dt = values[0].data_type(); | |
| macro_rules! primitive_helper { | |
| ($t:ty, $dt:ident) => {{ | |
| let interleaver = Interleave::new( | |
| values | |
| .iter() | |
| .map(|v| { | |
| downcast_any!(v, PrimitiveArray<$t>) | |
| .expect("Excepted a PrimitiveArray") | |
| .clone() | |
| }) | |
| .collect::<Vec<_>>(), | |
| ); | |
| let dt = $dt.clone(); | |
| return Ok(Box::new(move |indices| { | |
| if with_prefetching { | |
| interleave_primitive::<_, true>(&interleaver, indices, &dt) | |
| } else { | |
| interleave_primitive::<_, false>(&interleaver, indices, &dt) | |
| } | |
| })); | |
| }}; | |
| } | |
| downcast_primitive! { | |
| dt => (primitive_helper, dt), | |
| DataType::Utf8 => { | |
| let interleaver = Interleave::new(values | |
| .iter() | |
| .map(|v| downcast_any!(v, StringArray).expect("Excepted a StringArray").clone()) | |
| .collect::<Vec<_>>(), | |
| ); | |
| return Ok(Box::new(move |indices| if with_prefetching { | |
| interleave_bytes::<_, true>(&interleaver, indices) | |
| } else { | |
| interleave_bytes::<_, false>(&interleaver, indices) | |
| })); | |
| } | |
| DataType::Binary => { | |
| let interleaver = Interleave::new(values | |
| .iter() | |
| .map(|v| downcast_any!(v, BinaryArray).expect("Excepted a BinaryArray").clone()) | |
| .collect::<Vec<_>>(), | |
| ); | |
| return Ok(Box::new(move |indices| if with_prefetching { | |
| interleave_bytes::<_, true>(&interleaver, indices) | |
| } else { | |
| interleave_bytes::<_, false>(&interleaver, indices) | |
| })); | |
| } | |
| _ => {}, | |
| } | |
| } | |
| // fallback to arrow's implementation | |
| let values_cloned = values.to_vec(); | |
| Ok(Box::new(move |indices| { | |
| let value_refs = values_cloned.iter().map(|v| v.as_ref()).collect::<Vec<_>>(); | |
| Ok(arrow::compute::interleave(&value_refs, indices)?) | |
| })) | |
| } |