mito2/sst/parquet/
index_writer.rs1use bytes::Bytes;
16use datatypes::arrow::datatypes::SchemaRef;
17use datatypes::arrow::record_batch::RecordBatch;
18use futures::future::BoxFuture;
19use object_store::{ObjectStore, Writer};
20use parquet::arrow::AsyncArrowWriter;
21use parquet::arrow::async_writer::AsyncFileWriter;
22use parquet::basic::{Compression, Encoding, ZstdLevel};
23use parquet::errors::ParquetError;
24use parquet::file::metadata::KeyValue;
25use parquet::file::properties::WriterProperties;
26use snafu::{OptionExt, ResultExt};
27
28use crate::access_layer::TempFileCleaner;
29use crate::error::{OpenDalSnafu, Result, UnexpectedSnafu, WriteParquetSnafu};
30use crate::sst::{DEFAULT_WRITE_BUFFER_SIZE, DEFAULT_WRITE_CONCURRENCY};
31
32type ArrowWriter = AsyncArrowWriter<AsyncWriter>;
33
34struct AsyncWriter {
37 inner: Writer,
38 output_bytes: u64,
39}
40
41impl AsyncWriter {
42 fn new(inner: Writer) -> Self {
43 Self {
44 inner,
45 output_bytes: 0,
46 }
47 }
48
49 fn output_bytes(&self) -> u64 {
50 self.output_bytes
51 }
52
53 fn into_inner(self) -> Writer {
54 self.inner
55 }
56}
57
58impl AsyncFileWriter for AsyncWriter {
59 fn write(&mut self, bytes: Bytes) -> BoxFuture<'_, parquet::errors::Result<()>> {
60 Box::pin(async move {
61 let len = bytes.len() as u64;
62 self.inner
63 .write(bytes)
64 .await
65 .map_err(|error| ParquetError::External(Box::new(error)))?;
66 self.output_bytes += len;
67 Ok(())
68 })
69 }
70
71 fn complete(&mut self) -> BoxFuture<'_, parquet::errors::Result<()>> {
72 Box::pin(async move {
73 self.inner
74 .close()
75 .await
76 .map(|_| ())
77 .map_err(|error| ParquetError::External(Box::new(error)))
78 })
79 }
80}
81
82pub(crate) struct ParquetIndexWriter {
84 name: &'static str,
85 object_store: ObjectStore,
86 file_name: String,
87 writer: Option<ArrowWriter>,
88}
89
90impl ParquetIndexWriter {
91 pub(crate) async fn try_new(
93 name: &'static str,
94 object_store: ObjectStore,
95 path: &str,
96 schema: &SchemaRef,
97 row_group_size: usize,
98 key_value_metadata: Option<Vec<KeyValue>>,
99 ) -> Result<Self> {
100 let file_name = path.rsplit('/').next().unwrap_or(path).to_string();
101 let output = object_store
102 .writer_with(path)
103 .chunk(DEFAULT_WRITE_BUFFER_SIZE.as_bytes() as usize)
104 .concurrent(DEFAULT_WRITE_CONCURRENCY)
105 .await
106 .context(OpenDalSnafu)?;
107 let properties = WriterProperties::builder()
108 .set_compression(Compression::ZSTD(ZstdLevel::default()))
109 .set_encoding(Encoding::PLAIN)
110 .set_max_row_group_row_count(Some(row_group_size))
111 .set_column_index_truncate_length(None)
112 .set_statistics_truncate_length(None)
113 .set_key_value_metadata(key_value_metadata)
114 .build();
115 let writer =
116 AsyncArrowWriter::try_new(AsyncWriter::new(output), schema.clone(), Some(properties))
117 .context(WriteParquetSnafu)?;
118
119 Ok(Self {
120 name,
121 object_store,
122 file_name,
123 writer: Some(writer),
124 })
125 }
126
127 pub(crate) async fn write(&mut self, batch: &RecordBatch) -> Result<()> {
129 self.writer
130 .as_mut()
131 .with_context(|| UnexpectedSnafu {
132 reason: format!("{} Parquet writer is closed", self.name),
133 })?
134 .write(batch)
135 .await
136 .context(WriteParquetSnafu)
137 }
138
139 pub(crate) async fn finish(&mut self) -> Result<u64> {
141 self.writer
142 .as_mut()
143 .with_context(|| UnexpectedSnafu {
144 reason: format!("{} Parquet writer is closed", self.name),
145 })?
146 .finish()
147 .await
148 .context(WriteParquetSnafu)?;
149 let writer = self.writer.take().with_context(|| UnexpectedSnafu {
150 reason: format!("{} Parquet writer is closed", self.name),
151 })?;
152 Ok(writer.into_inner().output_bytes())
153 }
154
155 pub(crate) async fn abort(&mut self) {
157 if let Some(writer) = self.writer.take() {
158 let mut writer = writer.into_inner().into_inner();
159 if let Err(error) = writer.abort().await {
160 common_telemetry::warn!(error; "Failed to abort {} writer", self.name);
161 }
162 }
163
164 TempFileCleaner::clean_atomic_dir_files(&self.object_store, &[&self.file_name]).await;
165 }
166}