1#[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#[cfg(feature = "arrow-support")]
28pub struct ArrowDataset {
29 record_batches: Vec<RecordBatch>,
30 #[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 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, total_rows,
55 })
56 }
57
58 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 pub fn with_batch_size(mut self, batch_size: usize) -> Self {
88 self.batch_size = batch_size;
89 self
90 }
91
92 pub fn schema(&self) -> Option<SchemaRef> {
94 self.record_batches.first().map(|batch| batch.schema())
95 }
96
97 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 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 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 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 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 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 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 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
297pub mod arrow_utils {
299 use super::*;
300
301 pub const fn is_arrow_available() -> bool {
303 cfg!(feature = "arrow-support")
304 }
305
306 #[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 let schema = Arc::new(Schema::new(vec![
315 Field::new("id", DataType::Int32, false),
316 Field::new("value", DataType::Float32, false),
317 ]));
318
319 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 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 #[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 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 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}