1use std::fmt::Formatter;
21use std::fs::{File, OpenOptions};
22use std::io::BufReader;
23use std::path::PathBuf;
24use std::str::FromStr;
25use std::sync::Arc;
26
27use crate::{Session, TableProvider, TableProviderFactory};
28use arrow::array::{RecordBatch, RecordBatchReader, RecordBatchWriter};
29use arrow::datatypes::SchemaRef;
30use datafusion_common::{Constraints, DataFusionError, Result, config_err, plan_err};
31use datafusion_common_runtime::SpawnedTask;
32use datafusion_datasource::sink::{DataSink, DataSinkExec};
33use datafusion_execution::{SendableRecordBatchStream, TaskContext};
34use datafusion_expr::dml::InsertOp;
35use datafusion_expr::{CreateExternalTable, Expr, SortExpr, TableType};
36use datafusion_physical_expr::create_lex_ordering;
37use datafusion_physical_plan::stream::RecordBatchReceiverStreamBuilder;
38use datafusion_physical_plan::streaming::{PartitionStream, StreamingTableExec};
39use datafusion_physical_plan::{DisplayAs, DisplayFormatType, ExecutionPlan};
40
41use async_trait::async_trait;
42use futures::StreamExt;
43
44#[derive(Debug, Default)]
46pub struct StreamTableFactory {}
47
48#[async_trait]
49impl TableProviderFactory for StreamTableFactory {
50 async fn create(
51 &self,
52 state: &dyn Session,
53 cmd: &CreateExternalTable,
54 ) -> Result<Arc<dyn TableProvider>> {
55 let schema: SchemaRef = Arc::clone(cmd.schema.inner());
56 let location = match cmd.locations.as_slice() {
57 [single] => single.clone(),
58 _ => {
59 return config_err!(
60 "Stream tables support exactly one location; \
61 use a listing table to read multiple files"
62 );
63 }
64 };
65 let encoding = cmd.file_type.parse()?;
66 let header = if let Ok(opt) = cmd
67 .options
68 .get("format.has_header")
69 .map(|has_header| bool::from_str(has_header.to_lowercase().as_str()))
70 .transpose()
71 {
72 opt.unwrap_or(false)
73 } else {
74 return config_err!(
75 "Valid values for format.has_header option are 'true' or 'false'"
76 );
77 };
78
79 let source = FileStreamProvider::new_file(schema, location.into())
80 .with_encoding(encoding)
81 .with_batch_size(state.config().batch_size())
82 .with_header(header);
83
84 let config = StreamConfig::new(Arc::new(source))
85 .with_order(cmd.order_exprs.clone())
86 .with_constraints(cmd.constraints.clone());
87
88 Ok(Arc::new(StreamTable(Arc::new(config))))
89 }
90}
91
92#[derive(Debug, Clone)]
94pub enum StreamEncoding {
95 Csv,
97 Json,
99}
100
101impl FromStr for StreamEncoding {
102 type Err = DataFusionError;
103
104 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
105 match s.to_ascii_lowercase().as_str() {
106 "csv" => Ok(Self::Csv),
107 "json" => Ok(Self::Json),
108 _ => plan_err!("Unrecognized StreamEncoding {}", s),
109 }
110 }
111}
112
113pub trait StreamProvider: std::fmt::Debug + Send + Sync {
117 fn schema(&self) -> &SchemaRef;
119 fn reader(&self) -> Result<Box<dyn RecordBatchReader>>;
121 fn writer(&self) -> Result<Box<dyn RecordBatchWriter>> {
123 unimplemented!()
124 }
125 fn stream_write_display(
127 &self,
128 t: DisplayFormatType,
129 f: &mut Formatter,
130 ) -> std::fmt::Result;
131}
132
133#[derive(Debug)]
141pub struct FileStreamProvider {
142 location: PathBuf,
143 encoding: StreamEncoding,
144 pub schema: SchemaRef,
146 header: bool,
147 batch_size: usize,
148}
149
150impl FileStreamProvider {
151 pub fn new_file(schema: SchemaRef, location: PathBuf) -> Self {
159 Self {
160 schema,
161 location,
162 batch_size: 1024,
163 encoding: StreamEncoding::Csv,
164 header: false,
165 }
166 }
167
168 pub fn with_batch_size(mut self, batch_size: usize) -> Self {
170 self.batch_size = batch_size;
171 self
172 }
173
174 pub fn with_header(mut self, header: bool) -> Self {
176 self.header = header;
177 self
178 }
179
180 pub fn with_encoding(mut self, encoding: StreamEncoding) -> Self {
182 self.encoding = encoding;
183 self
184 }
185}
186
187impl StreamProvider for FileStreamProvider {
188 fn schema(&self) -> &SchemaRef {
189 &self.schema
190 }
191
192 fn reader(&self) -> Result<Box<dyn RecordBatchReader>> {
193 let file = File::open(&self.location)?;
194 let schema = Arc::clone(&self.schema);
195 match &self.encoding {
196 StreamEncoding::Csv => {
197 let reader = arrow::csv::ReaderBuilder::new(schema)
198 .with_header(self.header)
199 .with_batch_size(self.batch_size)
200 .build(file)?;
201
202 Ok(Box::new(reader))
203 }
204 StreamEncoding::Json => {
205 let reader = arrow::json::ReaderBuilder::new(schema)
206 .with_batch_size(self.batch_size)
207 .build(BufReader::new(file))?;
208
209 Ok(Box::new(reader))
210 }
211 }
212 }
213
214 fn writer(&self) -> Result<Box<dyn RecordBatchWriter>> {
215 match &self.encoding {
216 StreamEncoding::Csv => {
217 let header = self.header && !self.location.exists();
218 let file = OpenOptions::new()
219 .create(true)
220 .append(true)
221 .open(&self.location)?;
222 let writer = arrow::csv::WriterBuilder::new()
223 .with_header(header)
224 .build(file);
225
226 Ok(Box::new(writer))
227 }
228 StreamEncoding::Json => {
229 let file = OpenOptions::new()
230 .create(true)
231 .append(true)
232 .open(&self.location)?;
233 Ok(Box::new(arrow::json::LineDelimitedWriter::new(file)))
234 }
235 }
236 }
237
238 fn stream_write_display(
239 &self,
240 _t: DisplayFormatType,
241 f: &mut Formatter,
242 ) -> std::fmt::Result {
243 f.debug_struct("StreamWrite")
244 .field("location", &self.location)
245 .field("batch_size", &self.batch_size)
246 .field("encoding", &self.encoding)
247 .field("header", &self.header)
248 .finish_non_exhaustive()
249 }
250}
251
252#[derive(Debug)]
254pub struct StreamConfig {
255 source: Arc<dyn StreamProvider>,
256 order: Vec<Vec<SortExpr>>,
257 constraints: Constraints,
258}
259
260impl StreamConfig {
261 pub fn new(source: Arc<dyn StreamProvider>) -> Self {
263 Self {
264 source,
265 order: vec![],
266 constraints: Constraints::default(),
267 }
268 }
269
270 pub fn with_order(mut self, order: Vec<Vec<SortExpr>>) -> Self {
272 self.order = order;
273 self
274 }
275
276 pub fn with_constraints(mut self, constraints: Constraints) -> Self {
278 self.constraints = constraints;
279 self
280 }
281
282 fn reader(&self) -> Result<Box<dyn RecordBatchReader>> {
283 self.source.reader()
284 }
285
286 fn writer(&self) -> Result<Box<dyn RecordBatchWriter>> {
287 self.source.writer()
288 }
289}
290
291#[derive(Debug)]
302pub struct StreamTable(Arc<StreamConfig>);
303
304impl StreamTable {
305 pub fn new(config: Arc<StreamConfig>) -> Self {
307 Self(config)
308 }
309}
310
311#[async_trait]
312impl TableProvider for StreamTable {
313 fn schema(&self) -> SchemaRef {
314 Arc::clone(self.0.source.schema())
315 }
316
317 fn constraints(&self) -> Option<&Constraints> {
318 Some(&self.0.constraints)
319 }
320
321 fn table_type(&self) -> TableType {
322 TableType::Base
323 }
324
325 async fn scan(
326 &self,
327 state: &dyn Session,
328 projection: Option<&Vec<usize>>,
329 _filters: &[Expr],
330 limit: Option<usize>,
331 ) -> Result<Arc<dyn ExecutionPlan>> {
332 let projected_schema = match projection {
333 Some(p) => {
334 let projected = Arc::new(self.0.source.schema().project(p)?);
335 create_lex_ordering(&projected, &self.0.order, state.execution_props())?
336 }
337 None => create_lex_ordering(
338 self.0.source.schema(),
339 &self.0.order,
340 state.execution_props(),
341 )?,
342 };
343
344 Ok(Arc::new(StreamingTableExec::try_new(
345 Arc::clone(self.0.source.schema()),
346 vec![Arc::new(StreamRead(Arc::clone(&self.0))) as _],
347 projection,
348 projected_schema,
349 true,
350 limit,
351 )?))
352 }
353
354 async fn insert_into(
355 &self,
356 _state: &dyn Session,
357 input: Arc<dyn ExecutionPlan>,
358 _insert_op: InsertOp,
359 ) -> Result<Arc<dyn ExecutionPlan>> {
360 let schema = self.0.source.schema();
361 let orders =
362 create_lex_ordering(schema, &self.0.order, _state.execution_props())?;
363 let ordering = orders.into_iter().next().map(Into::into);
365
366 Ok(Arc::new(DataSinkExec::new(
367 input,
368 Arc::new(StreamWrite(Arc::clone(&self.0))),
369 ordering,
370 )))
371 }
372}
373
374#[derive(Debug)]
375struct StreamRead(Arc<StreamConfig>);
376
377impl PartitionStream for StreamRead {
378 fn schema(&self) -> &SchemaRef {
379 self.0.source.schema()
380 }
381
382 fn execute(&self, _ctx: Arc<TaskContext>) -> SendableRecordBatchStream {
383 let config = Arc::clone(&self.0);
384 let schema = Arc::clone(self.0.source.schema());
385 let mut builder = RecordBatchReceiverStreamBuilder::new(schema, 2);
386 let tx = builder.tx();
387 builder.spawn_blocking(move || {
388 let reader = config.reader()?;
389 for b in reader {
390 if tx.blocking_send(b.map_err(Into::into)).is_err() {
391 break;
392 }
393 }
394 Ok(())
395 });
396 builder.build()
397 }
398}
399
400#[derive(Debug)]
401struct StreamWrite(Arc<StreamConfig>);
402
403impl DisplayAs for StreamWrite {
404 fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> std::fmt::Result {
405 self.0.source.stream_write_display(t, f)
406 }
407}
408
409#[async_trait]
410impl DataSink for StreamWrite {
411 fn schema(&self) -> &SchemaRef {
412 self.0.source.schema()
413 }
414
415 async fn write_all(
416 &self,
417 mut data: SendableRecordBatchStream,
418 _context: &Arc<TaskContext>,
419 ) -> Result<u64> {
420 let config = Arc::clone(&self.0);
421 let (sender, mut receiver) = tokio::sync::mpsc::channel::<RecordBatch>(2);
422 let write_task = SpawnedTask::spawn_blocking(move || {
424 let mut count = 0_u64;
425 let mut writer = config.writer()?;
426 while let Some(batch) = receiver.blocking_recv() {
427 count += batch.num_rows() as u64;
428 writer.write(&batch)?;
429 }
430 Ok(count)
431 });
432
433 while let Some(b) = data.next().await.transpose()? {
434 if sender.send(b).await.is_err() {
435 break;
436 }
437 }
438 drop(sender);
439 write_task
440 .join_unwind()
441 .await
442 .map_err(|e| DataFusionError::ExecutionJoin(Box::new(e)))?
443 }
444}