use std::sync::Arc;
use arrow::{
array::{Array, ArrayRef},
record_batch::RecordBatch,
};
use super::types::{ColumnArray, ColumnarBatch};
use crate::errors::ExecutorError;
impl ColumnarBatch {
pub fn from_arrow_batch(batch: &RecordBatch) -> Result<Self, ExecutorError> {
let row_count = batch.num_rows();
let column_count = batch.num_columns();
let mut columns = Vec::with_capacity(column_count);
let mut column_names = Vec::with_capacity(column_count);
for (idx, field) in batch.schema().fields().iter().enumerate() {
column_names.push(field.name().clone());
let array = batch.column(idx);
let column = convert_arrow_array(array, field.data_type())?;
columns.push(column);
}
Ok(Self { row_count, columns, column_names: Some(column_names) })
}
}
fn convert_arrow_array(
array: &ArrayRef,
data_type: &arrow::datatypes::DataType,
) -> Result<ColumnArray, ExecutorError> {
use arrow::{
array::{
BooleanArray, Date32Array, Float32Array, Float64Array, Int32Array, Int64Array,
StringArray, TimestampMicrosecondArray,
},
datatypes::DataType as ArrowDataType,
};
match data_type {
ArrowDataType::Int64 => {
let arr = array.as_any().downcast_ref::<Int64Array>().ok_or_else(|| {
ExecutorError::ArrowDowncastError {
expected_type: "Int64Array".to_string(),
context: "Arrow batch conversion".to_string(),
}
})?;
let values: Vec<i64> = (0..arr.len()).map(|i| arr.value(i)).collect();
let nulls = if arr.null_count() > 0 {
Some(Arc::new((0..arr.len()).map(|i| arr.is_null(i)).collect()))
} else {
None
};
Ok(ColumnArray::Int64(Arc::new(values), nulls))
}
ArrowDataType::Int32 => {
let arr = array.as_any().downcast_ref::<Int32Array>().ok_or_else(|| {
ExecutorError::ArrowDowncastError {
expected_type: "Int32Array".to_string(),
context: "Arrow batch conversion".to_string(),
}
})?;
let values: Vec<i32> = (0..arr.len()).map(|i| arr.value(i)).collect();
let nulls = if arr.null_count() > 0 {
Some(Arc::new((0..arr.len()).map(|i| arr.is_null(i)).collect()))
} else {
None
};
Ok(ColumnArray::Int32(Arc::new(values), nulls))
}
ArrowDataType::Float64 => {
let arr = array.as_any().downcast_ref::<Float64Array>().ok_or_else(|| {
ExecutorError::ArrowDowncastError {
expected_type: "Float64Array".to_string(),
context: "Arrow batch conversion".to_string(),
}
})?;
let values: Vec<f64> = (0..arr.len()).map(|i| arr.value(i)).collect();
let nulls = if arr.null_count() > 0 {
Some(Arc::new((0..arr.len()).map(|i| arr.is_null(i)).collect()))
} else {
None
};
Ok(ColumnArray::Float64(Arc::new(values), nulls))
}
ArrowDataType::Float32 => {
let arr = array.as_any().downcast_ref::<Float32Array>().ok_or_else(|| {
ExecutorError::ArrowDowncastError {
expected_type: "Float32Array".to_string(),
context: "Arrow batch conversion".to_string(),
}
})?;
let values: Vec<f32> = (0..arr.len()).map(|i| arr.value(i)).collect();
let nulls = if arr.null_count() > 0 {
Some(Arc::new((0..arr.len()).map(|i| arr.is_null(i)).collect()))
} else {
None
};
Ok(ColumnArray::Float32(Arc::new(values), nulls))
}
ArrowDataType::Utf8 => {
let arr = array.as_any().downcast_ref::<StringArray>().ok_or_else(|| {
ExecutorError::ArrowDowncastError {
expected_type: "StringArray".to_string(),
context: "Arrow batch conversion".to_string(),
}
})?;
let values: Vec<Arc<str>> = (0..arr.len()).map(|i| Arc::from(arr.value(i))).collect();
let nulls = if arr.null_count() > 0 {
Some(Arc::new((0..arr.len()).map(|i| arr.is_null(i)).collect()))
} else {
None
};
Ok(ColumnArray::String(Arc::new(values), nulls))
}
ArrowDataType::Boolean => {
let arr = array.as_any().downcast_ref::<BooleanArray>().ok_or_else(|| {
ExecutorError::ArrowDowncastError {
expected_type: "BooleanArray".to_string(),
context: "Arrow batch conversion".to_string(),
}
})?;
let values: Vec<u8> =
(0..arr.len()).map(|i| if arr.value(i) { 1 } else { 0 }).collect();
let nulls = if arr.null_count() > 0 {
Some(Arc::new((0..arr.len()).map(|i| arr.is_null(i)).collect()))
} else {
None
};
Ok(ColumnArray::Boolean(Arc::new(values), nulls))
}
ArrowDataType::Date32 => {
let arr = array.as_any().downcast_ref::<Date32Array>().ok_or_else(|| {
ExecutorError::ArrowDowncastError {
expected_type: "Date32Array".to_string(),
context: "Arrow batch conversion".to_string(),
}
})?;
let values: Vec<i32> = (0..arr.len()).map(|i| arr.value(i)).collect();
let nulls = if arr.null_count() > 0 {
Some(Arc::new((0..arr.len()).map(|i| arr.is_null(i)).collect()))
} else {
None
};
Ok(ColumnArray::Date(Arc::new(values), nulls))
}
ArrowDataType::Timestamp(arrow::datatypes::TimeUnit::Microsecond, _) => {
let arr =
array.as_any().downcast_ref::<TimestampMicrosecondArray>().ok_or_else(|| {
ExecutorError::ArrowDowncastError {
expected_type: "TimestampMicrosecondArray".to_string(),
context: "Arrow batch conversion".to_string(),
}
})?;
let values: Vec<i64> = (0..arr.len()).map(|i| arr.value(i)).collect();
let nulls = if arr.null_count() > 0 {
Some(Arc::new((0..arr.len()).map(|i| arr.is_null(i)).collect()))
} else {
None
};
Ok(ColumnArray::Timestamp(Arc::new(values), nulls))
}
_ => Err(ExecutorError::UnsupportedArrayType {
operation: "Arrow batch conversion".to_string(),
array_type: format!("{:?}", data_type),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arrow_integration() {
use std::sync::Arc;
use arrow::{
array::{Float64Array, Int64Array},
datatypes::{DataType as ArrowDataType, Field, Schema},
record_batch::RecordBatch,
};
let schema = Arc::new(Schema::new(vec![
Field::new("id", ArrowDataType::Int64, false),
Field::new("value", ArrowDataType::Float64, false),
]));
let id_array = Arc::new(Int64Array::from(vec![1, 2, 3]));
let value_array = Arc::new(Float64Array::from(vec![10.5, 20.5, 30.5]));
let arrow_batch =
RecordBatch::try_new(schema.clone(), vec![id_array, value_array]).unwrap();
let columnar_batch = ColumnarBatch::from_arrow_batch(&arrow_batch).unwrap();
assert_eq!(columnar_batch.row_count(), 3);
assert_eq!(columnar_batch.column_count(), 2);
let names = columnar_batch.column_names().unwrap();
assert_eq!(names, &["id", "value"]);
let col0 = columnar_batch.column(0).unwrap();
if let Some((values, nulls)) = col0.as_i64() {
assert_eq!(values, &[1, 2, 3]);
assert!(nulls.is_none());
} else {
panic!("Expected i64 column");
}
let col1 = columnar_batch.column(1).unwrap();
if let Some((values, nulls)) = col1.as_f64() {
assert_eq!(values, &[10.5, 20.5, 30.5]);
assert!(nulls.is_none());
} else {
panic!("Expected f64 column");
}
}
#[test]
fn test_arrow_integration_with_nulls() {
use std::sync::Arc;
use arrow::{
array::{Float64Array, Int64Array},
datatypes::{DataType as ArrowDataType, Field, Schema},
record_batch::RecordBatch,
};
let schema = Arc::new(Schema::new(vec![
Field::new("id", ArrowDataType::Int64, true), Field::new("value", ArrowDataType::Float64, true),
]));
let id_array = Arc::new(Int64Array::from(vec![Some(1), None, Some(3)]));
let value_array = Arc::new(Float64Array::from(vec![Some(10.5), Some(20.5), None]));
let arrow_batch =
RecordBatch::try_new(schema.clone(), vec![id_array, value_array]).unwrap();
let columnar_batch = ColumnarBatch::from_arrow_batch(&arrow_batch).unwrap();
let col0 = columnar_batch.column(0).unwrap();
if let Some((values, Some(nulls))) = col0.as_i64() {
assert_eq!(values.len(), 3);
assert_eq!(nulls, &[false, true, false]);
} else {
panic!("Expected i64 column with nulls");
}
let col1 = columnar_batch.column(1).unwrap();
if let Some((values, Some(nulls))) = col1.as_f64() {
assert_eq!(values.len(), 3);
assert_eq!(nulls, &[false, false, true]);
} else {
panic!("Expected f64 column with nulls");
}
}
}