blob: 23f68636c156e901a121941c979d8c57b672ae33 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Module containing helper methods for the various file formats
//! See write.rs for write related helper methods
use std::any::Any;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use crate::file::FileSource;
use crate::file_compression_type::FileCompressionType;
use crate::file_scan_config::FileScanConfig;
use crate::file_sink_config::FileSinkConfig;
use arrow::datatypes::SchemaRef;
use datafusion_common::file_options::file_type::FileType;
use datafusion_common::{internal_err, not_impl_err, GetExt, Result, Statistics};
use datafusion_physical_expr::LexRequirement;
use datafusion_physical_plan::ExecutionPlan;
use datafusion_session::Session;
use async_trait::async_trait;
use object_store::{ObjectMeta, ObjectStore};
/// Default max records to scan to infer the schema
pub const DEFAULT_SCHEMA_INFER_MAX_RECORD: usize = 1000;
/// This trait abstracts all the file format specific implementations
/// from the [`TableProvider`]. This helps code re-utilization across
/// providers that support the same file formats.
///
/// [`TableProvider`]: https://docs.rs/datafusion/latest/datafusion/catalog/trait.TableProvider.html
#[async_trait]
pub trait FileFormat: Send + Sync + fmt::Debug {
/// Returns the table provider as [`Any`] so that it can be
/// downcast to a specific implementation.
fn as_any(&self) -> &dyn Any;
/// Returns the extension for this FileFormat, e.g. "file.csv" -> csv
fn get_ext(&self) -> String;
/// Returns the extension for this FileFormat when compressed, e.g. "file.csv.gz" -> csv
fn get_ext_with_compression(
&self,
_file_compression_type: &FileCompressionType,
) -> Result<String>;
/// Returns whether this instance uses compression if applicable
fn compression_type(&self) -> Option<FileCompressionType>;
/// Infer the common schema of the provided objects. The objects will usually
/// be analysed up to a given number of records or files (as specified in the
/// format config) then give the estimated common schema. This might fail if
/// the files have schemas that cannot be merged.
async fn infer_schema(
&self,
state: &dyn Session,
store: &Arc<dyn ObjectStore>,
objects: &[ObjectMeta],
) -> Result<SchemaRef>;
/// Infer the statistics for the provided object. The cost and accuracy of the
/// estimated statistics might vary greatly between file formats.
///
/// `table_schema` is the (combined) schema of the overall table
/// and may be a superset of the schema contained in this file.
///
/// TODO: should the file source return statistics for only columns referred to in the table schema?
async fn infer_stats(
&self,
state: &dyn Session,
store: &Arc<dyn ObjectStore>,
table_schema: SchemaRef,
object: &ObjectMeta,
) -> Result<Statistics>;
/// Take a list of files and convert it to the appropriate executor
/// according to this file format.
async fn create_physical_plan(
&self,
state: &dyn Session,
conf: FileScanConfig,
) -> Result<Arc<dyn ExecutionPlan>>;
/// Take a list of files and the configuration to convert it to the
/// appropriate writer executor according to this file format.
async fn create_writer_physical_plan(
&self,
_input: Arc<dyn ExecutionPlan>,
_state: &dyn Session,
_conf: FileSinkConfig,
_order_requirements: Option<LexRequirement>,
) -> Result<Arc<dyn ExecutionPlan>> {
not_impl_err!("Writer not implemented for this format")
}
/// Return the related FileSource such as `CsvSource`, `JsonSource`, etc.
fn file_source(&self) -> Arc<dyn FileSource>;
}
/// Factory for creating [`FileFormat`] instances based on session and command level options
///
/// Users can provide their own `FileFormatFactory` to support arbitrary file formats
pub trait FileFormatFactory: Sync + Send + GetExt + fmt::Debug {
/// Initialize a [FileFormat] and configure based on session and command level options
fn create(
&self,
state: &dyn Session,
format_options: &HashMap<String, String>,
) -> Result<Arc<dyn FileFormat>>;
/// Initialize a [FileFormat] with all options set to default values
fn default(&self) -> Arc<dyn FileFormat>;
/// Returns the table source as [`Any`] so that it can be
/// downcast to a specific implementation.
fn as_any(&self) -> &dyn Any;
}
/// A container of [FileFormatFactory] which also implements [FileType].
/// This enables converting a dyn FileFormat to a dyn FileType.
/// The former trait is a superset of the latter trait, which includes execution time
/// relevant methods. [FileType] is only used in logical planning and only implements
/// the subset of methods required during logical planning.
#[derive(Debug)]
pub struct DefaultFileType {
file_format_factory: Arc<dyn FileFormatFactory>,
}
impl DefaultFileType {
/// Constructs a [DefaultFileType] wrapper from a [FileFormatFactory]
pub fn new(file_format_factory: Arc<dyn FileFormatFactory>) -> Self {
Self {
file_format_factory,
}
}
/// get a reference to the inner [FileFormatFactory] struct
pub fn as_format_factory(&self) -> &Arc<dyn FileFormatFactory> {
&self.file_format_factory
}
}
impl FileType for DefaultFileType {
fn as_any(&self) -> &dyn Any {
self
}
}
impl fmt::Display for DefaultFileType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.file_format_factory)
}
}
impl GetExt for DefaultFileType {
fn get_ext(&self) -> String {
self.file_format_factory.get_ext()
}
}
/// Converts a [FileFormatFactory] to a [FileType]
pub fn format_as_file_type(
file_format_factory: Arc<dyn FileFormatFactory>,
) -> Arc<dyn FileType> {
Arc::new(DefaultFileType {
file_format_factory,
})
}
/// Converts a [FileType] to a [FileFormatFactory].
/// Returns an error if the [FileType] cannot be
/// downcasted to a [DefaultFileType].
pub fn file_type_to_format(
file_type: &Arc<dyn FileType>,
) -> Result<Arc<dyn FileFormatFactory>> {
match file_type
.as_ref()
.as_any()
.downcast_ref::<DefaultFileType>()
{
Some(source) => Ok(Arc::clone(&source.file_format_factory)),
_ => internal_err!("FileType was not DefaultFileType"),
}
}