Skip to main content

datafusion_datasource/write/
mod.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
18//! Module containing helper methods/traits related to enabling
19//! write support for the various file formats
20
21use std::io::Write;
22use std::sync::Arc;
23
24use crate::file_compression_type::FileCompressionType;
25use crate::file_sink_config::FileSinkConfig;
26use datafusion_common::error::Result;
27
28use arrow::array::RecordBatch;
29use arrow::datatypes::Schema;
30use bytes::Bytes;
31use object_store::ObjectStore;
32use object_store::buffered::BufWriter;
33use object_store::path::Path;
34use tokio::io::AsyncWrite;
35
36pub mod demux;
37pub mod orchestration;
38
39/// A buffer with interior mutability shared by the SerializedFileWriter and
40/// ObjectStore writer
41#[derive(Clone)]
42pub struct SharedBuffer {
43    /// The inner buffer for reading and writing
44    ///
45    /// The lock is used to obtain internal mutability, so no worry about the
46    /// lock contention.
47    pub buffer: Arc<futures::lock::Mutex<Vec<u8>>>,
48}
49
50impl SharedBuffer {
51    pub fn new(capacity: usize) -> Self {
52        Self {
53            buffer: Arc::new(futures::lock::Mutex::new(Vec::with_capacity(capacity))),
54        }
55    }
56}
57
58impl Write for SharedBuffer {
59    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
60        let mut buffer = self.buffer.try_lock().unwrap();
61        Write::write(&mut *buffer, buf)
62    }
63
64    fn flush(&mut self) -> std::io::Result<()> {
65        let mut buffer = self.buffer.try_lock().unwrap();
66        Write::flush(&mut *buffer)
67    }
68}
69
70/// A trait that defines the methods required for a RecordBatch serializer.
71pub trait BatchSerializer: Sync + Send {
72    /// Asynchronously serializes a `RecordBatch` and returns the serialized bytes.
73    /// Parameter `initial` signals whether the given batch is the first batch.
74    /// This distinction is important for certain serializers (like CSV).
75    fn serialize(&self, batch: RecordBatch, initial: bool) -> Result<Bytes>;
76}
77
78/// Converts table schema to writer schema, which may differ in the case
79/// of hive style partitioning where some columns are removed from the
80/// underlying files.
81pub fn get_writer_schema(config: &FileSinkConfig) -> Arc<Schema> {
82    if !config.table_partition_cols.is_empty() && !config.keep_partition_by_columns {
83        let schema = config.output_schema();
84        let partition_names: Vec<_> =
85            config.table_partition_cols.iter().map(|(s, _)| s).collect();
86        Arc::new(Schema::new_with_metadata(
87            schema
88                .fields()
89                .iter()
90                .filter(|f| !partition_names.contains(&f.name()))
91                .map(|f| (**f).clone())
92                .collect::<Vec<_>>(),
93            schema.metadata().clone(),
94        ))
95    } else {
96        Arc::clone(config.output_schema())
97    }
98}
99
100/// A builder for an [`AsyncWrite`] that writes to an object store location.
101///
102/// This can be used to specify file compression on the writer. The writer
103/// will have a default buffer size unless altered. The specific default size
104/// is chosen by [`BufWriter::new`].
105///
106/// We drop the `AbortableWrite` struct and the writer will not try to cleanup on failure.
107/// Users can configure automatic cleanup with their cloud provider.
108#[derive(Debug)]
109pub struct ObjectWriterBuilder {
110    /// Compression type for object writer.
111    file_compression_type: FileCompressionType,
112    /// Output path
113    location: Path,
114    /// The related store that handles the given path
115    object_store: Arc<dyn ObjectStore>,
116    /// The size of the buffer for the object writer.
117    buffer_size: Option<usize>,
118    /// The compression level for the object writer.
119    compression_level: Option<u32>,
120}
121
122impl ObjectWriterBuilder {
123    /// Create a new [`ObjectWriterBuilder`] for the specified path and compression type.
124    pub fn new(
125        file_compression_type: FileCompressionType,
126        location: &Path,
127        object_store: Arc<dyn ObjectStore>,
128    ) -> Self {
129        Self {
130            file_compression_type,
131            location: location.clone(),
132            object_store,
133            buffer_size: None,
134            compression_level: None,
135        }
136    }
137
138    /// Set buffer size in bytes for object writer.
139    ///
140    /// # Example
141    /// ```
142    /// # use datafusion_datasource::file_compression_type::FileCompressionType;
143    /// # use datafusion_datasource::write::ObjectWriterBuilder;
144    /// # use object_store::memory::InMemory;
145    /// # use object_store::path::Path;
146    /// # use std::sync::Arc;
147    /// # let compression_type = FileCompressionType::UNCOMPRESSED;
148    /// # let location = Path::from("/foo/bar");
149    /// # let object_store = Arc::new(InMemory::new());
150    /// let mut builder = ObjectWriterBuilder::new(compression_type, &location, object_store);
151    /// builder.set_buffer_size(Some(20 * 1024 * 1024)); //20 MiB
152    /// assert_eq!(
153    ///     builder.get_buffer_size(),
154    ///     Some(20 * 1024 * 1024),
155    ///     "Internal error: Builder buffer size doesn't match"
156    /// );
157    /// ```
158    pub fn set_buffer_size(&mut self, buffer_size: Option<usize>) {
159        self.buffer_size = buffer_size;
160    }
161
162    /// Set buffer size in bytes for object writer, returning the builder.
163    ///
164    /// # Example
165    /// ```
166    /// # use datafusion_datasource::file_compression_type::FileCompressionType;
167    /// # use datafusion_datasource::write::ObjectWriterBuilder;
168    /// # use object_store::memory::InMemory;
169    /// # use object_store::path::Path;
170    /// # use std::sync::Arc;
171    /// # let compression_type = FileCompressionType::UNCOMPRESSED;
172    /// # let location = Path::from("/foo/bar");
173    /// # let object_store = Arc::new(InMemory::new());
174    /// let builder = ObjectWriterBuilder::new(compression_type, &location, object_store)
175    ///     .with_buffer_size(Some(20 * 1024 * 1024)); //20 MiB
176    /// assert_eq!(
177    ///     builder.get_buffer_size(),
178    ///     Some(20 * 1024 * 1024),
179    ///     "Internal error: Builder buffer size doesn't match"
180    /// );
181    /// ```
182    pub fn with_buffer_size(mut self, buffer_size: Option<usize>) -> Self {
183        self.buffer_size = buffer_size;
184        self
185    }
186
187    /// Currently specified buffer size in bytes.
188    pub fn get_buffer_size(&self) -> Option<usize> {
189        self.buffer_size
190    }
191
192    /// Set compression level for object writer.
193    pub fn set_compression_level(&mut self, compression_level: Option<u32>) {
194        self.compression_level = compression_level;
195    }
196
197    /// Set compression level for object writer, returning the builder.
198    pub fn with_compression_level(mut self, compression_level: Option<u32>) -> Self {
199        self.compression_level = compression_level;
200        self
201    }
202
203    /// Currently specified compression level.
204    pub fn get_compression_level(&self) -> Option<u32> {
205        self.compression_level
206    }
207
208    /// Return a writer object that writes to the object store location.
209    ///
210    /// If a buffer size has not been set, the default buffer buffer size will
211    /// be used.
212    ///
213    /// # Errors
214    /// If there is an error applying the compression type.
215    pub fn build(self) -> Result<Box<dyn AsyncWrite + Send + Unpin>> {
216        let Self {
217            file_compression_type,
218            location,
219            object_store,
220            buffer_size,
221            compression_level,
222        } = self;
223
224        let buf_writer = match buffer_size {
225            Some(size) => BufWriter::with_capacity(object_store, location, size),
226            None => BufWriter::new(object_store, location),
227        };
228
229        file_compression_type
230            .convert_async_writer_with_level(buf_writer, compression_level)
231    }
232}