common_datasource/
file_format.rs1pub mod csv;
16pub mod json;
17pub mod orc;
18pub mod parquet;
19#[cfg(test)]
20pub mod tests;
21
22pub const DEFAULT_SCHEMA_INFER_MAX_RECORD: usize = 1000;
23
24use std::collections::HashMap;
25use std::result;
26use std::sync::Arc;
27use std::task::Poll;
28
29use arrow::record_batch::RecordBatch;
30use arrow_schema::{ArrowError, Schema as ArrowSchema};
31use async_trait::async_trait;
32use bytes::{Buf, Bytes};
33use common_recordbatch::DfSendableRecordBatchStream;
34use datafusion::datasource::file_format::file_compression_type::FileCompressionType as DfCompressionType;
35use datafusion::datasource::listing::PartitionedFile;
36use datafusion::datasource::object_store::ObjectStoreUrl;
37use datafusion::datasource::physical_plan::{
38 FileGroup, FileOpenFuture, FileScanConfigBuilder, FileSource, FileStream,
39};
40use datafusion::error::{DataFusionError, Result as DataFusionResult};
41use datafusion::physical_plan::SendableRecordBatchStream;
42use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
43use futures::{StreamExt, TryStreamExt};
44use object_store::ObjectStore;
45use snafu::ResultExt;
46use tokio::io::AsyncWriteExt;
47use tokio_util::compat::FuturesAsyncWriteCompatExt;
48
49use self::csv::CsvFormat;
50use self::json::JsonFormat;
51use self::orc::OrcFormat;
52use self::parquet::ParquetFormat;
53use crate::DEFAULT_WRITE_BUFFER_SIZE;
54use crate::buffered_writer::DfRecordBatchEncoder;
55use crate::compressed_writer::{CompressedWriter, IntoCompressedWriter};
56use crate::compression::CompressionType;
57use crate::error::{self, Result};
58use crate::share_buffer::SharedBuffer;
59
60pub const FORMAT_COMPRESSION_TYPE: &str = "compression_type";
61pub const FORMAT_DELIMITER: &str = "delimiter";
62pub const FORMAT_SCHEMA_INFER_MAX_RECORD: &str = "schema_infer_max_record";
63pub const FORMAT_HEADERS: &str = "headers";
64pub const FORMAT_HAS_HEADER: &str = "has_header";
65pub const FORMAT_SKIP_BAD_RECORDS: &str = "skip_bad_records";
66pub const FORMAT_STRICT_HEADERS: &str = "strict_headers";
67pub const FORMAT_TYPE: &str = "format";
68pub const FILE_PATTERN: &str = "pattern";
69pub const TIMESTAMP_FORMAT: &str = "timestamp_format";
70pub const TIME_FORMAT: &str = "time_format";
71pub const DATE_FORMAT: &str = "date_format";
72
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub enum Format {
75 Csv(CsvFormat),
76 Json(JsonFormat),
77 Parquet(ParquetFormat),
78 Orc(OrcFormat),
79}
80
81impl Format {
82 pub fn suffix(&self) -> &'static str {
83 match self {
84 Format::Csv(_) => ".csv",
85 Format::Json(_) => ".json",
86 Format::Parquet(_) => ".parquet",
87 &Format::Orc(_) => ".orc",
88 }
89 }
90}
91
92impl TryFrom<&HashMap<String, String>> for Format {
93 type Error = error::Error;
94
95 fn try_from(options: &HashMap<String, String>) -> Result<Self> {
96 let format = options
97 .get(FORMAT_TYPE)
98 .map(|format| format.to_ascii_uppercase())
99 .unwrap_or_else(|| "PARQUET".to_string());
100
101 match format.as_str() {
102 "CSV" => Ok(Self::Csv(CsvFormat::try_from(options)?)),
103 "JSON" => Ok(Self::Json(JsonFormat::try_from(options)?)),
104 "PARQUET" => Ok(Self::Parquet(ParquetFormat::default())),
105 "ORC" => Ok(Self::Orc(OrcFormat)),
106 _ => error::UnsupportedFormatSnafu { format: &format }.fail(),
107 }
108 }
109}
110
111#[async_trait]
112pub trait FileFormat: Send + Sync + std::fmt::Debug {
113 async fn infer_schema(&self, store: &ObjectStore, path: &str) -> Result<ArrowSchema>;
114}
115
116pub trait ArrowDecoder: Send + 'static {
117 fn decode(&mut self, buf: &[u8]) -> result::Result<usize, ArrowError>;
124
125 fn flush(&mut self) -> result::Result<Option<RecordBatch>, ArrowError>;
132}
133
134impl ArrowDecoder for arrow::csv::reader::Decoder {
135 fn decode(&mut self, buf: &[u8]) -> result::Result<usize, ArrowError> {
136 self.decode(buf)
137 }
138
139 fn flush(&mut self) -> result::Result<Option<RecordBatch>, ArrowError> {
140 self.flush()
141 }
142}
143
144impl ArrowDecoder for arrow::json::reader::Decoder {
145 fn decode(&mut self, buf: &[u8]) -> result::Result<usize, ArrowError> {
146 self.decode(buf)
147 }
148
149 fn flush(&mut self) -> result::Result<Option<RecordBatch>, ArrowError> {
150 self.flush()
151 }
152}
153
154pub fn open_with_decoder<T: ArrowDecoder, F: Fn() -> DataFusionResult<T>>(
155 object_store: Arc<ObjectStore>,
156 path: String,
157 compression_type: CompressionType,
158 decoder_factory: F,
159) -> DataFusionResult<FileOpenFuture> {
160 let mut decoder = decoder_factory()?;
161 Ok(Box::pin(async move {
162 let reader = object_store
163 .reader(&path)
164 .await
165 .map_err(|e| DataFusionError::External(Box::new(e)))?
166 .into_bytes_stream(..)
167 .await
168 .map_err(|e| DataFusionError::External(Box::new(e)))?;
169
170 let mut upstream = compression_type.convert_stream(reader).fuse();
171
172 let mut buffered = Bytes::new();
173
174 let stream = futures::stream::poll_fn(move |cx| {
175 loop {
176 if buffered.is_empty()
177 && let Some(result) = futures::ready!(upstream.poll_next_unpin(cx))
178 {
179 buffered = result?;
180 }
181
182 let decoded = decoder.decode(buffered.as_ref())?;
183
184 if decoded == 0 {
185 break;
186 } else {
187 buffered.advance(decoded);
188 }
189 }
190
191 Poll::Ready(decoder.flush().transpose())
192 });
193
194 Ok(stream.map_err(Into::into).boxed())
195 }))
196}
197
198pub async fn infer_schemas(
199 store: &ObjectStore,
200 files: &[String],
201 file_format: &dyn FileFormat,
202) -> Result<ArrowSchema> {
203 let mut schemas = Vec::with_capacity(files.len());
204 for file in files {
205 schemas.push(file_format.infer_schema(store, file).await?)
206 }
207 ArrowSchema::try_merge(schemas).context(error::MergeSchemaSnafu)
208}
209
210async fn write_to_compressed_writer(
214 compressed_writer: &mut CompressedWriter,
215 data: &[u8],
216) -> Result<()> {
217 if !data.is_empty() {
218 compressed_writer
219 .write_all(data)
220 .await
221 .context(error::AsyncWriteSnafu)?;
222 }
223 Ok(())
224}
225
226pub async fn stream_to_file<E>(
233 mut stream: SendableRecordBatchStream,
234 store: ObjectStore,
235 path: &str,
236 threshold: usize,
237 concurrency: usize,
238 compression_type: CompressionType,
239 encoder_factory: impl Fn(SharedBuffer) -> E,
240) -> Result<usize>
241where
242 E: DfRecordBatchEncoder,
243{
244 let writer = store
246 .writer_with(path)
247 .concurrent(concurrency)
248 .chunk(DEFAULT_WRITE_BUFFER_SIZE.as_bytes() as usize)
249 .await
250 .with_context(|_| error::WriteObjectSnafu { path })?
251 .into_futures_async_write()
252 .compat_write();
253
254 let mut compressed_writer = writer.into_compressed_writer(compression_type);
256
257 let buffer = SharedBuffer::with_capacity(threshold);
259 let mut encoder = encoder_factory(buffer.clone());
260
261 let mut rows = 0;
262
263 while let Some(batch) = stream.next().await {
265 let batch = batch.context(error::ReadRecordBatchSnafu)?;
266
267 encoder.write(&batch)?;
269 rows += batch.num_rows();
270
271 loop {
272 let chunk = {
273 let mut buffer_guard = buffer.buffer.lock().unwrap();
274 if buffer_guard.len() < threshold {
275 break;
276 }
277 buffer_guard.split_to(threshold)
278 };
279 write_to_compressed_writer(&mut compressed_writer, &chunk).await?;
280 }
281 }
282
283 if rows != 0 {
286 let final_data = {
288 let mut buffer_guard = buffer.buffer.lock().unwrap();
289 buffer_guard.split()
290 };
291 write_to_compressed_writer(&mut compressed_writer, &final_data).await?;
292 }
293
294 compressed_writer.shutdown().await?;
296
297 Ok(rows)
298}
299
300pub async fn file_to_stream(
305 store: &ObjectStore,
306 filename: &str,
307 file_source: Arc<dyn FileSource>,
308 projection: Option<Vec<usize>>,
309 compression_type: CompressionType,
310) -> Result<DfSendableRecordBatchStream> {
311 let df_compression: DfCompressionType = compression_type.into();
312 let config =
313 FileScanConfigBuilder::new(ObjectStoreUrl::local_filesystem(), file_source.clone())
314 .with_file_group(FileGroup::new(vec![PartitionedFile::new(
315 filename.to_string(),
316 0,
317 )]))
318 .with_projection_indices(projection)?
319 .with_file_compression_type(df_compression)
320 .build();
321
322 let store = Arc::new(object_store_opendal::OpendalStore::new(store.clone()));
323 let file_opener = config.file_source().create_file_opener(store, &config, 0)?;
324 let stream = FileStream::new(&config, 0, file_opener, &ExecutionPlanMetricsSet::new())?;
325
326 Ok(Box::pin(stream))
327}