Skip to main content

datafusion_datasource/
file_sink_config.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::sync::Arc;
19
20use crate::ListingTableUrl;
21use crate::file_groups::FileGroup;
22use crate::sink::DataSink;
23use crate::write::demux::{DemuxedStreamReceiver, start_demuxer_task};
24
25use arrow::datatypes::{DataType, SchemaRef};
26use datafusion_common::Result;
27use datafusion_common_runtime::SpawnedTask;
28use datafusion_execution::object_store::ObjectStoreUrl;
29use datafusion_execution::{SendableRecordBatchStream, TaskContext};
30use datafusion_expr::dml::InsertOp;
31
32use async_trait::async_trait;
33use object_store::ObjectStore;
34
35#[cfg(feature = "proto")]
36mod proto;
37
38/// Determines how `FileSink` output paths are interpreted.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum FileOutputMode {
41    /// Infer output mode from the output URL (for example, by extension / trailing `/`).
42    #[default]
43    Automatic,
44    /// Write to a single output file at the exact output path.
45    SingleFile,
46    /// Write to a directory under the output path with generated filenames.
47    Directory,
48}
49
50impl FileOutputMode {
51    /// Resolve this mode into a `single_file_output` boolean for the demuxer.
52    pub fn single_file_output(self, base_output_path: &ListingTableUrl) -> bool {
53        match self {
54            Self::Automatic => {
55                !base_output_path.is_collection()
56                    && base_output_path.file_extension().is_some()
57            }
58            Self::SingleFile => true,
59            Self::Directory => false,
60        }
61    }
62}
63
64impl From<Option<bool>> for FileOutputMode {
65    fn from(value: Option<bool>) -> Self {
66        match value {
67            None => Self::Automatic,
68            Some(true) => Self::SingleFile,
69            Some(false) => Self::Directory,
70        }
71    }
72}
73
74impl From<FileOutputMode> for Option<bool> {
75    fn from(value: FileOutputMode) -> Self {
76        match value {
77            FileOutputMode::Automatic => None,
78            FileOutputMode::SingleFile => Some(true),
79            FileOutputMode::Directory => Some(false),
80        }
81    }
82}
83
84/// General behaviors for files that do `DataSink` operations
85#[async_trait]
86pub trait FileSink: DataSink {
87    /// Retrieves the file sink configuration.
88    fn config(&self) -> &FileSinkConfig;
89
90    /// Spawns writer tasks and joins them to perform file writing operations.
91    /// Is a critical part of `FileSink` trait, since it's the very last step for `write_all`.
92    ///
93    /// This function handles the process of writing data to files by:
94    /// 1. Spawning tasks for writing data to individual files.
95    /// 2. Coordinating the tasks using a demuxer to distribute data among files.
96    /// 3. Collecting results using `tokio::join`, ensuring that all tasks complete successfully.
97    ///
98    /// # Parameters
99    /// - `context`: The execution context (`TaskContext`) that provides resources
100    ///   like memory management and runtime environment.
101    /// - `demux_task`: A spawned task that handles demuxing, responsible for splitting
102    ///   an input [`SendableRecordBatchStream`] into dynamically determined partitions.
103    ///   See `start_demuxer_task()`
104    /// - `file_stream_rx`: A receiver that yields streams of record batches and their
105    ///   corresponding file paths for writing. See `start_demuxer_task()`
106    /// - `object_store`: A handle to the object store where the files are written.
107    ///
108    /// # Returns
109    /// - `Result<u64>`: Returns the total number of rows written across all files.
110    async fn spawn_writer_tasks_and_join(
111        &self,
112        context: &Arc<TaskContext>,
113        demux_task: SpawnedTask<Result<()>>,
114        file_stream_rx: DemuxedStreamReceiver,
115        object_store: Arc<dyn ObjectStore>,
116    ) -> Result<u64>;
117
118    /// File sink implementation of the [`DataSink::write_all`] method.
119    async fn write_all(
120        &self,
121        data: SendableRecordBatchStream,
122        context: &Arc<TaskContext>,
123    ) -> Result<u64> {
124        let config = self.config();
125        let object_store = context
126            .runtime_env()
127            .object_store(&config.object_store_url)?;
128        let (demux_task, file_stream_rx) = start_demuxer_task(config, data, context);
129        self.spawn_writer_tasks_and_join(
130            context,
131            demux_task,
132            file_stream_rx,
133            object_store,
134        )
135        .await
136    }
137}
138
139/// The base configurations to provide when creating a physical plan for
140/// writing to any given file format.
141#[derive(Debug, Clone)]
142pub struct FileSinkConfig {
143    /// The unresolved URL specified by the user
144    pub original_url: String,
145    /// Object store URL, used to get an ObjectStore instance
146    pub object_store_url: ObjectStoreUrl,
147    /// A collection of files organized into groups.
148    /// Each FileGroup contains one or more PartitionedFile objects.
149    pub file_group: FileGroup,
150    /// Vector of partition paths
151    pub table_paths: Vec<ListingTableUrl>,
152    /// The schema of the output file
153    pub output_schema: SchemaRef,
154    /// A vector of column names and their corresponding data types,
155    /// representing the partitioning columns for the file
156    pub table_partition_cols: Vec<(String, DataType)>,
157    /// Controls how new data should be written to the file, determining whether
158    /// to append to, overwrite, or replace records in existing files.
159    pub insert_op: InsertOp,
160    /// Controls whether partition columns are kept for the file
161    pub keep_partition_by_columns: bool,
162    /// File extension without a dot(.)
163    pub file_extension: String,
164    /// Determines how the output path is interpreted.
165    pub file_output_mode: FileOutputMode,
166}
167
168impl FileSinkConfig {
169    /// Get output schema
170    pub fn output_schema(&self) -> &SchemaRef {
171        &self.output_schema
172    }
173}