use std::io::Write;
use super::common::write_continuation;
use super::{super::ARROW_MAGIC, common::WriteOptions};
use arrow::datatypes::Schema;
use arrow::array::Array;
use arrow::chunk::Chunk;
use arrow::error::{Error, Result};
use arrow::io::ipc::write::{default_ipc_fields, schema_to_bytes};
use arrow::io::parquet::write::to_parquet_schema;
use crate::ColumnMeta;
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum State {
None,
Started,
Written,
Finished,
}
pub struct NativeWriter<W: Write> {
pub(crate) writer: OffsetWriter<W>,
pub(crate) options: WriteOptions,
pub(crate) schema: Schema,
pub metas: Vec<ColumnMeta>,
pub(crate) scratch: Vec<u8>,
pub(crate) state: State,
}
impl<W: Write> NativeWriter<W> {
pub fn try_new(writer: W, schema: &Schema, options: WriteOptions) -> Result<Self> {
let mut slf = Self::new(writer, schema.clone(), options);
slf.start()?;
Ok(slf)
}
pub fn new(writer: W, schema: Schema, options: WriteOptions) -> Self {
let num_cols = schema.fields.len();
Self {
writer: OffsetWriter {
w: writer,
offset: 0,
},
options,
schema,
metas: Vec::with_capacity(num_cols),
scratch: Vec::with_capacity(0),
state: State::None,
}
}
pub fn into_inner(self) -> W {
self.writer.w
}
pub fn start(&mut self) -> Result<()> {
if self.state != State::None {
return Err(Error::OutOfSpec(
"The strawboat file can only be started once".to_string(),
));
}
self.writer.write_all(&ARROW_MAGIC[..])?;
self.writer.write_all(&[0, 0])?;
self.state = State::Started;
Ok(())
}
pub fn write(&mut self, chunk: &Chunk<Box<dyn Array>>) -> Result<()> {
if self.state == State::Written {
return Err(Error::OutOfSpec(
"The strawboat file can only accept one RowGroup in a single file".to_string(),
));
}
if self.state != State::Started {
return Err(Error::OutOfSpec(
"The strawboat file must be started before it can be written to. Call `start` before `write`".to_string(),
));
}
assert_eq!(chunk.arrays().len(), self.schema.fields.len());
let schema_descriptor = to_parquet_schema(&self.schema)?;
self.encode_chunk(schema_descriptor, chunk)?;
self.state = State::Written;
Ok(())
}
pub fn finish(&mut self) -> Result<()> {
if self.state != State::Written {
return Err(Error::OutOfSpec(
"The strawboat file must be written before it can be finished. Call `start` before `finish`".to_string(),
));
}
let schema_bytes = schema_to_bytes(&self.schema, &default_ipc_fields(&self.schema.fields));
self.writer.write_all(&schema_bytes)?;
let meta_start = self.writer.offset();
{
self.writer.write_all(&self.metas.len().to_le_bytes())?;
for meta in &self.metas {
self.writer.write_all(&meta.offset.to_le_bytes())?;
self.writer.write_all(&meta.pages.len().to_le_bytes())?;
for page in meta.pages.iter() {
self.writer.write_all(&page.length.to_le_bytes())?;
self.writer.write_all(&page.num_values.to_le_bytes())?;
}
}
}
let meta_end = self.writer.offset();
let schema_size = schema_bytes.len();
self.writer.write_all(&(schema_size as u32).to_le_bytes())?;
self.writer
.write_all(&((meta_end - meta_start) as u32).to_le_bytes())?;
write_continuation(&mut self.writer, 0)?;
self.writer.flush()?;
self.state = State::Finished;
Ok(())
}
pub fn total_size(&self) -> usize {
self.writer.offset()
}
}
pub struct OffsetWriter<W: Write> {
pub w: W,
pub offset: u64,
}
impl<W: Write> std::io::Write for OffsetWriter<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let size = self.w.write(buf)?;
self.offset += size as u64;
Ok(size)
}
fn flush(&mut self) -> std::io::Result<()> {
self.w.flush()
}
}
pub trait OffsetWrite: std::io::Write {
fn offset(&self) -> usize;
}
impl<W: std::io::Write> OffsetWrite for OffsetWriter<W> {
fn offset(&self) -> usize {
self.offset as usize
}
}