use std::{
ffi::CString,
os::raw::c_char,
path::{Path, PathBuf},
ptr,
};
use typed_builder::TypedBuilder;
use crate::{
core::{
error::{self, FACT_COLUMN_INDEX, Result},
schema,
table::{Table, TableView},
},
io::{path_to_cstring, resolve_write_execution},
runtime::Runtime,
};
#[derive(Debug, Clone, TypedBuilder)]
pub struct ParquetTableWriteOptions {
#[builder(setter(transform = |path: impl AsRef<Path>| path.as_ref().to_path_buf()))]
filepath: PathBuf,
#[builder(default, setter(transform = |names: &[&str]| Some(names.iter().map(|s| (*s).to_string()).collect())))]
column_names: Option<Vec<String>>,
#[builder(default)]
row_group_size_rows: i32,
#[builder(default)]
dictionary_policy: ParquetDictionaryPolicy,
#[builder(default, setter(prefix = "with_", transform = |runtime: &Runtime| Some(runtime.clone())))]
runtime: Option<Runtime>,
}
impl ParquetTableWriteOptions {
pub(crate) fn runtime(&self) -> Option<&Runtime> {
self.runtime.as_ref()
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum ParquetDictionaryPolicy {
#[default]
Default,
Adaptive,
Never,
Always,
}
impl ParquetDictionaryPolicy {
pub(crate) const fn ffi_value(self) -> i32 {
match self {
Self::Default => -1,
Self::Adaptive => 0,
Self::Never => 1,
Self::Always => 2,
}
}
}
pub fn write_parquet(table: &Table, options: &ParquetTableWriteOptions) -> Result<()> {
let operation = "io::parquet::write_parquet";
let table_view = table.view()?;
let execution = resolve_write_execution(&table_view, options.runtime(), operation)?;
write_parquet_with_exec(&table_view, options, execution.exec_options())
}
pub(crate) fn write_parquet_with_exec(
table: &TableView<'_>,
options: &ParquetTableWriteOptions,
exec_options: &cudf_sys::cudf_exec_options_t,
) -> Result<()> {
let filepath = path_to_cstring(&options.filepath, "io::parquet::write_parquet")?;
if let Some(ref names) = options.column_names {
let expected = table.num_columns()?;
if names.len() != expected {
return Err(schema::column_name_count_mismatch_error(
"io::parquet::write_parquet",
format!("column name count ({}) does not match table column count ({})", names.len(), expected),
names.len(),
expected,
));
}
}
let cstring_names: Option<Vec<CString>> = options
.column_names
.as_ref()
.map(|names| {
names
.iter()
.enumerate()
.map(|(index, s)| {
CString::new(s.as_str()).map_err(|_| {
error::interior_null_byte_error(
"io::parquet::write_parquet",
"column_name",
"column name contains interior null byte",
)
.with_fact(FACT_COLUMN_INDEX, index)
.with_column(s.to_string())
})
})
.collect::<Result<Vec<_>>>()
})
.transpose()?;
let mut name_ptrs_vec: Vec<*const c_char> =
cstring_names.as_ref().map(|names| names.iter().map(|c| c.as_ptr()).collect()).unwrap_or_default();
let (name_ptrs, num_cols) = if name_ptrs_vec.is_empty() {
(ptr::null_mut(), 0)
} else {
(name_ptrs_vec.as_mut_ptr(), name_ptrs_vec.len() as i32)
};
error::check_status_in_with_details(
unsafe {
cudf_sys::cudf_io_write_parquet_ex(
table.raw_handle(),
filepath.as_ptr(),
name_ptrs,
num_cols,
options.row_group_size_rows,
options.dictionary_policy.ffi_value(),
exec_options,
)
},
"io::parquet::write_parquet",
[("column_name_count", num_cols.to_string())],
)
}