use crate::dataframe::DataFrame;
use crate::error::{Error, Result};
use crate::optimized::OptimizedDataFrame;
use arrow::array::{ArrayRef, BooleanArray, Float64Array, Int64Array, StringArray};
use arrow::datatypes::{DataType, Field, Schema};
use arrow::record_batch::{RecordBatch, RecordBatchReader};
use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
use parquet::arrow::arrow_writer::ArrowWriter;
use parquet::basic::Compression;
use parquet::file::properties::WriterProperties;
use std::fs::File;
use std::path::Path;
use std::sync::Arc;
use super::convert::record_batches_to_dataframe;
use super::core::{validate_compression, ParquetReadOptions, ParquetWriteOptions};
pub fn read_parquet_advanced(
path: impl AsRef<Path>,
options: ParquetReadOptions,
) -> Result<DataFrame> {
let file = File::open(path.as_ref())
.map_err(|e| Error::IoError(format!("Failed to open Parquet file: {}", e)))?;
let mut builder = ParquetRecordBatchReaderBuilder::try_new(file)
.map_err(|e| Error::IoError(format!("Failed to parse Parquet file: {}", e)))?;
if let Some(batch_size) = options.batch_size {
builder = builder.with_batch_size(batch_size);
}
if let Some(row_groups) = options.row_groups {
builder = builder.with_row_groups(row_groups);
}
if let Some(columns) = &options.columns {
let schema = builder.schema();
let mut projection_indices = Vec::new();
for col_name in columns {
let idx = schema
.fields()
.iter()
.position(|field| field.name() == col_name)
.ok_or_else(|| Error::ColumnNotFound(col_name.clone()))?;
projection_indices.push(idx);
}
use parquet::arrow::ProjectionMask;
let mask = ProjectionMask::roots(builder.parquet_schema(), projection_indices);
builder = builder.with_projection(mask);
}
let reader = builder
.build()
.map_err(|e| Error::IoError(format!("Failed to read Parquet file: {}", e)))?;
let schema = reader.schema();
let mut all_batches = Vec::new();
for batch_result in reader {
let batch = batch_result
.map_err(|e| Error::IoError(format!("Failed to read record batch: {}", e)))?;
all_batches.push(batch);
}
if all_batches.is_empty() {
return Ok(DataFrame::new());
}
record_batches_to_dataframe(&all_batches, schema)
}
pub fn write_parquet_advanced(
df: &OptimizedDataFrame,
path: impl AsRef<Path>,
options: ParquetWriteOptions,
) -> Result<()> {
validate_compression(options.compression)?;
let schema_fields: Vec<Field> = df
.column_names()
.iter()
.filter_map(|col_name| {
if let Ok(col_view) = df.column(col_name) {
let data_type = match col_view.column_type() {
crate::column::ColumnType::Int64 => DataType::Int64,
crate::column::ColumnType::Float64 => DataType::Float64,
crate::column::ColumnType::Boolean => DataType::Boolean,
crate::column::ColumnType::String => DataType::Utf8,
};
Some(Field::new(col_name, data_type, true))
} else {
None
}
})
.collect();
let schema = Schema::new(schema_fields);
let schema_ref = Arc::new(schema);
let mut props_builder =
WriterProperties::builder().set_compression(Compression::from(options.compression));
if let Some(row_group_size) = options.row_group_size {
props_builder = props_builder.set_max_row_group_row_count(Some(row_group_size));
}
if let Some(page_size) = options.page_size {
props_builder = props_builder.set_data_page_size_limit(page_size);
}
props_builder = props_builder.set_dictionary_enabled(options.enable_dictionary);
let props = props_builder.build();
let arrays: Vec<ArrayRef> = df
.column_names()
.iter()
.filter_map(|col_name| {
let col_view = match df.column(col_name) {
Ok(s) => s,
Err(_) => return None,
};
match col_view.column_type() {
crate::column::ColumnType::Int64 => {
if let Some(int_col) = col_view.as_int64() {
let mut values = Vec::with_capacity(df.row_count());
let mut validity = Vec::with_capacity(df.row_count());
for i in 0..df.row_count() {
match int_col.get(i) {
Ok(Some(val)) => {
values.push(val);
validity.push(true);
}
Ok(None) => {
values.push(0);
validity.push(false);
}
Err(_) => {
values.push(0);
validity.push(false);
}
}
}
let array = Int64Array::new(values.into(), Some(validity.into()));
Some(Arc::new(array) as ArrayRef)
} else {
None
}
}
crate::column::ColumnType::Float64 => {
if let Some(float_col) = col_view.as_float64() {
let mut values = Vec::with_capacity(df.row_count());
let mut validity = Vec::with_capacity(df.row_count());
for i in 0..df.row_count() {
match float_col.get(i) {
Ok(Some(val)) => {
values.push(val);
validity.push(true);
}
Ok(None) => {
values.push(0.0);
validity.push(false);
}
Err(_) => {
values.push(0.0);
validity.push(false);
}
}
}
let array = Float64Array::new(values.into(), Some(validity.into()));
Some(Arc::new(array) as ArrayRef)
} else {
None
}
}
crate::column::ColumnType::Boolean => {
if let Some(bool_col) = col_view.as_boolean() {
let mut values = Vec::with_capacity(df.row_count());
let mut validity = Vec::with_capacity(df.row_count());
for i in 0..df.row_count() {
match bool_col.get(i) {
Ok(Some(val)) => {
values.push(val);
validity.push(true);
}
Ok(None) => {
values.push(false);
validity.push(false);
}
Err(_) => {
values.push(false);
validity.push(false);
}
}
}
let array = BooleanArray::new(values.into(), Some(validity.into()));
Some(Arc::new(array) as ArrayRef)
} else {
None
}
}
crate::column::ColumnType::String => {
if let Some(str_col) = col_view.as_string() {
let mut values = Vec::with_capacity(df.row_count());
let mut validity = Vec::with_capacity(df.row_count());
for i in 0..df.row_count() {
match str_col.get(i) {
Ok(Some(val)) => {
values.push(val.to_string());
validity.push(true);
}
Ok(None) => {
values.push(String::new());
validity.push(false);
}
Err(_) => {
values.push(String::new());
validity.push(false);
}
}
}
let string_values: Vec<Option<&str>> = values
.iter()
.zip(validity.iter())
.map(|(s, &is_valid)| if is_valid { Some(s.as_str()) } else { None })
.collect();
let array = StringArray::from(string_values);
Some(Arc::new(array) as ArrayRef)
} else {
None
}
}
}
})
.collect();
let batch = RecordBatch::try_new(schema_ref.clone(), arrays)
.map_err(|e| Error::Cast(format!("Failed to create record batch: {}", e)))?;
let file = File::create(path.as_ref())
.map_err(|e| Error::IoError(format!("Failed to create Parquet file: {}", e)))?;
let mut writer = ArrowWriter::try_new(file, schema_ref, Some(props))
.map_err(|e| Error::IoError(format!("Failed to create Parquet writer: {}", e)))?;
writer
.write(&batch)
.map_err(|e| Error::IoError(format!("Failed to write record batch: {}", e)))?;
writer
.close()
.map_err(|e| Error::IoError(format!("Failed to close Parquet file: {}", e)))?;
Ok(())
}