use arrow_array::{Array, Float32Array, Float64Array, RecordBatch};
use crate::error::{Result, TeraError};
pub enum TimeSeriesColumn<'a> {
F32(&'a [f32]),
F64(&'a [f64]),
OwningF32(Vec<f32>),
}
impl<'a> TimeSeriesColumn<'a> {
pub fn len(&self) -> usize {
match self {
Self::F32(slice) => slice.len(),
Self::F64(slice) => slice.len(),
Self::OwningF32(vec) => vec.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn to_f32_slice(&'a self) -> &'a [f32] {
match self {
Self::F32(slice) => slice,
Self::OwningF32(vec) => vec.as_slice(),
Self::F64(_) => panic!("Llama a convert_f64_to_f32() para obtener una representación f32"),
}
}
}
pub fn extract_float_column<'a>(
batch: &'a RecordBatch,
column_index: usize,
) -> Result<TimeSeriesColumn<'a>> {
if column_index >= batch.num_columns() {
return Err(TeraError::Arrow(format!(
"Índice de columna {} fuera de rango (columnas disponibles: {})",
column_index,
batch.num_columns()
)));
}
let column = batch.column(column_index);
if let Some(arr) = column.as_any().downcast_ref::<Float32Array>() {
let values_slice: &[f32] = arr.values().as_ref();
Ok(TimeSeriesColumn::F32(values_slice))
} else if let Some(arr) = column.as_any().downcast_ref::<Float64Array>() {
let values_slice: &[f64] = arr.values().as_ref();
Ok(TimeSeriesColumn::F64(values_slice))
} else {
Err(TeraError::InvalidLayout(format!(
"La columna en índice {} no es del tipo Float32 o Float64",
column_index
)))
}
}
pub fn cast_bytes_to_f32(bytes: &[u8]) -> Result<&[f32]> {
if bytes.len() % std::mem::size_of::<f32>() != 0 {
return Err(TeraError::InvalidLayout(format!(
"El tamaño de los bytes ({}) no es múltiplo de size_of::<f32>() (4 bytes)",
bytes.len()
)));
}
let (head, body, tail) = unsafe { bytes.align_to::<f32>() };
if !head.is_empty() || !tail.is_empty() {
return Err(TeraError::InvalidLayout(
"El buffer de bytes no tiene la alineación de memoria requerida para f32".to_string(),
));
}
Ok(body)
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use arrow_schema::{DataType, Field, Schema};
#[test]
fn test_cast_bytes_to_f32() -> Result<()> {
let original: Vec<f32> = vec![1.0, 2.5, -3.14, 42.0, 0.0];
let bytes: &[u8] = bytemuck::cast_slice(&original);
let recovered = cast_bytes_to_f32(bytes)?;
assert_eq!(recovered, original.as_slice());
Ok(())
}
#[test]
fn test_arrow_float32_extraction() -> Result<()> {
let array = Float32Array::from(vec![10.0, 20.0, 30.0]);
let schema = Arc::new(Schema::new(vec![
Field::new("signal", DataType::Float32, false),
]));
let batch = RecordBatch::try_new(schema, vec![Arc::new(array)])
.map_err(|e| TeraError::Arrow(e.to_string()))?;
let col = extract_float_column(&batch, 0)?;
assert_eq!(col.len(), 3);
assert_eq!(col.to_f32_slice(), &[10.0, 20.0, 30.0]);
Ok(())
}
}