use std::fs::File;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{Context, Result};
use arrow::datatypes::Schema;
use parquet::arrow::ArrowWriter as ParquetWriter;
use parquet::file::properties::WriterProperties;
use parquet::file::metadata::ParquetMetaData;
pub use parquet;
use super::{StructArrayBuilder, TableWriter};
#[derive(Debug, Default, Clone)]
pub struct ParquetTableWriterConfig {
pub autoflush_row_group_len: Option<usize>,
pub autoflush_buffer_size: Option<usize>,
}
pub struct ParquetTableWriter<Builder: Default + StructArrayBuilder> {
base_path: PathBuf,
pub autoflush_row_group_len: usize,
pub autoflush_buffer_size: Option<usize>,
schema: Arc<Schema>,
properties: WriterProperties,
file_writer: Option<(PathBuf, ParquetWriter<File>)>, num_written_files: u64,
builder: Builder,
}
impl<Builder: Default + StructArrayBuilder> TableWriter for ParquetTableWriter<Builder> {
type Schema = (Arc<Schema>, WriterProperties);
type CloseResult = ParquetMetaData;
type Config = ParquetTableWriterConfig;
fn new(
path: PathBuf,
(schema, properties): Self::Schema,
ParquetTableWriterConfig {
autoflush_row_group_len,
autoflush_buffer_size,
}: Self::Config,
) -> Result<Self> {
let base_path = path;
let mut writer = ParquetTableWriter {
base_path,
autoflush_row_group_len: autoflush_row_group_len
.unwrap_or(properties.max_row_group_size() * 9 / 10),
autoflush_buffer_size,
schema, properties,
file_writer: None,
num_written_files: 0,
builder: Builder::default(),
};
writer.new_file_writer()?;
Ok(writer)
}
fn flush(&mut self) -> Result<()> {
let struct_array = self.builder.finish()?;
let (path, file_writer) = self
.file_writer
.as_mut()
.expect("File writer is unexpectedly None");
file_writer
.write(&struct_array.into())
.with_context(|| format!("Could not write to {}", path.display()))?;
file_writer
.flush()
.with_context(|| format!("Could not flush to {}", path.display()))?;
if file_writer.flushed_row_groups().len() >= (i16::MAX - 2).try_into().expect("i16 overflowed usize") {
self.new_file_writer()?;
}
Ok(())
}
fn close(mut self) -> Result<ParquetMetaData> {
self.flush()?;
let (path, file_writer) = self.file_writer
.take()
.expect("File writer is unexpectedly None");
file_writer
.close()
.with_context(|| format!("Could not close {}", path.display()))
}
}
impl<Builder: Default + StructArrayBuilder> ParquetTableWriter<Builder> {
fn new_file_writer(&mut self) -> Result<()> {
if let Some((path, file_writer)) = self.file_writer.take() {
file_writer.close().with_context(|| format!("Could not close {}", path.display()))?;
self.num_written_files += 1;
}
let mut path = if self.num_written_files == 0 {
self.base_path.to_owned()
} else {
let mut file_name = self.base_path.file_name().expect("file has no name").to_owned();
file_name.push(format!("_{}", self.num_written_files));
self.base_path.with_file_name(&file_name)
};
path.set_extension("parquet");
let file =
File::create(&path).with_context(|| format!("Could not create {}", path.display()))?;
let file_writer = ParquetWriter::try_new(file, self.schema.clone(), Some(self.properties.clone()))
.with_context(|| {
format!(
"Could not create writer for {} with schema {} and properties {:?}",
path.display(),
self.schema,
self.properties.clone()
)
})?;
self.file_writer = Some((path, file_writer));
Ok(())
}
pub fn builder(&mut self) -> Result<&mut Builder> {
if self.builder.len() >= self.autoflush_row_group_len {
self.flush()?;
}
if let Some(autoflush_buffer_size) = self.autoflush_buffer_size {
if self.builder.buffer_size() >= autoflush_buffer_size {
self.flush()?;
}
}
Ok(&mut self.builder)
}
}
impl<Builder: Default + StructArrayBuilder> Drop for ParquetTableWriter<Builder> {
fn drop(&mut self) {
if self.file_writer.is_some() {
self.flush().unwrap();
let (path, file_writer) = self.file_writer
.take()
.unwrap();
file_writer
.close()
.with_context(|| format!("Could not close {}", path.display()))
.unwrap();
}
}
}