Skip to main content

torsh_data/
arrow_integration.rs

1//! Apache Arrow integration for efficient data exchange
2//!
3//! This module provides utilities for converting between torsh tensors
4//! and Apache Arrow arrays for efficient data processing pipelines.
5
6#[cfg(feature = "arrow-support")]
7use arrow::{
8    array::{Array, ArrayRef, Float32Array, Float64Array, Int32Array, Int64Array},
9    datatypes::{DataType, Field, Schema, SchemaRef, ToByteSlice},
10    record_batch::RecordBatch,
11};
12
13use crate::Dataset;
14use torsh_core::error::{Result, TorshError};
15use torsh_tensor::Tensor;
16
17#[cfg(feature = "arrow-support")]
18use torsh_core::{device::DeviceType, dtype::TensorElement};
19
20#[cfg(feature = "arrow-support")]
21use crate::utils;
22
23#[cfg(not(feature = "arrow-support"))]
24use std::marker::PhantomData;
25
26/// Arrow dataset for reading Arrow files and record batches
27#[cfg(feature = "arrow-support")]
28pub struct ArrowDataset {
29    record_batches: Vec<RecordBatch>,
30    // Placeholder for future batch iteration support
31    #[allow(dead_code)]
32    current_batch: usize,
33    batch_size: usize,
34    total_rows: usize,
35}
36
37#[cfg(not(feature = "arrow-support"))]
38pub struct ArrowDataset {
39    _phantom: PhantomData<()>,
40}
41
42#[cfg(feature = "arrow-support")]
43impl ArrowDataset {
44    /// Create a new Arrow dataset from record batches
45    pub fn from_record_batches(record_batches: Vec<RecordBatch>) -> Result<Self> {
46        utils::validate_not_empty(&record_batches, "record_batches")?;
47
48        let total_rows = record_batches.iter().map(|batch| batch.num_rows()).sum();
49
50        Ok(Self {
51            record_batches,
52            current_batch: 0,
53            batch_size: 1000, // Default batch size
54            total_rows,
55        })
56    }
57
58    /// Create from Arrow file path
59    pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
60        use arrow::ipc::reader::FileReader;
61        use std::fs::File;
62
63        let path = path.as_ref();
64        utils::validate_dataset_path(path, "Arrow file")?;
65        utils::validate_file_extension(path, &["arrow", "ipc"])?;
66
67        let file = File::open(path).map_err(|e| {
68            TorshError::InvalidArgument(format!("Failed to open Arrow file: {}", e))
69        })?;
70
71        let reader = FileReader::try_new(file, None).map_err(|e| {
72            TorshError::InvalidArgument(format!("Failed to create Arrow reader: {}", e))
73        })?;
74
75        let mut record_batches = Vec::new();
76        for batch_result in reader {
77            let batch = batch_result.map_err(|e| {
78                TorshError::InvalidArgument(format!("Failed to read Arrow batch: {}", e))
79            })?;
80            record_batches.push(batch);
81        }
82
83        Self::from_record_batches(record_batches)
84    }
85
86    /// Set batch size for iteration
87    pub fn with_batch_size(mut self, batch_size: usize) -> Self {
88        self.batch_size = batch_size;
89        self
90    }
91
92    /// Get the Arrow schema
93    pub fn schema(&self) -> Option<SchemaRef> {
94        self.record_batches.first().map(|batch| batch.schema())
95    }
96
97    /// Convert Arrow array to tensor
98    pub fn array_to_tensor<T: TensorElement + 'static>(
99        &self,
100        array: &dyn Array,
101    ) -> Result<Tensor<T>> {
102        match array.data_type() {
103            DataType::Float32 => {
104                let array = array
105                    .as_any()
106                    .downcast_ref::<Float32Array>()
107                    .ok_or_else(|| {
108                        TorshError::InvalidArgument(
109                            "Failed to downcast to Float32Array".to_string(),
110                        )
111                    })?;
112                let values: Vec<f32> = array.values().to_vec();
113                let shape = vec![values.len()];
114
115                // Type conversion if needed
116                if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() {
117                    let tensor_data = unsafe { std::mem::transmute::<Vec<f32>, Vec<T>>(values) };
118                    Ok(torsh_tensor::Tensor::from_data(
119                        tensor_data,
120                        shape,
121                        DeviceType::Cpu,
122                    )?)
123                } else {
124                    return Err(TorshError::InvalidArgument("Type mismatch".to_string()));
125                }
126            }
127            DataType::Float64 => {
128                let array = array
129                    .as_any()
130                    .downcast_ref::<Float64Array>()
131                    .ok_or_else(|| {
132                        TorshError::InvalidArgument(
133                            "Failed to downcast to Float64Array".to_string(),
134                        )
135                    })?;
136                let values: Vec<f64> = array.values().to_vec();
137                let shape = vec![values.len()];
138
139                if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f64>() {
140                    let tensor_data = unsafe { std::mem::transmute::<Vec<f64>, Vec<T>>(values) };
141                    Ok(torsh_tensor::Tensor::from_data(
142                        tensor_data,
143                        shape,
144                        DeviceType::Cpu,
145                    )?)
146                } else {
147                    return Err(TorshError::InvalidArgument("Type mismatch".to_string()));
148                }
149            }
150            DataType::Int32 => {
151                let array = array.as_any().downcast_ref::<Int32Array>().ok_or_else(|| {
152                    TorshError::InvalidArgument("Failed to downcast to Int32Array".to_string())
153                })?;
154                let values: Vec<i32> = array.values().to_vec();
155                let shape = vec![values.len()];
156
157                if std::any::TypeId::of::<T>() == std::any::TypeId::of::<i32>() {
158                    let tensor_data = unsafe { std::mem::transmute::<Vec<i32>, Vec<T>>(values) };
159                    Ok(torsh_tensor::Tensor::from_data(
160                        tensor_data,
161                        shape,
162                        DeviceType::Cpu,
163                    )?)
164                } else {
165                    return Err(TorshError::InvalidArgument("Type mismatch".to_string()));
166                }
167            }
168            DataType::Int64 => {
169                let array = array.as_any().downcast_ref::<Int64Array>().ok_or_else(|| {
170                    TorshError::InvalidArgument("Failed to downcast to Int64Array".to_string())
171                })?;
172                let values: Vec<i64> = array.values().to_vec();
173                let shape = vec![values.len()];
174
175                if std::any::TypeId::of::<T>() == std::any::TypeId::of::<i64>() {
176                    let tensor_data = unsafe { std::mem::transmute::<Vec<i64>, Vec<T>>(values) };
177                    Ok(torsh_tensor::Tensor::from_data(
178                        tensor_data,
179                        shape,
180                        DeviceType::Cpu,
181                    )?)
182                } else {
183                    return Err(TorshError::InvalidArgument("Type mismatch".to_string()));
184                }
185            }
186            _ => Err(TorshError::InvalidArgument(format!(
187                "Unsupported Arrow data type: {:?}",
188                array.data_type()
189            ))),
190        }
191    }
192
193    /// Convert tensor to Arrow array
194    pub fn tensor_to_array<T: TensorElement + ToByteSlice + 'static>(
195        tensor: &Tensor<T>,
196    ) -> Result<ArrayRef> {
197        let data = tensor.to_vec()?;
198
199        if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() {
200            let float_data = unsafe { std::mem::transmute::<Vec<T>, Vec<f32>>(data) };
201            Ok(std::sync::Arc::new(Float32Array::from(float_data)))
202        } else if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f64>() {
203            let double_data = unsafe { std::mem::transmute::<Vec<T>, Vec<f64>>(data) };
204            Ok(std::sync::Arc::new(Float64Array::from(double_data)))
205        } else if std::any::TypeId::of::<T>() == std::any::TypeId::of::<i32>() {
206            let int_data = unsafe { std::mem::transmute::<Vec<T>, Vec<i32>>(data) };
207            Ok(std::sync::Arc::new(Int32Array::from(int_data)))
208        } else if std::any::TypeId::of::<T>() == std::any::TypeId::of::<i64>() {
209            let long_data = unsafe { std::mem::transmute::<Vec<T>, Vec<i64>>(data) };
210            Ok(std::sync::Arc::new(Int64Array::from(long_data)))
211        } else {
212            Err(TorshError::InvalidArgument(
213                "Unsupported tensor element type for Arrow conversion".to_string(),
214            ))
215        }
216    }
217
218    /// Get column as tensor
219    pub fn get_column_as_tensor<T: TensorElement + 'static>(
220        &self,
221        column_name: &str,
222    ) -> Result<Tensor<T>> {
223        for batch in &self.record_batches {
224            if let Some(column_index) = batch.schema().column_with_name(column_name) {
225                let array = batch.column(column_index.0);
226                return self.array_to_tensor(array.as_ref());
227            }
228        }
229
230        Err(TorshError::InvalidArgument(format!(
231            "Column '{}' not found",
232            column_name
233        )))
234    }
235}
236
237#[cfg(not(feature = "arrow-support"))]
238impl ArrowDataset {
239    /// Placeholder for when Arrow feature is not enabled
240    pub fn from_record_batches(_record_batches: Vec<()>) -> Result<Self> {
241        Err(TorshError::InvalidArgument(
242            "Arrow support not enabled. Enable 'arrow-support' feature flag.".to_string(),
243        ))
244    }
245
246    /// Placeholder for file loading
247    pub fn from_file<P: AsRef<std::path::Path>>(_path: P) -> Result<Self> {
248        Err(TorshError::InvalidArgument(
249            "Arrow support not enabled. Enable 'arrow-support' feature flag.".to_string(),
250        ))
251    }
252}
253
254#[cfg(feature = "arrow-support")]
255impl Dataset for ArrowDataset {
256    type Item = RecordBatch;
257
258    fn len(&self) -> usize {
259        self.total_rows
260    }
261
262    fn get(&self, index: usize) -> Result<Self::Item> {
263        if index >= self.total_rows {
264            return Err(utils::errors::invalid_index(index, self.total_rows));
265        }
266
267        // Find which batch contains this index
268        let mut current_row = 0;
269        for batch in &self.record_batches {
270            if index < current_row + batch.num_rows() {
271                let local_index = index - current_row;
272                // Return a slice of the batch (single row)
273                return Ok(batch.slice(local_index, 1));
274            }
275            current_row += batch.num_rows();
276        }
277
278        Err(utils::errors::invalid_index(index, self.total_rows))
279    }
280}
281
282#[cfg(not(feature = "arrow-support"))]
283impl Dataset for ArrowDataset {
284    type Item = ();
285
286    fn len(&self) -> usize {
287        0
288    }
289
290    fn get(&self, _index: usize) -> Result<Self::Item> {
291        Err(TorshError::InvalidArgument(
292            "Arrow support not enabled".to_string(),
293        ))
294    }
295}
296
297/// Utility functions for Arrow integration
298pub mod arrow_utils {
299    use super::*;
300
301    /// Check if Arrow feature is available at compile time
302    pub const fn is_arrow_available() -> bool {
303        cfg!(feature = "arrow-support")
304    }
305
306    /// Create a sample Arrow dataset for testing
307    #[cfg(feature = "arrow-support")]
308    pub fn create_sample_dataset() -> Result<ArrowDataset> {
309        use arrow::array::{Float32Array, Int32Array};
310        use arrow::datatypes::{DataType, Field, Schema};
311        use std::sync::Arc;
312
313        // Create schema
314        let schema = Arc::new(Schema::new(vec![
315            Field::new("id", DataType::Int32, false),
316            Field::new("value", DataType::Float32, false),
317        ]));
318
319        // Create arrays
320        let id_array = Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5]));
321        let value_array = Arc::new(Float32Array::from(vec![1.0, 2.0, 3.0, 4.0, 5.0]));
322
323        // Create record batch
324        let batch = RecordBatch::try_new(schema, vec![id_array, value_array]).map_err(|e| {
325            TorshError::InvalidArgument(format!("Failed to create record batch: {}", e))
326        })?;
327
328        ArrowDataset::from_record_batches(vec![batch])
329    }
330
331    /// Convert multiple tensors to a record batch
332    #[cfg(feature = "arrow-support")]
333    pub fn tensors_to_record_batch<T: TensorElement + ToByteSlice + 'static>(
334        tensors: &[(&str, &Tensor<T>)],
335    ) -> Result<RecordBatch> {
336        let mut fields = Vec::new();
337        let mut arrays = Vec::new();
338
339        for (name, tensor) in tensors {
340            // Determine Arrow data type based on tensor element type
341            let data_type = if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() {
342                DataType::Float32
343            } else if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f64>() {
344                DataType::Float64
345            } else if std::any::TypeId::of::<T>() == std::any::TypeId::of::<i32>() {
346                DataType::Int32
347            } else if std::any::TypeId::of::<T>() == std::any::TypeId::of::<i64>() {
348                DataType::Int64
349            } else {
350                return Err(TorshError::InvalidArgument(
351                    "Unsupported tensor type for Arrow conversion".to_string(),
352                ));
353            };
354
355            fields.push(Field::new(*name, data_type, false));
356            arrays.push(ArrowDataset::tensor_to_array(tensor)?);
357        }
358
359        let schema = std::sync::Arc::new(Schema::new(fields));
360        RecordBatch::try_new(schema, arrays).map_err(|e| {
361            TorshError::InvalidArgument(format!("Failed to create record batch: {}", e))
362        })
363    }
364
365    #[cfg(not(feature = "arrow-support"))]
366    pub fn create_sample_dataset() -> Result<ArrowDataset> {
367        ArrowDataset::from_record_batches(vec![])
368    }
369
370    #[cfg(not(feature = "arrow-support"))]
371    pub fn tensors_to_record_batch<T: torsh_core::dtype::TensorElement>(
372        _tensors: &[(&str, &Tensor<T>)],
373    ) -> Result<()> {
374        Err(TorshError::InvalidArgument(
375            "Arrow support not enabled".to_string(),
376        ))
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383
384    #[test]
385    fn test_arrow_availability() {
386        // This test checks if we can detect Arrow availability
387        assert!(arrow_utils::is_arrow_available() || !arrow_utils::is_arrow_available());
388    }
389
390    #[cfg(feature = "arrow-support")]
391    #[test]
392    fn test_sample_dataset() -> Result<()> {
393        let dataset = arrow_utils::create_sample_dataset()?;
394        assert_eq!(dataset.len(), 5);
395
396        let first_item = dataset.get(0)?;
397        assert_eq!(first_item.num_rows(), 1);
398        assert_eq!(first_item.num_columns(), 2);
399
400        Ok(())
401    }
402
403    #[cfg(not(feature = "arrow-support"))]
404    #[test]
405    fn test_arrow_disabled() {
406        let result = ArrowDataset::from_file("test.arrow");
407        assert!(result.is_err());
408    }
409}